Pi camera v2 - schneller, vollständiger Sensoraufnahmemodus mit Downsampling

7

Ich habe eine Pi-Kamera v2, die ich mit einem RPi3 für ein Computer-Vision-Projekt verwenden möchte.

Ich muss vollständige Sensorbilder von der Kamera aufnehmen - kein Zuschneiden. Die tatsächlich von der Software verwendete Auflösung ist mit etwa 120 x 90 (plus oder minus ein paar Dutzend) sehr niedrig. Die Software erfasst einen Frame, verarbeitet ihn, erfasst den nächsten Frame usw. - so schnell wie möglich. Die Software wird in Python geschrieben, wobei Tensorflow die Bilderkennung übernimmt.

Gibt es eine Möglichkeit, Deep Binning direkt auf der Kamera durchzuführen? Ich versuche, die Bildverarbeitung in Software so weit wie möglich zu vermeiden, da alle 4 CPU-Kerne mit anderen Aufgaben beschäftigt sind. Daher muss ich mit minimalem CPU-Aufwand von den vollständigen Sensorbildern auf die niedrige Auflösung von 120 x 90 herunterrechnen. Es wäre großartig, wenn die Kamera ein wirklich tiefes Binning durchführen könnte, wie etwa 20 x 20.

Wenn nicht, was wäre eine gute Option in Bezug auf Python-Bibliotheken, um schnelles, tiefes Downsampling durchzuführen? Übrigens, ich kann keine Pixel löschen, ich brauche alle anfänglichen Informationen, also müsste eine Art Überblendung durchgeführt werden.

Die Software verarbeitet nur Schwarzweißbilder. Kann ich die Kamera so einstellen, dass s / w-Bilder ausgegeben werden? Wenn nicht, was wäre der schnellste Weg, dies in Python auf der RPi3-Plattform zu tun? Alle Hardwarebeschleunigungstricks sind sehr willkommen.

Das übergeordnete Ziel besteht darin, Bilderfassungsroutinen zu entwickeln, die den gesamten Sensor verwenden, ein Downsampling durchführen und ca. 120 x 90 Pixel große Schwarzweißbilder mit 8 Bit pro Pixel, wahrscheinlich als Numpy-Array, in den Computer-Vision-Teil von einspeisen die Software unter Verwendung der minimalen Anzahl von CPU-Zyklen, um die Gesamtgeschwindigkeit zu maximieren.

Florin Andrei
quelle
1
Ja, Sie können direkt mit MMAL-Code auf die Kameraebene zugreifen, aber das ist ein bisschen hardcore. Wenn Sie jedoch UV4L verwenden, steht eine API zur Verfügung, über die Sie mit dem Videostream arbeiten können, v4l2-ctl um Einstellungen an der Kamera vorzunehmen, mit denen Sie das /dev/videoGerät direkt verwenden können. Ich würde das versuchen und sehen, wie es dir geht. Wenn Sie nicht weiterkommen, wenden Sie sich an den Autor, wenn Sie ein echtes Problem oder eine Frage haben. Er ist ein sehr netter Kerl, der gerne bei echten Problemen hilft. Viel Glück und bitte antworten Sie, wie Sie Ihr Problem gelöst haben.
Piotr Kula

Antworten:

13

Schauen wir uns zunächst die Kameramodule selbst an. Das v1-Kameramodul kann 2x2- und 4x4-Binning durchführen (siehe Tabelle der Kameramodi ). Ich habe gehört, dass es auch einen 8x8-Binning-Modus gibt, aber die Firmware-Entwickler konnten ihn nicht zum Laufen bringen. Aus diesem Grund kann das v1-Modul in den meisten Modi das volle Sichtfeld (FoV) erreichen.

