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 Schwerpunkt – x, 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 Formdeskriptoren – pixels, 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-Metadaten – code, 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 Ecken – corners, 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.
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.3. Die Suche filtern¶
Ein aufgenommenes Einzelbild enthält typischerweise Pixel, die den Schwellenwert aus anderen Gründen als dem für die Anwendung relevanten Objekt treffen: Glanzlichter, entfernte Hintergrundobjekte, Bildrausch-Pixel, die zufällig in den LAB-Bereich fallen. Die Schlüsselwortargumente von find_blobs() sind die erste Verteidigungslinie.
roi beschränkt die Suche auf eine Region des Einzelbilds, so wie es jede andere image-Modul-Methode tut. Eine Anwendung, die weiß, dass das Objekt nur in der unteren Hälfte des Sichtfelds erscheinen kann, übergibt roi=(0, h//2, w, h//2) und ignoriert alles darüber; die gesparte Zeit fließt in die Bildrate zurück.
area_threshold und pixels_threshold filtern beide Blobs heraus, die zu klein sind, um von Interesse zu sein. area_threshold verwirft Blobs, deren Begrenzungsrahmen eine Fläche von weniger als so vielen Pixeln hat (gut zum Herausfiltern von verstreutem Rauschen); pixels_threshold verwirft Blobs, die weniger als so viele bestandene Pixel haben (gut zum Herausfiltern von Blobs, die groß, aber spärlich sind, wie ein schwellenwertgefiltertes Punktmuster mit hier und da ein oder zwei übereinstimmenden Pixeln). Beide Standardwerte sind 10; sie für ein wenige Zentimeter großes Vordergrundziel auf Hunderte hochzudrehen, wirft jedes Stäubchen kleinen Rauschens weg.
x_stride und y_stride legen den Pixelschritt fest, den der Scanner macht, während er nach einem Blob sucht, um mit dessen Nachverfolgung zu beginnen. Der Schritt ist nicht die Nachverfolgungsauflösung – die Nachverfolgung folgt stets der tatsächlichen Blob-Grenze mit Einzelpixel-Detailgenauigkeit – aber er steuert, wie schnell der Scan einen Startpixel findet. Wenn bekannt ist, dass Blobs groß sind (ein faustgroßes farbiges Ziel einen Fuß von der Kamera entfernt, leicht hundert Pixel breit), verkürzt x_stride=4, y_stride=4 die Scanzeit um das Sechzehnfache ohne praktischen Verlust bei der Erkennung. Wenn Blobs klein sind (eine entfernte LED-Bake, ein paar Pixel breit), müssen die Schritte bei 1 bleiben, um nicht ganz über sie hinwegzuschreiten. invert kehrt den Schwellenwerttest um: aus Übereinstimmung wird Nichtübereinstimmung, und die Routine gibt Blobs aus nicht bestandenen Pixeln zurück.
threshold_cb ist ein Python-Callback, der für jeden Blob nach der Schwellenwertbildung, aber vor dem Aufbau der endgültigen Ergebnisliste aufgerufen wird. Das Callback erhält den Blob und gibt True zurück, um ihn zu behalten, oder False, um ihn zu verwerfen. Dies ist der Ort, um beliebige Filter auf Python-Ebene auf Eigenschaften anzuwenden, die die Schlüsselwortargumente nicht direkt offenlegen – eine minimale Dichte, einen bestimmten Rotationsbereich, eine eigene Code-Bit-Kombination nach dem Zusammenführen. Die Schlüsselwortargumente sind Filter in nativem Code und laufen schnell; das Callback läuft in Python und ist langsamer, aber unbegrenzt in dem, was es ausdrücken kann.
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 nahe1.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 ist1.0; gezackte oder eingekerbte Blobs liegen niedriger.image.get_major_axis_line()undimage.get_minor_axis_line()gebenLine-Objekte entlang der Haupt- und Nebenachse des Blobs zurück, abgeleitet vom gedrehten flächenminimalen Rechteck.image.get_enclosing_circle()gibt einenCirclezurü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 indraw_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)