aHash, dHash e pHash a confronto

aHash, dHash e pHash partono dallo stesso gesto — ridurre l'immagine a una griglia di grigi — e finiscono con sessantaquattro bit. Quello che cambia è la domanda posta a ogni cella. Tre domande diverse producono tre impronte con punti ciechi diversi, ed è precisamente per questo che Probatio le calcola tutte e tre.

Il tronco comune

Tutti e tre gli algoritmi cominciano ridimensionando l'immagine a una griglia minuscola e convertendola in scala di grigi. A quel punto ogni cella contiene un solo numero: la sua luminanza. La differenza sta nel confronto che segue.

Alla profondità classica di 64 bit il lato della griglia è 8 — la radice quadrata dei bit richiesti. A 144 bit diventa 12, a 256 bit diventa 16.

aHash griglia 8×8 (qui 4×4) ● = pixel ≥ media → bit 1 bit = pixel ≥ media della griglia Un solo passaggio: ridimensiona, calcola la media, soglia. Il più veloce dei tre. Fragile: alza la luminosità di tutta l'immagine e i bit restano… quasi tutti. dHash griglia (8+1)×8 (qui 5×4) → = confronto col pixel a destra bit = pixel < pixel successivo Codifica il gradiente, cioè la direzione della variazione, non il livello assoluto. Robusto a luminosità e contrasto: il verso della pendenza non cambia. pHash DCT su 32×32, blocco 8×8 (qui 8×8 → 4×4) blocco di basse frequenze termine DC: escluso dalla mediana bit = coefficiente > mediana Le basse frequenze descrivono la struttura; le alte, il rumore e i dettagli fini. Il più robusto a compressione e scala. Anche il più costoso: serve una DCT 2D. La griglia reale è 8×8 (64 bit); qui è ridotta per leggibilità. Le luminanze sono illustrative.
Tre modi di trasformare una griglia di grigi in bit. Cambia la domanda posta a ogni cella — e con essa ciò a cui l'impronta è cieca.

aHash — confronto con la media

Il più semplice. Ridimensiona a 8×8, calcola la media delle 64 luminanze, poi scrive un bit per cella: 1 se la cella è maggiore o uguale alla media, 0 altrimenti.

È velocissimo e ha un'intuizione corretta: la mappa di «dove l'immagine è più chiara del suo grigio medio» è una firma della composizione. Ma la media è un riferimento che si sposta. Se qualcuno alza la luminosità dell'intera immagine, tutte le celle salgono e la media sale con loro: i bit restano quasi tutti uguali. Se invece l'aggiustamento è non lineare — una curva di gamma, un filtro di un'app — celle che stavano appena sopra la media scendono appena sotto, e i bit si ribaltano.

Punto cieco: aHash è il più sensibile alle manipolazioni di luminosità e contrasto. Quando aHash diverge molto più degli altri due, hai un indizio: qualcuno ha toccato le curve tonali.

dHash — confronto con il vicino

Cambio di prospettiva. dHash ridimensiona a una griglia larga un pixel in più — (8+1)×8 — e poi, per ogni riga, confronta ogni pixel con quello immediatamente a destra: il bit vale 1 se il pixel è più scuro del successivo.

Quello che sta codificando non è il livello della luce, ma la sua pendenza: dove l'immagine si schiarisce da sinistra a destra e dove si scurisce. È il gradiente orizzontale, ridotto al suo segno.

E il segno di una pendenza è notevolmente stabile. Alza la luminosità di tutta l'immagine, e ogni pixel sale insieme al suo vicino: la pendenza resta la stessa. Aumenta il contrasto, e le pendenze si accentuano ma non si invertono. Ecco perché dHash resiste dove aHash cede.

Punto cieco: guarda solo la direzione orizzontale, e solo fra vicini immediati. Un ritocco locale che alteri i micro-gradienti — sharpening, riduzione del rumore, una gomma su un dettaglio — sposta molti bit, anche se la fotografia è, nel complesso, la stessa.

pHash — confronto con la mediana dei coefficienti

Il più raffinato, e il solo che non lavora sui pixel. pHash ridimensiona a una griglia quattro volte più larga — 32×32 quando l'impronta è a 64 bit — e le applica una DCT-II bidimensionale, la stessa trasformata che sta dentro il JPEG.

