5.3. Pixelformat

En algoritm som detekterar kanter förväntar sig att varje pixel innehåller ett ljusstyrkevärde. En algoritm som spårar ett färgat objekt förväntar sig att varje pixel bär färg. En algoritm som kör morfologisk slutning förväntar sig att varje pixel är antingen på eller av. Pixelformatet som en Image bär – ett av de uppräknade Vision Sensors i katalogen – är det som gör dessa förväntningar kontrollerbara i förväg: formatet anger, i förväg, vilken form pixlarna har, och vilka algoritmer som därför kan köras på dem utan ett konverteringssteg.

Den här sidan handlar om hur den begränsningen utspelar sig i praktiken. Vilket format som är rätt val beror på vad pipelinen ska göra, och konverteringsmetoderna mellan format är hur en pipeline som behöver fler än ett av dem knyter samman stegen.

En vertikal stapel av fem märkta bytelayout-remsor. BINARY visar en byte uppdelad i åtta enbitsceller, markerade "8 pixels per byte". GRAYSCALE visar tre märkta enbyteceller var och en markerad "1 pixel". RGB565 visar två intilliggande bytes med bitfälten RRRRR GGGGGG BBBBB märkta "1 pixel". YUV422 visar fyra märkta byte- celler Y0, U, Y1, V markerade "2 pixels". BAYER visar två rader med fyra märkta byte- celler: R G R G på översta raden, G B G B på den nedre raden.

De fem okomprimerade pixelformaten och hur deras bytes packas. JPEG och PNG ritas inte här eftersom de är komprimerade strömmar med variabel längd snarare än pixelrutnät med fast storlek.

5.3.1. Arbetshästen gråskala

Det mesta av klassiskt maskinseende handlar om att arbeta med ljusstyrkevärden. Kantdetektering, mallmatchning, AprilTag-avkodning, optisk-flöde-skattning, de morfologiska operatorerna, blobanalys – alla dessa, på den nivå algoritmerna arbetar på, tittar på hur ljus varje pixel är och hur ljusstyrkan jämför sig med ljusstyrkan hos närliggande pixlar. Scenens färg är ofta användbar för applikationen som anropar dem, men algoritmerna själva behöver den inte.

Gråskaleformatet ger algoritmerna exakt det, utan något overhead. En byte per pixel innehåller ett ljusstyrkevärde från 0 (svart) till 255 (vitt). Formatet är hälften så stort som RGB565 och YUV422 och en tredjedel så stort som RGB888, så varje operation går igenom mindre data – både snabbare och med mindre belastning på bufferten. På de mindre kamerorna, där bildbufferten konkurrerar med resten av skriptet om RAM, kan den skillnaden i fotavtryck vara det som avgör om en pipeline överhuvudtaget får plats. Om färg inte är den ledtråd algoritmen behöver är gråskala rätt svar.

5.3.2. Färg genom RGB565

När färg är ledtråden – spåra en färgad markör, skilja röda äpplen från gröna, plocka ut ett UI-element utifrån dess kulör – ger två bytes per pixel tillräckligt med färg för den typ av klassificering algoritmerna utför. RGB565 är standardfärgformatet på kameran, och det som de färgmedvetna metoderna på ytan förväntar sig.

Att rendera en kommenterad bildruta – rita detekteringsrutor, skriva diagnostisk text, få bildrutan till en skärm eller ut till en fjärrvisare – kräver också naturligt RGB565. IDE-förhandsgranskningen, de inbyggda skärmstyrenheterna och de flesta nätverksdestinationer antingen konsumerar formatet direkt eller konverterar från det billigt.

5.3.3. Bayer som lagringsformat

