5.3. Pixelformaten¶
Een algoritme dat randen detecteert verwacht dat elke pixel een helderheidswaarde bevat. Een algoritme dat een gekleurd object volgt verwacht dat elke pixel kleur draagt. Een algoritme dat morfologische sluiting uitvoert verwacht dat elke pixel ofwel aan ofwel uit is. Het pixelformaat dat een Image draagt – een van de opgesomde Vision Sensors uit de catalogus – maakt die verwachtingen vooraf controleerbaar: het formaat zegt op voorhand in welke vorm de pixels zich bevinden, en welke algoritmes er dus zonder conversiestap op kunnen draaien.
Deze pagina gaat over hoe die beperking in de praktijk uitpakt. Welk formaat de juiste keuze is, hangt af van wat de pijplijn gaat doen, en de conversiemethoden tussen formaten zijn de manier waarop een pijplijn die er meer dan één nodig heeft de stadia aan elkaar rijgt.
De vijf ongecomprimeerde pixelformaten en hoe hun bytes worden gepakt. JPEG en PNG zijn hier niet getekend omdat het gecomprimeerde streams van variabele lengte zijn in plaats van pixelroosters met een vaste grootte.¶
5.3.1. Het grijswaarden-werkpaard¶
Het grootste deel van klassieke machine vision komt neer op het werken met helderheidswaarden. Randdetectie, template matching, AprilTag-decodering, optical-flow-schatting, de morfologische operatoren, blob-analyse – ze kijken allemaal, op het niveau waarop de algoritmes werken, naar hoe helder elke pixel is en hoe de helderheid zich verhoudt tot de helderheid van nabije pixels. De kleur van het tafereel is vaak nuttig voor de toepassing die ze aanroept, maar de algoritmes zelf hebben die niet nodig.
Het grijswaardenformaat geeft de algoritmes precies dat, zonder overhead. Eén byte per pixel bevat een helderheidswaarde van 0 (zwart) tot en met 255 (wit). Het formaat is half zo groot als RGB565 en YUV422 en een derde van de grootte van RGB888, dus elke bewerking verwerkt minder data – zowel sneller als met minder bufferdruk. Op de kleinere cams, waar de framebuffer met de rest van het script om RAM concurreert, kan dat verschil in voetafdruk bepalen of een pijplijn überhaupt past. Als kleur niet de aanwijzing is die het algoritme nodig heeft, is grijswaarden het juiste antwoord.
5.3.2. Kleur via RGB565¶
Wanneer kleur wel de aanwijzing is – het volgen van een gekleurde marker, rode appels van groene onderscheiden, een UI-element op tint herkennen – kopen twee bytes per pixel genoeg kleur voor de soorten classificatie die de algoritmes uitvoeren. RGB565 is het standaard kleurformaat op de cam, en het formaat dat de kleurbewuste methoden van de surface verwachten.
Het renderen van een geannoteerd frame – het tekenen van detectievakken, het schrijven van diagnostische tekst, het frame op een scherm of naar een externe viewer krijgen – vraagt ook van nature om RGB565. De IDE-preview, de display-controllers aan boord en de meeste netwerkbestemmingen verbruiken het formaat ofwel rechtstreeks of converteren er goedkoop vanuit.
5.3.3. Bayer als opslagformaat¶
Een Bayer-afbeelding is de ruwe sensoruitvoer, voordat de ISP deze debayerde tot een afgewerkte kleurrepresentatie. Elke pixel is één byte die een enkel kleurkanaal bevat – het kanaal dat het kleurenfilter op die positie in het mozaïek doorliet. Daardoor is een Bayer-afbeelding even groot als een grijswaardenafbeelding en een derde van de grootte van RGB888, wat aansluit bij waar Bayer eigenlijk nuttig voor is: het opslaan van veel frames tegelijk wanneer RAM de beperkende factor is.
Het addertje onder het gras is dat de algoritmes in de image-module niet rechtstreeks op Bayer-afbeeldingen werken. Zonder debayering draagt geen enkele pixel genoeg informatie om op zichzelf een kleuroordeel te vellen, en de patronen waar de algoritmes naar zoeken – randen, hoeken, blobs – zouden door het mozaïek vervormd worden. De enige manieren om een Bayer-afbeelding te lezen of te wijzigen zijn get_pixel() en set_pixel(); al het andere verwacht een afgewerkte representatie.
Het patroon dat hieruit voortvloeit is om frames als Bayer op te slaan zolang ze in een wachtrij moeten blijven staan en elk frame om te zetten naar grijswaarden of RGB565 op het moment dat de verwerking ervan daadwerkelijk begint. De conversie kost CPU-cycli maar bespaart de RAM die anders bezet zou worden gehouden door afgewerkte frames gedurende de levensduur van de toepassing.
Notitie
De enige bewerkingen van de image-module rechtstreeks op Bayer-pixels zijn get_pixel(), set_pixel(), en het JPEG-coderingspad dat de IDE-preview of een externe viewer voedt. Tekenen, analyse en filtering vereisen allemaal eerst een conversie naar grijswaarden, RGB565 of binair.
5.3.4. YUV422 voor pijplijnen die beide willen¶
YUV422 scheidt de informatie van elke pixel in een luminantiekanaal (Y) en twee chrominantiekanalen (U en V), en subsamplet de chrominantie zodat aangrenzende pixelparen één enkele U en één enkele V delen. De bytes per pixel komen gemiddeld op twee uit – hetzelfde als RGB565 – maar ze zijn zo ingedeeld dat het Y-kanaal al een doorlopende 8-bits grijswaardenafbeelding is die zich op bekende offsets in de buffer bevindt.
Die indeling is precies wat een pijplijn wil wanneer sommige van zijn stadia grijswaardenwerk zijn en sommige kleur nodig hebben. Het rechtstreeks lezen van de Y-waarden voor de grijswaardenstadia slaat de kosten van een expliciete conversie over; de U- en V-kanalen zijn er wanneer een later stadium daadwerkelijk kleur nodig heeft. Buiten dat specifieke patroon is RGB565 meestal de eenvoudigere keuze voor kleur en grijswaarden de eenvoudigere keuze voor werk met alleen helderheid – de waarde van YUV422 komt voort uit het tegelijkertijd goed zijn in beide.
Notitie
De image-module werkt op een meer beperkte manier op YUV422 dan op grijswaarden, RGB565 of binair – rechtstreekse Y-kanaal-lezingen voor grijswaardenwerk en het JPEG-coderingspad dat de IDE-preview of een externe viewer voedt. Kleurbewuste methoden verwachten RGB565; YUV422-frames hebben een expliciete conversie nodig vóór kleuranalyse of tekenen.
5.3.5. Binair, maskers en gedrempelde uitvoer¶
Een binaire afbeelding is één bit per pixel: elke pixel is ofwel 0 of 1. Het formaat verschijnt zelden als een sensoropname; in plaats daarvan verschijnt het als de natuurlijke uitvoer van drempelwaardebepaling (waar een kleur- of helderheidstest elke pixel classificeert als “ja, komt overeen” of “nee, komt niet overeen”) en als de natuurlijke invoer voor morfologische bewerkingen en voor het mask-argument dat veel methoden accepteren.
Het praktische voordeel van het formaat is de grootte. Een binaire afbeelding is een achtste van de voetafdruk van een grijswaardenafbeelding, dus het meedragen van een groot masker – een keuze per pixel van welke posities een bepaalde stroomafwaartse bewerking moet aanraken – is goedkoop. Het feit dat veel bewerkingen een binaire afbeelding accepteren als mask=-keyword-argument is de keerzijde van dezelfde medaille: het formaat is klein, en het aaneenschakelen van de binaire uitvoer van het ene stadium naar de maskerinvoer van het volgende is een veelvoorkomend pijplijnpatroon.
5.3.6. JPEG en PNG aan de grens¶
JPEG- en PNG-Image-objecten verschillen van de andere in de catalogus. Het zijn geen pixelroosters; het zijn gecomprimeerde byte-streams die pixeldata coderen in een vorm die bewerkingen op pixelniveau niet kunnen lezen. Het aanroepen van get_pixel() op een JPEG geeft niet de pixel op een positie terug; de pixel ligt nergens uitgepakt in de buffer voor de methode om op te halen.
JPEG en PNG verschijnen aan de grens van beeldverwerking, waar pixeldata de cam in gecomprimeerde vorm verlaat of binnenkomt. Een frame als JPEG naar schijf opslaan houdt het bestand klein; een frame als JPEG over een netwerk versturen houdt de transmissie goedkoop; een referentieframe uit een JPEG-bestand laden laat het in een veel kleinere vorm op schijf staan dan de ruwe pixels zouden doen. Voor al die toepassingen is de gecomprimeerde representatie het juiste antwoord. Om echter daadwerkelijke verwerking op een JPEG uit te voeren, converteert de toepassing deze eerst naar een bruikbaar formaat – en die conversie is waar de gecomprimeerde bytes worden uitgepakt tot pixels en waar de bufferballon (een JPEG van 30 KB kan 600 KB RGB565 worden) daadwerkelijk plaatsvindt.
5.3.7. Converteren tussen formaten¶
Het conversiepad is wat verschillende formaten tot één pijplijn aaneenrijgt. Vijf methoden op de Image-klasse nemen een bestaande afbeelding en geven een nieuwe terug in een ander formaat:
to_grayscale()produceert een afbeelding van één byte per pixel, het formaat dat de klassieke algoritmes willen.to_rgb565()produceert het kleurformaat van twee bytes per pixel dat zowel de kleurbewuste methoden als de IDE-preview spreken.to_bitmap()produceert een binaire afbeelding van één bit, het formaat dat morfologie enmask-argumenten accepteren.to_jpeg()produceert een JPEG-gecomprimeerde afbeelding die geschikt is voor opslag of transmissie.to_png()produceert een PNG-gecomprimeerde afbeelding wanneer verliesloze codering de voorkeur heeft boven de kleinere bestanden van JPEG.
Elke conversie loopt standaard in place: de buffer van de bronafbeelding wordt overschreven met het geconverteerde resultaat, en de originele pixels van de bron zijn weg nadat de aanroep terugkeert. Dat is de goedkoopste optie voor zowel CPU als geheugen, en het is het juiste antwoord wanneer het bronframe nergens anders meer voor nodig is.
Wanneer de bron wel nog nodig is – wanneer een later stadium van de pijplijn het originele frame moet zien – overschrijven twee keyword-argumenten de standaard van in-place. copy=True wijst een aparte buffer toe voor de geconverteerde afbeelding op de Python-heap en laat de bron intact. copy_to_fb=True doet dezelfde toewijzing maar plaatst deze in de framebuffer in plaats van op de heap – wat een toepassing inzet wanneer de geconverteerde afbeelding in de IDE-preview moet belanden, aangezien de IDE uit de framebuffer leest.
Twee verdere methoden produceren RGB565-afbeeldingen die gekleurd zijn via een palet in plaats van door een rechtstreekse conversie. to_rainbow() mapt elke invoerwaarde van één kanaal naar een kleur langs een vloeiend verloop dat door het zichtbare spectrum loopt. to_ironbow() mapt elke invoerwaarde naar het niet-lineaire palet van een warmtebeeldcamera dat loopt van zwart via donkerrood en oranje naar wit. Beide zijn visualisatie-hulpmiddelen in plaats van meethulpmiddelen; het doel is om een afbeelding met één kanaal, waarvan de ruwe waarden anders onzichtbaar voor het oog zouden zijn, in één oogopslag leesbaar te maken.
5.3.8. Buffergrootte¶
Nog één laatste detail over formaten dat het waard is om expliciet te benoemen. size() rapporteert altijd de grootte van de byte-buffer, niet het aantal pixels. Voor ongecomprimeerde formaten volgt dat rechtstreeks uit de afmetingen en de bytes per pixel: width * height * bytes_per_pixel. Voor JPEG en PNG is het de grootte van de gecomprimeerde stream, die per frame varieert afhankelijk van wat het tafereel bevat. Code die buffers toewijst op basis van byte-budgetten gebruikt size() voor het eerste geval; code die gecomprimeerde frames uit de cam streamt leest het na elke compressie om te weten hoeveel bytes de stream daadwerkelijk bevat.