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 — 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.
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
| Algoritmo | Griglia | Regola del bit | Robusto a | Fragile a |
|---|---|---|---|---|
| aHash | 8×8 | pixel ≥ media | ricompressione lieve, ridimensionamento | luminosità, gamma, contrasto |
| dHash | 9×8 | pixel < pixel a destra | luminosità, contrasto | ritocchi locali, sharpening, rumore |
| pHash | 32×32 → blocco 8×8 | coefficiente DCT > mediana | compressione, scala, rumore | ritaglio, 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
- Calcola sempre tutti e tre. Il costo è irrilevante e la discordanza è il dato più interessante.
- Non fidarti di un singolo algoritmo per affermare che due immagini sono la stessa. Chiedi che concordino.
- Se aHash è l'unico alto, sospetta un aggiustamento tonale, non una manipolazione del contenuto.
- Dichiara sempre la profondità in bit nella relazione: «distanza 4» non significa nulla senza sapere se il denominatore è 64 o 256.
- Riporta la distanza, non solo la percentuale. «6 bit su 64» è un fatto; «90,6%» è la sua traduzione, e nasconde il denominatore.