MostlyQR

I codici QR fotografici funzionano davvero? I nostri risultati del test di decodifica

Un codice QR che è una fotografia potrebbe sembrare che funzioni peggio. Abbiamo misurato il nostro con un decoder reale — sfocato, ridimensionato e letto come legge un lettore.

Max Čelar··Printing & scanning

Novità qui: Risultati del test di decodifica della nostra implementazione photo QR, misurati con jsQR rispetto a un codice di controllo standard: nessuno dei 4.225 moduli letto male a ogni apertura del lettore da 0,10 a 0,45 moduli, tolleranza di ridimensionamento 3x identica e tolleranza di sfocatura di 4-5 pixel rispetto ai 6 del codice standard — il costo specifico e ridotto di trasformare un codice in un'immagine.

Un codice QR che è una fotografia sembra debba implicare un compromesso: carino da vedere, ma perdi alcune scansioni. Questo istinto è giusto per la maggior parte delle implementazioni, ma sbagliato su come effettivamente deve funzionare — e nessuno pubblica il numero effettivo di scansioni perse.

Ecco i nostri, dalla suite di test che doveva superare prima di spedire completamente la funzione.

# Cosa è un codice QR fotografico e cosa non è

Due cose molto diverse vengono chiamate con lo stesso nome.

AI QR art — il tipo basato su modelli di diffusione — genera un’immagine basata su un codice QR e spera che il risultato si decodifichi ancora. A volte lo fa. La modalità di errore è invisibile fino a quando qualcuno in un bar buio non punta un telefono di tre anni fa su di esso.

Un codice foto con diffusione d’errore è l’altro approccio e quello misurato qui. L’immagine è ridotta a un bit — nero o bianco per sub-pixel — per diffusione d’errore, con un vincolo cruciale: a ogni centro del modulo il dither è forzato al valore che l’encoder richiede, e l’errore che la forzatura crea (fino a circa il 95% del valore di un pixel, per un modulo bianco in una regione scura) viene spinto nei sub-pixel vicini che non sono stati ancora sogliati. L’immagine è fatta dello spazio tra i centri dei moduli.

Questa distinzione è il fulcro dell’articolo. Significa che il codice non consuma la sua correzione d’errore per la decorazione: ogni modulo trasporta ancora il valore dell’encoder, quindi la ridondanza completa rimane intatta per le pieghe, l’abbagliamento, un dito su un angolo — le cose per cui la correzione d’errore esiste.

# Il test

Tutto ciò che segue è riproducibile dal nostro repository, offline:

  • Encoder: il nostro packages/qr, correzione d’errore H, versione minima minVersion: 12 → versione del simbolo 12, 65 moduli totali, 10 pixel per modulo, zona silenziosa di 4 moduli.
  • Stili: halftone (una vera fotografia diffusa) e mosaic (vengono disegnati solo i moduli, dimensionati dall’oscurità locale), rispetto a un codice semplice come controllo — stesso payload, stessa versione, stessa scala.
  • Immagini: immagini di test procedurali, incluso un gradiente diagonale (appaiono tutti i toni) e grigio piatto medio, che è il caso peggiore perché ogni punto forzato ha un valore diverso dai suoi dintorni.
  • Decoder: jsQR 1.4.0, un decoder QR reale, che lavora sui pixel effettivi.
  • Degradazioni: downscale nearest-neighbour, box blur seguito da re-thresholding (una fotocamera fuori fuoco), e una lettura di apertura.

La suite su cui è costruito è packages/qr/test/dither.test.js. 39/39 test superati il 2026-08-31, e un codice che non si decodifica correttamente è un test fallito piuttosto che un avviso — questa regola è il motivo per cui la funzione esiste nella forma attuale. Le tabelle sottostanti sono stampate da draft/mostlyqr/seo/benchmarks/photo.js, che esegue gli stessi stili attraverso gli stessi degradamenti; nulla qui è stato trascritto.

# Zero moduli errati, a ogni apertura

La misura che conta di più non è se si è decodificato correttamente, ma se il lettore ha visto il valore corretto in ogni cella, perché un codice che si decodifica solo perché la correzione d’errore lo ha salvato è un codice che fallirà quando qualcos’altro andrà storto.

Un lettore conforme non campiona un pixel per modulo. Calcola la media di una piccola apertura circolare a ogni centro del modulo e applica una soglia a quella media. Quindi abbiamo fatto lo stesso, a raggi di apertura di 0,10, 0,25, 0,36 e 0,45 moduli (0,5 sarebbe l’intero modulo), e abbiamo contato quanti dei 4.225 moduli sarebbero stati letti in modo scorretto:

