MostlyQR

Os códigos QR de foto realmente funcionam? Nossos resultados de teste de decodificação

Um código QR que é uma fotografia parece que deve funcionar pior. Medimos o nosso com um decodificador real — desfocado, reduzido e lido da forma como um leitor lê.

Max Čelar··Impressão e escaneamento

O que há de novo aqui: Resultados de teste de decodificação para nossa própria implementação de código QR de foto, medidos com jsQR contra um código de controle simples: zero de 4.225 módulos lidos incorretamente em cada abertura de leitor de 0,10 a 0,45 módulos, tolerância de redução idêntica de 3x e tolerância de desfoque de 4-5 pixels em relação aos 6 do código simples — o custo específico e pequeno de transformar um código em uma imagem.

Um código QR que é uma fotografia parece que deve ser uma troca: bonito, mas você perde algumas leituras. Essa intuição está certa sobre a maioria das implementações e errada sobre como deve funcionar — e “algumas leituras” não é um número que ninguém publica.

Aqui estão as nossas, do conjunto de testes que tinha que passar antes de enviarmos a funcionalidade.

# O que é um código QR com foto e o que não é

Duas coisas muito diferentes recebem o mesmo nome.

Arte QR com IA — o tipo modelo de difusão — gera uma imagem condicionada a um código QR e depois espera que o resultado ainda decodifique. Às vezes funciona. O modo de falha é invisível até que alguém em um bar escuro aponte um telefone de três anos para ele.

Um código de foto pontilhado é a outra abordagem e a que é medida aqui. A imagem é reduzida a um bit — preto ou branco por subpixel — por difusão de erro, com uma restrição crucial: em cada centro do módulo a pontuação é forçada ao valor que o codificador exige, e o erro que o forçamento cria (até cerca de 95% do valor de um pixel, para um módulo branco em uma região escura) é empurrado para os subpixels vizinhos que ainda não foram limitados. A imagem é feita do espaço entre os centros dos módulos.

Essa distinção é o artigo inteiro. Significa que o código não gasta sua correção de erro em decoração: cada módulo ainda carrega o valor do codificador, então a redundância completa está intacta para rugas, brilho, um polegar sobre um canto — as coisas para as quais a correção de erro realmente existe.

# O teste

Tudo abaixo é reproduzível do nosso repositório, offline:

  • Codificador: nosso próprio packages/qr, correção de erro H, piso de densidade minVersion: 12 → versão de símbolo 12, 65 módulos de largura, 10 pixels por módulo, zona silenciosa de 4 módulos.
  • Estilos: halftone (uma fotografia verdadeiramente pontilhada) e mosaic (apenas os módulos são desenhados, dimensionados pela escuridão local), contra um código não pontilhado simples como controle — mesma carga útil, mesma versão, mesma escala.
  • Imagens: imagens de teste procedurais, incluindo um gradiente diagonal (cada tom aparece) e cinza médio plano, que é o pior caso porque cada ponto forçado discorda de seus arredores.
  • Decodificador: jsQR 1.4.0, um decodificador QR real, dado os pixels reais.
  • Degradações: escala de vizinho mais próximo, desfoque de caixa seguido por re-limiar (uma câmera desfocada) e uma leitura de abertura.

O conjunto em que isso é construído é packages/qr/test/dither.test.js. Foram 39/39 passando em 2026-08-31, e um código que falha em decodificar é um teste falhando em vez de um aviso — essa regra é por que a funcionalidade existe na forma que é. As tabelas abaixo são impressas por draft/mostlyqr/seo/benchmarks/photo.js, que executa os mesmos estilos pelas mesmas degradações; nada aqui foi transcrito.

# Zero módulos interpretados incorretamente, em cada abertura

A medição que mais importa não é “decodificou” mas “o leitor viu o valor correto em cada célula”, porque um código que decodifica apenas porque a correção de erro o resgatou é um código que falhará quando outra coisa der errado.

Um leitor em conformidade não amostra um pixel por módulo. Ele calcula a média de uma pequena abertura circular em cada centro do módulo e limita essa média. Então fizemos o mesmo, em raios de abertura de 0,10, 0,25, 0,36 e 0,45 módulos (0,5 seria o módulo inteiro), e contamos quantos dos 4.225 módulos seriam lidos incorretamente:

