5.28. Codici QR e AprilTag

I rilevatori visti finora – blob, linee, cerchi, rettangoli – trovano caratteristiche geometriche: posizioni e contorni che uno stadio successivo interpreta. I rilevatori rimanenti trovano caratteristiche simboliche: pattern stampati la cui struttura visiva esiste specificamente per codificare un payload. La camera li individua, il decodificatore legge i bit, e ciò che si ottiene non è una posizione ma una stringa (o un ID) che chi ha stampato il simbolo ha scelto deliberatamente.

Due famiglie di questo tipo dominano le applicazioni con camere di piccole dimensioni. I codici QR trasportano testo arbitrario, URL, biglietti da visita o payload binari – i codici 2D rivolti al consumatore che compaiono su poster, confezioni e carte d’imbarco. Gli AprilTag trasportano un singolo ID numerico da un piccolo insieme fisso, si decodificano rapidamente anche a grande distanza e (quando vengono forniti i parametri intrinseci dell’obiettivo) riportano una posa a 6 gradi di libertà nel frame della camera – i codici 2D rivolti alla robotica che contrassegnano droni, target di calibrazione e fiduciali. Entrambi i rilevatori restituiscono oggetti risultato con lo stesso vocabolario di bounding box usato dai rilevatori di blob e rettangoli, ma il payload li rende veramente diversi da qualsiasi cosa trattata finora.

5.28.1. Codici QR

find_qrcodes() scansiona il frame alla ricerca di codici QR e restituisce un elenco di oggetti risultato 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)

Il rilevatore accetta un singolo roi opzionale per limitare la ricerca. Richiede un input in scala di grigi – un frame a colori viene convertito internamente prima della decodifica.

Ogni rilevamento porta con sé il bounding box (x, y, w, h, rect), i quattro angoli rilevati (corners, il quadrilatero proiettivo tracciato dai finder pattern del codice QR) e il payload decodificato come stringa. Gli angoli sono la cosa giusta da disegnare quando si annota il rilevamento – un codice QR visto fuori asse non è allineato agli assi e il bounding box ne fornisce solo un contorno approssimativo.

I metadati del decodificatore coprono tutto ciò che il decodificatore QR ha appreso lungo il percorso. version è la versione del codice QR, 1 – 40, che determina la dimensione della griglia di moduli (un codice di versione 1 è largo 21 moduli, un codice di versione 40 è largo 177). ecc_level è il livello di correzione degli errori (0 – 3 per L / M / Q / H); livelli più alti riservano più codeword alla correzione degli errori e sopravvivono a più danni a costo di meno spazio per il payload. mask è il pattern di maschera (0 – 7) che il codificatore ha scelto per minimizzare la confusione del decodificatore. data_type è la codifica riportata dal decodificatore – numerica, alfanumerica, binaria o Kanji – e i flag is_numeric / is_alphanumeric / is_binary / is_kanji espongono lo stesso valore sotto forma di booleani più comodi.

eci è il valore Extended Channel Interpretation, che identifica la codifica del testo in cui sono espressi i byte (UTF-8, ISO-8859-1 e così via). Un codice QR proveniente da materiale stampato arbitrario potrebbe non essere garantito come UTF-8; un’applicazione che deve decodificare correttamente i byte controlla eci e decodifica di conseguenza. Il caso Kanji in particolare: MicroPython non analizza la codifica Kanji, quindi un payload is_kanji deve essere trattato come un array di byte e decodificato dall’applicazione.

Un utilizzo tipico: una camera legge i codici QR da un nastro trasportatore e riporta il payload decodificato a un host. La cam esegue find_qrcodes() una volta per frame, itera l’elenco restituito, seleziona i codici il cui data_type corrisponde a ciò che l’applicazione si aspetta e inoltra c.payload tramite UART o USB. I dati del bounding box e degli angoli sono utili per l’anteprima dell’IDE ma non sono ciò che interessa all’host.

5.28.2. AprilTag

find_apriltags() scansiona il frame alla ricerca di AprilTag e restituisce un elenco di oggetti risultato 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)

Gli AprilTag differiscono dai codici QR negli obiettivi di progettazione. Un codice QR è costruito per codificare dati arbitrari in un singolo simbolo denso che l’utente legge una volta a distanza ravvicinata. Un AprilTag è costruito per codificare un piccolo ID in un simbolo rado che la camera legge continuamente da lontano, con tutta la tolleranza agli errori che il codice di Hamming della sua famiglia consente. Il compromesso si manifesta in entrambe le direzioni: un codice QR può trasportare centinaia di byte ma deve essere letto da vicino; un AprilTag trasporta solo poche centinaia di ID unici ma si legge in modo affidabile a metri di distanza.

