5.29. Kody kreskowe i kody Data Matrix

Dwie kolejne rodziny kodów dopełniają dekodery kamery. Jednowymiarowe kody kreskowe – paski na boku pudełka płatków, opaska na rękę w szpitalu, etykieta wysyłkowa – to najstarsze maszynowo czytelne symbole nadal używane na co dzień. Kody Data Matrix są dwuwymiarowe, jak kody QR, ale gęstsze przy tym samym rozmiarze ładunku i ukierunkowane na znakowanie przemysłowe – znak producenta wytrawiony laserowo na płytce drukowanej, a nie plakat na ścianie. Moduł image ma dedykowany dekoder do każdego z nich, obejmując zastosowania przemysłowe, detaliczne i inwentaryzacyjne, do których konsumenckie kody 2D nigdy do końca nie dotarły.

5.29.1. Kody kreskowe 1D

Jednowymiarowy kod kreskowy koduje swój ładunek jako sekwencję pionowych pasków o różnej szerokości, odczytywaną od lewej do prawej (lub od góry do dołu w przypadku kodów zorientowanych pionowo). Szerokości kwantyzują się do jednej z małego zbioru wartości, a sekwencja szerokości składa się na znaki w dowolnej symbolice wybranej przez drukarkę: numerycznej dla kodu produktu UPC, alfanumerycznej dla numeru części magazynowej lub dowolnego tekstu dla etykiety Code 128.

find_barcodes() skanuje ramkę w poszukiwaniu kodów kreskowych 1D w dowolnej z obsługiwanych symbolik i zwraca listę obiektów wynikowych BarCode:

codes = img.find_barcodes()

for c in codes:
    img.draw_rectangle(c.rect, color=(0, 255, 0))
    print(c.payload, c.type, c.quality)

Dekoder skanuje ramkę zarówno poziomo, jak i pionowo w pojedynczym wywołaniu, więc kod kreskowy wydrukowany pod dowolnym kątem zostaje znaleziony w jednym przebiegu, bez konieczności obracania wejścia przez aplikację. roi ogranicza obszar wyszukiwania; nie istnieją żadne inne parametry dostrajania – dekoder jest samowystarczalny.

Obsługiwane symboliki obejmują powszechne rodziny konsumenckie i przemysłowe. Zestaw detaliczny to image.EAN2, image.EAN5, image.EAN8, image.UPCE, image.UPCA, image.EAN13 (numeryczne kody o stałej długości na większości opakowań konsumenckich), image.ISBN10 oraz image.ISBN13 (te same rodziny przystosowane do książek). Zestaw ogólnego przeznaczenia to image.I25 (Interleaved 2 of 5, powszechny na etykietach wysyłkowych), image.CODABAR (używany w bibliotekach i bankach krwi), image.CODE39, image.CODE93 oraz image.CODE128 (symboliki alfanumeryczne o zmiennej długości dla dowolnego tekstu). Rodzina kodów na krawędzie półek image.DATABAR (RSS-14) i image.DATABAR_EXP (RSS-Expanded) dopełnia listę.

Każde wykrycie niesie ze sobą słownictwo ramki ograniczającej – x, y, w, h, rect, corners – oraz zdekodowany payload jako ciąg znaków. type to stała symboliki z powyższej listy, którą aplikacja sprawdza, gdy zależy jej konkretnie na tym, która rodzina została zdekodowana (np. akceptując tylko EAN13 w aplikacji skanera spożywczego).

Dwa pola, które mają znaczenie dla filtrowania, to rotation i quality. rotation to kąt kodu kreskowego w płaszczyźnie obrazu wyrażony w radianach: dekoder radzi sobie z dowolnymi obrotami, ale kod niższego poziomu, który chce wyświetlić wykrycie schludnie, może chcieć odfiltrować kody, które wracają przechylone powyżej pewnego progu.

quality to liczba dekodowań: liczba linii skanowania, które pomyślnie zdekodowały ten sam ładunek. Dekoder przebiega przez każdy wiersz (i kolumnę) ramki, który przecina kod kreskowy, i zwiększa licznik za każdym razem, gdy dekodowanie się powiedzie. Wydrukowany kod kreskowy w ostrym ujęciu i przy dobrym oświetleniu daje quality rzędu kilkudziesięciu; częściowo zasłonięty lub zamazany kod kreskowy może zostać zdekodowany na zaledwie jednej lub dwóch liniach skanowania i zgłosić quality równe 1 – 2. Odfiltrowanie wykryć poniżej quality > 5 odrzuca przejściowe błędne dekodowania z pojedynczej linii skanowania bez żadnych strat dla autentycznych wykryć.

Aplikacja korzystająca z kodów kreskowych 1D jest niewielka. Przechwyć ramkę, wywołaj find_barcodes(), przejdź po zwróconej liście, filtruj po c.type i c.quality oraz przekaż c.payload przez UART lub USB do dowolnego dalszego etapu, który rejestruje lub rozlicza zeskanowanie.

