MostlyQR

Scannen Foto-QR-Codes tatsächlich? Unsere Decode-Test-Ergebnisse

Ein QR-Code, der ein Foto ist, klingt nach schlechterer Lesbarkeit. Wir haben unseren mit einem echten Decoder gemessen — verschwommen, verkleinert und lesbar wie ein Reader liest.

Max Čelar··Druck & Scannen

Was ist hier neu: Dekodierungstestergebnisse unserer Photo-QR-Implementierung, gemessen mit jsQR gegen einen einfachen Kontrollcode: null von 4.225 Modulen fehlerhaft gelesen bei jeder Leserapertur von 0,10 bis 0,45 Modulen, identische 3x-Herunterskalierungstoleranz und eine Unschärfetoleranz von 4–5 Pixeln gegen die 6 des einfachen Codes – der spezifische, kleine Preis der Umwandlung eines Codes in ein Bild.

Ein QR-Code, der ein Foto ist, sieht aus, als müsse man einen Kompromiss eingehen: hübsch, aber man verliert einige Scans. Diese Intuition stimmt bei den meisten Implementierungen und ist falsch bei der Art, wie es funktionieren muss – und “einige Scans” ist keine Zahl, die irgendjemand veröffentlicht.

Hier sind unsere Ergebnisse aus der Test-Suite, die bestanden werden musste, bevor wir die Funktion überhaupt versenden würden.

# Was ein Photo-QR-Code ist und was nicht

Zwei sehr unterschiedliche Dinge werden mit dem gleichen Namen bezeichnet.

KI-QR-Kunst – die Art basierend auf Diffusionsmodellen – erzeugt ein Bild unter Bedingungen eines QR-Codes und hofft, dass das Ergebnis immer noch dekodiert werden kann. Manchmal geht das. Der Fehlermodus ist unsichtbar, bis jemand in einer dunklen Bar damit ein drei Jahre altes Telefon anvisiert.

Ein Photo-Code mit Fehlerdiffusion ist der andere Ansatz und der hier gemessene. Das Bild wird auf ein Bit reduziert – schwarz oder weiß pro Sub-Pixel – durch Fehlerverteilung, mit einer entscheidenden Einschränkung: bei jedem Modulzentrum wird das Fehlerdiffusions-Ergebnis erzwungen zu dem Wert, den der Encoder verlangt, und der Fehler, den dieses Erzwingen erzeugt (bis zu etwa 95 % eines Pixel-Wertes, für ein weißes Modul in einer dunklen Region), wird in die benachbarten Sub-Pixel geschoben, die noch nicht geschwellt wurden. Das Bild besteht aus dem Raum zwischen den Modulzentren.

Dieser Unterschied ist der ganze Artikel. Das bedeutet, dass der Code seine Fehlerkorrektur nicht für Dekoration verwendet: Jedes Modul trägt immer noch den Wert des Encoders, sodass die volle Redundanz für Falten, Blendung, einen Daumen über einer Ecke – die Dinge, für die Fehlerkorrektur wirklich existiert – intakt bleibt.

# Der Test

Alles Folgende ist reproduzierbar aus unserem Repository offline:

  • Encoder: unser eigener packages/qr, Fehlerkorrektur H, Dichte-Untergrenze minVersion: 12 → Symbol Version 12, 65 Module quer, 10 Pixel pro Modul, 4-Modul-Ruhezone.
  • Stile: Halbton (ein wirklich mit Fehlerverteilung bearbeitetes Foto) und Mosaik (nur die Module werden gezeichnet, größenabhängig von der lokalen Dunkelheit), gegen einen gewöhnlichen unbearbeiteten Code als Kontrolle – gleiches Payload, gleiche Version, gleiche Skalierung.
  • Bilder: prozedurale Test-Bilder, einschließlich eines diagonalen Gradienten (jede Nuance erscheint) und gleichmäßiges Mittelgrau, das der schlimmste Fall ist, weil jedes erzwungene Modul mit seiner Umgebung nicht übereinstimmt.
  • Decoder: jsQR 1.4.0, ein wirklicher QR-Decoder, mit den tatsächlichen Pixeln.
  • Verschlechterungen: Nearest-Neighbor-Herunterskalierung, Kastenunschärfe gefolgt von erneuter Schwellenwertbildung (eine defokussierte Kamera) und ein Apertur-Lesezugriff.