En Bayer-bild är den råa sensorutdatan, innan ISP:n debayrade den till en färdig färgrepresentation. Varje pixel är en byte som innehåller en enda färgkanal – den som färgfiltret på den positionen i mosaiken släppte igenom. Det gör att en Bayer-bild är lika stor som en gråskalebild och en tredjedel så stor som RGB888, vilket stämmer med vad Bayer faktiskt är användbart till: att lagra många bildrutor samtidigt när RAM är den begränsande faktorn.

Haken är att algoritmerna i image-modulen inte arbetar på Bayer-bilder direkt. Utan debayring bär ingen pixel tillräckligt med information för att göra en färgbedömning på egen hand, och mönstren algoritmerna letar efter – kanter, hörn, blobbar – skulle förvrängas av mosaiken. De enda sätten att läsa eller ändra en Bayer-bild är get_pixel() och set_pixel(); allt annat förväntar sig en färdig representation.

Mönstret som faller ut är att lagra bildrutor som Bayer så länge de behöver ligga i en kö och konvertera var och en till antingen gråskala eller RGB565 i det ögonblick dess bearbetning faktiskt börjar. Konverteringen kostar CPU-cykler men sparar det RAM som annars skulle bindas upp för att hålla färdiga bildrutor under applikationens livstid.

Anteckning

Image-modulens enda operationer på Bayer-pixlar direkt är get_pixel(), set_pixel(), och JPEG-kodningsvägen som matar IDE-förhandsgranskningen eller en fjärrvisare. Ritning, analys och filtrering kräver alla konvertering till gråskala, RGB565 eller binär först.

5.3.4. YUV422 för pipelines som vill ha båda

YUV422 separerar varje pixels information i en luminanskanal (Y) och två krominanskanaler (U och V), och subsamplar krominansen så att intilliggande pixelpar delar en enda U och en enda V. Antalet bytes per pixel blir i genomsnitt två – detsamma som RGB565 – men de är upplagda så att Y-kanalen redan är en kontinuerlig 8-bitars gråskalebild som ligger på kända offset i bufferten.

Den layouten är precis vad en pipeline vill ha när vissa av dess steg är gråskalearbete och vissa behöver färg. Att läsa Y-värdena direkt för gråskalestegen hoppar över kostnaden för en explicit konvertering; U- och V-kanalerna finns där när ett senare steg faktiskt behöver färg. Utanför det specifika mönstret är RGB565 vanligtvis det enklare valet för färg och gråskala det enklare valet för arbete med enbart ljusstyrka – YUV422:s värde kommer från att vara bra på båda samtidigt.

Anteckning

Image-modulen arbetar på YUV422 på ett mer begränsat sätt än på gråskala, RGB565 eller binär – direkta Y-kanalläsningar för gråskalearbete och JPEG-kodningsvägen som matar IDE-förhandsgranskningen eller en fjärrvisare. Färgmedvetna metoder förväntar sig RGB565; YUV422-bildrutor behöver en explicit konvertering före färganalys eller ritning.

5.3.5. Binär, masker och tröskelvärdesbehandlad utdata

En binär bild är en bit per pixel: varje pixel är antingen 0 eller 1. Formatet dyker sällan upp som en sensorinfångning; istället framträder det som den naturliga utdatan från tröskelvärdesbehandling (där ett färg- eller ljusstyrketest klassificerar varje pixel till ”ja, matchar” eller ”nej, gör det inte”) och som den naturliga indatan till morfologiska operationer och till mask-argumentet som många metoder accepterar.

Formatets praktiska fördel är dess storlek. En binär bild är en åttondel av en gråskalebilds fotavtryck, så att bära runt en stor mask – ett per-pixel-val av vilka positioner någon nedströmsoperation ska beröra – är billigt. Att många operationer accepterar en binär bild som ett mask=-nyckelordsargument är baksidan av samma poäng: formatet är litet, och att kedja den binära utdatan från ett steg in i maskindatan till ett annat är ett vanligt pipelinemönster.

5.3.6. JPEG och PNG vid gränsen

