5.25. Blobs finden

Die Schwellenwertbildung verwandelt das aufgenommene Einzelbild in eine binäre Maske: Jeder Pixel besteht den Schwellenwerttest entweder, oder eben nicht. Das beantwortet, welche der für die Anwendung relevanten Farben in der Szene auftauchen, aber nicht wo – die Maske ist nur ein Meer aus Einsen und Nullen. Der nächste Schritt ist die Blob-Erkennung: das Durchlaufen der Maske, das Finden zusammenhängender Regionen bestandener Pixel und die Rückgabe jeder einzelnen als Objekt mit Position, Größe, Ausrichtung und den weiteren Eigenschaften, auf die eine Anwendung reagieren kann.

find_blobs() ist die Arbeitspferdmethode für diesen Schritt und der häufigste Einstiegspunkt in die Welt der Ergebnisobjekte des image-Moduls. Das Verfolgen eines farbigen Balls, das Folgen einer auf den Boden gemalten Linie, das Zählen, wie viele helle Punkte ein Thermosensor sieht, das Entscheiden, ob eine blaue LED an oder aus ist – derselbe Aufruf deckt all das ab. Die Eingaben ändern sich (die Schwellenwerte, die durchsuchte Region, die auf das Ergebnis angewandten Filter), aber das Aufrufmuster bleibt gleich.

5.25.1. Der grundlegende Aufruf

find_blobs nimmt eine Liste von Schwellenwerten entgegen und gibt eine Liste von Blob-Ergebnisobjekten zurück:

thresholds = [(30, 100, 15, 127, 15, 127)]  # LAB threshold for red
blobs = img.find_blobs(thresholds)

for b in blobs:
    img.draw_rectangle(b.rect, color=(255, 0, 0))
    img.draw_cross(b.cx, b.cy, color=(255, 0, 0))

Jedes Schwellenwert-Tupel hat dieselbe Form wie die an binary() übergebenen Schwellenwerte – sechs Einträge (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) für ein RGB565-Bild (die Grenzen sind in LAB), zwei Einträge (lo, hi) für ein Graustufenbild. Bis zu 32 Schwellenwerte können in einem einzigen Aufruf angegeben werden, was find_blobs() so flexibel macht: rote, grüne und blaue Baken können gleichzeitig verfolgt werden, wobei jede ihre eigenen Blobs zur zurückgegebenen Liste beisteuert, und die code-Eigenschaft jedes Blobs gibt an, welchen Schwellenwert er getroffen hat.

Die obigen Aufrufe von draw_rectangle() und draw_cross() versehen das aufgenommene Einzelbild für die IDE-Vorschau mit Anmerkungen. Das Blob-Ergebnis trägt bereits b.rect (den Begrenzungsrahmen als 4-Tupel) sowie b.cx / b.cy (den ganzzahligen Schwerpunkt) in sich, sodass das Zurückzeichnen der Erkennung in das Einzelbild nur zwei Methodenaufrufe erfordert.

5.25.2. Was das Ergebnis enthält

Jeder Blob ist ein Attribut-Tupel, das alles bündelt, was der Detektor über die Region gemessen hat. Die Eigenschaften gliedern sich in vier Gruppen.

Die Gruppe Begrenzungsrahmen und Schwerpunktx, y, w, h, rect, cx, cy, cxf, cyf – beschreibt die Position des Blobs. rect ist das 4-Tupel (x, y, w, h), das Zeichenmethoden erwarten; cx und cy sind der Schwerpunkt in ganzzahligen Pixelkoordinaten; cxf und cyf sind der Schwerpunkt in Subpixel-Gleitkommakoordinaten, nützlich, wenn eine vorgelagerte Kalibrierung auf Bruchteilpositionen angewiesen ist.

Die Formdeskriptorenpixels, area, density, perimeter, roundness, elongation, compactness, rotation – beschreiben, wie der Blob aussieht. pixels ist die Anzahl der bestandenen Pixel; area ist die Fläche des achsenausgerichteten Begrenzungsrahmens (w * h); density ist das Verhältnis der beiden, das sich für ein massives Rechteck 1.0 nähert und für einen dünnen diagonalen Strich gegen 0.0 fällt. roundness und compactness bewerten beide aus unterschiedlichen geometrischen Blickwinkeln, wie rund der Blob ist (roundness aus den Momenten zweiter Ordnung, compactness aus dem Verhältnis von Umfang zu Fläche); elongation ist der Bequemlichkeit halber 1.0 - roundness. rotation ist die Ausrichtung der Hauptachse im Bogenmaß, die bei länglichen Blobs am genauesten ist und bei nahezu runden verrauscht wird (eine mehrdeutige Achse hat keine klar definierte Richtung).

