5.3. Formáty pixelů¶
Algoritmus, který detekuje hrany, očekává, že každý pixel nese hodnotu jasu. Algoritmus, který sleduje barevný objekt, očekává, že každý pixel nese barvu. Algoritmus, který provádí morfologické uzavírání, očekává, že každý pixel je buď zapnutý, nebo vypnutý. Formát pixelů, který objekt Image nese – jeden z výčtu uvedeného v katalogu Vision Sensors – je to, co tato očekávání umožňuje ověřit předem: formát dopředu říká, v jaké podobě pixely jsou, a které algoritmy na nich tedy mohou běžet bez kroku konverze.
Tato stránka je o tom, jak se toto omezení projevuje v praxi. Který formát je správnou volbou závisí na tom, co bude pipeline dělat, a metody konverze mezi formáty jsou způsobem, jakým pipeline, která potřebuje více než jeden z nich, spojuje jednotlivé fáze dohromady.
Pět nekomprimovaných formátů pixelů a způsob, jakým se jejich bajty balí. JPEG a PNG zde nejsou vykresleny, protože jsou to komprimované toky proměnné délky, nikoli mřížky pixelů s pevnou velikostí.¶
5.3.1. Tahoun stupňů šedi¶
Většina klasického strojového vidění se redukuje na práci s hodnotami jasu. Detekce hran, porovnávání šablon, dekódování AprilTag, odhad optického toku, morfologické operátory, analýza blobů – všechny tyto algoritmy se na úrovni, na níž operují, dívají na to, jak jasný je každý pixel a jak se jeho jas srovnává s jasem okolních pixelů. Barva scény je často užitečná pro aplikaci, která je volá, ale samotné algoritmy ji nepotřebují.
Formát stupňů šedi předává algoritmům přesně toto, bez jakékoli režie. Jeden bajt na pixel nese hodnotu jasu od 0 (černá) po 255 (bílá). Tento formát má poloviční velikost oproti RGB565 a YUV422 a třetinovou oproti RGB888, takže každá operace zpracovává méně dat – je rychlejší a zatěžuje buffer méně. U menších kamer, kde snímkový buffer (frame buffer) soupeří se zbytkem skriptu o RAM, může být tento rozdíl v paměťové stopě tím, co rozhodne, zda se pipeline vůbec vejde. Pokud barva není vodítkem, které algoritmus potřebuje, stupně šedi jsou tou správnou odpovědí.
5.3.2. Barva prostřednictvím RGB565¶
Když je vodítkem barva – sledování barevné značky, rozlišení červených jablek od zelených, vybrání UI prvku podle odstínu – dva bajty na pixel poskytují dostatek barvy pro druhy klasifikace, které algoritmy provádějí. RGB565 je výchozí barevný formát na kameře a ten, který očekávají metody pracující s barvou.
Vykreslení anotovaného snímku – kreslení rámečků detekce, zápis diagnostického textu, dostání snímku na obrazovku nebo do vzdáleného prohlížeče – také přirozeně vyžaduje RGB565. Náhled v IDE, řadiče zabudovaného displeje a většina síťových cílů buď tento formát konzumují přímo, nebo z něj levně konvertují.
5.3.3. Bayer jako úložný formát¶
Bayerův obraz je surový výstup senzoru, ještě než jej ISP debayeruje do hotové barevné reprezentace. Každý pixel je jeden bajt nesoucí jediný barevný kanál – ten, který propustil barevný filtr na dané pozici v mozaice. To činí Bayerův obraz stejně velkým jako obraz ve stupních šedi a třetinovým oproti RGB888, což odpovídá tomu, k čemu je Bayer skutečně užitečný: k uložení mnoha snímků najednou, když je omezujícím faktorem RAM.
Háček je v tom, že algoritmy v modulu image nepracují s Bayerovými obrazy přímo. Bez debayerizace žádný pixel nenese dostatek informací k tomu, aby sám o sobě učinil barevný úsudek, a vzory, které algoritmy hledají – hrany, rohy, bloby – by byly mozaikou zkresleny. Jediné způsoby, jak Bayerův obraz číst nebo upravit, jsou get_pixel() a set_pixel(); vše ostatní očekává hotovou reprezentaci.
Vzorec, který z toho vyplývá, je ukládat snímky jako Bayer tak dlouho, dokud potřebují čekat ve frontě, a každý z nich konvertovat na stupně šedi nebo RGB565 v okamžiku, kdy jeho zpracování skutečně začíná. Konverze stojí cykly CPU, ale šetří RAM, která by jinak byla vázána držením hotových snímků po celou dobu životnosti aplikace.
Poznámka
Jedinými operacemi modulu image s Bayerovými pixely přímo jsou get_pixel(), set_pixel() a cesta JPEG kódování, která napájí náhled v IDE nebo vzdálený prohlížeč. Kreslení, analýza a filtrování vyžadují nejprve konverzi na stupně šedi, RGB565 nebo binární formát.
5.3.4. YUV422 pro pipeline, které chtějí obojí¶
YUV422 odděluje informaci každého pixelu na kanál jasu (Y) a dva kanály chrominance (U a V) a chrominanci podvzorkovává tak, že sousední dvojice pixelů sdílejí jediné U a jediné V. Bajty na pixel v průměru vycházejí na dva – stejně jako u RGB565 – ale jsou rozloženy tak, že kanál Y je už spojitý 8bitový obraz ve stupních šedi umístěný na známých offsetech v bufferu.
Toto rozložení je přesně to, co pipeline chce, když jsou některé její fáze prací ve stupních šedi a některé potřebují barvu. Přímé čtení hodnot Y pro fáze ve stupních šedi vynechá náklady na explicitní konverzi; kanály U a V jsou tu k dispozici, když pozdější fáze barvu skutečně potřebuje. Mimo tento konkrétní vzorec je RGB565 obvykle jednodušší volbou pro barvu a stupně šedi jednodušší volbou pro práci pouze s jasem – hodnota YUV422 spočívá v tom, že je dobré v obojím zároveň.
Poznámka
Modul image pracuje s YUV422 omezenějším způsobem než se stupni šedi, RGB565 nebo binárním formátem – přímé čtení kanálu Y pro práci ve stupních šedi a cesta JPEG kódování, která napájí náhled v IDE nebo vzdálený prohlížeč. Metody pracující s barvou očekávají RGB565; snímky YUV422 vyžadují před barevnou analýzou nebo kreslením explicitní konverzi.
5.3.5. Binární formát, masky a prahovaný výstup¶
Binární obraz je jeden bit na pixel: každý pixel je buď 0, nebo 1. Tento formát se zřídka objevuje jako snímek ze senzoru; místo toho se objevuje jako přirozený výstup prahování (kde barevný nebo jasový test klasifikuje každý pixel na „ano, odpovídá“ nebo „ne, neodpovídá“) a jako přirozený vstup morfologických operací a argumentu mask, který mnohé metody přijímají.
Praktickou výhodou tohoto formátu je jeho velikost. Binární obraz má osminovou paměťovou stopu oproti obrazu ve stupních šedi, takže nosit s sebou velkou masku – volbu na úrovni jednotlivých pixelů, kterých pozic se má nějaká navazující operace dotknout – je levné. Skutečnost, že mnohé operace přijímají binární obraz jako klíčový argument mask=, je druhou stranou téže mince: formát je malý a zřetězení binárního výstupu jedné fáze do vstupu masky jiné fáze je běžným vzorcem pipeline.
5.3.6. JPEG a PNG na hranici¶
Objekty Image typu JPEG a PNG se od ostatních v katalogu liší. Nejsou to mřížky pixelů; jsou to komprimované toky bajtů, které kódují data pixelů ve formě, kterou operace na úrovni pixelů nedokážou číst. Volání get_pixel() na JPEG nevrátí pixel na dané pozici; tento pixel není nikde v bufferu rozbalený, aby si jej metoda mohla vyzvednout.
JPEG a PNG se objevují na hranici zpracování obrazu, kde data pixelů opouštějí nebo vstupují do kamery v komprimované podobě. Uložení snímku na disk jako JPEG udrží soubor malý; odeslání snímku po síti jako JPEG udrží přenos levný; načtení referenčního snímku ze souboru JPEG umožní, aby ležel na disku v mnohem menší podobě, než by zabíraly surové pixely. Pro kterýkoli z těchto případů použití je komprimovaná reprezentace tou správnou odpovědí. Aby se však na JPEG provedlo jakékoli skutečné zpracování, aplikace jej nejprve konvertuje do použitelného formátu – a tato konverze je tím místem, kde se komprimované bajty rozbalí na pixely a kde k nafouknutí bufferu (30KB JPEG se může stát 600KB RGB565) skutečně dojde.
5.3.7. Konverze mezi formáty¶
Cesta konverze je tím, co sešívá různé formáty do jediné pipeline. Pět metod třídy Image přijímá existující obraz a vrací nový v jiném formátu:
to_grayscale()vytváří obraz s jedním bajtem na pixel, formát, který chtějí klasické algoritmy.to_rgb565()vytváří dvoubajtový barevný formát na pixel, kterým mluví jak metody pracující s barvou, tak náhled v IDE.to_bitmap()vytváří jednobitový binární obraz, formát, který přijímá morfologie a argumentymask.to_jpeg()vytváří obraz komprimovaný do JPEG vhodný pro uložení nebo přenos.to_png()vytváří obraz komprimovaný do PNG, když je upřednostňováno bezztrátové kódování před menšími soubory JPEG.
Každá konverze probíhá ve výchozím nastavení na místě: buffer zdrojového obrazu je přepsán konvertovaným výsledkem a původní pixely zdroje jsou po návratu z volání pryč. To je nejlevnější možnost jak z hlediska CPU, tak z hlediska paměti, a je správnou odpovědí, když zdrojový snímek nebude potřeba k ničemu dalšímu.
Když je zdroj stále potřeba – když pozdější fáze pipeline musí vidět původní snímek – výchozí chování na místě přepíší dva klíčové argumenty. copy=True alokuje pro konvertovaný obraz samostatný buffer na haldě Pythonu a ponechá zdroj nedotčený. copy_to_fb=True provede stejnou alokaci, ale umístí ji do snímkového bufferu (frame buffer) místo na haldu – což je to, po čem aplikace sáhne, když konvertovaný obraz potřebuje skončit v náhledu IDE, protože IDE čte ze snímkového bufferu (frame buffer).
Dvě další metody vytvářejí obrazy RGB565 obarvené prostřednictvím palety místo přímou konverzí. to_rainbow() mapuje každou jednokanálovou vstupní hodnotu na barvu podél plynulého přechodu, který prochází viditelným spektrem. to_ironbow() mapuje každou vstupní hodnotu na nelineární paletu termální kamery, která přechází od černé přes tmavě červené a oranžové až po bílou. Obě jsou spíše nástroji vizualizace než měření; smyslem je učinit jednokanálový obraz, jehož surové hodnoty by byly jinak oku neviditelné, čitelným na první pohled.
5.3.8. Velikost bufferu¶
Jeden poslední detail o formátech, který stojí za to explicitně uvést. size() vždy uvádí velikost bajtového bufferu, nikoli počet pixelů. U nekomprimovaných formátů to vyplývá přímo z rozměrů a počtu bajtů na pixel: width * height * bytes_per_pixel. U JPEG a PNG je to velikost komprimovaného toku, která se mění snímek od snímku v závislosti na tom, co scéna obsahuje. Kód, který alokuje buffery z bajtových rozpočtů, používá size() pro první případ; kód, který streamuje komprimované snímky z kamery, jej čte po každé kompresi, aby věděl, kolik bajtů tok skutečně obsahuje.