Im Vergleich dazu kann das v2-Kameramodul leider nur 2x2-Binning durchführen, was erklärt, warum viele seiner Modi einen partiellen FoV aufweisen (ich denke, das v2-Modul kann auch Zeilen überspringen, aber ich glaube nicht, dass dies von der Kamera des Pi verwendet wird Firmware). Dies ist jedoch nicht die ganze Geschichte. Dies ist nur der Anfang der Bildverarbeitungspipeline. Mit anderen Worten, dies ist genau das, was der Sensor selbst an den ISP-Block in der GPU weiterleitet, der den Rest der Verarbeitung einschließlich etwaiger Größenänderungen übernimmt. Die Kamera verfügt zwar über mehrere diskrete Modi (in dieser Tabelle aufgeführt), kann jedoch in jedem Modus effektiv arbeitenAuflösung bis zum angegebenen Maximum. Wenn Sie etwas weiter von diesen Tabellen in der Dokumentation lesen, finden Sie eine Beschreibung der Heuristik, mit der der Sensormodus entsprechend der angeforderten Auflösung und Bildrate ausgewählt wird.

Vorausgesetzt, Sie landen in einem Vollbild-Sensormodus oder erzwingen dessen Verwendung, werden Sie ohnehin von allen Pixeln auf dem Sensor erfasst. Bedenken hinsichtlich der CPU-Auslastung: Machen Sie sich keine Sorgen. Die CPU wird für keinen Teil der Imaging-Pipeline der Kamera verwendet, sie basiert fast ausschließlich auf der GPU (mit Ausnahme des ersten Binning-Schritts, der vom Sensor-ISP ausgeführt wird). Die CPU wird nur dann involviert, wenn sie die endgültige Ausgabe empfängt und etwas damit zu tun hat.

