5.28. Codes QR et AprilTags¶
Les détecteurs vus jusqu’ici – blobs, lignes, cercles, rectangles – trouvent des caractéristiques géométriques : des positions et des contours qu’une étape ultérieure interprète. Les détecteurs restants trouvent des caractéristiques symboliques : des motifs imprimés dont la structure visuelle existe précisément pour encoder une charge utile. La caméra les localise, le décodeur en lit les bits, et ce qui en ressort n’est pas une position mais une chaîne (ou un identifiant) que l’imprimeur du symbole a délibérément choisi.
Deux familles de ce type dominent les applications de petites caméras. Les codes QR transportent du texte arbitraire, des URL, des cartes de contact ou des charges utiles binaires – les codes 2D grand public que l’on trouve sur les affiches, les emballages et les cartes d’embarquement. Les AprilTags transportent un unique identifiant numérique issu d’un petit ensemble fixe, se décodent rapidement même à grande distance et (lorsque les paramètres intrinsèques de l’objectif sont fournis) rapportent une pose à 6 degrés de liberté dans le repère de la caméra – les codes 2D orientés robotique qui marquent les drones, les cibles de calibration et les repères fiduciaires. Les deux détecteurs renvoient des objets résultats partageant le même vocabulaire de boîte englobante que les détecteurs de blobs et de rectangles, mais la charge utile les rend véritablement différents de tout ce qui a été couvert jusqu’à présent.
5.28.1. Codes QR¶
find_qrcodes() parcourt la trame à la recherche de codes QR et renvoie une liste d’objets résultats 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)
Le détecteur prend un unique roi optionnel pour restreindre la recherche. Il a besoin d’une entrée en niveaux de gris – une trame en couleur est convertie en interne avant le décodage.
Chaque détection comporte la boîte englobante (x, y, w, h, rect), les quatre coins détectés (corners, le quadrilatère projectif tracé par les motifs de repérage du code QR) et la charge utile décodée sous forme de chaîne. Les coins sont ce qu’il convient de dessiner pour annoter la détection – un code QR vu de biais n’est pas aligné sur les axes et la boîte englobante n’en donne qu’un contour approximatif.
Les métadonnées du décodeur couvrent tout ce que le décodeur QR a appris en chemin. version est la version du code QR, de 1 à 40, qui détermine la taille de la grille de modules (un code de version 1 fait 21 modules de large, un code de version 40 en fait 177). ecc_level est le niveau de correction d’erreur (de 0 à 3 pour L / M / Q / H) ; les niveaux supérieurs réservent davantage de mots de code à la correction d’erreur et survivent à davantage de dommages au prix d’un espace réduit pour la charge utile. mask est le motif de masque (de 0 à 7) que l’encodeur a choisi pour minimiser la confusion du décodeur. data_type est l’encodage rapporté par le décodeur – numérique, alphanumérique, binaire ou Kanji – et les indicateurs is_numeric / is_alphanumeric / is_binary / is_kanji exposent la même valeur sous forme de booléens plus conviviaux.
eci est la valeur d’Extended Channel Interpretation, qui identifie l’encodage de texte des octets (UTF-8, ISO-8859-1, et ainsi de suite). Un code QR provenant d’un imprimé arbitraire n’est pas nécessairement garanti en UTF-8 ; une application qui doit décoder correctement les octets vérifie eci et décode en conséquence. Le cas du Kanji en particulier : MicroPython n’analyse pas l’encodage Kanji, de sorte qu’une charge utile is_kanji doit être traitée comme un tableau d’octets et décodée par l’application.
Une utilisation typique : une caméra lit des codes QR sur un convoyeur et rapporte la charge utile décodée à un hôte. La caméra exécute find_qrcodes() une fois par trame, parcourt la liste renvoyée, sélectionne les codes dont le data_type correspond à ce que l’application attend, puis transmet c.payload via UART ou USB. Les données de boîte englobante et de coins sont utiles pour l’aperçu de l’IDE mais ne sont pas ce qui intéresse l’hôte.
5.28.2. AprilTags¶
find_apriltags() parcourt la trame à la recherche d’AprilTags et renvoie une liste d’objets résultats 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)
Les AprilTags diffèrent des codes QR par leurs objectifs de conception. Un code QR est conçu pour encoder des données arbitraires dans un unique symbole dense que l’utilisateur lit une fois à courte distance. Un AprilTag est conçu pour encoder un petit identifiant dans un symbole épars que la caméra lit en continu à distance, avec autant de tolérance aux erreurs que le permet le code de Hamming de sa famille. Le compromis se manifeste dans les deux sens : un code QR peut transporter des centaines d’octets mais doit être lu de près ; un AprilTag ne transporte que quelques centaines d’identifiants uniques mais se lit de manière fiable à plusieurs mètres de distance.
Le mot-clé families prend un masque binaire des familles de tags à décoder. Les familles disponibles sont image.TAG16H5, image.TAG25H9, image.TAG36H10, image.TAG36H11, image.TAGCIRCLE21H7, image.TAGCIRCLE49H12, image.TAGCUSTOM48H12, image.TAGSTANDARD41H12 et image.TAGSTANDARD52H13. Chaque famille fait un compromis entre le nombre d’identifiants et la robustesse. Le nombre H dans le nom est la distance de Hamming minimale entre deux codes quelconques de la famille – combien de bits doivent basculer avant qu’un code valide ne se transforme en un autre – TAG16H5 a 30 identifiants à une distance de 5, TAG25H9 a 35 identifiants à une distance de 9, et TAG36H11 (la valeur par défaut et la plus courante) a 587 identifiants à une distance de 11. Le détecteur corrige jusqu’à deux erreurs de bit quelle que soit la famille, de sorte que la distance décide du risque de cette correction : un motif aléatoire dans une trame bruitée n’a qu’à tomber à moins de deux bits d’un code valide pour être décodé comme une fausse détection, et les familles à plus grande distance répartissent leurs codes de manière si éparse que de telles collisions deviennent rares – la raison pour laquelle TAG36H11 est le choix recommandé. Le temps de détection croît avec le nombre de familles activées, de sorte qu’une application n’active que ce qu’elle imprime réellement. Le masque binaire est le OU bit à bit des constantes de famille lorsque plusieurs familles sont nécessaires en un seul appel.
Chaque détection comporte le vocabulaire de boîte englobante – x, y, w, h, rect, area, les centroïdes entiers et sous-pixels (cx, cy, cxf, cyf) – et les quatre coins détectés (corners). Les champs d’identification suivent : id est l’identifiant numérique au sein de la famille (de 0 à 586 pour TAG36H11), family est la constante numérique de la famille, et name est le nom de la famille sous forme de chaîne.
Les champs de qualité de correspondance sont ce qu’une application utilise pour filtrer les détections. decision_margin est un score de confiance de 0.0 à 1.0 ; plus il est élevé, mieux c’est, et filtrer les détections en dessous de decision_margin > 0.1 élimine la plupart des résultats parasites sans surcoût. hamming compte les erreurs de bit que le décodeur a acceptées pour ce tag – plus c’est bas, mieux c’est, 0 signifiant un décodage parfait. goodness est une métrique historique de qualité d’image que le décodeur actuel ne calcule plus ; elle vaut toujours 0.0 et peut être ignorée.
5.28.3. Pose à partir des paramètres intrinsèques¶
La fonctionnalité transformatrice de find_apriltags(), celle qui justifie le choix des AprilTags comme repère fiduciaire de référence en robotique, est que la méthode peut récupérer la pose à 6 degrés de liberté du tag dans le repère de la caméra directement à partir des coins détectés et d’un petit ensemble de paramètres intrinsèques de calibration. Les paramètres intrinsèques sont les distances focales X et Y de la caméra en pixels (fx, fy) et le centre optique en pixels (cx, cy), tous quatre étant mesurés une fois par l’application au moyen d’une procédure de calibration puis figés dans le code par la suite.
Lorsque les paramètres intrinsèques sont fournis, l’objet AprilTag renvoyé remplit ses champs x_translation, y_translation, z_translation avec la position du tag par rapport à la caméra, et x_rotation, y_rotation, z_rotation (ainsi que le champ rotation dupliqué par symétrie) avec l’orientation du tag. Sans paramètres intrinsèques, ces six champs valent tous 0.0 et l’application est responsable de toute estimation de pose dont elle a besoin.
Les champs de translation sont exprimés en largeurs de tag : le décodeur considère le tag comme ayant 1 unité de large, de sorte que l’application multiplie chaque translation par la largeur physique du tag imprimé pour obtenir des distances métriques. Un tag imprimé à 100 mm de large et rapportant z_translation = 8.3 se trouve à 830 mm de la caméra ; le même tag imprimé à 50 mm de large à la même distance rapporterait z_translation = 16.6. Les champs de rotation sont en radians et ne nécessitent aucune mise à l’échelle.
L’estimation de pose est la base d’un large éventail d’applications robotiques : amarrer un robot à une station de recharge marquée d’un tag, suivre une piste de points de passage imprimés, récupérer la propre pose de la caméra à partir de plusieurs tags connus dans l’environnement. Une caméra qui connaît les paramètres intrinsèques, voit un tag et dispose d’une position réelle pour le tag dispose, par le même calcul, d’une position réelle pour elle-même.
5.28.4. Quand choisir lequel¶
Les codes QR et les AprilTags résolvent des problèmes différents. Le choix entre eux se résume à ce que transporte le symbole imprimé.
Lorsque l’application doit transporter des données arbitraires via le symbole imprimé – une URL, une chaîne de numéro de série, une fiche de contact – le code QR est le bon choix. Des centaines d’octets tiennent dans un code de taille modeste, l’encodage est public et pris en charge par tous les smartphones, et le décodeur s’accommode de la rotation, de dommages modérés et des angles obliques.
Lorsque l’application a besoin d”un petit identifiant lu en continu à distance avec une pose optionnelle – un repère sur un robot en mouvement, une cible de calibration dans une pièce, un marqueur d’amarrage sur une station de recharge – l’AprilTag est le bon choix. Des centaines d’identifiants suffisent largement pour ce cas d’usage, le code de Hamming se remet d’erreurs de bit qui mettraient en échec un code QR, et l’estimation de pose est gratuite une fois les paramètres intrinsèques calibrés.
Certaines applications utilisent les deux : un AprilTag marque un emplacement connu et un code QR associé (imprimé à côté) transporte les métadonnées sur ce que cet emplacement signifie. Les deux détecteurs s’exécutent indépendamment sur la même trame et l’application corrèle leurs boîtes englobantes pour associer chaque tag à son code compagnon.