JPEG- och PNG-Image-objekt skiljer sig från de andra i katalogen. De är inte pixelrutnät; de är komprimerade byteströmmar som kodar pixeldata i en form som operationer på pixelnivå inte kan läsa. Att anropa get_pixel() på en JPEG returnerar inte pixeln på en position; pixeln ligger inte uppackad någonstans i bufferten för metoden att hämta.

JPEG och PNG dyker upp vid gränsen för bildbehandling, där pixeldata lämnar eller kommer in i kameran i komprimerad form. Att spara en bildruta till disk som JPEG håller filen liten; att skicka en bildruta över ett nätverk som JPEG håller överföringen billig; att läsa in en referensbildruta från en JPEG-fil låter den ligga på disk i en mycket mindre form än de råa pixlarna skulle göra. För något av dessa användningsfall är den komprimerade representationen rätt svar. För att göra någon faktisk bearbetning på en JPEG konverterar dock applikationen den till ett arbetbart format först – och den konverteringen är där de komprimerade bytena expanderas till pixlar och där buffertuppblåsningen (en 30 KB JPEG kan bli 600 KB RGB565) faktiskt sker.

5.3.7. Konvertera mellan format

Konverteringsvägen är det som syr ihop olika format till en enda pipeline. Fem metoder på klassen Image tar en befintlig bild och returnerar en ny i ett annat format:

  • to_grayscale() producerar en bild med en byte per pixel, formatet de klassiska algoritmerna vill ha.

  • to_rgb565() producerar färgformatet med två bytes per pixel som både de färgmedvetna metoderna och IDE-förhandsgranskningen talar.

  • to_bitmap() producerar en binär bild med en bit, formatet morfologi och mask-argument accepterar.

  • to_jpeg() producerar en JPEG-komprimerad bild lämplig för lagring eller överföring.

  • to_png() producerar en PNG-komprimerad bild när förlustfri kodning föredras framför JPEG:s mindre filer.

Varje konvertering körs på plats som standard: källbildens buffert skrivs över med det konverterade resultatet, och källans ursprungliga pixlar är borta efter att anropet returnerar. Det är det billigaste alternativet både för CPU och för minne, och det är rätt svar när källbildrutan inte kommer att behövas för något annat.

När källan fortfarande behövs – när ett senare steg i pipelinen måste se den ursprungliga bildrutan – åsidosätter två nyckelordsargument standardvärdet för på-plats. copy=True allokerar en separat buffert för den konverterade bilden på Python-heapen och lämnar källan intakt. copy_to_fb=True gör samma allokering men placerar den i bildbufferten istället för på heapen – vilket är vad en applikation tar till när den konverterade bilden behöver hamna i IDE-förhandsgranskningen, eftersom IDE:n läser från bildbufferten.

Två ytterligare metoder producerar RGB565-bilder färgade genom en palett istället för genom en rak konvertering. to_rainbow() mappar varje enkanaligt indatavärde till en färg längs en mjuk gradient som löper genom det synliga spektrumet. to_ironbow() mappar varje indatavärde till den icke-linjära värmekamerapaletten som löper från svart genom mörkröda och orange toner till vitt. Båda är visualiseringsverktyg snarare än mätverktyg; poängen är att göra en enkanalig bild vars råa värden annars skulle vara osynliga för ögat läsbar med en blick.

5.3.8. Buffertstorlek

En sista detalj om format värd att vara tydlig med. size() rapporterar alltid storleken på bytebufferten, inte pixelantalet. För okomprimerade format följer det direkt av dimensionerna och bytes per pixel: width * height * bytes_per_pixel. För JPEG och PNG är det storleken på den komprimerade strömmen, som varierar från bildruta till bildruta beroende på vad scenen innehåller. Kod som allokerar buffertar från bytebudgetar använder size() för det förra fallet; kod som strömmar komprimerade bildrutor ut ur kameran läser det efter varje komprimering för att veta hur många bytes strömmen faktiskt innehåller.