La DCT riscrive l'immagine come somma di onde di frequenza crescente. In alto a sinistra della matrice risultante finiscono le basse frequenze: le variazioni lente, cioè la struttura, l'impianto compositivo, i grandi volumi di luce. In basso a destra finiscono le alte frequenze: il dettaglio fine, il rumore del sensore, gli artefatti della compressione.

pHash tiene solo il blocco 8×8 in alto a sinistra e butta via tutto il resto. Poi calcola la mediana di quei coefficienti — escludendo il termine costante in posizione (0,0), il cosiddetto DC, che rappresenta la luminosità media e sposterebbe la mediana senza dire nulla sulla struttura — e scrive un bit per coefficiente: 1 se è sopra la mediana.

Il risultato è un'impronta della struttura di frequenze basse. Ricomprimere un JPEG danneggia le alte frequenze e lascia intatte le basse: pHash non se ne accorge. Ridimensionare fa lo stesso. È per questo che è il più robusto dei tre.

Punto cieco: è il più costoso — richiede una trasformata 2D su 1024 pixel, non una media su 64 — ed è sensibile a tutto ciò che sposta la composizione: un ritaglio, un bordo aggiunto, una rotazione.

Quando divergono: leggere la discordanza

Se i tre algoritmi concordassero sempre, calcolarne tre sarebbe uno spreco. Non concordano, e la loro discordanza è informazione.

distanza dHash BASSA ALTA distanza pHash BASSA distanza pHash ALTA Stessa immagine Ricompressione, ridimensionamento, cambio di formato. Nessuna sorpresa. Micro-struttura alterata Struttura globale intatta, gradienti locali no: ritocco locale, sharpening, denoise, rumore. Struttura globale alterata Dettagli locali coerenti, impianto no: ritaglio, scala molto diversa, bordi aggiunti. Immagini diverse Entrambe le impronte scorrelate. Nessuna parentela visiva. Sono ipotesi di lavoro, non verdetti: indicano dove guardare, e vanno confermate ispezionando le immagini.
Il valore di calcolare tutti e tre gli algoritmi non sta nella ridondanza, ma nella loro discordanza: è lì che si nasconde l'informazione.

Il ragionamento è sempre lo stesso: dato che questo algoritmo è cieco a X e vede Y, la sua distanza mi dice qualcosa su Y. Se pHash è basso e dHash è alto, la struttura globale coincide ma i dettagli locali no — e la spiegazione più economica è un intervento locale, non un soggetto diverso. Se pHash è alto e dHash è basso, i dettagli combaciano ma la composizione è cambiata: un ritaglio spiega tutto.

Sono ipotesi di lavoro. Indicano dove guardare, non cosa concludere. La conclusione arriva quando apri le due immagini e vedi il ritaglio.

Tabella riassuntiva

AlgoritmoGrigliaRegola del bitRobusto aFragile a
aHash8×8pixel ≥ mediaricompressione lieve, ridimensionamentoluminosità, gamma, contrasto
dHash9×8pixel < pixel a destraluminosità, contrastoritocchi locali, sharpening, rumore
pHash32×32 → blocco 8×8coefficiente DCT > medianacompressione, scala, rumoreritaglio, rotazione, bordi

Due cose che non troverai

wHash, la variante basata sulla trasformata wavelet, è spesso citata insieme agli altri tre. Probatio non la implementa: se leggi un confronto che la menziona, non viene da qui.

ssdeep, che Probatio calcola nell'analisi file, non è un hash percettivo: lavora sui byte, non sulla percezione, e in Probatio viene mostrato ma non confrontato. Sono strumenti diversi per problemi diversi.

Come usarli in pratica

  1. Calcola sempre tutti e tre. Il costo è irrilevante e la discordanza è il dato più interessante.
  2. Non fidarti di un singolo algoritmo per affermare che due immagini sono la stessa. Chiedi che concordino.
  3. Se aHash è l'unico alto, sospetta un aggiustamento tonale, non una manipolazione del contenuto.
  4. Dichiara sempre la profondità in bit nella relazione: «distanza 4» non significa nulla senza sapere se il denominatore è 64 o 256.
  5. Riporta la distanza, non solo la percentuale. «6 bit su 64» è un fatto; «90,6%» è la sua traduzione, e nasconde il denominatore.