5.6. Formen und Text zeichnen

Ein Algorithmus, der etwas über ein Bild entscheidet, muss diese Entscheidung oft sichtbar machen. Ein Blob-Detektor findet einen Bereich, der für die Anwendung von Interesse ist; die Anwendung möchte, dass der Bereich auf dem Einzelbild gezeichnet wird, damit ein Bediener – oder der Entwickler, der das Skript ausführt – sehen kann, was gefunden wurde. Eine Koordinatentransformation bildet eine Eingabeposition auf eine Ausgabeposition ab; sie zu debuggen bedeutet meist, die beiden Positionen auf demselben Bild zu markieren. Die IDE-Vorschau liest, was sich im Moment der Abfrage im Framebuffer befindet, sodass der einfachste Weg, die Ausgabe eines Algorithmus sichtbar zu machen, darin besteht, Annotationen direkt in den Framebuffer zu schreiben. Die Zeichen-Familie der Klasse Image ist das Werkzeug genau für diese Aufgabe.

5.6.1. Die Primitive

Jede Zeichenmethode platziert eine bestimmte Art von Markierung auf dem Bild. Der Katalog ist klein und hält sich eng an die geometrischen Primitive, die eine Annotation tatsächlich benötigt:

  • draw_line() – ein gerades Liniensegment zwischen zwei Endpunkten.

  • draw_rectangle() – ein achsenparalleles Rechteck, hohl oder gefüllt.

  • draw_circle() – ein Kreis um einen Mittelpunkt, hohl oder gefüllt.

  • draw_ellipse() – eine Ellipse mit beliebiger Drehung.

  • draw_cross() – ein Pluszeichen an einem Punkt, die übliche Markierung für einen Schwerpunkt oder ein Klickziel.

  • draw_arrow() – ein Pfeil von einem Startpunkt zu einem Endpunkt.

  • draw_edges() – die vier Seiten eines beliebigen Vierecks, gegeben durch die vier Eckpunkte; die natürliche Art, einen erkannten Tag oder einen perspektivisch verzerrten Bereich zu umranden.

  • draw_string() – Text aus einer integrierten Bitmap-Schriftart.

Jede dieser Methoden verändert das Quellbild direkt und gibt dasselbe Bild zur Verkettung zurück, gemäß der zuvor festgelegten Konvention für operierende Methoden.

Ein Raster aus kleinen Feldern, die jedes der acht Zeichenprimitive einmal angewendet zeigen. Jedes Feld enthält eine Linie, ein Rechteck, einen Kreis, eine Ellipse, ein Kreuz, einen Pfeil, ein Viereck oder eine kurze Textzeichenkette, mit dem darunter beschrifteten Namen der Methode, die es erzeugt hat.

Die acht Zeichenprimitive, eines pro Feld. Jede Methode erzeugt eine Art von Markierung.

5.6.2. Farbe

Jede Zeichenmethode nimmt ein color-Argument, das entscheidet, welcher Wert in jeden gemalten Pixel geschrieben wird. Welche Form dieses Argument annimmt, hängt vom Format des Bildes ab. Für ein RGB565-Bild ist die natürliche Form ein (r, g, b)-Tupel mit jedem Kanal im Bereich 0255; das Modul packt dies in das 16-Bit-RGB565-Wort, bevor es geschrieben wird. Für ein Graustufenbild ist die natürliche Form eine einzelne ganzzahlige Helligkeit von 0 (schwarz) bis 255 (weiß). Die Methoden akzeptieren auch den im Format gespeicherten Rohwert – ein 16-Bit-gepacktes Wort für RGB565, eine 8-Bit-Ganzzahl für Graustufen – was die effiziente Form ist, wenn die Farbe anderswo berechnet wurde und bereits in der gespeicherten Form vorliegt.

Wird das color-Argument weggelassen, wird weiß gemalt. Dieser Standard ist für die Arbeit mit Graustufen praktisch, wo weiß der Maximalwert ist und sich gegen die meisten Hintergründe deutlich abhebt. Für RGB565-Debug-Overlays ist er fast immer falsch: grün oder rot hebt sich gegen die Art von Szene, die eine Kamera tatsächlich sieht, meist besser ab, und eine explizite Farbe vermittelt die Absicht.

5.6.3. Dicke und Füllung

Die meisten geometrischen Methoden nehmen zwei Flags, die entscheiden, wie die Markierung gezeichnet wird:

  • thickness=N legt die Linienbreite in Pixeln fest. Der Standard ist 1, was für die meisten Overlays in Ordnung ist; ein größerer Wert ist nützlich, wenn eine Annotation gegen eine unruhige Szene sichtbar bleiben muss oder nachdem eine spätere Stufe der Pipeline das Bild weiter verändert.

  • fill=True schaltet die Markierung von einer Umrisslinie auf eine ausgefüllte um und malt jeden Innenpixel mit der gewählten Farbe. Der Standard ist False.

Diese Flags gelten nicht für die Primitive, die keinen zu füllenden Innenraum haben – die Linie, das Kreuz, den Pfeil, das Viereck – bei denen nur thickness von Bedeutung ist.

5.6.4. Text zeichnen

draw_string() schreibt Zeichen aus einer integrierten 8-mal-10-Pixel-Bitmap-Schriftart. x und y sind die obere linke Ecke der Zelle des ersten Zeichens, text ist die zu zeichnende Zeichenkette, und color folgt derselben Konvention wie die geometrischen Methoden. Die Schriftart umfasst den vollständigen druckbaren ASCII-Bereich und hat kein Kerning – jedes Zeichen belegt dieselbe 8 Pixel breite Zelle – was die Ausgabe leicht positionierbar macht.

img.draw_string(10, 10, "blobs: 3", color=(0, 255, 0))

Die Zeichenkette kann Zeilenumbrüche (\n) enthalten; jeder Zeilenumbruch verschiebt das nächste Zeichen an den Anfang einer neuen Zeile zehn Pixel unterhalb der vorherigen. Das Argument scale zeichnet jedes Zeichen um einen Fließkommafaktor größer, und x_spacing sowie y_spacing fügen um jedes Zeichen herum Abstand hinzu. Ein kleiner Satz von Dreh-/Spiegel-/Kipp-Flags gilt entweder für die gesamte Zeichenkette oder für jedes Zeichen einzeln – genug Kontrolle, um Text entlang eines Winkels oder an einer nicht horizontalen Kante anzuordnen, wenn das Layout es erfordert.

5.6.5. Die Leinwand löschen

Eine Methode der Familie zeichnet keine bestimmte Markierung. Sie setzt einfach jeden Pixel des Bildes auf null zurück:

  • clear() – setzt jeden Pixel auf null, optional auf eine ROI beschränkt oder durch eine Maske eingegrenzt.

clear() ist die richtige Wahl, wenn eine Anwendung eine Annotation in jedem Einzelbild von Grund auf neu zusammensetzt – mit einer schwarzen Leinwand beginnen, die neuen Annotationen zeichnen, das Ergebnis an die Anzeige übergeben – anstatt sie über das erfasste Einzelbild zu legen. Es ist auch die günstigste Art, ein Scratch-Bild zur Verwendung als Maskenpuffer vorzubereiten.

Ein frisch reserviertes Bild ist vom Konstruktor her bereits null, daher ist clear() speziell für Puffer von Bedeutung, die zwischen Einzelbildern wiederverwendet werden.