6.18. Bilder und ndarrays¶
Die Klasse Image ist die schnelle Schnittstelle für kameranatives Pixel-Handling: Jede ihrer Methoden arbeitet direkt auf dem Framebuffer im nativen Pixelformat der Kamera. numpy ist die generische numerische Schnittstelle für alles andere. Zwei Methoden verbinden beide:
image.Image.to_ndarray()– kopiert die Pixel eines Bildes in einndarray.Der Konstruktor
image.Image– erzeugt ein neues Bild aus einemndarray.
Gemeinsam ermöglichen sie es einer Anwendung, ein Einzelbild aufzunehmen, es zur eigenen Transformation an numpy zu übergeben und das Ergebnis anschließend wieder in ein Bild zu schreiben, um es anzuzeigen, zu speichern oder dem Rest der Bildbibliothek zuzuführen.
6.18.1. Bild zu ndarray¶
to_ndarray() reserviert ein neues ndarray und kopiert die Pixeldaten des Bildes hinein (mit der unten beschriebenen dtype-Zuordnung). Es ist niemals eine Sicht (View) auf den Framebuffer des Bildes – das numpy-Array besitzt immer seine eigenen Bytes. Die Signatur lautet to_ndarray(dtype, *, buffer=None), und die Form der Ausgabe hängt vom Bildformat ab:
GRAYSCALE – 2D-Array, Form
(height, width).RGB565 – 3D-Array, Form
(height, width, 3), Ebenen in R/G/B-Reihenfolge.
Das Argument dtype steuert, wie jeder 8-Bit-Pixelwert v abgebildet wird:
|
Element |
Zuordnung für einen 8-Bit-Pixelwert |
|---|---|---|
|
|
|
|
|
|
|
|
|
Beispiel – ein Graustufen-Einzelbild als uint8-Matrix betrachten:
import csi
from ulab import numpy as np
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
img = csi0.snapshot()
a = img.to_ndarray('B') # shape (240, 320), dtype=uint8
print(a.shape, a.dtype)
print("mean brightness:", np.mean(a))
Mit dem Schlüsselwort buffer= kann die Anwendung ein bereits reserviertes bytearray wiederverwenden, sodass die Kamera nicht für jedes Einzelbild ein neues reservieren muss:
buf = bytearray(320 * 240)
while True:
img = csi0.snapshot()
a = img.to_ndarray('B', buffer=buf)
# ... process a ...
6.18.2. ndarray zu Bild¶
Für die umgekehrte Richtung übergibt man das ndarray als erstes Argument an image.Image. Der Konstruktor reserviert einen neuen Bildpuffer und kopiert die Werte des Arrays hinein, begrenzt und gerundet auf 0..255:
image.Image(arr, *, buffer=None, copy_to_fb=False)
Der Konstruktor leitet Geometrie und Pixelformat aus der Form des Arrays ab:
Form
(h, w)–GRAYSCALE-Bild.Form
(h, w, 3)–RGB565-Bild.
Das ndarray muss den dtype float haben; der Konstruktor unterstützt derzeit nur diesen Fall. Werte werden gerundet und auf den Bereich 0..255 begrenzt.
Mit buffer= kann die Anwendung ein bereits reserviertes bytearray für das resultierende Bild bereitstellen. copy_to_fb=True schreibt das Ergebnis in den Framebuffer der Kamera, was die richtige Wahl ist, wenn das Ergebnis in der IDE-Vorschau erscheinen soll.
6.18.3. Hin und zurück¶
import csi
import image
from ulab import numpy as np
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
img = csi0.snapshot()
a = img.to_ndarray('f') # work in float space
a = 255.0 * (a / 255.0) ** 0.5 # gamma correction
out = image.Image(a, copy_to_fb=True) # back to an image
6.18.4. Wann man die Brücke nutzt¶
Diese Brücke ist die richtige Antwort, wenn die Anwendung eine generische numerische Operation benötigt, die die eingebauten image-Methoden nicht bereitstellen – eigene Filter, eigene Überblendungen, ungewöhnliche Nichtlinearitäten – oder wenn Pixeldaten mit Nicht-Bilddaten (IMU-Achsen, Audiosamples) in einer einzigen Berechnung kombiniert werden müssen.
Sie ist nicht die richtige Antwort für die durchsatzstarke Pixelverarbeitung, die die Klasse Image bereits abdeckt. Die eingebauten Methoden arbeiten direkt auf dem Framebuffer im nativen Pixelformat der Kamera und sind deutlich schneller als der entsprechende numpy-Ausdruck. Greifen Sie zur Brücke für jene Operationen, die die Bildbibliothek noch nicht bereitstellt.