Stile Moduli errati, apertura 0,10 / 0,25 / 0,36 / 0,45
codice semplice (controllo) 0 / 0 / 0 / 0
foto a mosaico 0 / 0 / 0 / 0
foto a mezzitoni (gradiente) 0 / 0 / 0 / 0
foto a mezzitoni (grigio piatto medio, caso peggiore) 0 / 0 / 0 / 0

Nemmeno un modulo, in qualsiasi stile, a qualsiasi apertura, inclusa l’immagine del caso peggiore. Il codice foto e il codice semplice presentano al lettore gli stessi valori. La correzione d’errore è intatta e ancora completamente disponibile.

# Il costo: circa un passo di sfocatura

La correzione d’errore intatta non significa identico. Il dither cambia davvero l’aspetto dei pixel tra i centri, e una fotocamera gravemente fuori fuoco media quei pixel insieme. Quindi abbiamo spinto ogni stile fino a quando si è rotto:

Stile Massimo downscale ancora decodificato Massimo raggio di sfocatura ancora decodificato
codice semplice (controllo) 6 px
foto a mosaico 5 px
foto a mezzitoni (grigio piatto medio) 6 px
foto a mezzitoni (gradiente) 4 px

Il downscaling — il codice renderizzato più piccolo del nativo — è identico in ogni stile: ognuno ha sopravvissuto a 3× e ognuno ha fallito per la prima volta a 4×. (Il downscaling nearest-neighbour non è monotonico — alcuni si sono decodificati di nuovo a 5×, dove la griglia di campionamento si è allineata meglio — quindi la figura che riportiamo è il primo fallimento, non l’ultimo successo.)

La sfocatura è dove emerge il costo, e solo per lo stile a mezzitoni sull’immagine più difficile: un terzo meno margine rispetto al controllo semplice. I mezzitoni di un campo grigio piatto medio corrispondono esattamente al codice semplice, quindi questo è un costo dipendente dall’immagine piuttosto che una proprietà della tecnica. In termini semplici: un codice foto ha una tolleranza di fuoco inferiore di circa uno step rispetto a un codice semplice. È un costo reale e modesto, ed è la risposta onesta a “si scansionano?” — non “sì” e non “no”, ma “sì, con una tolleranza di fuoco inferiore di circa uno step”.

Lo stile a mosaico si colloca tra i due, il che ha senso: disegna solo i moduli, quindi c’è meno inchiostro tra i centri che una sfocatura possa spalmare.

# Perché la densità è il requisito reale

La prima versione di questa funzione era tecnicamente corretta ma l’aspetto era sbagliato, per una ragione degna di conoscenza se usi qualsiasi strumento foto-QR:

https://mqr.sh/AB12CD a correzione d’errore H — che è quello che un codice foto forza — si codifica in una griglia di 29 moduli. Sono 841 celle per una fotografia intera, e il risultato si legge come rumore piuttosto che come un’immagine: le persone concludono che la tecnica non funziona quando il vero problema è che non c’è abbastanza spazio per l’immagine. Forzare un simbolo più denso lo risolve:

Versione minima Simbolo Moduli totali Celle disponibili per l’immagine
nessuno (quello che il payload richiede) v3 29 841
8 v8 49 2.401
12 (il nostro predefinito) v12 65 4.225

Cinque volte le celle della versione 12. Ogni strumento image-QR serio forza questo per lo stesso motivo, e la versione minima non deve mai scendere al di sotto di quella che il payload richiede — i dati vincono sempre sulla densità.

La conseguenza per la stampa è diretta, ed è il motivo per cui questo articolo si trova nello stesso cluster di quanti moduli il tuo payload ha bisogno: un codice 65-modulo ha bisogno di 73 moduli-larghezze di carta compresa la sua zona silenziosa. Con un modulo di 0,5 mm è 36,5 mm — quindi un codice foto è un poster, un menu, un pannello di imballaggio o uno schermo. Non è un biglietto da visita.

# Cosa non copre il test

