MostlyQR

Scannen fotocodes QR echt? Onze decode-testresultaten

Een QR-code die een foto is, klinkt alsof het slechter moet scannen. We hebben de onze gemeten met een echte decoder — onscherp, verkleind en gelezen zoals een lezer leest.

Max Čelar··Afdrukken & scannen

Wat is hier nieuw: Decode-testresultaten voor onze eigen foto-QR-implementatie, gemeten met jsQR tegen een gewone controlecode: nul van de 4.225 modules verkeerd gelezen bij elke lezersopening van 0,10 tot 0,45 modules, identieke 3x downscale-tolerantie en een vervagingstolerantie van 4-5 pixels tegen de gewone code's 6 — de specifieke, kleine kostprijs van het omzetten van een code in een afbeelding.

Een QR-code die een foto is, ziet eruit alsof het een afweging moet zijn: mooi, maar je verliest scans. Die intuïtie klopt voor de meeste implementaties en klopt niet voor hoe het hoeft te werken — en “wat scans” is geen getal dat iemand publiceert.

Hier zijn die van ons, uit de testsuite die moest slagen voordat we de functie zouden uitrollen.

# Wat een fotografie-QR-code is, en wat niet

Twee heel verschillende dingen worden hetzelfde genoemd.

AI QR-kunst — het diffusiemodel-soort — genereert een afbeelding geconditioneerd op een QR-code en hoopt dan dat het resultaat nog steeds decodeert. Soms doet het dat. De foutmodus is onzichtbaar totdat iemand in een donkere bar er met een drie jaar oud telefoon naar wijst.

Een geditheringde fotografie-code is de andere benadering en degene die hier gemeten wordt. De afbeelding wordt gereduceerd tot één bit — zwart of wit per sub-pixel — door foutdiffusie, met één cruciale beperking: op elk modulemiddelpunt wordt de dithering geforceerd naar de waarde die de encoder eist, en de fout die deze forcing creëert (tot ongeveer 95% van een pixels waarde, voor een witte module in een donker gebied) wordt naar buiten geduwd in de naburige sub-pixels die nog niet zijn drempelwaarde. De afbeelding is gemaakt van de ruimte tussen de modulemiddelpunten.

Dat onderscheid is het hele artikel. Dit betekent dat de code geen foutcorrectie aan decoratie besteedt: elk module draagt nog steeds de waarde van de encoder, dus de volledige redundantie is intact voor vouwen, glans, een duim over één hoek — de dingen waarvoor foutcorrectie werkelijk bestaat.

# De test

Alles hieronder is reproduceerbaar uit onze repository, offline:

  • Encoder: onze eigen packages/qr, foutcorrectie H, dichtheidsvloer minVersion: 12 → symboolversie 12, 65 modules breed, 10 pixels per module, 4-module stille zone.
  • Stijlen: halftone (een echte geditheringde foto) en mozaïek (alleen de modules worden getekend, grootte naar lokale duisternis), tegen een gewone ongeditheringde code als controle — dezelfde payload, dezelfde versie, dezelfde schaal.
  • Afbeeldingen: procedurele testafbeeldingen, inclusief een diagonaal verloop (elk toon verschijnt) en vlak middelgrijs, wat het ergste geval is omdat elke geforceerde stip het oneens is met zijn omgeving.
  • Decoder: jsQR 1.4.0, een echte QR-decoder, gegeven de werkelijke pixels.
  • Degradaties: dichtst-nabuurman schaling, doos-blur gevolgd door her-drempeling (een camera zonder focus), en een apertuur-lezing.

De suite dit is gebouwd op is packages/qr/test/dither.test.js. Het liep 39/39 geslaagd op 2026-08-31, en een code die niet decodeert is een mislukte test in plaats van een waarschuwing — die regel is waarom de functie in deze vorm bestaat. De tabellen hieronder worden afgedrukt door draft/mostlyqr/seo/benchmarks/photo.js, die dezelfde stijlen door dezelfde degradaties loopt; niets hier is overgeschreven.

# Nul fout gelezen modules, bij elke apertuur

De meting die het meest uitmaakt is niet “decodeert het” maar “zag de lezer de juiste waarde in elke cel”, omdat een code die alleen decodeert omdat foutcorrectie het redde is een code die zal falen als iets anders mis gaat.

