5.25. Ricerca dei blob

L’applicazione della soglia ha trasformato il frame catturato in una maschera binaria: ogni pixel supera il test di soglia oppure no. Questo risponde alla domanda su quali colori che interessano all’applicazione compaiono nella scena, ma non dove – la maschera è solo un mare di 1 e 0. Il passo successivo è il rilevamento dei blob: percorrere la maschera, trovare regioni contigue di pixel che superano la soglia e restituire ognuna come un oggetto con una posizione, una dimensione, un orientamento e le altre proprietà su cui un’applicazione può agire.

find_blobs() è il metodo di riferimento per questo passo ed è il punto di accesso più comune al mondo degli oggetti-risultato del modulo image. Tracciare una pallina colorata, seguire una linea dipinta sul pavimento, contare quanti punti luminosi vede un sensore termico, decidere se un LED blu è acceso o spento – la stessa chiamata copre tutti questi casi. Gli ingressi cambiano (le soglie, la regione cercata, i filtri applicati al risultato), ma lo schema della chiamata è lo stesso.

5.25.1. La chiamata di base

find_blobs prende una lista di soglie e restituisce una lista di oggetti-risultato blob:

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))

Ogni tupla di soglia ha la stessa forma delle soglie passate a binary() – sei voci (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) per un’immagine RGB565 (i limiti sono in LAB), due voci (lo, hi) per un’immagine in scala di grigi. In una singola chiamata si possono fornire fino a 32 soglie, ed è questo che rende find_blobs() così flessibile: beacon rossi, verdi e blu possono essere tracciati simultaneamente, ognuno contribuendo con i propri blob alla lista restituita, e la proprietà code di ogni blob identifica quale soglia ha trovato corrispondenza.

Le chiamate draw_rectangle() e draw_cross() qui sopra annotano il frame catturato per l’anteprima nell’IDE. L’oggetto-risultato blob contiene già b.rect (il bounding box come tupla di 4 elementi) e b.cx / b.cy (il centroide intero), quindi ridisegnare il rilevamento nel frame richiede due chiamate di metodo.

5.25.2. Cosa contiene il risultato

Ogni Blob è una tupla con attributi che raccoglie tutto ciò che il rilevatore ha misurato della regione. Le proprietà si dividono in quattro gruppi.

Il gruppo bounding box e centroidex, y, w, h, rect, cx, cy, cxf, cyf – descrive la posizione del blob. rect è la tupla di 4 elementi (x, y, w, h) che i metodi di disegno si aspettano; cx e cy sono il centroide in coordinate intere di pixel; cxf e cyf sono il centroide in coordinate float sub-pixel, utili quando una calibrazione a monte tiene conto di posizioni frazionarie.

Il gruppo descrittori di formapixels, area, density, perimeter, roundness, elongation, compactness, rotation – descrive l’aspetto del blob. pixels è il conteggio dei pixel che superano la soglia; area è l’area del bounding box allineato agli assi (w * h); density è il rapporto tra i due, che si avvicina a 1.0 per un rettangolo pieno e scende verso 0.0 per un tratto diagonale sottile. roundness e compactness valutano entrambi quanto il blob sia rotondo, da punti di vista geometrici diversi (roundness dai momenti del secondo ordine, compactness dal rapporto perimetro-area); elongation è 1.0 - roundness per comodità. rotation è l’orientamento dell’asse maggiore in radianti, che è più accurato sui blob allungati e diventa rumoroso su quelli quasi rotondi (un asse ambiguo non ha una direzione ben definita).

Il gruppo metadati di soglia e fusionecode, count – identifica quale soglia ha trovato corrispondenza e quanti blob di origine sono stati fusi in quello restituito. code è una bitmap a 32 bit con un bit impostato per ogni soglia corrispondente (una singola soglia dà code == 1; un blob multicolore fuso può avere più bit impostati); count è 1 a meno che merge=True non abbia combinato diversi rilevamenti in uno solo.

Il gruppo angolicorners, min_corners – fornisce la geometria ruotata del blob. corners è la tupla di 4 elementi di estremi (x, y) ricavati dal contorno del blob, ordinati in senso orario a partire dall’angolo in alto a sinistra; min_corners è la tupla di 4 angoli del rettangolo ruotato di area minima che racchiude il blob. Il rettangolo di area minima è l’adattamento stretto; il rect allineato agli assi è l’adattamento lasco allineato alla griglia di pixel. Entrambi sono utili a seconda che uno stadio a valle necessiti di un box orientato o di uno semplice.

A blob detection illustrated against a binary threshold mask. The left panel shows a tilted oval mask of passing pixels. The right panel shows the same mask annotated with the axis-aligned bounding box drawn around it, the centroid marked with a cross in the middle, a dashed minimum-area rotated rectangle hugging the oval at its true angle, and the major-axis line through the centroid pointing along the oval's long direction.

Un blob contiene il bounding box allineato agli assi (rect, x, y, w, h), il centroide (cx, cy o i sub-pixel cxf, cyf), il rettangolo ruotato di area minima (min_corners più rotation) e le linee opzionali dell’asse maggiore / minore calcolate dalle funzioni di supporto a livello di modulo descritte più avanti.

5.25.4. Fondere blob sovrapposti