Die Schwellenwert- und Zusammenführungs-Metadatencode, count – geben an, welcher Schwellenwert getroffen wurde und wie viele Quell-Blobs zu dem zurückgegebenen zusammengeführt wurden. code ist eine 32-Bit-Bitmaske mit einem gesetzten Bit pro übereinstimmendem Schwellenwert (ein einzelner Schwellenwert ergibt code == 1; ein zusammengeführter mehrfarbiger Blob kann mehrere gesetzte Bits haben); count ist 1, sofern nicht merge=True mehrere Erkennungen zu einer kombiniert hat.

Die Gruppe Eckencorners, min_corners – liefert die gedrehte Geometrie des Blobs. corners ist das 4-Tupel der (x, y)-Extreme, die aus der Kontur des Blobs gezogen und im Uhrzeigersinn ab oben links sortiert sind; min_corners ist das 4-Tupel der Ecken des flächenminimalen gedrehten Rechtecks, das den Blob umschließt. Das flächenminimale Rechteck ist die enge Passung; das achsenausgerichtete rect ist die lose Passung, die am Pixelraster ausgerichtet ist. Beide sind je nachdem nützlich, ob eine nachgelagerte Stufe einen orientierten oder einen schlichten Rahmen benötigt.

Eine Blob-Erkennung, illustriert gegen eine binäre Schwellenwertmaske. Das linke Feld zeigt eine geneigte ovale Maske aus bestandenen Pixeln. Das rechte Feld zeigt dieselbe Maske, versehen mit dem darum gezeichneten achsenausgerichteten Begrenzungsrahmen, dem in der Mitte mit einem Kreuz markierten Schwerpunkt, einem gestrichelten flächenminimalen gedrehten Rechteck, das sich im wahren Winkel eng an das Oval schmiegt, und der Hauptachsenlinie durch den Schwerpunkt, die entlang der Längsrichtung des Ovals zeigt.

Ein Blob trägt den achsenausgerichteten Begrenzungsrahmen (rect, x, y, w, h), den Schwerpunkt (cx, cy oder die Subpixel-Werte cxf, cyf), das flächenminimale gedrehte Rechteck (min_corners plus rotation) sowie die optionalen Haupt- / Nebenachsenlinien, die von den weiter unten beschriebenen modulweiten Hilfsfunktionen berechnet werden.

5.25.4. Überlappende Blobs zusammenführen

merge=True verarbeitet die Ergebnisliste nach, um Blobs zu kombinieren, deren Begrenzungsrechtecke sich überlappen. Der natürliche Anwendungsfall ist die Erkennung eines Ziels, dessen Farbe die Kamera aufgrund von Glanzlichtern, Schattenlinien oder ungleichmäßiger Beleuchtung über das Objekt hinweg als mehrere schwellenwertgefilterte Regionen sieht: Ein einzelner roter Ball könnte als drei oder vier kleine rote Blobs zurückkommen, die zusammengenommen den Ball nachzeichnen. Mit merge=True werden die drei Blobs zu einem großen Blob, das rect deckt die Vereinigung ab, das code ist das bitweise ODER der Codes der zusammengeführten Blobs (sodass eine mehrfarbige Zusammenführung angibt, welche Farben beigetragen haben), und count meldet, wie viele Quell-Blobs kombiniert wurden.

margin vergrößert oder verkleinert die Begrenzungsrechtecke vor dem Überlappungstest. Mit margin=2 werden Blobs, deren Begrenzungsrechtecke einander bis auf 2 Pixel nahekommen, dennoch zusammengeführt; mit margin=-2 werden nur Blobs zusammengeführt, deren Begrenzungsrechtecke sich um mindestens 2 Pixel überlappen. Die natürliche Abstimmung: positiver Rand, um Blobs zu behandeln, die der Schwellenwert in benachbarte Stücke zerbrochen hat; negativer Rand, um eng gruppierte, eigenständige Objekte getrennt zu halten.

merge_cb läuft für jedes Kandidatenpaar, bevor die Zusammenführung erfolgt. Das Callback erhält die beiden Blobs und gibt True zurück, um die Zusammenführung zuzulassen, oder False, um sie zu verhindern. Dies ist das richtige Werkzeug, um Zusammenführungen gegenzuprüfen, die die geometrische Regel verfehlt – zum Beispiel die Weigerung, zwei Blobs zusammenzuführen, deren rotation-Winkel um mehr als einen Schwellenwert voneinander abweichen, oder die Weigerung, einen kleinen Blob in einen viel größeren zu integrieren, wenn der kleine nur Flecken ist.

