PDF Library for Delphi kann über DeduplicateSimilarImages Bilder innerhalb eines PDFs zusammenführen, die gleich aussehen, aber nicht bytegenau identisch sind. Exakte Deduplizierung führt nur Bilder zusammen, deren Dictionaries und kodierte Nutzdaten übereinstimmen; diese hier vergleicht, wie die Bilder tatsächlich aussehen, mittels eines perzeptuellen Hashes plus zweier Pixelfehler-Schwellen, und leitet jede Referenz auf ein einziges kanonisches Objekt um
Die Dateien, bei denen sich das lohnt, sind diejenigen, die versehentlich gewachsen sind. Ein gescannter Stapel, bei dem jede Seite dasselbe Logo trägt, von einem anderen Scanner neu kodiert. Ein Bericht, zusammengesetzt aus Quellen, die jeweils dasselbe Diagramm mit leicht unterschiedlicher JPEG-Qualität eingebettet haben. Ein Katalog, dessen Vorschaubilder von zwei Werkzeugen neu erzeugt wurden. In jedem Fall enthält die Datei viele Kopien eines Bildes, und exakte Deduplizierung kann keine davon anfassen
Was „ähnlich genug“ bedeuten muss
Drei Schwellen müssen alle erfüllt sein, bevor zwei Bilder zusammengeführt werden, und jede fängt eine andere Art von Unterschied ab. Der Abstand des perzeptuellen Hashes begrenzt strukturelle Unterschiede: Ein 64-Bit-Differenz-Hash wird aus einer 64-mal-64-Stichprobe des dekodierten Bilds berechnet, und der zulässige Hamming-Abstand reicht von 0 bis 7. Der mittlere Kanalfehler begrenzt die Gesamtabweichung, die eine Neukodierung erzeugt. Der maximale Einzelkanalfehler begrenzt lokalisierte Unterschiede, die eine kleine Bearbeitung erzeugt
Alle drei zu verlangen macht die Zusammenführung vertretbar. Ein Bild, das sich durch ein Wasserzeichen in einer Ecke unterscheidet, hat einen niedrigen mittleren Fehler und einen hohen maximalen Fehler, sodass das Maximalfehler-Tor es ablehnt. Ein neu komprimiertes Bild hat überall einen kleinen Fehler, besteht also. Genau diese Unterscheidung muss ein Größenoptimierer treffen
uses
PDFlibrary;
var
Lib: TPDFlib;
Stats: TPDFlibImageSimilarityDedupStats;
Merged: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('scanned-batch.pdf', '');
// Hash-Abstand 2, mittlerer Fehler 2, Einzelkanalfehler 4.
// Optionen können einen Trockenlauf anfordern, sodass nichts geändert wird
Merged := Lib.DeduplicateSimilarImages(2, 2, 4, 0, Stats);
if Merged < 0 then
raise Exception.Create('deduplication failed');
Log(Format('%d scanned, %d merged, %d refs repointed, %d bytes duplicated',
[Stats.ImagesScanned, Stats.ImagesDeduplicated,
Stats.ReferencesRepointed, Stats.DuplicatePayloadBytes]));
Log(Format('worst accepted: hash %d, mean %.2f, channel %d',
[Stats.MaxAcceptedHashDistance, Stats.MaxAcceptedMeanError,
Stats.MaxAcceptedChannelError]));
Lib.SaveToFile('scanned-batch-optimised.pdf');
finally
Lib.Free;
end;
end;
Semantische Identität kommt vor visueller Ähnlichkeit
Bevor überhaupt Pixel verglichen werden, müssen Kandidatenbilder semantisch übereinstimmen. Die Bibliothek baut eine stabile Signatur aus dem vollständigen Bild-Dictionary abzüglich /Length, /Filter und /DecodeParms, sodass Abmessungen, Farbraum, Bittiefe, Masken, das /Decode-Array, Interpolation, Zugehörigkeit zu optionalem Inhalt und Metadaten alle übereinstimmen müssen
Diese Reihenfolge verhindert eine ganze Klasse falscher Zusammenführungen. Zwei Bilder können in einer 64-mal-64-Stichprobe identisch aussehen und sich auf der Seite unterschiedlich verhalten: Eines mit /Interpolate wahr und eines ohne wird beim Skalieren unterschiedlich geglättet, und eines, das zu einer Gruppe optionalen Inhalts gehört, verschwindet, wenn diese Ebene ausgeblendet wird. Über diese Grenzen hinweg zusammenzuführen erzeugt ein Dokument, das kleiner und falsch ist
Nur indirekte Image-XObjects werden gescannt. Inline-Bilder sind ein anderes Konstrukt mit anderer Kostenstruktur, und Bilder, die sich nicht dekodieren lassen, werden vorsichtshalber übersprungen und gezählt statt blind zusammengeführt — keine Pixel-Evidenz, keine Zusammenführung
Wie die Suche günstig bleibt
Jedes Bild mit jedem anderen zu vergleichen ist quadratisch, was bei einem Dokument mit Tausenden von Bildern inakzeptabel ist. Der Hash wird daher in acht 8-Bit-Bänder aufgeteilt, mit einem geordneten Index über jedem
Das Schubfachprinzip erledigt den Rest: Unterscheiden sich zwei Hashes um höchstens 7 Bit, müssen sie in mindestens einem der acht Bänder exakt übereinstimmen. Kandidaten Band für Band nachzuschlagen findet daher jede zulässige Übereinstimmung ohne einen erschöpfenden paarweisen Scan, und die veröffentlichte Grenze von 7 existiert genau deshalb, weil die Garantie nur bis zu diesem Abstand gilt
Kandidaten und Band-Treffer werden deterministisch nach Objektnummer und Generation geordnet, und die niedrigste Objektnummer wird kanonisch. Das macht die Ausgabe reproduzierbar: Den Optimierer zweimal mit derselben Eingabe laufen zu lassen erzeugt dieselben kanonischen Entscheidungen, was wichtig ist, wenn das Ergebnis verglichen oder signiert wird
Trockenläufe und ehrliche Berichterstattung
Da dies eine verlustbehaftete Optimierung ist in dem Sinne, dass die zusammengeführten Bilder nicht identisch waren, ist der vernünftige Arbeitsablauf, erst zu schauen, bevor man springt. Die Trockenlauf-Option meldet, was zusammengeführt würde und wie viel Nutzlast zurückgewonnen würde, ohne den Objektgraphen zu ändern, sodass ein Batch-Job den Effekt protokollieren kann und ein Mensch die Schwellen einmal statt pro Datei genehmigen kann
Zwei Ehrlichkeitshinweise in der Berichterstattung verdienen Erwähnung. Die rückgewinnbare Zahl schätzt nur die kodierte Nutzlast doppelter Streams; sie zählt nicht den Overhead von Dictionary, indirektem Objekt und Querverweis, sodass die tatsächliche Dateigrößenreduktion etwas größer ausfällt als gemeldet, nicht kleiner. Und der Bericht legt den maximal akzeptierten Fehler und den Vergleichsaufwand offen, sodass Sie sehen können, wie nah die Zusammenführungen an Ihre Schwellen herankamen
Das feste 64-mal-64-Sampling-Raster ist eine bewusste Näherung und hat eine Konsequenz, die es klar zu benennen lohnt: Ein Unterschied, der kleiner als eine Stichprobenzelle ist, kann selbst bei einer Nullschwelle unbemerkt bleiben. Ein einzelnes geändertes Wort auf einer gescannten Seite übersteht dieses Sampling möglicherweise nicht. Wird ein Nachweis auf Byte-Ebene benötigt, verwenden Sie stattdessen den exakten Deduplizierungspfad — er führt nur wirklich identische Ressourcen zusammen und trifft überhaupt kein visuelles Urteil
Wo es in eine Größenreduktions-Pipeline gehört
Ordnen Sie die Durchläufe nach Risiko. Führen Sie zuerst exakte Ressourcendeduplizierung durch, denn sie ist verlustfrei und kostenlos. Dann Font-Subsetting, das ebenfalls verlustfrei und meist der größte Einzelgewinn bei textlastigen Dokumenten ist, wie unter PDF-Dateigrößenoptimierung und Font-Subsetting behandelt. Dann Bild-Neukomprimierung oder Downsampling, sofern das Zielmedium das erlaubt. Perzeptuelle Deduplizierung gehört danach, denn sie ist der einzige Schritt, dessen Ergebnis von einer Ermessensentscheidung abhängt
Wählen Sie Schwellen nach Dokumentklasse statt global. Bei gescannten Stapeln, in denen jede Seite denselben Briefkopf trägt, führt ein Hash-Abstand von 2 bis 3 mit einem mittleren Fehler um 2 den Briefkopf zuverlässig zusammen. Bei Dokumenten, in denen Bilder Daten tragen — Diagramme, Karten, medizinische Bilder —, verwenden Sie Null- oder Nahe-Null-Schwellen oder überspringen Sie den Durchlauf ganz, denn zwei Diagramme, die sich in einem geplotteten Punkt unterscheiden, sind zwei verschiedene Diagramme, egal wie ähnlich sie bei 64 mal 64 aussehen
Nach dem Optimieren verifizieren. Rendern Sie eine Stichprobe von Seiten vor und nach der Optimierung und vergleichen Sie sie, und prüfen Sie, ob sich Seitenzahl und Bildanzahl wie erwartet geändert haben. Der Inspektionsweg dafür, was ein Dokument tatsächlich enthält, ist unter Prüfung der PDF-Dateigröße nach Kategorie beschrieben, was auch der schnellste Weg ist, um überhaupt herauszufinden, ob Bilder Ihr Problem sind
Perzeptuelle Deduplizierung, exakte Ressourcendeduplizierung und die Größenprüfung laufen für Delphi, C++Builder und Free Pascal auf demselben Objektgraphen; die vollständige Funktionsliste finden Sie auf der Seite der PDF Library for Delphi