merge=True post-elabora la lista dei risultati per combinare i blob i cui rettangoli di delimitazione si sovrappongono. L’uso naturale è rilevare un bersaglio il cui colore la camera vede come più regioni con soglia a causa di riflessi speculari, linee d’ombra o illuminazione non uniforme sull’oggetto: una singola palla rossa potrebbe tornare come tre o quattro piccoli blob rossi che, presi insieme, tracciano la palla. Con merge=True, i tre blob diventano un unico grande blob, il rect copre l’unione, il code è l’OR bit a bit dei code dei blob fusi (così una fusione multicolore identifica quali colori hanno contribuito) e count riporta quanti blob di origine sono stati combinati.

margin ingrandisce o riduce i rettangoli di delimitazione prima del test di sovrapposizione. Con margin=2, i blob i cui rettangoli di delimitazione arrivano entro 2 pixel l’uno dall’altro si fondono comunque; con margin=-2, si fondono solo i blob i cui rettangoli di delimitazione si sovrappongono di almeno 2 pixel. La regolazione naturale: margine positivo per gestire blob che la soglia ha spezzato in parti adiacenti; margine negativo per mantenere separati oggetti distinti ravvicinati.

merge_cb viene eseguito su ogni coppia candidata prima che avvenga la fusione. Il callback riceve i due blob e restituisce True per consentire la fusione o False per impedirla. Questo è lo strumento giusto per i controlli incrociati sulle fusioni che la regola geometrica non coglie – ad esempio, rifiutare di fondere due blob i cui angoli di rotation differiscono di oltre una soglia, o rifiutare di fondere un piccolo blob in uno molto più grande se quello piccolo è solo granulosità.

5.25.5. Istogrammi di proiezione

x_hist_bins_max e y_hist_bins_max allegano istogrammi di proiezione opzionali a ogni blob. Un istogramma di proiezione è il conteggio dei pixel che superano la soglia lungo un asse: l’istogramma dell’asse X somma i pixel che superano la soglia per colonna all’interno del bounding box del blob, e l’istogramma dell’asse Y somma per riga. Entrambi hanno valore predefinito zero – gli istogrammi non vengono calcolati a meno che non venga fornito un max diverso da zero, poiché altrimenti aggiungerebbero lavoro a ogni rilevamento.

Quando vengono calcolati, gli istogrammi forniscono un segnale monodimensionale economico su cui un’applicazione può eseguire ulteriori analisi: rilevare la posizione di una striscia verticale all’interno del blob, trovare il punto di rottura di un bersaglio a due colori, contare quanti vuoti compaiono lungo l’asse lungo. Vengono popolati come le proprietà x_hist_bins e y_hist_bins su ogni Blob.

5.25.6. Ulteriori funzioni di supporto geometrico

Una manciata di ulteriori misure geometriche esistono come funzioni a livello di modulo che prendono un blob e restituiscono la misura richiesta:

  • image.get_solidity() restituisce la solidità del blob – i pixel divisi per l’area dell’inviluppo convesso. Una regione piena solida è vicina a 1.0; un blob con concavità (un ferro di cavallo, una mano con le dita aperte) scende ben al di sotto.

  • image.get_convexity() restituisce la convessità – il perimetro dell’inviluppo convesso diviso per il perimetro del blob. Un blob perfettamente convesso vale 1.0; blob frastagliati o intaccati hanno valori inferiori.

  • image.get_major_axis_line() e image.get_minor_axis_line() restituiscono oggetti Line lungo gli assi maggiore e minore del blob, derivati dal rettangolo ruotato di area minima.

  • image.get_enclosing_circle() restituisce un Circle che racchiude il blob – utile quando uno stadio a valle vuole un cerchio da disegnare o con cui confrontarsi.

  • image.get_enclosed_ellipse() restituisce la tupla di 5 elementi (cx, cy, rx, ry, rotation) per un’ellisse inscritta nel rettangolo di area minima del blob. I valori si inseriscono direttamente in draw_ellipse().

5.25.7. Apprendimento automatico di una soglia

Un rilevatore di blob è valido tanto quanto le soglie con cui viene eseguito, e il lavoro di trovare la soglia giusta per un colore bersaglio è un problema a sé. Due schemi comuni riducono questo lavoro.

Il primo è la selezione interattiva nell’IDE: catturare un frame, trascinare un rettangolo attorno a un esempio del colore bersaglio e lasciare che l”editor di soglia dell’IDE riporti i limiti LAB che rileva. Quei limiti finiscono nello script come soglie di find_blobs() e il rilevatore è pronto.

Il secondo è l’apprendimento automatico programmatico: una routine di calibrazione in esecuzione sulla camera cattura un frame, prende un istogramma di una zona nota in cui si trova il bersaglio (get_histogram() con roi=) e legge l’intervallo di valori della zona dall’istogramma con get_percentile(). Il 5° percentile imposta il limite inferiore di ogni canale e il 95° quello superiore, ignorando i pixel anomali sparsi a entrambe le estremità. Su un’immagine RGB565 una chiamata di percentile riporta tutti e tre i canali LAB contemporaneamente, quindi le due chiamate producono i sei numeri che find_blobs() si aspetta:

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)