4.16. Bildvorschau

Der Framebuffer-Pool ist der Ort, an dem die Anwendung ihre Einzelbilder liest. Während die Anwendung an diesen Einzelbildern arbeitet, benötigt auch alles, was mit der Kamera verbunden ist, um sie vorzuschauen, eine Kopie jedes Einzelbildes. Die Kamera verfügt zu diesem Zweck über einen zweiten, dedizierten Puffer und eine einzige Regel dafür, wann er gefüllt wird: Jedes Mal, wenn die Anwendung snapshot() aufruft, wird das zuvor aufgenommene Einzelbild in den Vorschaupuffer kopiert, bevor das neue Einzelbild zurückgegeben wird.

Die Anwendung und der Vorschauer konkurrieren nie um denselben Speicher. Die Anwendung liest ihr Einzelbild aus dem Pool; der Vorschauer liest sein Einzelbild aus dem Vorschaupuffer. Beides geschieht parallel.

4.16.1. Der Stream-Framebuffer

Der Vorschaupuffer – der Stream-Framebuffer – ist ein einzelner Bereich fester Größe im RAM, der vom Framebuffer-Pool getrennt ist. Seine Größe wird zur Firmware-Build-Zeit festgelegt und ändert sich nicht mit framesize() oder pixformat(). Etwa ein Megabyte ist auf aktuellen OpenMV Cams typisch – groß genug, um eine Vorschau mittlerer Auflösung aufzunehmen, viel kleiner als ein Einzelbild voller Auflösung bei den größten Sensorgrößen.

Der Anwendungscode liest oder schreibt diesen Puffer nicht direkt; der Kameratreiber füllt ihn als Nebeneffekt von snapshot().

4.16.2. Was der Schnappschuss für die Vorschau tut

Bei jedem Aufruf von snapshot() kopiert der Treiber, bevor er den vorherigen Framebuffer der Anwendung zurück in den Pool gibt und den neuen herausreicht, das vorherige Einzelbild in den Vorschaupuffer durch – mit allem, was die Anwendung während der Verarbeitung darauf gezeichnet hat, noch auf dem Bild. Zwei Verzweigungen sind möglich. Welche davon ausgeführt wird, bestimmt der Vorschauer, nicht die Kamera: Der Verbraucher, der die Vorschau geöffnet hat, teilt dem Treiber mit, ob er das Rohbild oder ein JPEG möchte und welche Rohfenstergröße er akzeptieren kann.

  • Herunterskalierte Rohkopie. Wenn der Vorschauer Rohbilder angefordert hat, kopiert der Treiber das vorherige Einzelbild in seinem nativen Pixelformat (RGB565, Graustufen usw.) hinüber. Ist das Einzelbild größer als das vom Vorschauer angeforderte Rohfenster, skaliert der Treiber es mit bilinearer Filterung herunter, bis es passt; andernfalls werden die Pixel unverändert durchgereicht. Keine Kompressionsartefakte; der Vorschauer sieht dieselben Pixel, an denen die Anwendung gearbeitet hat.

  • JPEG-Kompression. Wenn der Vorschauer JPEG angefordert hat – oder wenn die Rohkopie überhaupt nicht in den Stream-Puffer passen würde – komprimiert der Treiber das vorherige Einzelbild in voller Auflösung als JPEG in den Stream-Puffer. Die Qualität wird adaptiv pro Einzelbild angepasst, damit die komprimierte Ausgabe innerhalb der Kapazität des Stream-Puffers bleibt. Wenn ein Einzelbild passt, erhöht der Treiber die Qualität schrittweise um eine Stufe in Richtung einer Obergrenze, die von der Pixelgröße des aufgenommenen Einzelbildes abhängt (kleineren Einzelbildern wird höhere Qualität erlaubt; größere werden niedriger gedeckelt, damit sie bei einer geringen Inhaltsänderung nicht überlaufen können). Wenn ein Einzelbild nicht passt, halbiert der Treiber die aktuelle Qualität, hält sie für die nächsten mehrere Dutzend Einzelbilder auf dem reduzierten Niveau, damit die neue Einstellung Zeit hat, sich einzupendeln, und lässt das übergelaufene Einzelbild aus der Vorschau fallen. Die Anwendungsschleife läuft unbeeinträchtigt weiter; nur der Vorschauer verpasst das fallengelassene Einzelbild.

Einzelbilder, die die Kamera in einem bereits komprimierten Format erzeugt (das JPEG-Pixelformat auf Sensoren, die JPEG direkt ausgeben), überspringen beide Verzweigungen: Der codierte Bitstrom wird unverändert direkt in den Vorschaupuffer kopiert.

Der Vorschauer fragt nach seinem eigenen Zeitplan ab, im Allgemeinen viel langsamer, als die Kamera aufnimmt, sodass er die Roh-Aufnahmerate unterabtastet: Nur die Schnappschüsse, die er zufällig rechtzeitig ausliest, werden angezeigt. Wenn ein frischer snapshot() am Vorschaupuffer eintrifft, bevor der Vorschauer das vorherige Einzelbild ausgelesen hat, ist der Puffer noch vom Vorschauer gesperrt und die neue Vorschau-Aktualisierung wird übersprungen – diese Aufnahme geht für den Vorschaustream verloren. Der eigene Framebuffer-Pool der Anwendung ist davon nicht betroffen; das aufgenommene Einzelbild geht weiterhin normal an die Anwendung.

4.16.3. Das letzte Einzelbild manuell senden

Da die Vorschau als Nebeneffekt von snapshot() aktualisiert wird, lässt ein Skript, das endet, ohne jemals wieder snapshot aufzurufen, alles, was es zuletzt an die Vorschau gesendet hat, unbegrenzt auf dem Vorschauer stehen – was bei einem Skript, das seine Arbeit vor dem ersten Schnappschuss erledigt und dann beendet wird, eine leere Vorschau bedeutet. image.Image.flush() (oder das äquivalente flush() auf dem CSI-Objekt) kopiert den aktuellen Inhalt des Framebuffers der Anwendung bei Bedarf in den Stream-Puffer, ohne ein neues Einzelbild aufzunehmen:

img = csi0.snapshot()
# process the image and draw on it
img.flush()                               # previewer sees the annotated frame

Derselbe Aufruf ist auch nützlich, wenn ein langlaufender Vorgang zwischen den Schnappschüssen liegt und der Vorschauer andernfalls die ganze Zeit eine veraltete Vorschau anzeigen würde.

Bemerkung

Die Vorschauanwendung muss das Einzelbild aus dem Stream-Puffer auslesen, bevor das Skript beendet wird. Ein Flush am Ende eines kurzen Skripts bereitet das Einzelbild nur vor; wenn das Skript dann die Kontrolle an die Kamera zurückgibt, bevor der Vorschauer abgefragt hat, wird der Puffer beim nächsten Durchlauf wiederverwendet und dieses letzte Einzelbild geht verloren. Geben Sie für Vorschauen am Skriptende dem Vorschauer einen Moment Zeit, das Einzelbild aufzunehmen (ein kurzer Sleep nach dem Flush oder einfach kein sofortiges Beenden), bevor das Skript endet.