Articolo tecnico

Ridurre i PDF unendo immagini quasi identiche in Delphi

PDF Library for Delphi può unire immagini all'interno di un PDF che appaiono uguali ma non sono identiche byte per byte, tramite DeduplicateSimilarImages. La deduplicazione esatta unisce solo immagini i cui dizionari e payload codificati corrispondono; questa invece confronta come le immagini appaiono realmente, usando un hash percettivo più due soglie di errore sui pixel, e reindirizza ogni riferimento verso un unico oggetto canonico

I file in cui questo ripaga sono quelli cresciuti per caso. Un batch scansionato dove ogni pagina porta lo stesso logo ri-codificato da uno scanner diverso. Un report assemblato da fonti che hanno ciascuna incorporato lo stesso grafico a una qualità JPEG leggermente diversa. Un catalogo le cui miniature sono state rigenerate da due strumenti. In ogni caso il file contiene molte copie di una singola immagine, e la deduplicazione esatta non può toccarne nessuna

Cosa deve significare "abbastanza simile"

Tre soglie devono essere tutte soddisfatte prima che due immagini vengano unite, e ciascuna cattura un tipo diverso di differenza. La distanza dell'hash percettivo delimita la differenza strutturale: un difference hash a 64 bit viene calcolato da un campione 64 per 64 dell'immagine decodificata, e la distanza di Hamming permessa va da 0 a 7. L'errore medio di canale delimita la deriva complessiva, che è ciò che produce la ri-codifica. L'errore massimo su un singolo canale delimita la differenza localizzata, che è ciò che produce una piccola modifica

Richiedere tutte e tre è ciò che rende difendibile l'unione. Un'immagine che differisce per una filigrana in un angolo ha un errore medio basso e un errore massimo alto, così il gate dell'errore massimo la respinge. Un'immagine che è stata ricompressa ha un errore piccolo ovunque, così passa. È esattamente la distinzione che un ottimizzatore di dimensione deve tracciare

Tre soglie regolano l'unione di immagini quasi identiche: distanza hash, errore medio di canale ed errore massimo di singolo canale.
Tutte e tre le soglie usano i pixel decodificati — una coppia con filigrana viene bloccata dalla soglia dell'errore massimo, mentre un gemello ricodificato la supera senza problemi
uses
  PDFlibrary;

var
  Lib: TPDFlib;
  Stats: TPDFlibImageSimilarityDedupStats;
  Merged: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('scanned-batch.pdf', '');

    // Distanza hash 2, errore medio 2, errore su singolo canale 4
    // Le opzioni possono richiedere una simulazione a secco così nulla viene modificato
    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;

L'identità semantica viene prima della somiglianza visiva

Prima che venga confrontato qualsiasi pixel, le immagini candidate devono concordare semanticamente. La libreria costruisce una firma stabile dal dizionario immagine completo escludendo /Length, /Filter e /DecodeParms, così dimensioni, spazio colore, profondità di bit, maschere, l'array /Decode, l'interpolazione, l'appartenenza a contenuto opzionale e i metadati devono tutti corrispondere

Questo ordinamento previene un'intera classe di unioni sbagliate. Due immagini possono apparire identiche in un campione 64 per 64 e comportarsi diversamente sulla pagina: una con /Interpolate vero e una senza verranno smussate diversamente quando scalate, e una appartenente a un gruppo di contenuto opzionale sparirà quando quel livello viene nascosto. Unire attraverso quei confini produce un documento più piccolo e sbagliato

Vengono scansionati solo gli Image XObject indiretti. Le immagini inline sono un costrutto diverso con un'economia diversa, e le immagini che non possono essere decodificate vengono saltate e conteggiate in modo conservativo anziché unite per fede — nessuna prova sui pixel, nessuna unione

Come la ricerca resta economica

Confrontare ogni immagine con ogni altra immagine è quadratico, il che è inaccettabile su un documento contenente migliaia di immagini. L'hash viene quindi suddiviso in otto bande da 8 bit con un indice ordinato su ciascuna

Il principio dei cassetti fa il resto: se due hash differiscono al massimo di 7 bit, devono concordare esattamente su almeno una delle otto bande. Cercare i candidati banda per banda trova quindi ogni corrispondenza ammissibile senza una scansione esaustiva a coppie, e il limite pubblicato di 7 esiste proprio perché la garanzia vale solo fino a quella distanza