5.29.2. Data Matrix

Kod Data Matrix to symbol 2D, który koduje swój ładunek jako siatkę czarnych i białych komórek, tak jak robi to kod QR. Różni się od kodu QR pod dwoma praktycznymi względami: jest mniejszy przy tym samym rozmiarze ładunku (kodowanie jest gęstsze) i jest ukierunkowany na zastosowania przemysłowe zamiast konsumenckich (gdzie dominują kody QR). Wzory wytrawione laserowo w metalowych częściach na hali produkcyjnej, etykiety drukowane na obudowach układów scalonych, znaki umieszczane na strzykawkach medycznych – wszystkie te elementy to zazwyczaj kody Data Matrix, a nie kody QR.

find_datamatrices() skanuje ramkę w poszukiwaniu kodów Data Matrix i zwraca listę obiektów wynikowych DataMatrix:

codes = img.find_datamatrices()

for c in codes:
    img.draw_rectangle(c.rect, color=(0, 255, 0))
    print(c.payload, c.rows, c.columns)

roi ogranicza obszar wyszukiwania w zwykły sposób. Jedynym pokrętłem dostrajania specyficznym dla dekodera jest effort, liczba całkowita kontrolująca, jak intensywnie dekoder pracuje, aby znaleźć dopasowanie. Wyższe wartości poprawiają wykrywanie słabych, uszkodzonych lub ukośnych kodów kosztem liczby klatek na sekundę; niższe wartości działają szybciej, ale mogą pominąć kody, które wyższy wysiłek by znalazł. Wartości poniżej około 160 praktycznie nie wykrywają niczego; wartości powyżej około 240 dają malejące korzyści. Domyślna wartość 200 to rozsądny kompromis dla wyraźnego obrazu, a właściwym punktem wyjścia dla nowej aplikacji jest wartość domyślna plus minus 20, w zależności od tego, czy obiekty są czyste (niżej), czy zniszczone (wyżej).

Każde wykrycie niesie ze sobą słownictwo ramki ograniczającej oraz cztery wykryte narożniki, zdekodowany payload i rotation w płaszczyźnie obrazu wyrażony w radianach. Metadane układu opisują rozmiar i gęstość symbolu odczytanego przez dekoder: rows i columns to liczby komórek siatki danych; capacity to maksymalna liczba znaków ładunku, jaką symbol mógł przenieść przy tym rozmiarze; padding to liczba tych slotów, które pozostały niewykorzystane (capacity - len(payload)).

Pola układu są przydatne dla aplikacji, które muszą zweryfikować format wytrawionego znaku, a nie jego zawartość. System śledzenia części może wymagać, aby wszystkie znaki były kodami 12 na 12 z co najwyżej dwoma znakami dopełnienia; wykrycie, które wróciło jako 8 na 8 (symbol mniejszy niż przewiduje specyfikacja) lub z 10 znakami dopełnienia (w większości puste), jest oznaczane do ponownego znakowania.

5.29.3. Kiedy co wybrać

O ile QR kontra AprilTag sprowadzało się do rodzaju ładunku (dowolne dane kontra mały identyfikator), o tyle kody kreskowe kontra kody Data Matrix sprowadzają się do gęstości fizycznej i branży.

Gdy aplikacja jest skierowana do konsumenta, a kody już istnieją w terenie – artykuły spożywcze, książki, etykiety wysyłkowe, książki biblioteczne – właściwym detektorem jest find_barcodes(). Kody, które aplikacja odczytuje, zostały wydrukowane, aby odczytał je inny system, a ustandaryzowane symboliki detaliczne są tym, czego ten system oczekiwał.

Gdy aplikacja jest przemysłowa, a kody są drukowane na potrzeby aplikacji – śledzenie zapasów na hali produkcyjnej, kody partii wytrawione na częściach, znaki identyfikowalności na urządzeniach medycznych – właściwym detektorem jest find_datamatrices() lub find_qrcodes(), w zależności od tego, czy aplikacja potrzebuje większej gęstości Data Matrix, czy szerszego wsparcia narzędziowego QR.

Garstka aplikacji łączy wszystkie cztery detektory w jednym potoku. Kamera kontroli paczek może uruchomić przebieg find_barcodes() dla wydrukowanego UPC, przebieg find_qrcodes() dla wysyłkowego kodu QR na tym samym pudełku oraz przebieg find_datamatrices() dla wytrawionego kodu części, wszystko na tej samej przechwyconej ramce; trzy listy wyników są skorelowane na podstawie pozycji ramki ograniczającej i raportowane jako pojedynczy rekord wykrycia. Koszt każdego detektora się sumuje, więc aplikacje, które tak robią, zazwyczaj zawężają każdy przebieg odpowiednim roi, zamiast przeszukiwać całą ramkę dla każdego rodzaju kodu.