5.28. Kody QR i znaczniki AprilTag¶
Dotychczasowe detektory – plamy (blob), linie, okręgi, prostokąty – znajdują cechy geometryczne: pozycje i kontury, które interpretuje kolejny etap przetwarzania. Pozostałe detektory znajdują cechy symboliczne: wydrukowane wzory, których struktura wizualna istnieje specjalnie po to, aby zakodować pewną zawartość. Kamera je lokalizuje, dekoder odczytuje bity, a tym, co zwraca, nie jest pozycja, lecz łańcuch znaków (lub identyfikator), który drukujący symbol wybrał celowo.
W zastosowaniach z małymi kamerami dominują dwie takie rodziny. Kody QR przenoszą dowolny tekst, adresy URL, wizytówki lub dane binarne – to dwuwymiarowe kody skierowane do konsumentów, które pojawiają się na plakatach, opakowaniach i kartach pokładowych. Znaczniki AprilTag przenoszą pojedynczy numeryczny identyfikator z niewielkiego, ustalonego zbioru, dekodują się szybko nawet z dużej odległości i (gdy podane są parametry wewnętrzne obiektywu) raportują 6-DoF pozę w układzie kamery – to dwuwymiarowe kody skierowane do robotyki, którymi oznacza się drony, cele kalibracyjne i znaczniki odniesienia. Oba detektory zwracają obiekty wyników z tym samym słownictwem ramki ograniczającej, którego używają detektory plam i prostokątów, ale to zawartość sprawia, że są one zasadniczo różne od wszystkiego, co omówiono do tej pory.
5.28.1. Kody QR¶
find_qrcodes() przeszukuje ramkę w poszukiwaniu kodów QR i zwraca listę obiektów wyników 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)
Detektor przyjmuje jeden opcjonalny argument roi, który ogranicza obszar wyszukiwania. Wymaga wejścia w skali szarości – ramka kolorowa jest wewnętrznie konwertowana przed dekodowaniem.
Każde wykrycie niesie ze sobą ramkę ograniczającą (x, y, w, h, rect), cztery wykryte narożniki (corners, czworokąt rzutowy wyznaczony przez wzory namierzające kodu QR) oraz zdekodowaną zawartość w postaci łańcucha znaków. Przy oznaczaniu wykrycia warto rysować właśnie narożniki – kod QR oglądany pod kątem nie jest wyrównany do osi, a ramka ograniczająca daje jedynie zgrubny kontur.
Metadane dekodera obejmują wszystko, czego dekoder QR dowiedział się po drodze. version to wersja kodu QR, od 1 do 40, która ustala rozmiar siatki modułów (kod w wersji 1 ma szerokość 21 modułów, a kod w wersji 40 – 177). ecc_level to poziom korekcji błędów (0 – 3 dla L / M / Q / H); wyższe poziomy rezerwują więcej słów kodowych na korekcję błędów i przetrwają większe uszkodzenia kosztem mniejszej przestrzeni na dane. mask to wzór maski (0 – 7), który koder wybrał, aby zminimalizować pomyłki dekodera. data_type to kodowanie zgłoszone przez dekoder – numeryczne, alfanumeryczne, binarne lub Kanji – a flagi is_numeric / is_alphanumeric / is_binary / is_kanji udostępniają tę samą wartość w postaci przyjaźniejszych wartości logicznych.
eci to wartość Extended Channel Interpretation, która identyfikuje kodowanie tekstu zawartego w bajtach (UTF-8, ISO-8859-1 itd.). Kod QR z dowolnego wydrukowanego materiału nie musi mieć gwarantowanego kodowania UTF-8; aplikacja, która potrzebuje poprawnie zdekodować bajty, sprawdza eci i dekoduje odpowiednio. Szczególnie przypadek Kanji: MicroPython nie analizuje kodowania Kanji, więc zawartość is_kanji musi być traktowana jako tablica bajtów i dekodowana przez aplikację.
Typowe zastosowanie: kamera odczytuje kody QR z taśmy transportowej i raportuje zdekodowaną zawartość do hosta. Kamera uruchamia find_qrcodes() raz na ramkę, iteruje po zwróconej liście, wybiera kody, których data_type odpowiada oczekiwaniom aplikacji, i przekazuje c.payload przez UART lub USB. Dane ramki ograniczającej i narożników są przydatne dla podglądu w IDE, ale nie są tym, na czym zależy hostowi.
5.28.2. Znaczniki AprilTag¶
find_apriltags() przeszukuje ramkę w poszukiwaniu znaczników AprilTag i zwraca listę obiektów wyników 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)
Znaczniki AprilTag różnią się od kodów QR celami projektowymi. Kod QR jest zbudowany tak, aby zakodować dowolne dane w pojedynczym, gęstym symbolu, który użytkownik odczytuje raz z bliska. Znacznik AprilTag jest zbudowany tak, aby zakodować niewielki identyfikator w rzadkim symbolu, który kamera odczytuje w sposób ciągły z odległości, z taką tolerancją błędów, na jaką pozwala kod Hamminga jego rodziny. Kompromis ujawnia się w obu kierunkach: kod QR może przenosić setki bajtów, ale musi być odczytywany z bliska; znacznik AprilTag przenosi tylko kilkaset unikalnych identyfikatorów, ale odczytuje się niezawodnie z odległości wielu metrów.
Słowo kluczowe families przyjmuje maskę bitową rodzin znaczników do zdekodowania. Dostępne rodziny to image.TAG16H5, image.TAG25H9, image.TAG36H10, image.TAG36H11, image.TAGCIRCLE21H7, image.TAGCIRCLE49H12, image.TAGCUSTOM48H12, image.TAGSTANDARD41H12 oraz image.TAGSTANDARD52H13. Każda rodzina równoważy liczbę identyfikatorów z odpornością. Liczba H w nazwie to minimalna odległość Hamminga między dowolnymi dwoma kodami w rodzinie – ile bitów musi się zmienić, zanim jeden prawidłowy kod przejdzie w inny – TAG16H5 ma 30 identyfikatorów przy odległości 5, TAG25H9 ma 35 identyfikatorów przy odległości 9, a TAG36H11 (domyślna i najczęściej stosowana) ma 587 identyfikatorów przy odległości 11. Detektor koryguje do dwóch błędów bitowych niezależnie od rodziny, więc odległość decyduje o tym, jak ryzykowna jest ta korekcja: losowy wzór w zaszumionej ramce musi tylko trafić w obrębie dwóch bitów od prawidłowego kodu, aby zostać zdekodowany jako fałszywe wykrycie, a rodziny o większej odległości rozkładają swoje kody na tyle rzadziej, że takie kolizje stają się rzadkie – to powód, dla którego TAG36H11 jest zalecanym wyborem. Czas wykrywania skaluje się wraz z liczbą włączonych rodzin, więc aplikacja włącza tylko to, co rzeczywiście drukuje. Maska bitowa jest bitową sumą (OR) stałych rodzin, gdy w jednym wywołaniu potrzebnych jest wiele rodzin.
Każde wykrycie niesie ze sobą słownictwo ramki ograniczającej – x, y, w, h, rect, area, całkowite i subpikselowe centroidy (cx, cy, cxf, cyf) – oraz cztery wykryte narożniki (corners). Następnie pojawiają się pola identyfikacyjne: id to numeryczny identyfikator w obrębie rodziny (0 – 586 dla TAG36H11), family to numeryczna stała rodziny, a name to nazwa rodziny w postaci łańcucha znaków.
Pola jakości dopasowania to te, których aplikacja używa do filtrowania wykryć. decision_margin to wynik pewności w zakresie 0.0 – 1.0; im wyższy, tym lepiej, a odfiltrowanie wykryć poniżej decision_margin > 0.1 usuwa większość fałszywych trafień bezkosztowo. hamming zlicza błędy bitowe, które dekoder zaakceptował dla tego znacznika – im mniej, tym lepiej, przy czym 0 oznacza idealne zdekodowanie. goodness to historyczna metryka jakości obrazu, której obecny dekoder już nie oblicza; zawsze wynosi 0.0 i można ją zignorować.
5.28.3. Poza z parametrów wewnętrznych¶
Przełomową cechą find_apriltags(), tą, która uzasadnia AprilTag jako preferowany znacznik odniesienia w robotyce, jest to, że metoda potrafi odtworzyć 6-DoF pozę znacznika w układzie kamery bezpośrednio z wykrytych narożników i niewielkiego zestawu parametrów kalibracyjnych. Parametry wewnętrzne to ogniskowe kamery w pikselach dla osi X i Y (fx, fy) oraz środek optyczny w pikselach (cx, cy); wszystkie cztery aplikacja mierzy raz za pomocą procedury kalibracyjnej i następnie wpisuje je na stałe.
Gdy parametry wewnętrzne są podane, zwrócony obiekt AprilTag wypełnia pola x_translation, y_translation, z_translation pozycją znacznika względem kamery, a pola x_rotation, y_rotation, z_rotation (oraz duplikat rotation dla symetrii) orientacją znacznika. Bez parametrów wewnętrznych wszystkie sześć pól ma wartość 0.0, a aplikacja sama odpowiada za potrzebne jej oszacowanie pozy.
Pola translacji są raportowane w szerokościach znacznika: dekoder traktuje znacznik jako mający szerokość 1 jednostki, więc aplikacja mnoży każdą translację przez fizyczną szerokość wydrukowanego znacznika, aby uzyskać odległości metryczne. Znacznik wydrukowany o szerokości 100 mm, raportujący z_translation = 8.3, znajduje się 830 mm od kamery; ten sam znacznik wydrukowany o szerokości 50 mm w tej samej odległości raportowałby z_translation = 16.6. Pola rotacji są w radianach i nie wymagają skalowania.
Oszacowanie pozy stanowi podstawę szerokiego zakresu zastosowań robotycznych: dokowania robota do stacji ładowania oznaczonej znacznikiem, podążania wydrukowaną trasą punktów nawigacyjnych, odtwarzania własnej pozy kamery na podstawie wielu znanych znaczników w otoczeniu. Kamera, która zna parametry wewnętrzne, widzi znacznik i ma rzeczywistą pozycję tego znacznika, ma na mocy tej samej arytmetyki rzeczywistą pozycję samej siebie.
5.28.4. Kiedy co wybrać¶
Kody QR i znaczniki AprilTag rozwiązują różne problemy. Wybór między nimi sprowadza się do tego, co przenosi wydrukowany symbol.
Gdy aplikacja musi przenieść dowolne dane przez wydrukowany symbol – adres URL, łańcuch numeru seryjnego, rekord kontaktu – właściwym wyborem jest kod QR. Setki bajtów mieszczą się w kodzie o umiarkowanym rozmiarze, kodowanie jest publiczne i obsługiwane na każdym smartfonie, a dekoder radzi sobie z obrotem, umiarkowanymi uszkodzeniami i ukośnymi kątami.
Gdy aplikacja potrzebuje niewielkiego identyfikatora odczytywanego w sposób ciągły z odległości, opcjonalnie z pozą – znacznika odniesienia na poruszającym się robocie, celu kalibracyjnego w pomieszczeniu, znacznika dokowania na stacji ładowania – właściwym wyborem jest AprilTag. Setki identyfikatorów w zupełności wystarczają do tego zastosowania, kod Hamminga odzyskuje dane z błędów bitowych, które pokonałyby kod QR, a oszacowanie pozy jest darmowe, gdy parametry wewnętrzne są skalibrowane.
Niektóre aplikacje używają obu: znacznik AprilTag oznacza znaną lokalizację, a powiązany kod QR (wydrukowany obok) przenosi metadane o tym, co ta lokalizacja oznacza. Oba detektory działają niezależnie na tej samej ramce, a aplikacja koreluje ich ramki ograniczające, aby dopasować każdy znacznik do jego towarzyszącego kodu.