Estilo Módulos interpretados incorretamente, abertura 0,10 / 0,25 / 0,36 / 0,45
código simples (controle) 0 / 0 / 0 / 0
foto de mosaico 0 / 0 / 0 / 0
foto halftone (gradiente) 0 / 0 / 0 / 0
foto halftone (cinza médio plano, pior caso) 0 / 0 / 0 / 0

Nem um módulo, em nenhum estilo, em nenhuma abertura, incluindo a imagem do pior caso. O código de foto e o código simples apresentam ao leitor os mesmos valores. A correção de erro não é tocada e ainda está totalmente disponível.

# Qual é o custo: cerca de um passo de desfoque

Correção de erro não tocada não significa idêntica. A pontuação muda o que os pixels parecem entre os centros, e uma câmera muito fora de foco calcula a média desses pixels. Então empurramos cada estilo até quebrar:

Estilo Escala máxima de redução que ainda decodifica Raio de desfoque máximo que ainda decodifica
código simples (controle) 6 px
foto de mosaico 5 px
foto halftone (cinza médio plano) 6 px
foto halftone (gradiente) 4 px

A redução de escala — o código renderizado menor que o nativo — é idêntica em todos os estilos: cada um sobreviveu a 3× e cada um falhou primeiro em 4×. (A redução de escala de vizinho mais próximo não é monótona — alguns deles decodificaram novamente em 5×, onde a grade de amostragem caiu mais favoravelmente — então a figura que relatamos é a primeira falha, não o último sucesso.)

O desfoque é onde o custo aparece, e apenas para o estilo halftone no imagem mais difícil: um terço menos margem do que o controle simples. O halftone de um plano campo cinza médio correspondeu ao código simples exatamente, então este é um custo dependente de imagem em vez de uma propriedade da técnica. Em termos simples: um código de foto precisa que a câmera esteja cerca de um passo mais próxima de focada do que um código simples. Esse é um custo real e é um pequeno, e é a resposta honesta para “eles leem?” — não “sim” e não “não” mas “sim, com cerca de um passo menos tolerância de foco”.

O estilo de mosaico fica entre os dois, o que faz sentido: ele desenha apenas os módulos, então há menos tinta entre os centros para um desfoque desmantelar.

# Por que a densidade é o requisito real

A primeira versão dessa funcionalidade estava tecnicamente correta e parecia errada, por uma razão que vale a pena saber se você usar qualquer ferramenta de QR com foto:

https://mqr.sh/AB12CD com correção de erro H — que é o que um código de foto força — codifica para uma grade de 29 módulos. São 841 células para uma fotografia inteira, e o resultado parece ruído em vez de uma imagem: as pessoas concluem que a técnica é quebrada quando o problema real é que não há lugar para a imagem viver. Forçar um símbolo mais denso corrige:

Versão mínima Símbolo Módulos de largura Células disponíveis para a imagem
nenhuma (o que a carga útil precisa) v3 29 841
8 v8 49 2.401
12 (nosso padrão) v12 65 4.225

Cinco vezes as células na versão 12. Todas as ferramentas de imagem QR sérias forçam isso para o mesmo motivo, e o piso nunca deve encolher um símbolo abaixo do que a carga útil realmente precisa — dados vencem densidade, sempre.

A consequência para impressão é direta, e é a razão pela qual este artigo fica no mesmo cluster que quantos módulos sua carga útil precisa: um código de 65 módulos precisa de 73 larguras de módulo de papel incluindo sua zona silenciosa. Em um módulo de 0,5mm são 36,5mm — então um código de foto é um cartaz, um cardápio, um painel de embalagem ou uma tela. Não é um cartão de visita.

# O que o teste não cobre

