5.32. Opslaan en compressie¶
Elke pagina tot nu toe heeft gewerkt met afbeeldingen op de cam: vastgelegd in de framebuffer of toegewezen op de MicroPython-heap, bewerkt via de methodes van de image-module, en ofwel weergegeven in de IDE-preview of doorgegeven aan een vervolgstap in hetzelfde script. De meeste toepassingen moeten op een bepaald moment het omgekeerde doen: een afbeelding die zich momenteel in het RAM bevindt nemen en ergens persistent neerzetten – op de SD-kaart, op een USB-host, over een netwerk – waar iets anders dan de camera het kan lezen.
De image-module biedt twee paden voor dat werk. Het save-pad schrijft de afbeelding naar een bestand op het bestandssysteem, waarbij het bestandsformaat door de extensie wordt gekozen en de coderingsdetails door de methode worden afgehandeld. Het to-format-pad retourneert een Image-object dat de gecodeerde bytestroom bevat, geschikt om door te geven aan een streaming- of netwerkaanroep zonder ooit het bestandssysteem aan te raken. Elk past bij een andere toepassing; beide bouwen op dezelfde compressiemotor eronder.
5.32.1. Opslaan naar een bestand¶
save() schrijft de afbeelding naar het bestandssysteem op een pad:
img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)
Het formaat wordt afgeleid uit de bestandsextensie. Vijf extensies worden herkend: .bmp schrijft een Windows-bitmap (verliesvrij, geen compressie, byte-voor-byte de vastgelegde pixels); .pgm schrijft een portable graymap (verliesvrij, alleen grijswaarden); .ppm schrijft een portable pixmap (verliesvrij, RGB); .jpg en .jpeg schrijven beide een JPEG (lossy, gecomprimeerd). De ontvangende afbeelding moet al in het juiste kleurformaat staan voor de gekozen container – een kleurenafbeelding opslaan als .pgm is een fout.
roi beperkt het opslaan tot een deelrechthoek van de afbeelding, op dezelfde manier als het roi-trefwoord van elke andere methode van de image-module dat doet. De volledige afbeelding is de standaard. Het trefwoord wordt genegeerd bij het opslaan van een JPEG-gecomprimeerde afbeelding, omdat de vorm op schijf het volledige frame al dekt en opnieuw coderen via een uitsnede het doel van het opslaan van de bestaande gecomprimeerde bytes teniet zou doen.
quality is de JPEG-compressiekwaliteit van 0 tot 100 en is alleen zinvol wanneer de uitvoer JPEG is (het trefwoord wordt genegeerd voor de verliesvrije formaten). De standaard van 50 is de juiste balans voor de meeste toepassingen; 70 tot 85 is de band voor hogere visuele kwaliteit, 30 tot 50 is het juiste bereik voor kleine thumbnails en transmissie met beperkte bandbreedte, en 90 en hoger is gereserveerd voor gevallen waarin de afbeelding handmatig wordt geïnspecteerd of door een vervolgalgoritme wordt gehaald dat gevoelig is voor compressieartefacten.
De ontvangende afbeelding wordt geretourneerd zodat de aanroep aaneengeschakeld kan worden: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). Het geretourneerde object is dezelfde afbeelding in het geheugen; het opslaan is een neveneffect.
Een typisch gebruik is het capture-and-log-patroon. Een trigger gaat af (een blob wordt gedetecteerd, een knop wordt ingedrukt, een timer verstrijkt); het script legt een frame vast; het voegt een tijdstempel toe aan de bestandsnaam; en het roept save() aan om de afbeelding naar de SD-kaart te schrijven. De IDE-preview blijft draaien, de volgende trigger gaat af, en de opgeslagen bestanden stapelen zich op.
5.32.2. Coderen naar geheugen¶
Wanneer de bestemming niet het bestandssysteem is maar een netwerkverbinding, een seriële poort of de invoer van een andere module, heeft de toepassing de gecodeerde bytestroom in het geheugen nodig in plaats van op schijf. to_jpeg() en to_png() produceren precies dat:
encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)
Het standaardgedrag is conversie ter plaatse: de ontvanger wordt omgezet in een JPEG- (of PNG-)afbeelding en hetzelfde object wordt geretourneerd. Met copy=True schrijft de conversie naar een vers toegewezen heap-object; met copy_to_fb=True belandt de uitvoer in de framebuffer. De keuze is dezelfde die elke andere conversiemethode biedt – standaard ter plaatse, kopiëren wanneer het origineel daarna nog nodig is.
quality en subsampling zijn dezelfde JPEG-afstemknoppen die het save-pad biedt. subsampling kiest het chroma-subsamplingschema: image.JPEG_SUBSAMPLING_AUTO kiest het beste voor de gekozen kwaliteit, image.JPEG_SUBSAMPLING_444 houdt chroma op volledige resolutie (grootste bestand, beste kleurnauwkeurigheid), image.JPEG_SUBSAMPLING_422 en image.JPEG_SUBSAMPLING_420 halveren de chroma-resolutie langs één of beide assen (kleinere bestanden, lichte kleurverzachting die op typische kijkafstanden onzichtbaar is). De standaard van AUTO is de juiste keuze tenzij de toepassing een specifieke behoefte heeft.
PNG via to_png() is verliesvrij maar trager om te coderen en produceert grotere bestanden dan JPEG voor fotografische inhoud (fotografische inhoud comprimeert slecht onder het voorspellingsschema van PNG). Gebruik PNG wanneer de afbeelding lijntekeningen is, een schermafbeelding, of harde grafische elementen bevat die over een vastgelegd frame zijn getekend – de verliesvrije codering behoudt de scherpe randen die JPEG zou verzachten. Anders is JPEG de juiste standaard.
Zowel to_jpeg() als to_png() accepteren dezelfde positionele en schaaltrefwoorden in tekenstijl die andere conversiemethodes nemen – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – zodat dezelfde aanroep in één stap een geschaalde, uitgesneden of palet-gemapte versie van de bron kan coderen. compress() is de verouderde schrijfwijze van to_jpeg(); de twee nemen dezelfde argumenten en produceren hetzelfde resultaat.
5.32.3. Wat compressie oplevert¶
De cijfers achter de afweging JPEG-versus-raw zijn het waard om één keer door te werken.
Een 320-bij-240 RGB565-frame is 153.600 bytes (één vastgelegd frame op QVGA). Een 640-bij-480-frame is 614.400 bytes; een 1280-bij-960-frame is 2.457.600 bytes. Geen daarvan is groot vergeleken met een desktop- of telefoonscherm, maar ze zijn aanzienlijk in de context van een cam die in totaal een paar MB RAM heeft, een SD-kaart met een eindige schrijfbandbreedte, en een hostverbinding die doorgaans over USB CDC, een UART, of een draadloze module op bescheiden snelheden draait.
JPEG op quality=50 comprimeert een fotografisch vastgelegd frame doorgaans met 10x tot 20x: dat 614 KB grote 640-bij-480-frame wordt een gecodeerde bytestroom van 30 tot 60 KB. Op quality=85 daalt de compressie naar 5x tot 10x (60 tot 120 KB voor hetzelfde frame). Op quality=10 – vol artefacten maar nog steeds herkenbaar – bereikt de compressie 30x tot 50x (12 tot 20 KB).
Die cijfers bepalen wat praktisch haalbaar is met de opgeslagen frames. Een SD-kaartpad dat 10 MB/s volhoudt verwerkt 30 frames per seconde aan VGA-inhoud gecodeerd op quality=50 JPEG met ruimte over (ongeveer 1 tot 2 MB/s); dezelfde inhoud ongecomprimeerd opslaan vereist meer dan 18 MB/s, voorbij wat het bestandssysteempad van de cam naar de kaart volhoudt. Een USB-host die JPEG-gecodeerde frames over CDC op 1 MB/s binnenhaalt ontvangt frames van 30 tot 60 KB met ongeveer 15 tot 30 frames per seconde; bij het binnenhalen van ruwe frames op dezelfde snelheid krijgt hij één of twee frames per seconde.
Kortom: de compressiemethodes zijn niet alleen een gemak voor het opslaan. Ze zijn wat het vastgelegde frame bruikbaar maakt buiten de cam op framesnelheden waar de toepassing om geeft. De juiste compressie kiezen – JPEG-kwaliteit 50 voor algemene logging, 80 voor kwaliteitswerk, PNG voor het vastleggen van lijntekeningen – is onderdeel van het routinewerk van elke niet-triviale cam-toepassing.