4.14. CSI-Grundlagen

Das Modul csi ist die Art und Weise, wie Python-Code den Kamerasensor ansteuert. Jedes Skript, das ein Einzelbild aufnimmt, folgt derselben dreiteiligen Form: Importe oben, einmalige Konfiguration in der Mitte und eine while True-Schleife unten, die Einzelbilder eines nach dem anderen aus der Kamera abruft.

4.14.1. Die typische Schleife

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. Was jeder Aufruf bewirkt

import csi, image, time

Bindet drei Module ein. csi steuert den Sensor, image definiert die Klasse Image, die snapshot() zurückgibt, und time stellt den Helfer time.clock() bereit, der zum Messen der Bilder pro Sekunde verwendet wird.

csi.CSI()

Konstruiert eine CSI-Instanz, die einen physischen Kamerasensor umhüllt. Der Konstruktor beansprucht das Kamera-Peripheriegerät und erfasst die sensorspezifische Konfiguration. Kameras mit einem einzigen Sensor haben eine CSI-Instanz; Kameras mit zwei Sensoren (Farbe plus Wärme, Farbe plus Event) haben zwei, die jeweils über ein cid-Argument des Konstruktors ausgewählt werden.

csi0.reset()

Versorgt und konfiguriert den Sensor. Standardmäßig pulst es den Reset-Pin des Sensors und schreibt dann die I2C-Register des Sensors in einen bekannten Ausgangszustand. Nachfolgende Konfigurationsaufrufe – pixformat, framesize, die Auto-Steuerungsknöpfe – senden weitere Register-Schreibvorgänge über denselben I2C-Steuerbus.

csi0.pixformat(csi.RGB565)

Schreibt die Sensorregister, die das Ausgabe-Pixelformat auswählen. Die verfügbaren Optionen sind die Formate, die die Seite Pixelformate vorgestellt hat: RGB565, GRAYSCALE, BAYER, YUV422 und JPEG auf den Sensoren, die es unterstützen.

csi0.framesize(csi.QVGA)

Schreibt die Register, die die Ausgabeauflösung auswählen. QVGA ist 320 × 240; die benannten Größen reichen bis WQXGA2 (2592 × 1944, etwa 5 MP) auf Sensoren, die sie unterstützen. Ein benutzerdefiniertes (width, height)-Tupel funktioniert ebenfalls, solange es zu den Ausgabefähigkeiten des Sensors passt.

clock = time.clock()

Erzeugt einen Uhr-Helfer. Jeder Aufruf von clock.tick() innerhalb der Schleife erfasst die Startzeit der Iteration; time.clock.fps() meldet die aktuelle Schleifenrate in Bildern pro Sekunde.

img = csi0.snapshot()

Nimmt ein Einzelbild vom Sensor auf und gibt es als Image zurück. Die Mechanik, wie dieses Einzelbild im Speicher landet, ist einen genaueren Blick wert.

4.14.3. Wie snapshot den Speicher füllt

Der Sensor liefert Pixel über den Pixeldaten-Bus, der in Sensor-Busse beschrieben ist, mit Raten von Hunderten von Megabyte pro Sekunde – viel zu schnell, als dass die CPU sie in Software Pixel für Pixel kopieren könnte.

Stattdessen lagert die MCU die Übertragung an Direct Memory Access (DMA) aus – eine von der CPU getrennte Hardware-Engine, die Bytes innerhalb der MCU von einem Ort zum anderen kopiert, ganz ohne Beteiligung der CPU. Das Kameraeingangs-Peripheriegerät fängt jedes eingehende Pixel-Byte in einem kleinen On-Chip-FIFO auf; welche ISP-Stufen auch immer auf MCU-Seite laufen, verarbeiten die Daten unterwegs; und die DMA-Engine schreibt die fertigen Pixel an den entsprechenden Pixel-Offset in einen Framebuffer im RAM. Nichts in dieser Kette benötigt die CPU, sobald der DMA-Kanal programmiert wurde.

Wenn snapshot() aufgerufen wird:

  1. Der CSI-Treiber programmiert die DMA-Engine mit der Adresse des Framebuffers, der Übertragungslänge (Pixel im Wert eines Einzelbilds) und einem Callback für den DMA-Fertig-Interrupt.

  2. Der Treiber aktiviert das Kameraeingangs-Peripheriegerät und wartet darauf, dass der Sensor den Beginn des nächsten Einzelbilds signalisiert.

  3. Während der Sensor das Einzelbild ausgibt, reicht das Peripheriegerät jedes Pixel-Byte durch den ISP und weiter an die DMA-Engine, die das Ergebnis an den nächsten Framebuffer-Offset in den RAM schreibt. Die CPU kann während der Übertragung anderen Code ausführen.

  4. Wenn das letzte Pixel des Einzelbilds eintrifft, löst die DMA ihren Fertig-Interrupt aus, der Treiber hüllt den Framebuffer in ein Image, und snapshot() gibt es an den Benutzercode zurück.

Das zurückgegebene Image besitzt keine Kopie der Pixeldaten – es zeigt auf einen der Framebuffer der Kamera im RAM. Wie viele Framebuffer die Kamera vorhält und wie sie bei jedem Aufruf von snapshot() zwischen DMA und Benutzercode übergeben werden, hängt vom Pufferungsmodus ab, den die Anwendung über framebuffers() ausgewählt hat.