Por qué un código QR artístico puede fallar si lo escaneas demasiado cerca
Todo el mundo espera que un código QR falle por ser demasiado pequeño, estar demasiado lejos o tener poca luz. Los códigos QR con foto tienen también el fallo contrario: pueden leerse perfectamente a distancia y fallar cuando acercas el teléfono del todo. Aquí está el porqué, con las cifras de la medición que lo descubrió.
Cómo decide un escáner qué es oscuro
Antes de que un lector pueda buscar módulos, tiene que convertir una fotografía en blanco y negro. Un umbral global no sobrevive a la iluminación real — una sombra sobre una esquina dejaría en negro medio código — así que la mayoría de decodificadores umbralizan localmente. ZXing, la biblioteca de código abierto de la que aún desciende una gran parte del escaneo de códigos de barras, divide la imagen en bloques de 8×8 píxeles, calcula el promedio de cada bloque, lo suaviza con los bloques vecinos y usa eso como umbral para esos 64 píxeles. Un píxel más oscuro que el umbral de su bloque es oscuro; el resto son claros. Para un código QR normal esto sale casi gratis. Sea cual sea el tamaño del bloque, un módulo es sólido, así que el promedio de un bloque está cerca del oscuro o cerca del claro, o queda entre los dos cuando el bloque cruza una frontera, y la decisión es fácil en cualquier caso. El esquema es robusto, barato y se ha copiado ampliamente, y precisamente por eso importa: el decodificador con el que se encuentra tu código impreso sobre el terreno bien puede ser uno que umbraliza así.
Qué cambia cuando los módulos están hechos de dithering
En un código con dithering, un módulo no es sólido. Es un pequeño núcleo forzado rodeado de píxeles que llevan la fotografía. Su promedio es correcto; su textura no es uniforme. Ahora considera la distancia de escaneo en la única unidad que importa: píxeles de cámara por módulo. Sostén el teléfono lo bastante lejos para que un módulo caiga sobre 5–8 píxeles, y un bloque de 8×8 cruza fronteras entre módulos. El promedio local es aproximadamente "este módulo y sus vecinos", el umbral queda entre oscuro y claro, y el código se lee. Acerca el teléfono para que un módulo cubra 20 píxeles, y un bloque de 8×8 cabe ahora entero dentro de un solo módulo. El promedio del bloque es la textura local de ese módulo. El umbral sigue al dithering en lugar de al módulo — y el decodificador recupera el moteado, no el código. Nada en la impresión ha cambiado; solo el número de píxeles del sensor que cubre cada módulo, y con él la relación entre el bloque del decodificador y el módulo. Por eso el fallo es una propiedad de la distancia y no del tamaño o la iluminación.
La cifra
Lo medimos mientras construíamos el modo foto de SWAPQR: tres longitudes de carga útil, seis tipos de imagen, ambas polaridades de color, tres ajustes de balance, siete distancias de escaneo simuladas, decodificados por dos bibliotecas independientes. La frontera es limpia. Con aproximadamente un 20% de cada módulo forzado — el ajuste cargado hacia la foto — y con aproximadamente un 44% forzado, los códigos se leían a 5–8 píxeles por módulo y los lectores de la clase ZXing se volvían poco fiables en primer plano, a 10–28 píxeles por módulo. Con aproximadamente un 60% forzado o más, se leían a todas las distancias. Cuando más de aproximadamente la mitad del área de un módulo se fuerza a sólido, cualquier bloque de 8×8 que caiga dentro del módulo aterriza mayoritariamente sobre píxeles sólidos, y el efecto desaparece. Por debajo de eso, no — y ningún ajuste del sesgo de tono lo arregla, porque un umbral local resta de todos modos el promedio local. Hacia el tono que inclines un módulo, un bloque que quede totalmente dentro de él medirá esa inclinación como su línea base y la eliminará al umbralizar. La única palanca que funciona es la proporción del módulo que es sólida, y por eso existe el control de balance.
Decodificadores más nuevos, y por qué eso no es el final
Los decodificadores más nuevos lo hacen mejor. En el mismo barrido, jsQR — que usa una estrategia de binarización distinta — leyó todas las configuraciones a todas las distancias, y los escáneres integrados en los teléfonos actuales son aún más fuertes. Pero "la mayoría de teléfonos se las arreglará" no es lo mismo que "todo lector lo hará", y un código impreso se encuentra con el lector que casualmente le apunte: un teléfono antiguo, un escáner de punto de venta, un quiosco, una aplicación que incluye una biblioteca derivada de ZXing. El balance cargado hacia la foto es por tanto honesto sobre su intercambio — el estudio señala que los escáneres de teléfonos modernos lo leen a cualquier distancia mientras que lectores más antiguos y simples pueden tener dificultades en primer plano extremo — y la decisión de dónde situarse en el balance depende de quién escaneará el código y desde qué distancia. También depende de dónde acaba naturalmente un teléfono: nadie sostiene el teléfono con el brazo extendido ante un póster, pero todo el mundo lo acerca del todo a una tarjeta de visita, y ese es el caso en que un código cargado hacia la foto y un lector más simple pueden encontrarse.
Qué hacer al respecto
Si el código va a un sitio donde la gente escanea desde una distancia normal — un póster, un caballete de mesa, una etiqueta de estante — los ajustes cargados hacia la foto están bien; a esa distancia un módulo cubre un puñado de píxeles de cámara y los bloques cruzan fronteras como deben. Si va a un sitio donde la gente pegará el teléfono — una pegatina pequeña, una tarjeta de visita, una pantalla — mueve el balance hacia Code, para que más de la mitad de cada módulo sea sólida y el efecto de primer plano desaparezca para todas las clases de lector. En cualquier caso, imprime uno y escanéalo con un teléfono. No una captura en un monitor: la pieza impresa real, al tamaño real, desde la distancia que la gente usará de verdad, e idealmente con más de un teléfono. SWAPQR muestra esta advertencia en el propio estudio y no en la letra pequeña, porque un código que falla sobre el terreno es peor que un código simple que funciona, y porque la solución es un deslizador y no una reimpresión si lo detectas antes de la tirada.
| Proporción forzada de cada módulo | Lejos / normal (5–8 px por módulo) | Primer plano (10–28 px por módulo) |
|---|---|---|
| ~20% (cargado hacia la foto) | Se lee | Lectores de clase ZXing poco fiables |
| ~44% (medio) | Se lee | Lectores de clase ZXing poco fiables |
| ~60% y más (cargado hacia el código) | Se lee | Se lee |
| Cualquier ajuste, clase jsQR y escáneres de teléfonos actuales | Se lee | Se lee en el mismo barrido |
Preguntas frecuentes
Mi teléfono lee el código con foto a cualquier distancia. ¿Por qué la advertencia?
Los escáneres de teléfonos actuales usan una binarización más fuerte y leyeron todas las configuraciones de nuestro barrido. La advertencia se refiere a lectores más antiguos o simples que umbralizan en bloques de 8×8, con los que un código impreso todavía puede encontrarse.
¿Por qué alejar el teléfono lo arregla?
Más lejos, cada módulo cubre menos píxeles de cámara, así que el bloque de 8×8 de un decodificador cruza varios módulos y su promedio queda entre oscuro y claro. De cerca, un bloque cae dentro de un solo módulo y sigue la textura del dithering.
¿Subir el sesgo de tono ayuda en primer plano?
No. Un umbral local resta el promedio local, así que cualquier inclinación uniforme dentro de un módulo se elimina al umbralizar. Solo la proporción del módulo que se fuerza a sólido cambia el resultado.
¿Qué balance es seguro para una tarjeta de visita?
Muévelo hacia Code, para que más de la mitad aproximadamente de cada módulo sea sólida. En ese punto los lectores de clase ZXing leen a todas las distancias en nuestra medición, y la imagen sigue siendo visible, solo más tenue.
Imprime el cuadrado una vez. Decide después adónde lleva. SWAPQR crea códigos QR estáticos gratis, con todas las opciones de estilo, sin cuenta y sin marca de agua. Un plan de pago convierte un código en dinámico: el cuadrado impreso sigue igual mientras tú cambias su destino, y ves cuántas veces se escaneó, por día y por dispositivo.
Crea un código QR en el navegador