5.5. Regionen und Masken

Jede Operation im image-Modul berührt standardmäßig jeden Pixel ihres Quellbildes. Das ist das am einfachsten zu beschreibende Verhalten und das richtige, wenn die Aufgabe des Algorithmus tatsächlich das gesamte Einzelbild umspannt – eine gleichmäßige Farbkorrektur, ein globales Histogramm, ein Kodierungsdurchlauf zur Übertragung. Doch die meisten Algorithmen in der Praxis möchten weniger als das betrachten. Ein Blob-Tracker, der einen farbigen Marker beobachtet, kümmert sich um den Teil der Szene, in dem der Marker erscheinen kann, nicht um die Wand dahinter. Ein morphologischer Bereinigungsdurchlauf ist nur über jenen Pixeln sicher, die eine frühere Stufe als Kandidaten markiert hat. Ein Gesichtsdetektor läuft womöglich nur innerhalb des Begrenzungsrahmens, den ein gröberer Detektor bereits eingegrenzt hat. Das image-Modul unterstützt diese Arbeit durch zwei Mechanismen, die eine Operation auf eine Teilmenge der Pixel beschränken: rechteckige Interessensbereiche (ROI) und binäre Masken. Sie lassen sich frei kombinieren, und nahezu jede Methode, die Pixel berührt, akzeptiert die eine oder die andere – oder beide – als Schlüsselwortargument.

5.5.1. Interessensbereiche (ROI)

Ein Interessensbereich ist ein Rechteck aus Pixeln, das durch das Vierertupel (x, y, w, h) benannt wird, welches auf der Koordinatenseite eingeführt wurde. Etwa dreißig Methoden auf der Oberfläche akzeptieren ein roi-Schlüsselwortargument; ist es vorhanden, läuft die Operation nur auf den Pixeln innerhalb dieses Rechtecks und lässt den Rest des Bildes unberührt. Wenn roi None ist oder weggelassen wird, läuft die Operation über das gesamte Bild – so, als wäre roi=(0, 0, width, height) übergeben worden.

Im Code steht das Schlüsselwort neben den übrigen Argumenten, die die Operation entgegennimmt:

# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))

Das Erste, was ROIs einbringen, ist Kontrolle über Falsch-Positive. Ein Farb-Tracker, der nur auf den Tisch schaut, wird niemals durch das vorbeigehende Hemd ausgelöst; ein Kantendetektor, der nur innerhalb des definierten Arbeitsbereichs läuft, wird niemals die Kanten der Kamerahalterung selbst melden. Den Suchbereich auf den Teil der Szene zu beschränken, der den Algorithmus tatsächlich interessiert, ist die kostengünstigste Verbesserung, die eine Pipeline an ihrer eigenen Zuverlässigkeit vornehmen kann.

Das Zweite, was sie einbringen, ist die Grob-zu-Fein-Pipeline. Ergebnisobjekte der Erkennung – ein blob, ein rect, ein apriltag und so weiter – legen ihre Begrenzungsrahmen als dasselbe Vierertupel (x, y, w, h) offen, das roi akzeptiert. So kann eine grobe erste Stufe einen Begrenzungsrahmen zurückgeben, der Rahmen fällt direkt in das roi der nächsten Stufe, und die zweite Stufe läuft über den engeren Bereich. Jede fortschreitende Eingrenzung beschleunigt die nächste Stufe und macht zugleich ihre Ergebnisse zuverlässiger, da der Suchraum bereits gefiltert wurde.

5.5.2. Binäre Masken

Ein Rechteck ist die richtige Form, wenn der Interessensbereich achsenparallel ist. Wenn er es nicht ist – eine gekrümmte Region, eine nicht-konvexe, die Pixel, die eine frühere Stufe als „Treffer“ klassifiziert hat – muss der Operation mitgeteilt werden, sich stattdessen auf ein beliebiges Pixelmuster zu beschränken. Der Mechanismus dafür ist eine binäre Maske: ein separates Image mit denselben Abmessungen wie die Quelle, das als ein Pixel-für-Pixel-Ein-/Ausschalter dient. Ein Pixel ungleich null in der Maske bedeutet „beziehe den entsprechenden Quellpixel ein“; ein Null-Pixel bedeutet „lasse den Quellpixel unverändert.“

Eine Maske ist üblicherweise ein BINARY-Bild – das Format mit einem Bit pro Pixel, das genau zu diesem Zweck existiert – doch jedes einkanalige Bild funktioniert, da der Verbraucher jeden Wert ungleich null als eingeschaltet behandelt.