L'hash di differenza a 64 bit si divide in otto bande da 8 bit, e il principio dei cassetti garantisce che almeno una banda continui a corrispondere dopo sette bit invertiti.
Sette bit invertiti distribuiti su otto bande devono lasciarne almeno una intatta, il che trasforma una scansione quadratica in otto ricerche ordinate

I candidati e le corrispondenze di banda vengono ordinati in modo deterministico per numero di oggetto e generazione, e il numero di oggetto più basso diventa canonico. Questo rende l'output riproducibile: eseguire l'ottimizzatore due volte sullo stesso input produce le stesse scelte canoniche, il che conta quando il risultato viene confrontato o firmato

Simulazioni a secco e report onesti

Poiché questa è un'ottimizzazione lossy nel senso che le immagini unite non erano identiche, il flusso di lavoro sensato è guardare prima di agire. L'opzione di simulazione a secco riporta cosa verrebbe unito e quanto payload verrebbe recuperato senza modificare il grafo degli oggetti, così un job batch può registrare l'effetto e un essere umano può approvare le soglie una volta sola anziché per ogni file

Due note di onestà nella reportistica meritano menzione. La cifra recuperabile stima solo il payload codificato degli stream duplicati; non conta l'overhead di dizionario, oggetto indiretto e riferimenti incrociati, quindi la riduzione effettiva della dimensione del file è leggermente maggiore di quella riportata anziché minore. E il report espone l'errore massimo accettato e il carico di lavoro di confronto, così potete vedere quanto le unioni si siano avvicinate alle vostre soglie

La griglia di campionamento fissa 64 per 64 è un'approssimazione intenzionale, e ha una conseguenza che vale la pena affermare chiaramente: una differenza più piccola di una cella di campionamento può passare inosservata anche a soglia zero. Una singola parola modificata in una pagina scansionata potrebbe non sopravvivere a quel campionamento. Quando è richiesta una prova a livello di byte, usate invece il percorso di deduplicazione esatta — unisce solo risorse genuinamente identiche e non formula alcun giudizio visivo

Dove si colloca in una pipeline di riduzione della dimensione

Ordinate i passaggi per rischio. Eseguite prima la deduplicazione esatta delle risorse, poiché è senza perdita e gratuita. Poi il subsetting dei font, che è anch'esso senza perdita e di solito il singolo guadagno maggiore sui documenti ricchi di testo, come trattato in ottimizzazione della dimensione del file PDF e subsetting dei font. Poi la ricompressione o il sottocampionamento delle immagini se il mezzo di destinazione lo consente. La deduplicazione percettiva va dopo questi passaggi, perché è l'unico i cui risultati dipendono da un giudizio soggettivo

Ordine di pipeline consigliato, dalla deduplicazione esatta senza perdita e dal subsetting dei font fino alla ricompressione condizionale delle immagini, alla deduplicazione percettiva e alla verifica.
Ogni passaggio aggiunge rischio, quindi i vantaggi senza perdita vengono per primi e l'unione basata su giudizio viene per ultima, seguita da un controllo di rendering e confronto

Scegliete le soglie per classe di documento anziché globalmente. Per i batch scansionati dove ogni pagina porta la stessa carta intestata, una distanza hash di 2 o 3 con un errore medio attorno a 2 unisce in modo affidabile la carta intestata. Per i documenti dove le immagini portano dati — grafici, mappe, immagini mediche — usate soglie zero o quasi zero, oppure saltate del tutto il passaggio, perché due grafici che differiscono per un punto tracciato sono due grafici diversi indipendentemente da quanto si somiglino a 64 per 64

Dopo aver ottimizzato, verificate. Renderizzate un campione di pagine prima e dopo e confrontatele, e controllate che il conteggio delle pagine e delle immagini sia cambiato come previsto. Il percorso di ispezione di ciò che un documento contiene realmente è descritto in verificare la dimensione del file PDF per categoria, che è anche il modo più rapido per scoprire se le immagini sono il vostro problema fin dall'inizio

La deduplicazione percettiva, la deduplicazione esatta delle risorse e la verifica della dimensione operano sullo stesso grafo di oggetti per Delphi, C++Builder e Free Pascal; l'elenco completo delle funzionalità è disponibile sulla pagina di PDF Library for Delphi