5.28. Códigos QR e AprilTags

Os detectores vistos até aqui – blobs, linhas, círculos, retângulos – encontram características geométricas: posições e contornos que um estágio posterior interpreta. Os detectores restantes encontram características simbólicas: padrões impressos cuja estrutura visual existe especificamente para codificar uma carga útil. A câmera os localiza, o decodificador lê os bits, e o que retorna não é uma posição, mas uma string (ou um ID) que o impressor do símbolo escolheu deliberadamente.

Duas dessas famílias dominam as aplicações com câmeras pequenas. Os códigos QR carregam texto arbitrário, URLs, cartões de contato ou cargas úteis binárias – os códigos 2D voltados ao consumidor que aparecem em cartazes, embalagens e cartões de embarque. As AprilTags carregam um único ID numérico de um pequeno conjunto fixo, decodificam rapidamente mesmo a longa distância e (quando os parâmetros intrínsecos da lente são fornecidos) reportam uma pose de 6 graus de liberdade (6-DoF) no referencial da câmera – os códigos 2D voltados à robótica que marcam drones, alvos de calibração e marcadores fiduciais. Ambos os detectores retornam objetos de resultado com o mesmo vocabulário de caixa delimitadora que os detectores de blob e de retângulo usam, mas a carga útil os torna genuinamente diferentes de tudo o que foi visto até agora.

5.28.1. Códigos QR

find_qrcodes() varre o quadro em busca de códigos QR e retorna uma lista de objetos de resultado QRCode:

codes = img.find_qrcodes()

for c in codes:
    img.draw_rectangle(c.rect, color=(0, 255, 0))
    for corner in c.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))
    print(c.payload)

O detector recebe um único roi opcional para restringir a busca. Ele precisa de entrada em escala de cinza – um quadro colorido é convertido internamente antes da decodificação.

Cada detecção carrega a caixa delimitadora (x, y, w, h, rect), os quatro cantos detectados (corners, o quadrilátero projetivo que os padrões localizadores do código QR traçam) e a carga útil decodificada como uma string. Os cantos são a coisa certa a desenhar ao anotar a detecção – um código QR visto fora do eixo não está alinhado aos eixos, e a caixa delimitadora fornece apenas um contorno aproximado.

Os metadados do decodificador cobrem tudo o que o decodificador de QR aprendeu ao longo do caminho. version é a versão do código QR, de 1 a 40, que define o tamanho da grade de módulos (um código de versão 1 tem 21 módulos de largura, um código de versão 40 tem 177). ecc_level é o nível de correção de erros (0 a 3 para L / M / Q / H); níveis mais altos reservam mais palavras-código para correção de erros e resistem a mais danos ao custo de menos espaço para a carga útil. mask é o padrão de máscara (0 a 7) que o codificador escolheu para minimizar a confusão do decodificador. data_type é a codificação que o decodificador reportou – numérica, alfanumérica, binária ou Kanji – e as flags is_numeric / is_alphanumeric / is_binary / is_kanji expõem o mesmo valor como booleanos mais amigáveis.

eci é o valor de Extended Channel Interpretation, que identifica a codificação de texto em que os bytes estão (UTF-8, ISO-8859-1 e assim por diante). Um código QR de material impresso arbitrário pode não ser garantidamente UTF-8; uma aplicação que precisa decodificar os bytes corretamente verifica eci e decodifica conforme. O caso Kanji em particular: o MicroPython não interpreta a codificação Kanji, então uma carga útil is_kanji precisa ser tratada como um array de bytes e decodificada pela aplicação.

Um uso típico: uma câmera lê códigos QR de uma esteira e reporta a carga útil decodificada a um host. A câmera executa find_qrcodes() uma vez por quadro, itera a lista retornada, seleciona os códigos cujo data_type corresponde ao que a aplicação espera e encaminha c.payload via UART ou USB. Os dados de caixa delimitadora e de cantos são úteis para a pré-visualização na IDE, mas não são o que importa ao host.

5.28.2. AprilTags

find_apriltags() varre o quadro em busca de AprilTags e retorna uma lista de objetos de resultado AprilTag:

tags = img.find_apriltags(families=image.TAG36H11)

for t in tags:
    img.draw_rectangle(t.rect, color=(0, 255, 0))
    img.draw_cross(t.cx, t.cy, color=(0, 255, 0))
    print(t.id, t.decision_margin)

As AprilTags diferem dos códigos QR em seus objetivos de projeto. Um código QR é construído para codificar dados arbitrários em um único símbolo denso que o usuário lê uma vez de perto. Uma AprilTag é construída para codificar um pequeno ID em um símbolo esparso que a câmera lê continuamente a distância, com toda a tolerância a erros que o código de Hamming de sua família permite. A compensação aparece em ambas as direções: um código QR pode carregar centenas de bytes, mas precisa ser lido de perto; uma AprilTag carrega apenas algumas centenas de IDs únicos, mas é lida de forma confiável a metros de distância.