La parola chiave families accetta una bitmask delle famiglie di tag da decodificare. Le famiglie disponibili sono image.TAG16H5, image.TAG25H9, image.TAG36H10, image.TAG36H11, image.TAGCIRCLE21H7, image.TAGCIRCLE49H12, image.TAGCUSTOM48H12, image.TAGSTANDARD41H12 e image.TAGSTANDARD52H13. Ogni famiglia bilancia il numero di ID rispetto alla robustezza. Il numero H nel nome è la distanza di Hamming minima tra due codici qualsiasi della famiglia – quanti bit devono cambiare prima che un codice valido si trasformi in un altro – TAG16H5 ha 30 ID a distanza 5, TAG25H9 ha 35 ID a distanza 9 e TAG36H11 (la famiglia predefinita e la più comune) ha 587 ID a distanza 11. Il rilevatore corregge fino a due errori di bit indipendentemente dalla famiglia, quindi la distanza decide quanto sia rischiosa quella correzione: un pattern casuale in un frame rumoroso deve solo cadere entro due bit da un codice valido per essere decodificato come falso rilevamento, e le famiglie con distanza maggiore distribuiscono i loro codici in modo molto più rado, rendendo rare tali collisioni – la ragione per cui TAG36H11 è la scelta consigliata. Il tempo di rilevamento aumenta con il numero di famiglie abilitate, quindi un’applicazione abilita solo ciò che effettivamente stampa. La bitmask è l’OR bit a bit delle costanti di famiglia quando in una sola chiamata sono necessarie più famiglie.

Ogni rilevamento porta con sé il vocabolario del bounding box – x, y, w, h, rect, area, centroidi interi e sub-pixel (cx, cy, cxf, cyf) – e i quattro angoli rilevati (corners). Seguono i campi di identificazione: id è l’ID numerico all’interno della famiglia (0 – 586 per TAG36H11), family è la costante numerica della famiglia e name è il nome della famiglia come stringa.

I campi di qualità della corrispondenza sono ciò che un’applicazione utilizza per filtrare i rilevamenti. decision_margin è un punteggio di confidenza da 0.0 a 1.0; più alto è meglio, e filtrare i rilevamenti al di sotto di decision_margin > 0.1 elimina la maggior parte dei rilevamenti spuri senza alcun costo. hamming conta gli errori di bit che il decodificatore ha accettato per questo tag – più basso è meglio, 0 significa una decodifica perfetta. goodness è una metrica storica di qualità dell’immagine che il decodificatore attuale non calcola più; è sempre 0.0 e può essere ignorata.

5.28.3. Posa dai parametri intrinseci

La caratteristica trasformativa di find_apriltags(), quella che giustifica gli AprilTag come fiduciale di scelta per la robotica, è che il metodo può recuperare la posa a 6 gradi di libertà del tag nel frame della camera direttamente dagli angoli rilevati e da un piccolo insieme di parametri intrinseci di calibrazione. I parametri intrinseci sono le lunghezze focali X e Y della camera in pixel (fx, fy) e il centro ottico in pixel (cx, cy), tutti e quattro misurati dall’applicazione una sola volta con una procedura di calibrazione e poi codificati in modo fisso.

Quando vengono forniti i parametri intrinseci, l’oggetto AprilTag restituito popola i suoi campi x_translation, y_translation, z_translation con la posizione del tag rispetto alla camera, e x_rotation, y_rotation, z_rotation (e il duplicato rotation per simmetria) con l’orientamento del tag. Senza i parametri intrinseci, tutti e sei i campi sono 0.0 e l’applicazione è responsabile di qualsiasi stima della posa di cui ha bisogno.

I campi di traslazione sono riportati in larghezze di tag: il decodificatore tratta il tag come largo 1 unità, quindi l’applicazione moltiplica ogni traslazione per la larghezza fisica del tag stampato per ottenere distanze metriche. Un tag stampato con larghezza di 100 mm che riporta z_translation = 8.3 si trova a 830 mm dalla camera; lo stesso tag stampato con larghezza di 50 mm alla stessa distanza riporterebbe z_translation = 16.6. I campi di rotazione sono in radianti e non necessitano di alcuna scalatura.

La stima della posa è la base per un’ampia gamma di applicazioni robotiche: l’aggancio di un robot a una stazione di ricarica contrassegnata da un tag, il seguire una traiettoria di waypoint stampati, il recupero della posa stessa della camera da più tag noti nell’ambiente. Una camera che conosce i parametri intrinseci, vede un tag e dispone di una posizione nel mondo reale per il tag possiede, con la stessa aritmetica, una posizione nel mondo reale per se stessa.

5.28.4. Quando scegliere quale

I codici QR e gli AprilTag risolvono problemi diversi. La scelta tra i due si riduce a ciò che il simbolo stampato trasporta.

Quando l’applicazione deve trasportare dati arbitrari attraverso il simbolo stampato – un URL, una stringa di numero seriale, un record di contatto – il codice QR è la scelta giusta. Centinaia di byte stanno in un codice di dimensioni modeste, la codifica è pubblica e supportata su ogni smartphone, e il decodificatore gestisce rotazione, danni moderati e angoli obliqui.

Quando l’applicazione ha bisogno di un piccolo ID letto continuamente da una distanza con posa opzionale – un fiduciale su un robot in movimento, un target di calibrazione in una stanza, un marcatore di aggancio su una stazione di ricarica – l’AprilTag è la scelta giusta. Centinaia di ID sono più che sufficienti per il caso d’uso, il codice di Hamming recupera da errori di bit che sconfiggerebbero un codice QR, e la stima della posa è gratuita una volta calibrati i parametri intrinseci.

Alcune applicazioni usano entrambi: un AprilTag contrassegna una posizione nota e un codice QR associato (stampato accanto) trasporta i metadati su cosa quella posizione significhi. I due rilevatori vengono eseguiti in modo indipendente sullo stesso frame e l’applicazione correla i loro bounding box per abbinare ogni tag al suo codice compagno.