Wenn Sie nach einer minimalen CPU-Auslastung suchen, ist dies ein großes Plus des Kameramoduls im Vergleich zu USB-Webcams, die, da der USB-Bus von der CPU abgefragt wird, eine erhebliche CPU-Zeit benötigen (USB3 verwendet Interrupts anstelle von Abfragen, aber wir sind es Ich spreche hier von Pis, die nur USB2 haben, und außerdem: Die meisten USB-Webcams verwenden zum Zeitpunkt des Schreibens kein USB3.

Auf Ihre spezifischen Anforderungen. Sie wollen:

  • um ein 120x90 Bild zu erzeugen
  • mit vollem Sichtfeld
  • in schwarz und weiß (ich nehme an, dass nur Luma in Ordnung ist)
  • zu einem Python-Numpy-Array
  • so schnell wie möglich

Leicht genug. Wir werden den Sensormodus 4 verwenden, der das volle Sichtfeld bietet und den Sensor verwendet, um eine anfängliche 2x2-Gruppierung durchzuführen. Wir stellen die Ausgabeauflösung der Kamera auf 120 x 90 ein (dies bedeutet einfach, dass der Größenänderungsblock der GPU die 2x2-Sensordaten im Vollbildmodus auf 120 x 90 verkleinert). Schließlich erfassen wir direkt ein Numpy-Array, machen es jedoch nur groß genug für die Y-Ebene (Luminanz) der Daten. Es wird ein Fehler ausgegeben, da das Array nicht groß genug für alle Daten ist, aber das ist in Ordnung. Wir können das ignorieren und die Y-Daten werden trotzdem ausgeschrieben:

import time
import picamera
import numpy as np

with picamera.PiCamera(
         sensor_mode=4,
         resolution='120x90',
         framerate=40) as camera:
    time.sleep(2) # let the camera warm up and set gain/white balance
    y_data = np.empty((96, 128), dtype=np.uint8)
    try:
        camera.capture(y_data, 'yuv')
    except IOError:
        pass
    y_data = y_data[:120, :90]
    # y_data now contains the Y-plane only
    print(y_data.max())

Dies wird mehr oder weniger direkt aus dem YUV- Rezept (Unencoded Image Capture) kopiert, was auch erklärt, warum wir hier tatsächlich 128x96 aufnehmen (die Kamera arbeitet in 32x16-Blöcken).

Was ist mit einer schnellen kontinuierlichen Erfassung? Ich gehe davon aus, dass Sie daran interessiert sind, nur weil Sie dies so schnell wie möglich wollen (was im Allgemeinen bedeutet, dass Sie auch so viele wie möglich wollen). In diesem Fall ist es am besten, eine benutzerdefinierte Ausgabe mit einer YUV-Aufzeichnung zu verwenden (die einen write () -Aufruf pro Frame empfängt) und dann mit der sehr praktischen frombufferMethode von numpy ein numpy-Array über die Vorderseite der erfassten Daten zu legen (Hinweis: Dies ist extrem schnell, da wir das Numpy-Array nicht zuweisen oder die Daten nicht kopieren. Wir sagen nur "Erstellen Sie ein Numpy-Array für diesen vorhandenen Speicherblock "):

import time
import picamera
import numpy as np

class MyOutput(object):
    def write(self, buf):
        # write will be called once for each frame of output. buf is a bytes
        # object containing the frame data in YUV420 format; we can construct a
        # numpy array on top of the Y plane of this data quite easily:
        y_data = np.frombuffer(
            buf, dtype=np.uint8, count=128*96).reshape((96, 128))
        # do whatever you want with the frame data here... I'm just going to
        # print the maximum pixel brightness:
        print(y_data[:90, :120].max())

    def flush(self):
        # this will be called at the end of the recording; do whatever you want
        # here
        pass

with picamera.PiCamera(
        sensor_mode=4,
        resolution='120x90',
        framerate=40) as camera:
    time.sleep(2) # let the camera warm up and set gain/white balance
    output = MyOutput()
    camera.start_recording(output, 'yuv')
    camera.wait_recording(10) # record 10 seconds worth of data
    camera.stop_recording()

Obwohl 40 Bilder pro Sekunde Bilddaten in Numpy-Arrays erzeugt werden, ist die CPU-Auslastung dieses Skripts minimal. Kommentieren Sie die print-Anweisung aus, die eigentlich ziemlich CPU-lastig ist, um die Gesamt-CPU-Auslastung zu sehen: Auf meinem Pi3 sind es ungefähr 2%, sodass für die gewünschte Bildverarbeitung noch viel übrig bleibt.

Dave Jones
quelle
1
Schöne Antwort und Analyse! +1 verdient.
Dmitry Grigoryev
Vielen Dank Dave, deine Antworten sind immer großartig! Genau das brauche ich. Die Bildgröße ist nicht in Stein gemeißelt. Ich werde versuchen, die maximale Größe zu ermitteln, mit der der Rest des .py-Codes (Tensorflow) die Bilderkennung auf dem Pi3 in einem ausreichend guten Tempo ausführen kann. Ich brauche nur vollständige Sensordaten für einen anständigen Blickwinkel. Ich überlege mir immer noch, ob das Ganze eine Schleife sein wird (Capture / Image Recog / React), oder ob ich zwei separate Einheiten haben sollte (Capture wie in Ihrem zweiten Beispiel in einem Prozess und teilen Sie numpys async mit dem Tensorflow-Prozess). . # 1 ist einfacher, # 2 ist schneller. Ich muss darüber nachdenken.
Florin Andrei
Übrigens, die Informationen in dieser Antwort sind so nützlich, dass sie irgendwo in die Dokumentation des Picamera-Moduls aufgenommen werden sollten. Zumindest die erste Hälfte der Antwort. Ich hatte keine Ahnung, wie all diese Verarbeitung auf der GPU usw. durchgeführt wurde. Vielleicht sollte die Kameradokumentation selbst dieses Zeug in einem Klappentext mit technischen Details irgendwo erwähnen. Die Art und Weise, wie die Kamera funktioniert und sich in die RPi-Plattform und die Software integriert, ist viel besser als ich dachte.
Florin Andrei
1
Ich habe das Kapitel zur Kamerahardware für die nächste Version ausgearbeitet, aber ich sollte wahrscheinlich ein wenig hinzufügen, dass alles in der GPU implementiert ist. Ich halte dieses Wissen für selbstverständlich, aber das Lesen der Dokumente ist überhaupt nicht offensichtlich.
Dave Jones
@ DaveJones Ihr erstes Beispiel wirft einige Fehler. Hier ist der feste Code: gist.github.com/FlorinAndrei/281662a59dec0d3cbb902cb3be6d79f6
Florin Andrei
3

Pi Kamera v2 ist eine besonders schlechte Wahl für das, was Sie versuchen zu tun. Schauen Sie sich die Liste der unterstützten Auflösungen an :

Geben Sie hier die Bildbeschreibung ein

Wie Sie sehen können, beträgt die kleinste unterstützte Auflösung 640 × 480 und ist im Vergleich zur gesamten Sensorfläche bereits beschnitten.

Möglicherweise gibt es eine Orange Pi-Kamera , die eine ähnliche Größe hat und Auflösungen von bis zu 320 × 240 unterstützt. Nicht genau das, was Sie brauchen (wenn auch näher), aber ich weiß nicht, ob Sie Ihr Projekt problemlos auf einen anderen SBC umstellen können.

Mein Rat wäre jedoch, eine billige USB-Webcam zu kaufen, die CIF- Auflösungen unterstützt , insbesondere SQCIF (128 × 96). Sie können herausfinden, welche Auflösung eine Webcam unterstützt, indem Sie ausführen v4l2-ctl --list-formats-ext.

Wenn Sie ein großes Sichtfeld benötigen, sollten Sie eine Webcam mit 180 ° -Objektiv (sogenanntes Fisheye) kaufen. Wenn Sie sich nicht für Full HD interessieren, gibt es Fisheye-Webcams für nur 30 US-Dollar. Wenn Sie 640 × 480 Bilder mit 180 ° FOV aufnehmen und einen 120 × 90-Bereich schneiden, erhalten Sie ein effektives FOV von etwa 33 °.

Dmitry Grigoryev
quelle
1
Dies sind die nativen Sensormodi der Kamera, aber Sie können sie mit jeder Auflösung aufnehmen. Die GPU übernimmt das Downsampling des Bildes auf die gewünschte Auflösung. Ich werde in einem Monat eine vollständigere Antwort schreiben, aber ich fürchte, diese Interpretation ist einfach falsch.
Dave Jones
@ DaveJones Ich irre mich in der Tat ist sehr gut möglich. Trotzdem finde ich es etwas verschwenderisch, eine 5-Megapixel-Kamera zu kaufen, um 120 × 90-Bilder aufzunehmen. Daher würde ich immer noch empfehlen, eine billige Webcam zu kaufen. Außerdem sollte für diese GPU-Verarbeitung ein Preis zu zahlen sein (Video-Overlay? Framebuffer-Speicher?)
Dmitry Grigoryev
1
Es gibt keinen CPU- Preis für das Video-Overlay (es verwendet überhaupt keinen Framebuffer - es wird nur direkt auf den HDMI / Composite-Ausgang gezogen, sodass Linux die Kameravorschau nicht einmal kennt - das ist einer der Gründe, die Sie nicht einfach können "steck es in ein Fenster"). Offensichtlich verbraucht es eine bestimmte Menge an GPU-Leistung, aber im Allgemeinen ist das kein Problem (außer dem offensichtlichen, wenn Sie Ihren Pi mit Batterien betreiben :)
Dave Jones
1
Ich sollte auch erklären, warum ich Ihre Interpretation als "falsch" bezeichnet habe: Die aufgeführten Modi sind nicht die "unterstützten Auflösungen". Wenn Sie versuchen, v4l2-ctl --list-formats-extden V4L2-Treiber des pi-Kameramoduls zu laden ( sudo modprobe bcm2835-v4l2), wird eine Reihe von Modi mit "Größe: Schrittweise 32x32 - 2592x1944 mit Schritt 2/2" (das ist von einem v1-Modul) aufgelistet. Das heißt, es unterstützt buchstäblich jede Auflösung von 32x32 bis 2592x1944 in Schritten von 2x2; Die Firmware wird ihr Bestes tun, um den FoV für die ausgewählte Auflösung zu maximieren, aber dem sind natürlich Grenzen gesetzt.
Dave Jones