5.32. Speichern und Komprimierung¶
Jede Seite bis hierher hat mit Bildern auf der Kamera gearbeitet: in den Framebuffer aufgenommen oder auf dem MicroPython-Heap allokiert, über die Methoden des image-Moduls bearbeitet und entweder in der IDE-Vorschau angezeigt oder im selben Skript an eine nachgelagerte Stufe weitergereicht. Die meisten Anwendungen müssen irgendwann das Gegenteil tun: ein Bild, das sich gerade im RAM befindet, an einen dauerhaften Ort bringen – auf die SD-Karte, auf einen USB-Host, über ein Netzwerk – wo etwas anderes als die Kamera es lesen kann.
Das image-Modul bietet zwei Wege für diese Aufgabe. Der Speicher-Weg schreibt das Bild in eine Datei im Dateisystem, wobei das Dateiformat durch die Erweiterung bestimmt wird und die Kodierungsdetails von der Methode übernommen werden. Der To-Format-Weg gibt ein Image-Objekt zurück, das den kodierten Byte-Stream enthält und sich zur Übergabe an einen Streaming- oder Netzwerkaufruf eignet, ohne jemals das Dateisystem zu berühren. Jeder passt zu einer anderen Anwendung; beide bauen auf derselben Komprimierungs-Engine im Hintergrund auf.
5.32.1. In eine Datei speichern¶
save() schreibt das Bild unter einem Pfad in das Dateisystem:
img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)
Das Format wird anhand der Dateierweiterung gewählt. Fünf Erweiterungen werden erkannt: .bmp schreibt eine Windows-Bitmap (verlustfrei, keine Komprimierung, Byte für Byte die aufgenommenen Pixel); .pgm schreibt eine Portable Graymap (verlustfrei, nur Graustufen); .ppm schreibt eine Portable Pixmap (verlustfrei, RGB); .jpg und .jpeg schreiben beide ein JPEG (verlustbehaftet, komprimiert). Das Empfängerbild muss bereits im richtigen Farbformat für den gewählten Container vorliegen – ein Farbbild, das als .pgm gespeichert wird, ist ein Fehler.
roi beschränkt das Speichern auf ein Teilrechteck des Bildes, so wie es das roi-Schlüsselwort jeder anderen Methode des image-Moduls tut. Standardmäßig wird das gesamte Bild verwendet. Das Schlüsselwort wird beim Speichern eines JPEG-komprimierten Bildes ignoriert, da die Form auf dem Datenträger bereits das gesamte Einzelbild abdeckt und eine erneute Kodierung über einen Zuschnitt den Sinn des Speicherns der vorhandenen komprimierten Bytes zunichtemachen würde.
quality ist die JPEG-Komprimierungsqualität von 0 bis 100 und ist nur dann von Bedeutung, wenn die Ausgabe JPEG ist (für die verlustfreien Formate wird das Schlüsselwort ignoriert). Der Standardwert 50 ist die richtige Balance für die meisten Anwendungen; 70 bis 85 ist der Bereich für höhere visuelle Qualität, 30 bis 50 ist der richtige Bereich für kleine Thumbnails und bandbreitenbeschränkte Übertragung, und 90 und höher ist Fällen vorbehalten, in denen das Bild manuell inspiziert oder durch einen nachgelagerten Algorithmus verarbeitet wird, der empfindlich auf Komprimierungsartefakte reagiert.
Das Empfängerbild wird zurückgegeben, sodass sich der Aufruf verketten lässt: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). Das zurückgegebene Objekt ist dasselbe Bild im Speicher; das Speichern ist ein Nebeneffekt.
Ein typischer Anwendungsfall ist das Capture-and-Log-Muster. Ein Auslöser tritt ein (ein Blob wird erkannt, ein Knopf wird gedrückt, ein Timer läuft ab); das Skript nimmt ein Einzelbild auf; es hängt einen Zeitstempel an den Dateinamen an; und es ruft save() auf, um das Bild auf die SD-Karte zu schreiben. Die IDE-Vorschau läuft weiter, der nächste Auslöser tritt ein, und die gespeicherten Dateien sammeln sich an.
5.32.2. In den Speicher kodieren¶
Wenn das Ziel nicht das Dateisystem ist, sondern eine Netzwerkverbindung, ein serieller Port oder die Eingabe eines anderen Moduls, benötigt die Anwendung den kodierten Byte-Stream im Speicher statt auf dem Datenträger. to_jpeg() und to_png() erzeugen genau das:
encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)
Das Standardverhalten ist eine In-Place-Konvertierung: Der Empfänger wird in ein JPEG- (oder PNG-)Bild konvertiert und dasselbe Objekt wird zurückgegeben. Mit copy=True schreibt die Konvertierung in ein frisch allokiertes Heap-Objekt; mit copy_to_fb=True landet die Ausgabe im Framebuffer. Die Wahl ist dieselbe, die jede andere Konvertierungsmethode bietet – standardmäßig In-Place, kopieren, wenn das Original danach noch benötigt wird.
quality und subsampling sind dieselben JPEG-Stellschrauben, die der Speicher-Weg bietet. subsampling wählt das Chroma-Subsampling-Schema: image.JPEG_SUBSAMPLING_AUTO wählt das beste für die gewählte Qualität, image.JPEG_SUBSAMPLING_444 behält die Chroma-Information in voller Auflösung (größte Datei, beste Farbgenauigkeit), image.JPEG_SUBSAMPLING_422 und image.JPEG_SUBSAMPLING_420 halbieren die Chroma-Auflösung entlang einer oder beider Achsen (kleinere Dateien, geringfügige Farbweichzeichnung, die bei typischen Betrachtungsabständen unsichtbar ist). Der Standardwert AUTO ist die richtige Wahl, sofern die Anwendung keinen besonderen Bedarf hat.
PNG über to_png() ist verlustfrei, aber langsamer zu kodieren und erzeugt für fotografische Inhalte größere Dateien als JPEG (fotografische Inhalte lassen sich unter dem Prädiktionsschema von PNG schlecht komprimieren). Verwenden Sie PNG, wenn das Bild Strichgrafik oder ein Screenshot ist oder hartkantige Grafiken enthält, die über ein aufgenommenes Einzelbild gezeichnet wurden – die verlustfreie Kodierung bewahrt die scharfen Kanten, die JPEG weichzeichnen würde. Andernfalls ist JPEG der richtige Standard.
Sowohl to_jpeg() als auch to_png() akzeptieren dieselben Zeichen-Positionsargumente und Skalierungs-Schlüsselwörter wie andere Konvertierungsmethoden – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – sodass derselbe Aufruf in einem Schritt eine skalierte, zugeschnittene oder palettenzugeordnete Version der Quelle kodieren kann. compress() ist die alte Schreibweise von to_jpeg(); beide nehmen dieselben Argumente entgegen und liefern dasselbe Ergebnis.
5.32.3. Was Komprimierung bringt¶
Die Zahlen hinter dem Kompromiss zwischen JPEG und Rohdaten lohnt es sich, einmal durchzurechnen.
Ein RGB565-Einzelbild mit 320 mal 240 Pixeln hat 153.600 Bytes (ein aufgenommenes Einzelbild bei QVGA). Ein Einzelbild mit 640 mal 480 hat 614.400 Bytes; ein Einzelbild mit 1280 mal 960 hat 2.457.600 Bytes. Keines davon ist groß im Vergleich zu einem Desktop- oder Telefon-Display, aber sie sind beträchtlich im Kontext einer Kamera, die insgesamt nur ein paar MB RAM hat, einer SD-Karte mit begrenzter Schreibbandbreite und einer Host-Verbindung, die typischerweise über USB CDC, einen UART oder ein Funkmodul mit moderaten Geschwindigkeiten läuft.
JPEG bei quality=50 komprimiert ein fotografisches aufgenommenes Einzelbild typischerweise um den Faktor 10 bis 20: Aus diesem 614 KB großen 640-mal-480-Einzelbild wird ein kodierter Byte-Stream von 30 bis 60 KB. Bei quality=85 sinkt die Komprimierung auf das 5- bis 10-fache (60 bis 120 KB für dasselbe Einzelbild). Bei quality=10 – artefaktbehaftet, aber noch erkennbar – erreicht die Komprimierung das 30- bis 50-fache (12 bis 20 KB).
Diese Zahlen bestimmen, was sich mit den gespeicherten Einzelbildern praktisch machen lässt. Ein SD-Karten-Pfad, der 10 MB/s aufrechterhält, bewältigt 30 Einzelbilder pro Sekunde mit quality=50 JPEG-kodiertem VGA-Inhalt mit reichlich Spielraum (etwa 1 bis 2 MB/s); der gleiche Inhalt unkomprimiert gespeichert erfordert über 18 MB/s, mehr als der Dateisystem-Pfad der Kamera zur Karte aufrechterhalten kann. Ein USB-Host, der JPEG-kodierte Einzelbilder über CDC mit 1 MB/s abruft, empfängt 30 bis 60 KB große Einzelbilder mit etwa 15 bis 30 Einzelbildern pro Sekunde; ruft er Rohdaten mit derselben Rate ab, erhält er ein oder zwei Einzelbilder pro Sekunde.
Kurz gesagt: Die Komprimierungsmethoden sind nicht nur eine Annehmlichkeit zum Speichern. Sie sind das, was das aufgenommene Einzelbild außerhalb der Kamera bei Bildraten, die für die Anwendung wichtig sind, nutzbar macht. Die Wahl der richtigen Komprimierung – JPEG-Qualität 50 für allgemeines Protokollieren, 80 für Qualitätsarbeit, PNG für die Aufnahme von Strichgrafiken – gehört zur Routinearbeit jeder nichttrivialen Kamera-Anwendung.