Filter-, Schwellenwert- und Arithmetikmethoden akzeptieren ein mask-Schlüsselwortargument. Die Form ist bei jeder gleich: ein separat allokiertes Binärbild mit denselben Abmessungen wie die Quelle, das übergeben wird.

ROIs und Masken kombinieren sich. Übergibt man beide, läuft die Operation nur auf Pixeln, die innerhalb der ROI und in der Maske eingeschaltet sind. Die beiden Mechanismen geben dem Anwendungscode unabhängige Hebel – einen für den rechteckigen Interessensbereich, einen für das beliebige Muster darin – ohne dass eine Form Beschränkungen von der anderen erbt.

Ein kleines Gitter, das ein Bild darstellt. Ein gestricheltes Rechteck, das über den oberen mittleren Teil des Gitters gezeichnet ist, kennzeichnet die ROI: nur Pixel innerhalb dieses Rechtecks werden berücksichtigt. Innerhalb der ROI kennzeichnet eine grob kreisförmige Menge gefüllter Zellen die Maske: nur diese gefüllten Zellen werden tatsächlich verändert. Die übrigen Zellen sind leicht schattiert, um anzuzeigen, dass sie unberührt bleiben.

Eine ROI begrenzt eine Operation auf ein achsenparalleles Rechteck. Eine Maske grenzt sie weiter auf ein beliebiges Pixelmuster ein. Die beiden kombinieren sich: nur Pixel innerhalb der ROI und eingeschaltet in der Maske werden verändert.

5.5.3. Masken erstellen

Drei Image-Methoden erstellen gängige Maskengeometrien an Ort und Stelle, indem sie die Pixel außerhalb der gewählten Region auf null setzen:

Jede nimmt (x, y, w, h) (für das Rechteck und die Ellipse) oder (x, y, radius) (für den Kreis) entgegen. Ruft man eine davon ohne Argumente auf, wird die Geometrie zentriert und so dimensioniert, dass sie das Bild ausfüllt – die Form, zu der eine Anwendung greift, wenn das Ziel ein einfaches bildfüllendes Oval oder ein Kreis ist, der nichts außer den Ecken verbirgt.

mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear()              # start from all zeros
mask.mask_ellipse()       # centred, full-size oval

Die interessanten Masken stammen selten allein von den mask_*-Methoden. Sie stammen aus früheren Stufen der Pipeline: ein Schwellenwertdurchlauf erzeugt ein Binärbild, dessen Pixel ungleich null die Treffer markieren – genau die richtige Form, um sie in das mask=-Argument der nächsten Stufe einzuspeisen. Ein morphologischer Bereinigungsdurchlauf verfeinert diese Maske, ohne ihre Form zu ändern. Alles, was als einkanaliges Bild endet, ist selbst eine gültige Maske.

5.5.4. Wie Operationen das Bild verändern

Ein Muster, das in jedem Code-Schnipsel auf den letzten Seiten sichtbar ist – die Operation gibt dasselbe img zur Verkettung zurück – ist es wert, ausdrücklich hervorgehoben zu werden, damit es nicht jedes Mal, wenn eine neue Methode eingeführt wird, erneut formuliert werden muss. Drei Methodenfamilien erscheinen auf der Image-Oberfläche, von denen jede das Quellbild unterschiedlich behandelt:

  • Operierende Methoden verändern die Pixel der Quelle an Ort und Stelle und geben dasselbe Bild zur Verkettung zurück. Die Zeichen-, Arithmetik-, Schwellenwert- und Filterfamilien verhalten sich alle auf diese Weise. img.gaussian(1) weichzeichnet img und gibt dasselbe img zurück; eine Neuzuweisung – img = img.gaussian(1) – ist harmlos, aber unnötig.

  • Konvertierungsmethoden arbeiten standardmäßig an Ort und Stelle, genauso wie operierende Methoden, akzeptieren aber copy=True und copy_to_fb=True, um ein separates Ergebnisbild zu allokieren, wenn die Quelle erhalten bleiben muss. Die Formatkonvertierungen und die geometrischen Kopien sind die Hauptvertreter dieser Familie.

  • Inspektionsmethoden lesen die Pixel und geben ein Ergebnisobjekt zurück – eine Liste erkannter Merkmale, ein Histogramm, einen Satz Statistiken – ohne das Quellbild überhaupt zu verändern.

Diese Dreiteilung ist über die gesamte Oberfläche hinweg konsistent. Zu wissen, welcher Familie eine Methode angehört, sagt der Anwendung, was sie von einem Aufruf zu erwarten hat: ob die Pixel der Quelle unversehrt überstehen, ob ein separates Ergebnisbild allokiert wird und ob der Rückgabewert die Quelle selbst oder etwas anderes ist.