Een conforme lezer bemonstert niet één pixel per module. Het middelt een kleine circulaire apertuur op elk modulemiddelpunt en drempelt dat gemiddelde. Dus we deden hetzelfde, bij apertuurstralen van 0.10, 0.25, 0.36 en 0.45 modules (0.5 zou het hele module zijn), en telden hoeveel van de 4.225 modules fout zouden worden gelezen:

Stijl Fout gelezen modules, apertuur 0.10 / 0.25 / 0.36 / 0.45
gewone code (controle) 0 / 0 / 0 / 0
mozaïek foto 0 / 0 / 0 / 0
halftone foto (verloop) 0 / 0 / 0 / 0
halftone foto (vlak middelgrijs, ergste geval) 0 / 0 / 0 / 0

Niet één module, in enige stijl, bij enige apertuur, inclusief de ergste-geval afbeelding. De fotografie-code en de gewone code presenteren de lezer met dezelfde waarden. De foutcorrectie blijft intact en is nog volledig beschikbaar.

# Wat het kost: ongeveer één stap blur

Intacte foutcorrectie betekent niet identiek. De dithering verandert wel wat de pixels zien tussen de middelpunten, en een slecht scherp gestelde camera middelt die pixels samen. Dus we pushen elke stijl totdat het brak:

Stijl Max schaling-naar-beneden die nog decodeert Max blur-straal die nog decodeert
gewone code (controle) 6 px
mozaïek foto 5 px
halftone foto (vlak middelgrijs) 6 px
halftone foto (verloop) 4 px

Downscaling — de code weergegeven kleiner dan native — is identiek over elke stijl: elk overleefde 3× en elk faalde eerst bij 4×. (Dichtst- nabuurman schaling is niet monotoon — sommige ervan decodeert opnieuw bij 5×, waar het bemonsteringsrooster gelukkiger landde — dus het getal dat we rapporteren is de eerste fout, niet het laatste succes.)

Defocus is waar de kosten verschijnen, en alleen voor de halftone stijl op de hardste afbeelding: een derde minder marge dan de gewone controle. De halftone van een vlak middelgrijs veld kwam precies overeen met de gewone code, dus dit is afhankelijk van de afbeelding kosten in plaats van een eigenschap van de techniek. In gewone bewoording: een fotografie-code heeft de camera nodig om ongeveer één stap dichter in focus te zijn dan een gewone code. Dat is echte kosten en het is een kleine, en het is het eerlijke antwoord op “scannen ze?” — niet “ja” en niet “nee” maar “ja, met ongeveer één stap minder focustolerrantie”.

De mozaïekstijl zit tussen de twee, wat logisch is: het trekt alleen de modules, dus er is minder inkt tussen de middelpunten voor een blur om uit te smeren.

# Waarom dichtheid is de echte vereiste

De eerste versie van deze functie was technisch correct en zag er verkeerd uit, om een reden die het waard is om te weten als je enig fotografie-QR-gereedschap gebruikt:

https://mqr.sh/AB12CD op foutcorrectie H — wat een fotografie-code forceert — codeert naar een 29-module rooster. Dat is 841 cellen voor een hele afbeelding, en het resultaat ziet er als ruis uit in plaats van als een afbeelding: mensen concluderen dat de techniek verbroken is als het echte probleem is dat er nergens voor de afbeelding is om te wonen. Het forceren van een dichter symbool verhelpt het:

Minimale versie Symbool Modules breed Cellen beschikbaar voor de afbeelding
geen (wat de payload nodig heeft) v3 29 841
8 v8 49 2.401
12 (onze standaard) v12 65 4.225

Vijfmaal de cellen bij versie 12. Elk serieus afbeelding-QR-gereedschap forceert dit omhoog om dezelfde reden, en de vloer mag nooit een symbool kleiner maken dan wat de payload werkelijk nodig heeft — gegevens winnen altijd van dichtheid.

De gevolg voor afdrukken is direct, en het is de reden waarom dit artikel in dezelfde cluster zit als hoeveel modules je payload nodig heeft: een 65-module code heeft 73 module-breedtes papier nodig inclusief de stille zone. Bij een 0.5mm module dat is 36.5mm — dus een fotografie-code is een poster, een menukaart, een verpakkingspaneel of een scherm. Het is geen visitekaartje.

# Wat de test niet dekt