5.25.5. Projektionshistogramme

x_hist_bins_max und y_hist_bins_max hängen jedem Blob optionale Projektionshistogramme an. Ein Projektionshistogramm ist die Anzahl der bestandenen Pixel entlang einer Achse: Das X-Achsen-Histogramm summiert die bestandenen Pixel pro Spalte innerhalb des Begrenzungsrahmens des Blobs, und das Y-Achsen-Histogramm summiert pro Zeile. Beide stehen standardmäßig auf null – die Histogramme werden nur berechnet, wenn ein von null verschiedenes max angegeben wird, da sie andernfalls jeder Erkennung Arbeit hinzufügen würden.

Wenn sie berechnet werden, liefern die Histogramme ein günstiges 1-D-Signal, auf dem eine Anwendung weitere Analysen durchführen kann: das Erkennen der Position eines vertikalen Streifens innerhalb des Blobs, das Finden des Bruchpunkts eines zweifarbigen Ziels, das Zählen, wie viele Lücken entlang der Längsachse auftreten. Sie werden als die Eigenschaften x_hist_bins und y_hist_bins an jedem Blob gefüllt.

5.25.6. Zusätzliche geometrische Hilfsmittel

Eine Handvoll weiterer geometrischer Maße existieren als modulweite Funktionen, die einen Blob entgegennehmen und die angeforderte Messung zurückgeben:

  • image.get_solidity() gibt die Festigkeit des Blobs zurück – die Pixel geteilt durch die Fläche der konvexen Hülle. Eine massive ausgefüllte Region liegt nahe 1.0; ein Blob mit Konkavitäten (ein Hufeisen, eine Hand mit gespreizten Fingern) fällt deutlich darunter.

  • image.get_convexity() gibt die Konvexität zurück – den Umfang der konvexen Hülle geteilt durch den Umfang des Blobs. Ein perfekt konvexer Blob ist 1.0; gezackte oder eingekerbte Blobs liegen niedriger.

  • image.get_major_axis_line() und image.get_minor_axis_line() geben Line-Objekte entlang der Haupt- und Nebenachse des Blobs zurück, abgeleitet vom gedrehten flächenminimalen Rechteck.

  • image.get_enclosing_circle() gibt einen Circle zurück, der den Blob umschließt – nützlich, wenn eine nachgelagerte Stufe einen Kreis zum Zeichnen oder zum Abgleichen möchte.

  • image.get_enclosed_ellipse() gibt das 5-Tupel (cx, cy, rx, ry, rotation) für eine in das flächenminimale Rechteck des Blobs eingeschriebene Ellipse zurück. Die Werte fließen direkt in draw_ellipse() ein.

5.25.7. Einen Schwellenwert automatisch lernen

Ein Blob-Detektor ist nur so gut wie die Schwellenwerte, mit denen er betrieben wird, und die Arbeit, den richtigen Schwellenwert für eine Zielfarbe zu finden, ist ein eigenes Problem. Zwei verbreitete Muster verringern diese Arbeit.

Das erste ist die interaktive Auswahl in der IDE: ein Einzelbild aufnehmen, ein Rechteck um ein Beispiel der Zielfarbe ziehen und den Schwellenwert-Editor der IDE die LAB-Grenzen melden lassen, die er sieht. Diese Grenzen wandern als die find_blobs()-Schwellenwerte in das Skript, und der Detektor ist einsatzbereit.

Das zweite ist das programmatische automatische Lernen: Eine auf der Kamera laufende Kalibrierungsroutine nimmt ein Einzelbild auf, erstellt ein Histogramm eines bekannten Flecks, in dem sich das Ziel befindet (get_histogram() mit roi=), und liest den Wertebereich des Flecks mit get_percentile() aus dem Histogramm ab. Das 5. Perzentil legt die untere Grenze jedes Kanals fest und das 95. dessen obere Grenze, wobei vereinzelte Ausreißerpixel an beiden Enden ignoriert werden. Bei einem RGB565-Bild meldet ein einziger Perzentil-Aufruf alle drei LAB-Kanäle auf einmal, sodass die beiden Aufrufe die sechs Zahlen erzeugen, die find_blobs() erwartet:

h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
             lo.a_value, hi.a_value,
             lo.b_value, hi.b_value)