Die Suite, auf der dies aufgebaut ist, ist packages/qr/test/dither.test.js. Sie lief 39/39 bestanden am 2026-08-31, und ein Code, der nicht dekodiert wird, ist ein fehlgeschlagener Test statt einer Warnung – diese Regel ist der Grund, warum die Funktion in der Form existiert, in der sie ist. Die folgenden Tabellen werden von draft/mostlyqr/seo/benchmarks/photo.js gedruckt, die die gleichen Stile durch die gleichen Verschlechterungen führt; nichts hier wurde abgeschrieben.

# Null Fehler beim Lesen von Modulen bei jeder Apertur

Die Messung, die am meisten zählt, ist nicht “wurde es dekodiert” sondern “sah der Leser den richtigen Wert in jeder Zelle”, weil ein Code, der nur dekodiert, weil die Fehlerkorrektur ihn rettete, ein Code ist, der fehlschlagen wird, wenn etwas anderes schiefgeht.

Ein konformer Leser tastet nicht einen Pixel pro Modul ab. Er mittelt eine kleine kreisförmige Apertur bei jedem Modulzentrum und schwellt diesen Durchschnitt. Also taten wir dasselbe, bei Apertur-Radien von 0,10, 0,25, 0,36 und 0,45 Modulen (0,5 würde das gesamte Modul sein), und zählten, wie viele der 4.225 Module falsch gelesen würden:

Stil Falsch gelesene Module, Apertur 0,10 / 0,25 / 0,36 / 0,45
gewöhnlicher Code (Kontrolle) 0 / 0 / 0 / 0
Mosaik-Foto 0 / 0 / 0 / 0
Halbton-Foto (Gradient) 0 / 0 / 0 / 0
Halbton-Foto (flaches Mittelgrau, schlimmster Fall) 0 / 0 / 0 / 0

Kein Modul, in keinem Stil, bei keiner Apertur, einschließlich des Bildes im schlimmsten Fall. Der Photo-Code und der gewöhnliche Code präsentieren dem Leser die gleichen Werte. Die Fehlerkorrektur ist unversehrt und immer noch vollständig verfügbar.

# Was es kostet: etwa ein Schritt der Unschärfe

Unversehrte Fehlerkorrektur bedeutet nicht identisch. Die Fehlerverteilung ändert tatsächlich, wie die Pixel zwischen den Zentren aussehen, und eine schlecht fokussierte Kamera mittelt diese Pixel zusammen. Also drückten wir jeden Stil, bis er brach:

Stil Max. Herunterskalierung, die noch dekodiert Max. Unschärferadius, der noch dekodiert
gewöhnlicher Code (Kontrolle) 6 px
Mosaik-Foto 5 px
Halbton-Foto (flaches Mittelgrau) 6 px
Halbton-Foto (Gradient) 4 px

Herunterskalierung – der Code wurde kleiner als nativ dargestellt – ist identisch über alle Stile: jeder überlebte 3× und jeder scheiterte zunächst bei 4×. (Nearest-Neighbor-Herunterskalierung ist nicht monoton – einige von ihnen dekodierten erneut bei 5×, wo das Sampling-Gitter zufällig günstiger landete – also ist die Zahl, die wir berichten, der erste Fehler, nicht der letzte Erfolg.)

