Čtení QR kódů řádkovou kamerou¶
Cvičení je zaměřené na práci s řádkovými kamerami. Řádkové kamery jsou díky svým specifickým vlastnostem vynikající pro řadu úloh, typickým použitím je snímání nekonečného pásu ve výrobních linkách. Poněvadž nekonečné pásy nemáme v dostatečném počtu, budeme je simulovat pomocí povrchu otočného válce - plechovky. Na povrchu plechovky bude nalepen QR kód, který chceme přečíst.
Potřebné knihovny¶
Pro detekci a čtení QR kódu použijeme knihovnu qreader, která umožňuje detekci a a segmentaci QR kódu pomocí modelu YOLOv8 a jeho následné čtení pomocí knihovny pyzbar. qreader je již zakomponován do Improutils (modul recognition). Je potřeba mít Improutils alespoň ve verzi 1.14. Dále je potřeba mít systémovou knihovnu zbar. Na windows je již nainstalována díky samotné instalaci pythonu, na linuxu a macu je potřeba ji doinstalovat zvlášť - sudo apt-get install libzbar0 zbar-tools
, případně brew install zbar
.
Řádkové kamery¶
Řádková kamera neboli řádkový skener je speciální druh kamery, která snímá pouze 1 řádek. Díky tomu je schopna dosáhnout obrovských frekvencí snímání (až ticíce řádků za 1 sekundu). V labu máme kamery Basler Racer o velikosti řádku 6k, 8k a 12k pixelů.
Z podstaty skeneru však vyplývá, že se objekt pod kamerou musí pohybovat. V případě, že se objekt nebude pohybovat, vytvoří kamera obrázek, který bude mít všechny řádky totožné (pouze ten 1, který kamera snímá).
Tím, že je kamera schopná dosahovat neskutečných frekvencí snímání dále umožňuje získávat obrazová data v neskutečně velkém rozlišení. Na druhou stranu nás tím nutí nastavovat nízkou dobu expozice a tedy je zapotřebí mnohem více světla.
Důležité pojmy související s řádkovými kamerami:
- frame - během komunikace s počítačem řádková kamera typicky neposílá každý řádek zvlášť, ale ukládá si je do lokálního bufferu a odesílá je pohromadě v jednom "frame" (rámci). Pylon Viewer pak řádky z jednoho frame zobrazuje v jednom obrázku, nad sebou. Počet řádků je možné v konfiguraci kamery nastavit parametrem height.
- acquisition - režim snímání kamery, typicky single shot/continuous. Single shot vyfotí pouze jeden frame, režim continuous rámce snímá nepřetržitě. V aplikacích jako jsou nekonečné pásy je možné použít buď single shot akvizici a mít zvláštní trigger pro každý objekt, nebo continuous akvizici a objekty rozlišovat v post-processingu.
Import knihoven a konfigurace¶
from improutils import *
Úkol 1¶
Seznamte se s řádkovou kamerou a jejími parametry, jejichž nastavení je pro správné snímání kritické. Kamera se nejlépe zaostřuje na jakýkoliv černobílý či kontrastní barevný vzor. Ideálně jako čárový kód, ale jen s několika čárami. K tomuto účelu jsou vhodné obrázky vytištěny.
Sestavte snímací soustavu včetně osvětlení a zaostřete kameru. Vytvořte snímek zaostřeného vzorce svislých čar. Vložte obrázek sestavené snímací soustavy.
Poznámka: Vyplatí se zkoumat vhodné ostření s nastavením nízkého počtu řádků (např. 200) a vysokým gainem, aby bylo dosaženo alespoň nějaké vyšší snímkovací frekvence pro ladění v reálném čase.
image = load_image('data/lines.jpg') ###
plot_images(image)
Úkol 2¶
Celou zaostřenou kameru pomocí kličky zdvihněte, aby byla kamera zaostřená na vršek plechovky (plechovka i s držákem je vysoká zhruba 6,4 cm). Nakonec získejte snímek QR kódu, který přečtěte a vizualizujte na původním snímku.
1) Získejte snímek černobílého QR kódu¶
Za stálého otáčení plechovky zachyťte snímek černobílého QR kódu na jejím povrchu. Rychlost otáčení plechovky je třeba udržovat pokud možno konstantní, aby různé části snímku QR kódu nebyly různě protáhlé - knihovna, kterou používáme, by pak měla problém kód přečíst. Důležité také je, aby vyfocený QR kód byl co nejpodobnější obdélníku, tedy aby nebyl zkosený či jinak deformovaný.
image = load_image('data/best.bmp') ###
plot_images(image)
2) Předzpracujte snímek QR kódu¶
V této sekci je nutné zejména přeškálovat obrázek tak, aby nebyl nevhodně protáhlý z důvodu nekorespondující snímkové frekvence vůči rychlosti otáčení plechovky. QR kód ve zpracovaném obrázku by měl být přibližně čtvercový (knihovna pyzbar, kterou použijeme ke čtení QR kódu, naštěstí poskytuje poměrně velkou toleranci). Obrázek můžete dále zpracovat dle libosti, například otočit nebo oříznout.
# new_width, new_height = ... ###
# resized_image = ... ###
resized_image = crop(image, 2600, 2400, 3550, 3350)
a = 1
b = 2
resized_image = cv2.convertScaleAbs(resized_image, a, b)
plot_images(resized_image)
3) Pomocí improutils přečtěte z QR kódu zakódovaný text a získejte souřadnice bounding boxu¶
Pro čtení QR kódu z obrázku používáme funkci qr_detect_and_decode
z improutils parametrem return_detections=True
. Funkce vrací informace o všech detekovaných QR kódech - především jejich dekódovaná data a souřadnice v původním obrázku. Pokud funkce QR kód ve vašem obrázku nedetekuje, zkuste data nasnímat znovu a případně kontaktujte cvičící, kteří vám poradí, co by bylo vhodné ve snímku zlepšit.
def detect_qr_code(image):
qr_data, qr_rect = qr_detect_and_decode(image, return_detections=True)
return qr_data, qr_rect
qr_data, qr_rect = detect_qr_code(resized_image)
qr_data, qr_rect
(('https://courses.fit.cvut.cz/BI-SVZ/exam/index.html',), [{'confidence': 0.9612981677055359, 'bbox_xyxy': array([ 35.812, 81.683, 915.57, 883.5], dtype=float32), 'bbox_xyxyn': array([ 0.037697, 0.085982, 0.96376, 0.93], dtype=float32), 'cxcy': (475.69097900390625, 482.5927734375), 'cxcyn': (0.5007273463199013, 0.5079923930921053), 'wh': (879.757568359375, 801.8193359375), 'whn': (0.9260605982730263, 0.8440203536184211), 'polygon_xy': array([[ 48.984, 83.125], [ 46.016, 86.094], [ 46.016, 95], [ 47.5, 96.484], [ 47.5, 175.16], [ 46.016, 176.64], [ 46.016, 209.3], [ 44.531, 210.78], [ 44.531, 230.08], [ 46.016, 231.56], [ 46.016, 271.64], [ 44.531, 273.12], [ 44.531, 290.94], [ 43.047, 292.42], [ 43.047, 326.56], [ 44.531, 328.05], [ 44.531, 375.55], [ 46.016, 377.03], [ 46.016, 382.97], [ 44.531, 384.45], [ 44.531, 390.39], [ 43.047, 391.87], [ 43.047, 417.11], [ 41.562, 418.59], [ 41.562, 448.28], [ 43.047, 449.77], [ 43.047, 454.22], [ 41.562, 455.7], [ 41.562, 500.23], [ 43.047, 501.72], [ 43.047, 593.75], [ 41.562, 595.23], [ 41.562, 633.83], [ 43.047, 635.31], [ 43.047, 703.59], [ 41.562, 705.08], [ 41.562, 857.97], [ 43.047, 859.45], [ 43.047, 860.94], [ 93.516, 860.94], [ 95, 862.42], [ 120.23, 862.42], [ 121.72, 863.91], [ 151.41, 863.91], [ 152.89, 865.39], [ 230.08, 865.39], [ 231.56, 866.87], [ 274.61, 866.87], [ 276.09, 868.36], [ 293.91, 868.36], [ 295.39, 869.84], [ 320.62, 869.84], [ 322.11, 871.33], [ 377.03, 871.33], [ 378.52, 872.81], [ 408.2, 872.81], [ 409.69, 874.3], [ 440.86, 874.3], [ 442.34, 875.78], [ 497.27, 875.78], [ 498.75, 874.3], [ 503.2, 874.3], [ 504.69, 875.78], [ 515.08, 875.78], [ 516.56, 877.27], [ 526.95, 877.27], [ 528.44, 875.78], [ 534.38, 875.78], [ 535.86, 877.27], [ 538.83, 877.27], [ 540.31, 875.78], [ 546.25, 875.78], [ 547.73, 877.27], [ 549.22, 875.78], [ 553.67, 875.78], [ 555.16, 874.3], [ 564.06, 874.3], [ 565.55, 872.81], [ 568.52, 872.81], [ 570, 874.3], [ 577.42, 874.3], [ 578.91, 875.78], [ 584.84, 875.78], [ 586.33, 877.27], [ 605.62, 877.27], [ 607.11, 878.75], [ 632.34, 878.75], [ 633.83, 877.27], [ 644.22, 877.27], [ 645.7, 878.75], [ 657.58, 878.75], [ 659.06, 880.23], [ 662.03, 880.23], [ 663.52, 881.72], [ 672.42, 881.72], [ 673.91, 883.2], [ 898.05, 883.2], [ 899.53, 881.72], [ 903.98, 881.72], [ 905.47, 880.23], [ 905.47, 878.75], [ 906.95, 877.27], [ 906.95, 808.98], [ 908.44, 807.5], [ 908.44, 795.62], [ 906.95, 794.14], [ 906.95, 758.52], [ 908.44, 757.03], [ 908.44, 623.44], [ 906.95, 621.95], [ 906.95, 261.25], [ 908.44, 259.77], [ 908.44, 188.52], [ 909.92, 187.03], [ 909.92, 133.59], [ 911.41, 132.11], [ 911.41, 102.42], [ 906.95, 97.969], [ 905.47, 97.969], [ 903.98, 96.484], [ 828.28, 96.484], [ 826.8, 95], [ 691.72, 95], [ 690.23, 93.516], [ 684.3, 93.516], [ 682.81, 95], [ 656.09, 95], [ 654.61, 93.516], [ 575.94, 93.516], [ 574.45, 92.031], [ 534.38, 92.031], [ 532.89, 90.547], [ 504.69, 90.547], [ 503.2, 89.062], [ 491.33, 89.062], [ 489.84, 90.547], [ 457.19, 90.547], [ 455.7, 89.062], [ 350.31, 89.062], [ 348.83, 87.578], [ 285, 87.578], [ 283.52, 86.094], [ 230.08, 86.094], [ 228.59, 84.609], [ 192.97, 84.609], [ 191.48, 83.125]], dtype=float32), 'polygon_xyn': array([[ 0.051562, 0.0875], [ 0.048437, 0.090625], [ 0.048437, 0.1], [ 0.05, 0.10156], [ 0.05, 0.18438], [ 0.048437, 0.18594], [ 0.048437, 0.22031], [ 0.046875, 0.22187], [ 0.046875, 0.24219], [ 0.048437, 0.24375], [ 0.048437, 0.28594], [ 0.046875, 0.2875], [ 0.046875, 0.30625], [ 0.045313, 0.30781], [ 0.045313, 0.34375], [ 0.046875, 0.34531], [ 0.046875, 0.39531], [ 0.048437, 0.39687], [ 0.048437, 0.40312], [ 0.046875, 0.40469], [ 0.046875, 0.41094], [ 0.045313, 0.4125], [ 0.045313, 0.43906], [ 0.04375, 0.44062], [ 0.04375, 0.47187], [ 0.045313, 0.47344], [ 0.045313, 0.47812], [ 0.04375, 0.47969], [ 0.04375, 0.52656], [ 0.045313, 0.52812], [ 0.045313, 0.625], [ 0.04375, 0.62656], [ 0.04375, 0.66719], [ 0.045313, 0.66875], [ 0.045313, 0.74063], [ 0.04375, 0.74219], [ 0.04375, 0.90312], [ 0.045313, 0.90469], [ 0.045313, 0.90625], [ 0.098437, 0.90625], [ 0.1, 0.90781], [ 0.12656, 0.90781], [ 0.12812, 0.90937], [ 0.15937, 0.90937], [ 0.16094, 0.91094], [ 0.24219, 0.91094], [ 0.24375, 0.9125], [ 0.28906, 0.9125], [ 0.29063, 0.91406], [ 0.30937, 0.91406], [ 0.31094, 0.91562], [ 0.3375, 0.91562], [ 0.33906, 0.91719], [ 0.39687, 0.91719], [ 0.39844, 0.91875], [ 0.42969, 0.91875], [ 0.43125, 0.92031], [ 0.46406, 0.92031], [ 0.46562, 0.92187], [ 0.52344, 0.92187], [ 0.525, 0.92031], [ 0.52969, 0.92031], [ 0.53125, 0.92187], [ 0.54219, 0.92187], [ 0.54375, 0.92344], [ 0.55469, 0.92344], [ 0.55625, 0.92187], [ 0.5625, 0.92187], [ 0.56406, 0.92344], [ 0.56719, 0.92344], [ 0.56875, 0.92187], [ 0.575, 0.92187], [ 0.57656, 0.92344], [ 0.57812, 0.92187], [ 0.58281, 0.92187], [ 0.58438, 0.92031], [ 0.59375, 0.92031], [ 0.59531, 0.91875], [ 0.59844, 0.91875], [ 0.6, 0.92031], [ 0.60781, 0.92031], [ 0.60938, 0.92187], [ 0.61563, 0.92187], [ 0.61719, 0.92344], [ 0.6375, 0.92344], [ 0.63906, 0.925], [ 0.66562, 0.925], [ 0.66719, 0.92344], [ 0.67813, 0.92344], [ 0.67969, 0.925], [ 0.69219, 0.925], [ 0.69375, 0.92656], [ 0.69687, 0.92656], [ 0.69844, 0.92812], [ 0.70781, 0.92812], [ 0.70938, 0.92969], [ 0.94531, 0.92969], [ 0.94687, 0.92812], [ 0.95156, 0.92812], [ 0.95312, 0.92656], [ 0.95312, 0.925], [ 0.95469, 0.92344], [ 0.95469, 0.85156], [ 0.95625, 0.85], [ 0.95625, 0.8375], [ 0.95469, 0.83594], [ 0.95469, 0.79844], [ 0.95625, 0.79687], [ 0.95625, 0.65625], [ 0.95469, 0.65469], [ 0.95469, 0.275], [ 0.95625, 0.27344], [ 0.95625, 0.19844], [ 0.95781, 0.19687], [ 0.95781, 0.14062], [ 0.95937, 0.13906], [ 0.95937, 0.10781], [ 0.95469, 0.10312], [ 0.95312, 0.10312], [ 0.95156, 0.10156], [ 0.87187, 0.10156], [ 0.87031, 0.1], [ 0.72812, 0.1], [ 0.72656, 0.098437], [ 0.72031, 0.098437], [ 0.71875, 0.1], [ 0.69063, 0.1], [ 0.68906, 0.098437], [ 0.60625, 0.098437], [ 0.60469, 0.096875], [ 0.5625, 0.096875], [ 0.56094, 0.095312], [ 0.53125, 0.095312], [ 0.52969, 0.09375], [ 0.51719, 0.09375], [ 0.51562, 0.095312], [ 0.48125, 0.095312], [ 0.47969, 0.09375], [ 0.36875, 0.09375], [ 0.36719, 0.092188], [ 0.3, 0.092188], [ 0.29844, 0.090625], [ 0.24219, 0.090625], [ 0.24062, 0.089062], [ 0.20312, 0.089062], [ 0.20156, 0.0875]], dtype=float32), 'quad_xy': array([[ 46.11, 82.055], [ 908.87, 98.032], [ 906.74, 886.17], [ 40.42, 861.6]], dtype=float32), 'quad_xyn': array([[ 0.048537, 0.086373], [ 0.95671, 0.10319], [ 0.95447, 0.93281], [ 0.042547, 0.90695]]), 'padded_quad_xy': array([[ 44.031, 80.394], [ 911.5, 96.459], [ 909.36, 889.88], [ 38.303, 865.18]], dtype=float32), 'padded_quad_xyn': array([[ 0.046348, 0.084626], [ 0.95948, 0.10154], [ 0.95722, 0.93672], [ 0.040319, 0.91071]]), 'image_shape': (950, 950)}])
4) Do obrázku vykreslete ohraničující obdélník a vypište získaný text¶
Třešničkou na dortu je pro nás vizualizace QR kódu v obrázku - tj. zobrazení bounding boxu a vypsání dekódovaného textu vedle něj. K tomu je možné použít například funkce cv2.rectangle() a cv2.putText() knihovny opencv.
tmp0 = qr_rect[0]['bbox_xyxy']
tmp = [int(i) for i in tmp0]
mid = tmp0[0:2] + tmp0[2:4]
print(mid)
mid = [int(i/2) for i in mid]
[ 951.38 965.19]
annotated_image = resized_image.copy()
cv2.rectangle(annotated_image, tmp[0:2], tmp[2:4], (0, 255, 0), 3) ###
cv2.putText(annotated_image, qr_data[0], (30, 70), 0, 0.7, (0, 255, 0), 2) ###
plot_images(annotated_image)
Bonus¶
Knihovna nejlépe přečte QR kód tehdy, jestliže jsou čtverce v rozích kódu stejně velké. Nedodržením konstantní rychlosti otáčení plechovky mohou vzniknout deformace, které se nejvíce projeví například "protažením" pozicujících čtverců QR kódu. Bonusovým úkolem je detekce pozicujících čtverců a kontrola, zda jsou všechny stejné.
### Vlastní postup
gray = to_gray(resized_image)
mask = segmentation_auto_threshold(gray)
# plot_images(mask)
contour_drawn, contours_count, contours = find_contours(mask, min_area=7500, max_area=10000,external=False)
contour_drawn = cv2.bitwise_and(~mask, contour_drawn)
plot_images(contour_drawn)
# len(contours)
areas = []
for c in contours:
area = cv2.contourArea(c)
areas.append(area)
from itertools import combinations
for x,y in combinations(areas, 2):
diff = x-y
if abs(diff) > x * 0.05:
print("Not OK")
print(x, y)
else:
print("OK")
OK