Affermato chiaramente, perché un benchmark con limiti non dichiarati è peggio di nessuno:

  • Un decoder, pixel sintetici. jsQR è un decoder reale ma non è una pipeline di fotocamera del telefono, e l’input è un output reso pulito piuttosto che una fotografia di una cosa stampata.
  • Niente stampa, niente abbagliamento, niente prospettiva, niente accartocciamento. Ognuno di questi ha un costo in margine, e il costo differisce tra un codice foto e un codice semplice.
  • Nessun colore. Questi sono codici in bianco e nero a 1 bit.
  • La nostra stessa implementazione. Questo non dice nulla sui codici QR foto o AI-art di altri. Se sovrascrivono moduli e si affidano alla correzione d’errore per riparare il danno, si comporteranno molto diversamente, e il test equo sarebbe quello sopra eseguito su di loro.

# Quando usare uno

  • Sì: poster, menu sotto vetro, pannelli di imballaggio, schermi, grafica espositiva, qualsiasi cosa stampata in grande.
  • Probabilmente: una copertina di album, una sovraccoperta di libro, un’etichetta di prodotto grande — testalo.
  • No: biglietti da visita, volantini di carta che vengono piegati, qualsiasi cosa sotto circa 35 mm, qualsiasi cosa scansionata con cattiva luce a distanza.

E se quello che vuoi non è una fotografia intera ma il tuo logo nel mezzo di un codice altrimenti ordinario, è una tecnica diversa con un budget molto più piccolo — abbiamo misurato quanto grande può essere un logo anche, e la risposta è più piccola di quello che pensa Internet.

I codici foto di MostlyQR sono l’implementazione misurata qui. Lo stesso nucleo dithering funziona nell’anteprima del browser e nel PNG reso dal server, quindi quello che vedi nel builder è quello che ottieni dalla stampa. La nota del builder afferma “meglio su schermi e stampe grandi — un codice foto è più difficile da leggere su carta piccola o accartocciata”, che dopo questi numeri sembra la cosa giusta da dire.

Domande frequenti

I codici QR fotografici si leggono in modo affidabile?

Nei nostri test, sì — con un costo misurabile. Leggendo i nostri codici fotografici come farebbe un lettore conforme, calcolando la media di un'apertura circolare al centro di ogni modulo, nessuno dei 4.225 moduli è stato letto male a nessuna apertura che abbiamo testato. Hanno tollerato lo stesso ridimensionamento 3x di un codice standard. L'unica differenza misurata era la sfocatura, e solo sull'immagine più difficile: il controllo standard ha sopportato un raggio di sfocatura di 6 pixel, lo stile mosaico 5 e lo stile mezzitoni 4 su una sfumatura (6 su grigio piatto). Questo è più o meno un passo della messa a fuoco della fotocamera, non una categoria diversa di affidabilità.

I codici QR artistici esauriscono la correzione degli errori?

Molte implementazioni lo fanno, ed è per questo che la tecnica ha una cattiva reputazione. La nostra no. Invece di sovrascrivere interi moduli con dati di immagine e fare affidamento su Reed-Solomon per riparare la differenza, forza un punto al centro di ogni modulo al valore richiesto dall'encoder e diffonde l'errore risultante nei sub-pixel circostanti. L'immagine viene pagata con lo spazio tra i centri dei moduli, quindi il budget completo di correzione degli errori è ancora disponibile per il mondo reale — per pieghe, riflessi e un dito sull'angolo.

Posso stampare un codice QR fotografico su un volantino?

No, non lo faremmo. Il nostro builder dice "meglio su schermi e stampe di grandi dimensioni", e lo pensiamo davvero: un codice fotografico ha bisogno di un simbolo denso (65 moduli attraverso il test qui sotto, contro 29 per lo stesso collegamento breve non forzato), quindi a dimensioni di volantino ogni modulo diventa molto piccolo molto velocemente. Poster, menu sotto vetro, pannelli di imballaggio e schermi sono i posti giusti. Un volantino di carta che viene piegato in una tasca no.

Perché il mio codice QR fotografico assomiglia a rumore invece di un'immagine?

Quasi sempre perché il simbolo è troppo grossolano. Un collegamento breve con correzione degli errori H si codifica in una griglia di 29 moduli — 841 celle, troppo poche per contenere un'immagine riconoscibile, quindi l'immagine non ha dove stare. Forzare un simbolo più denso lo risolve: la versione 8 dà 49 moduli e la versione 12 dà 65 (4.225 celle, cinque volte di più), e il nostro builder predefinisce la versione 12 per i codici fotografici esattamente per questo motivo. I dati vincono sempre, quindi un payload più lungo ottiene ancora un simbolo più grande.

Fonti
  1. jsQR — the decoder used in the test
  2. The published description of the dithered-QR technique this implementation was built from
  3. QR Code standardization (ISO/IEC 18004) — Denso Wave
Altro in questo argomento