Unschärfe ist, wo die Kosten auftauchen, und nur für den Halbton-Stil auf dem schwierigsten Bild: um ein Drittel weniger Spielraum als die gewöhnliche Kontrolle. Der Halbton eines flachen Mittelgrau-Feldes stimmte genau mit dem gewöhnlichen Code überein, also ist dies ein bildabhängiger Kostenfaktor statt einer Eigenschaft der Technik. In einfachen Worten: Ein Photo-Code benötigt, dass die Kamera etwa einen Schritt näher an Fokus ist als ein gewöhnlicher Code. Das ist ein echter Kostenfaktor und er ist klein, und es ist die ehrliche Antwort auf “Scannen sie?” – nicht “ja” und nicht “nein” sondern “ja, mit etwa einem Schritt weniger Fokus-Toleranz”.

Der Mosaik-Stil sitzt dazwischen, was sinnvoll ist: er zeichnet nur die Module, also gibt es weniger Tinte zwischen den Zentren zum Verschmieren durch Unschärfe.

# Warum Dichte die echte Anforderung ist

Die erste Version dieser Funktion war technisch korrekt und sah falsch aus, aus einem Grund, der es wert ist zu wissen, wenn man irgendeinen Photo-QR-Tool verwendet:

https://mqr.sh/AB12CD bei Fehlerkorrektur H – was ein Photo-Code erzwingt – kodiert zu einem 29-Modul-Gitter. Das sind 841 Zellen für ein ganzes Foto, und das Ergebnis liest sich als Lärm statt als Bild: Leute schließen, dass die Technik kaputt ist, wenn das echte Problem ist, dass es nirgends für das Bild zu leben gibt. Das Erzwingen eines dichteren Symbols behebt es:

Mindestversion Symbol Module quer Zellen verfügbar für das Bild
keine (was das Payload braucht) v3 29 841
8 v8 49 2.401
12 (unser Standard) v12 65 4.225

Fünfmal die Zellen bei Version 12. Jeder ernsthafte Image-QR-Tool erzwingt dies aus dem gleichen Grund, und der Boden muss niemals ein Symbol unter das reduzieren, was das Payload wirklich braucht – Daten gewinnen über Dichte, immer.

Die Konsequenz für den Druck ist direkt, und sie ist der Grund, warum dieser Artikel in der gleichen Gruppe wie wie viele Module dein Payload braucht sitzt: Ein 65-Modul-Code braucht 73 Modul-Breiten Papier einschließlich seiner Ruhezone. Bei einem 0,5-mm-Modul sind das 36,5 mm – also ist ein Photo-Code ein Poster, ein Menü, ein Verpackungs-Panel oder ein Bildschirm. Es ist keine Visitenkarte.

# Was der Test nicht abdeckt

Offen gesagt, weil ein Benchmark mit unstatierten Grenzen schlimmer ist als keiner:

  • Ein Decoder, synthetische Pixel. jsQR ist ein wirklicher Decoder, aber er ist kein Handy-Kamera-Stack, und die Eingabe ist saubere gerenderte Ausgabe statt einer Fotografie eines gedruckten Dings.
  • Kein Druck, keine Blendung, keine Perspektive, kein Knittern. Jede davon kostet Spielraum, und sie kosten einen Photo-Code und einen gewöhnlichen Code unterschiedlich.
  • Keine Farbe. Dies sind 1-Bit schwarz und weiß Codes.
  • Unsere eigene Implementierung. Nichts hier sagt etwas über die Photo- oder KI-Art-QR-Codes von irgendjemand anderem. Wenn sie Module überschreiben und sich auf Fehlerkorrektur lehnen, um den Schaden zu reparieren, verhalten sie sich nichts wie dies, und der faire Test ist der oben Durchgelaufene gegen sie.

# Wann man eine verwendet

  • Ja: Poster, Menüs unter Glas, Verpackungs-Panels, Bildschirme, Ausstellungs-Grafiken, alles groß gedruckt.
  • Wahrscheinlich: ein Album-Titelbild, ein Bucheinband, ein großes Produkt-Label – testen Sie es.
  • Nein: Visitenkarten, Papier-Flugblätter, die gefaltet werden, alles unter etwa 35 mm, alles, das in schlechtem Licht in der Ferne gescannt wird.