Rechtuit gezegd, omdat een benchmark met onverklaarde limieten erger is dan geen:

  • Één decoder, synthetische pixels. jsQR is een echte decoder maar het is niet een camerastapel van een telefoon, en de invoer is schone weergegeven uitvoer in plaats van een foto van een afgedrukt ding.
  • Geen afdruk, geen glans, geen perspectief, geen verkreukeling. Elk van deze kost marge, en ze kosten een fotografie-code en een gewone code anders.
  • Geen kleur. Dit zijn 1-bits zwart-witte codes.
  • Onze eigen implementatie. Niets hier zegt iets over iemands anders fotografie of AI-art QR-codes. Als ze modules overschrijven en foutcorrectie gebruiken om de schade te repareren, zullen ze zich heel anders gedragen dan dit, en de eerlijke test is de bovengenoemde uitgevoerd tegen hen.

# Wanneer er één gebruiken

  • Ja: posters, menu’s onder glas, verpakkingspanelen, schermen, tentoonstellingsgrafiek, alles groot afgedrukt.
  • Waarschijnlijk: een albumhoes, een boekcover, een groot productlabel — test het.
  • Nee: visitekaartjes, papieren flyers die worden gevouwen, alles onder ongeveer 35mm, alles gescand in slecht licht op afstand.

En als wat je wilt niet een hele afbeelding is maar je logo in het midden van een anderszins normale code, dat is een ander techniek met een veel kleiner budget — we hebben gemeten hoe groot een logo kan worden ook, en het antwoord is kleiner dan het internet denkt.

MostlyQR’s fotografie-codes zijn de implementatie gemeten hier. Dezelfde dithering-kern loopt in de browserpreview en in de server-weergegeven PNG, dus wat je in de builder ziet is wat de printer krijgt. De eigen opmerking van de builder zegt “best op schermen en grote afdrukken — een fotografie-code is moeilijker te lezen op klein of verkreukeld papier”, wat na deze nummers lijkt op het juiste voor te zeggen.

Veelgestelde vragen

Scannen fotocodes QR betrouwbaar?

In onze eigen tests ja — met één meetbare kostprijs. Door onze fotocodes op dezelfde manier te lezen als een conform lezer, door een cirkelvormige opening op elk modulecentrum te middelen, werd niet één van de 4.225 modules verkeerd gelezen bij elke opening die we hebben getest. Ze verdroegen dezelfde 3x downscale als een gewone code. Het enige gemeten verschil was defocus, en alleen op de moeilijkste afbeelding: de gewone code hield 6 pixels vervagingsradius vol, de mozaïekstijl 5 en de halftone-stijl 4 op een verloop (6 op plat grijs). Dat is ruwweg één stap van camerafocus, niet een ander betrouwbaarheidscategorie.

Gebruiken kunstzinnige QR-codes de foutcorrectie op?

Veel implementaties doen dat, wat verklaart waarom de techniek een slechte reputatie heeft. De onze niet. In plaats van hele modules met afbeeldingsgegevens te overschrijven en op Reed-Solomon te vertrouwen om het verschil te herstellen, dwingt het een stip op elk modulecentrum naar de waarde die de encoder eist en verspreidt de resulterende fout naar de omringende subpixels. De afbeelding wordt betaald uit de ruimte tussen modulecentra, dus het volledige foutcorrectiebudget is nog steeds beschikbaar voor de echte wereld — voor vouwen, glans en een duim over de hoek.

Kan ik een fotocode QR op een flyer afdrukken?

Dat zouden we niet doen. Onze eigen builder zegt "het beste op schermen en grote afdrukken", en we menen het: een fotocode heeft een dicht symbool nodig (65 modules in de test hieronder, tegen 29 voor dezelfde korte koppeling ongedwongen), dus bij flyergrootte wordt elke module heel snel erg klein. Posters, menu's onder glas, verpakkingspanelen en schermen zijn de juiste plaatsen. Een papieren flyer die in een zak wordt gevouwen, is niet.

Waarom ziet mijn fotocode QR eruit als ruis in plaats van een afbeelding?

Bijna altijd omdat het symbool te grof is. Een korte koppeling bij foutcorrectie H codeert naar een rooster van 29 modules — 841 cellen, veel te weinig voor een herkenbare afbeelding, dus de afbeelding heeft geen plaats. Een dichter symbool afdwingen helpt: versie 8 geeft 49 modules en versie 12 geeft 65 (4.225 cellen, vijf keer zoveel), en onze builder heeft versie 12 als standaard voor fotocodes om precies deze reden. Gegevens winnen altijd, dus een langere payload krijgt nog steeds een groter symbool.

Bronnen
  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
Meer in dit onderwerp