Dito claramente, porque um benchmark com limites não declarados é pior que nenhum:

  • Um decodificador, pixels sintéticos. jsQR é um decodificador real mas não é uma pilha de câmera de telefone, e a entrada é saída renderizada limpa em vez de uma fotografia de algo impresso.
  • Sem impressão, sem brilho, sem perspectiva, sem amassamento. Cada um desses custa margem, e custam um código de foto e um código simples diferentemente.
  • Sem cor. Estes são códigos preto e branco de 1 bit.
  • Nossa própria implementação. Nada aqui diz nada sobre QR de foto ou códigos QR de arte de IA de ninguém. Se eles sobrescrevem módulos e contam com a correção de erro para reparar o dano, eles se comportarão nada como isso, e o teste justo é o acima executado contra eles.

# Quando usar um

  • Sim: cartazes, cardápios sob vidro, painéis de embalagem, telas, gráficos de exposição, qualquer coisa impressa grande.
  • Provavelmente: uma capa de álbum, uma jaqueta de livro, um grande rótulo de produto — teste.
  • Não: cartões de visita, panfletos de papel que são dobrados, qualquer coisa sob cerca de 35mm, qualquer coisa digitalizada com pouca luz à distância.

E se o que você quer não é uma fotografia inteira mas seu logo no meio de um código normal, essa é uma técnica diferente com um orçamento muito menor — medimos o tamanho que um logo pode atingir também, e a resposta é menor do que a internet pensa.

Os códigos de foto do MostlyQR são a implementação medida aqui. O mesmo núcleo de pontilhado é executado na visualização do navegador e no PNG renderizado pelo servidor, então o que você vê no construtor é o que a impressora recebe. A própria nota do construtor diz “melhor em telas e grandes impressões — um código de foto é uma leitura mais difícil em papel pequeno ou amassado”, que depois desses números parece ser a coisa certa para dizer.

Perguntas frequentes

Os códigos QR de foto funcionam com segurança?

Em nossos próprios testes, sim — com um custo mensurável. Ao ler nossos códigos de foto da maneira que um leitor compatível faz, fazendo a média de uma abertura circular em cada centro de módulo, nenhum dos 4.225 módulos foi lido incorretamente em qualquer abertura que testamos. Eles toleraram a mesma redução de 3x como um código simples. A única diferença medida foi o desfoque, e apenas na imagem mais difícil: o controle simples sobreviveu a um raio de desfoque de 6 pixels, o estilo mosaico de 5 e o estilo meio-tom de 4 em um gradiente (6 em cinza sólido). Isso é aproximadamente um passo de foco da câmera, não uma categoria diferente de confiabilidade.

Os códigos QR artísticos usam toda a correção de erros?

Muitas implementações fazem isso, é por isso que a técnica tem uma má reputação. A nossa não. Em vez de sobrescrever módulos inteiros com dados de imagem e confiar no Reed-Solomon para reparar a diferença, força um ponto em cada centro do módulo para o valor que o codificador exige e difunde o erro resultante para os sub-pixels circundantes. A imagem é paga com o espaço entre os centros dos módulos, portanto o orçamento completo de correção de erros ainda está lá para o mundo real — para vincos, brilho e um polegar sobre o canto.

Posso imprimir um código QR de foto em um panfleto?

Nós não faríamos. Nosso próprio gerador diz "melhor em telas e impressões grandes" e falamos sério: um código de foto precisa de um símbolo denso (65 módulos de largura no teste abaixo, contra 29 para o mesmo link curto não forçado), então no tamanho do panfleto cada módulo fica muito pequeno muito rapidamente. Cartazes, menus sob vidro, painéis de embalagem e telas são os locais certos. Um panfleto de papel que é dobrado em um bolso não é.

Por que meu código QR de foto parece ruído em vez de uma imagem?

Quase sempre porque o símbolo é muito grosseiro. Um link curto na correção de erros H é codificado em uma grade de 29 módulos — 841 células, muito poucos para carregar uma imagem reconhecível, então a imagem não tem lugar para viver. Forçar um símbolo mais denso conserta: a versão 8 oferece 49 módulos e a versão 12 oferece 65 (4.225 células, cinco vezes mais), e nosso gerador usa como padrão a versão 12 para códigos de foto exatamente por esse motivo. Os dados sempre vencem, portanto uma carga útil mais longa ainda obtém um símbolo maior.

Fontes
  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
Mais neste tópico