A palavra-chave families recebe uma máscara de bits das famílias de tags a decodificar. As famílias disponíveis são image.TAG16H5, image.TAG25H9, image.TAG36H10, image.TAG36H11, image.TAGCIRCLE21H7, image.TAGCIRCLE49H12, image.TAGCUSTOM48H12, image.TAGSTANDARD41H12 e image.TAGSTANDARD52H13. Cada família faz uma compensação entre a quantidade de IDs e a robustez. O número H no nome é a distância de Hamming mínima entre quaisquer dois códigos da família – quantos bits precisam ser invertidos antes que um código válido se transforme em outro – TAG16H5 tem 30 IDs à distância 5, TAG25H9 tem 35 IDs à distância 9, e TAG36H11 (a padrão e a mais comum) tem 587 IDs à distância 11. O detector corrige até dois erros de bit independentemente da família, então a distância decide quão arriscada é essa correção: um padrão aleatório em um quadro ruidoso só precisa cair a dois bits de um código válido para decodificar como uma falsa detecção, e as famílias de distância mais alta espalham seus códigos de forma tão mais esparsa que tais colisões se tornam raras – a razão pela qual TAG36H11 é a escolha recomendada. O tempo de detecção cresce com o número de famílias habilitadas, então uma aplicação habilita apenas o que realmente imprime. A máscara de bits é o OR bit a bit das constantes de família quando múltiplas famílias são necessárias em uma única chamada.

Cada detecção carrega o vocabulário de caixa delimitadora – x, y, w, h, rect, area, centroides inteiros e sub-pixel (cx, cy, cxf, cyf) – e os quatro cantos detectados (corners). Os campos de identificação vêm em seguida: id é o ID numérico dentro da família (0 a 586 para TAG36H11), family é a constante numérica da família, e name é o nome da família como uma string.

Os campos de qualidade de correspondência são o que uma aplicação usa para filtrar detecções. decision_margin é uma pontuação de confiança de 0.0 a 1.0; quanto maior, melhor, e filtrar detecções abaixo de decision_margin > 0.1 limpa a maioria dos acertos espúrios sem custo. hamming conta os erros de bit que o decodificador aceitou para esta tag – quanto menor, melhor, com 0 significando uma decodificação perfeita. goodness é uma métrica histórica de qualidade de imagem que o decodificador atual não computa mais; é sempre 0.0 e pode ser ignorada.

5.28.3. Pose a partir dos parâmetros intrínsecos

A característica transformadora de find_apriltags(), a que justifica as AprilTags como o marcador fiducial preferido na robótica, é que o método pode recuperar a pose de 6 graus de liberdade (6-DoF) da tag no referencial da câmera diretamente dos cantos detectados e de um pequeno conjunto de parâmetros intrínsecos de calibração. Os parâmetros intrínsecos são as distâncias focais X e Y da câmera em pixels (fx, fy) e o centro óptico em pixels (cx, cy), todos os quatro medidos uma vez pela aplicação com um procedimento de calibração e fixados em código a partir daí.

Quando os parâmetros intrínsecos são fornecidos, a AprilTag retornada preenche seus campos x_translation, y_translation, z_translation com a posição da tag em relação à câmera, e x_rotation, y_rotation, z_rotation (além do rotation duplicado por simetria) com a orientação da tag. Sem os parâmetros intrínsecos, todos os seis campos são 0.0 e a aplicação fica responsável por qualquer estimativa de pose de que necessite.

Os campos de translação são reportados em larguras de tag: o decodificador trata a tag como tendo 1 unidade de largura, então a aplicação multiplica cada translação pela largura física da tag impressa para obter distâncias métricas. Uma tag impressa com 100 mm de largura e reportando z_translation = 8.3 está a 830 mm da câmera; a mesma tag impressa com 50 mm de largura à mesma distância reportaria z_translation = 16.6. Os campos de rotação estão em radianos e não precisam de escalonamento.

A estimativa de pose é a base para uma ampla gama de aplicações de robótica: acoplar um robô a uma estação de carregamento marcada com uma tag, seguir uma trilha impressa de pontos de passagem, recuperar a própria pose da câmera a partir de múltiplas tags conhecidas no ambiente. Uma câmera que conhece os parâmetros intrínsecos, vê uma tag e tem uma posição no mundo real para a tag tem, pela mesma aritmética, uma posição no mundo real para si mesma.

5.28.4. Quando escolher qual

Códigos QR e AprilTags resolvem problemas diferentes. A escolha entre eles se resume ao que o símbolo impresso carrega.

Quando a aplicação precisa carregar dados arbitrários através do símbolo impresso – uma URL, uma string de número de série, um registro de contato – o código QR é a escolha certa. Centenas de bytes cabem em um código de tamanho modesto, a codificação é pública e suportada em todos os smartphones, e o decodificador lida com rotação, danos moderados e ângulos oblíquos.

Quando a aplicação precisa de um pequeno ID lido continuamente a distância com pose opcional – um marcador fiducial em um robô em movimento, um alvo de calibração em uma sala, um marcador de acoplamento em uma estação de carregamento – a AprilTag é a escolha certa. Centenas de IDs são suficientes para o caso de uso, o código de Hamming se recupera de erros de bit que derrotariam um código QR, e a estimativa de pose vem de graça uma vez que os parâmetros intrínsecos estejam calibrados.

Algumas aplicações usam ambos: uma AprilTag marca um local conhecido e um código QR associado (impresso ao lado) carrega os metadados sobre o que aquele local significa. Os dois detectores rodam independentemente no mesmo quadro, e a aplicação correlaciona suas caixas delimitadoras para casar cada tag com seu código companheiro.