4.16. Afbeeldingsvoorbeeld

De framebufferpool is de plek waar de applicatie haar frames leest. Terwijl de applicatie met die frames werkt, heeft wat er ook met de camera verbonden is om ze te bekijken eveneens een kopie van elk frame nodig. De camera heeft een tweede, speciale buffer voor dat doel, en één enkele regel voor wanneer die gevuld wordt: elke keer dat de applicatie snapshot() aanroept, wordt het vorige opgenomen frame naar de voorbeeldbuffer gekopieerd voordat het nieuwe frame wordt teruggegeven.

De applicatie en de voorbeeldweergave strijden nooit om hetzelfde geheugen. De applicatie leest haar frame uit de pool; de voorbeeldweergave leest haar frame uit de voorbeeldbuffer. Beide gebeuren parallel.

4.16.1. De stream-framebuffer

De voorbeeldbuffer – de stream-framebuffer – is een enkel gebied van het RAM met vaste grootte, los van de framebufferpool. De grootte wordt ingesteld tijdens het bouwen van de firmware en verandert niet met framesize() of pixformat(). Ongeveer een megabyte is typisch op recente OpenMV Cams – groot genoeg om een voorbeeld van gematigde resolutie te bevatten, veel kleiner dan een frame op volledige resolutie bij de grootste sensorformaten.

De applicatiecode leest of schrijft deze buffer niet rechtstreeks; de camerastuurprogramma vult hem als neveneffect van snapshot().

4.16.2. Wat snapshot doet voor het voorbeeld

Bij elke aanroep van snapshot(), voordat het stuurprogramma de vorige framebuffer van de applicatie terug in de pool vrijgeeft en de nieuwe uitreikt, kopieert het het vorige frame door naar de voorbeeldbuffer – met alles wat de applicatie er tijdens de verwerking bovenop tekende nog op de afbeelding. Twee vertakkingen zijn mogelijk. Welke er draait wordt gekozen door de voorbeeldweergave, niet door de camera: de consument die het voorbeeld opende vertelt het stuurprogramma of hij de ruwe afbeelding of een JPEG wil, en welke ruwe venstergrootte hij kan accepteren.

  • Ruwe verkleinde kopie. Wanneer de voorbeeldweergave om ruwe frames heeft gevraagd, kopieert het stuurprogramma het vorige frame over in zijn oorspronkelijke pixelformaat (RGB565, grijswaarden, enz.). Als het frame groter is dan het ruwe venster dat de voorbeeldweergave aanvroeg, schaalt het stuurprogramma het met bilineaire filtering omlaag totdat het past; anders gaan de pixels onveranderd door. Geen compressieartefacten; de voorbeeldweergave ziet dezelfde pixels waar de applicatie mee werkte.

  • JPEG-compressie. Wanneer de voorbeeldweergave om JPEG heeft gevraagd – of wanneer de ruwe kopie helemaal niet in de streambuffer zou passen – comprimeert het stuurprogramma het vorige frame op volledige resolutie als JPEG in de streambuffer. De kwaliteit wordt per frame adaptief aangepast zodat de gecomprimeerde uitvoer binnen de capaciteit van de streambuffer blijft. Wanneer een frame past, voert het stuurprogramma de kwaliteit met één stap omhoog naar een plafond dat afhangt van de pixelgrootte van het opgenomen frame (kleinere frames mogen een hogere kwaliteit hebben; grotere frames worden lager begrensd zodat ze niet kunnen overlopen bij een kleine wijziging in de inhoud). Wanneer een frame niet past, halveert het stuurprogramma de huidige kwaliteit, houdt deze op het verlaagde niveau voor de volgende paar tientallen frames zodat de nieuwe instelling tijd heeft om te stabiliseren, en laat het overgelopen frame uit het voorbeeld vallen. De applicatielus blijft ongehinderd doorlopen; alleen de voorbeeldweergave mist het weggevallen frame.

Frames die de camera produceert in een formaat dat al gecomprimeerd is (het JPEG-pixelformaat op sensoren die rechtstreeks JPEG uitstoten) slaan beide vertakkingen over: de gecodeerde bitstream wordt ongewijzigd rechtstreeks naar de voorbeeldbuffer gekopieerd.

De voorbeeldweergave pollt op zijn eigen schema, doorgaans veel langzamer dan de camera opneemt, dus hij subsamplet de ruwe opnamesnelheid: alleen de momentopnamen die hij toevallig op tijd uitleest worden weergegeven. Als een nieuwe snapshot() bij de voorbeeldbuffer arriveert voordat de voorbeeldweergave het vorige frame heeft uitgelezen, is de buffer nog vergrendeld door de voorbeeldweergave en wordt de nieuwe voorbeeldupdate overgeslagen – die opname gaat verloren uit de voorbeeldstream. De eigen framebufferpool van de applicatie blijft onaangetast; het opgenomen frame gaat nog steeds normaal naar de applicatie.

4.16.3. Het laatste frame handmatig doorsturen

Omdat het voorbeeld als neveneffect van snapshot() wordt bijgewerkt, laat een script dat eindigt zonder ooit opnieuw snapshot aan te roepen, voor onbepaalde tijd wat het laatst naar het voorbeeld stuurde op de voorbeeldweergave staan – wat, voor een script dat zijn werk doet vóór de eerste momentopname en dan afsluit, een leeg voorbeeld is. image.Image.flush() (of het equivalent flush() op het CSI-object) kopieert de huidige inhoud van de framebuffer van de applicatie op aanvraag naar de streambuffer, zonder een nieuw frame op te nemen:

img = csi0.snapshot()
# process the image and draw on it
img.flush()                               # previewer sees the annotated frame

Dezelfde aanroep is ook nuttig wanneer een langlopende bewerking zich tussen momentopnamen bevindt en de voorbeeldweergave anders de hele tijd een verouderd voorbeeld zou tonen.

Notitie

De voorbeeldapplicatie moet het frame uit de streambuffer lezen voordat het script afsluit. Een flush aan het einde van een kort script stelt het frame alleen op; als het script vervolgens de controle teruggeeft aan de camera voordat de voorbeeldweergave heeft gepollt, wordt de buffer bij de volgende run hergebruikt en gaat dat laatste frame verloren. Geef voor voorbeelden aan het einde van een script de voorbeeldweergave even de tijd om het frame op te pakken (een korte slaap na de flush, of simpelweg niet onmiddellijk afsluiten) voordat het script eindigt.