Und wenn das, was Sie wollen, nicht ein ganzes Foto ist, sondern Ihr Logo in der Mitte eines ansonsten gewöhnlichen Codes, das ist eine andere Technik mit einem viel kleineren Budget – wir maßen wie groß ein Logo werden kann auch, und die Antwort ist kleiner als das Internet denkt.

MostlyQR’s Photo-Codes sind die Implementierung, die hier gemessen wird. Der gleiche Fehlerdiffusions-Kern läuft in der Browser-Vorschau und im Server-gerenderten PNG, also sehen Sie im Builder, was der Drucker bekommt. Die Notiz des Builders selbst sagt “am besten auf Bildschirmen und großen Drucken – ein Photo-Code ist ein schwierigerer Lesezugriff auf kleinen oder zerknitterten Papieren”, was nach diesen Zahlen das richtige Ding zu sagen scheint.

Häufig gestellte Fragen

Lassen sich Foto-QR-Codes zuverlässig scannen?

In unseren eigenen Tests, ja — mit einem messbaren Nachteil. Beim Lesen unserer Fotocodes wie ein konformer Reader, durch Mittelung einer kreisförmigen Apertur bei jeder Modulmitte, wurde keines der 4.225 Module falsch gelesen. Sie vertragen die gleiche 3x Herunterskalierung wie ein einfacher Code. Der einzige gemessene Unterschied war Unschärfe, und nur beim schwierigsten Bild: die einfache Variante überstand einen 6-Pixel-Blur-Radius, der Mosaikstil 5 und der Halbtonenstil 4 auf Farbverlauf (6 auf einfarbigem Grau). Das ist etwa ein Fokussierungsschritt der Kamera, keine andere Zuverlässigkeitskategorie.

Verbrauchen künstlerische QR-Codes die Fehlerkorrektur?

Viele Implementierungen tun das, weshalb die Technik einen schlechten Ruf hat. Unsere nicht. Anstatt ganze Module mit Bilddaten zu überschreiben und sich auf Reed-Solomon zu verlassen, um den Unterschied zu reparieren, erzwingt sie einen Punkt in der Mitte jedes Moduls auf den Wert, den der Encoder verlangt, und verteilt den resultierenden Fehler in die umgebenden Subpixel. Das Bild wird durch den Platz zwischen den Modulmitten ermöglicht, sodass das vollständige Fehlerkorrektur-Budget immer noch für die reale Welt vorhanden ist – für Falten, Blendung und einen Daumen über der Ecke.

Kann ich einen Foto-QR-Code auf einem Flyer drucken?

Wir würden nicht. Unser eigener Builder sagt "beste auf Bildschirmen und großen Drucken", und wir meinen es ernst: Ein Fotocode benötigt ein dichtes Symbol (65 Module in der Breite im Test unten, gegen 29 für denselben kurzen Link ungezwungen), also bei Flyer-Größe wird jedes Modul sehr schnell sehr klein. Plakate, Menüs unter Glas, Verpackungspaneele und Bildschirme sind die richtigen Plätze. Ein Papierflyer, der in eine Tasche gefaltet wird, ist es nicht.

Warum sieht mein Foto-QR-Code wie Rauschen aus statt wie ein Bild?

Fast immer liegt das daran, dass das Symbol zu grob ist. Ein kurzer Link mit error correction H wird zu einem 29-Modul-Raster kodiert — 841 Zellen, viel zu wenig, um ein erkennbares Bild zu tragen, daher hat das Bild keinen Platz. Ein dichteres Symbol behebt das: version 8 bietet 49 Module und version 12 bietet 65 (4.225 Zellen, fünfmal so viele), und unser Builder setzt standardmäßig version 12 für Fotocodes aus genau diesem Grund. Daten gewinnen immer, daher erhält eine längere Nutzlast immer noch ein größeres Symbol.

Quellen
  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
Mehr zu diesem Thema