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 centroide – x, 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 forma – pixels, 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 fusione – code, 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 angoli – corners, 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.
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.3. Filtrare la ricerca¶
Un frame catturato di solito contiene pixel che corrispondono alla soglia per motivi diversi dall’oggetto che interessa all’applicazione: riflessi speculari, oggetti di sfondo distanti, pixel di rumore dell’immagine che capitano nell’intervallo LAB. Gli argomenti keyword di find_blobs() sono la prima linea di difesa.
roi limita la ricerca a una regione del frame, come fa ogni altro metodo del modulo image. Un’applicazione che sa che l’oggetto può comparire solo nella metà inferiore del campo visivo passa roi=(0, h//2, w, h//2) e ignora tutto ciò che sta sopra; il tempo risparmiato si traduce in frame rate.
area_threshold e pixels_threshold filtrano entrambi i blob troppo piccoli per essere rilevanti. area_threshold scarta i blob il cui bounding box ha un’area inferiore a quel numero di pixel (utile per filtrare il rumore sparso); pixels_threshold scarta i blob che hanno meno di quel numero di pixel che superano la soglia (utile per filtrare blob grandi ma radi, come un motivo a puntini con soglia in cui solo uno o due pixel corrispondono qua e là). Entrambi hanno valore predefinito 10; portandoli a centinaia per un bersaglio in primo piano di qualche centimetro si elimina ogni granello di piccolo rumore.
x_stride e y_stride impostano il passo in pixel che lo scanner compie mentre cerca un blob da cui iniziare il tracciamento. Lo stride non è la risoluzione del tracciamento – il tracciamento segue sempre il bordo effettivo del blob al dettaglio del singolo pixel – ma controlla quanto rapidamente la scansione trova un pixel di partenza. Quando si sa che i blob sono grandi (un bersaglio colorato delle dimensioni di un pugno a una trentina di centimetri dalla camera, facilmente largo un centinaio di pixel), x_stride=4, y_stride=4 riduce il tempo di scansione di sedici volte senza perdita pratica nel rilevamento. Quando i blob sono piccoli (un beacon LED distante, largo pochi pixel), gli stride devono restare a 1 per evitare di scavalcarli del tutto. invert inverte il test di soglia: la corrispondenza diventa non-corrispondenza e la routine restituisce blob di pixel che non superano la soglia.
threshold_cb è un callback Python invocato su ogni blob dopo l’applicazione della soglia ma prima che venga costruita la lista finale dei risultati. Il callback riceve il blob e restituisce True per mantenerlo o False per scartarlo. Questo è il punto in cui applicare filtri arbitrari a livello Python su proprietà che gli argomenti keyword non espongono direttamente – una densità minima, uno specifico intervallo di rotazione, una combinazione personalizzata di bit di code dopo la fusione. Gli argomenti keyword sono filtri in codice nativo e girano velocemente; il callback gira in Python ed è più lento ma illimitato in ciò che può esprimere.
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 a1.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 vale1.0; blob frastagliati o intaccati hanno valori inferiori.image.get_major_axis_line()eimage.get_minor_axis_line()restituiscono oggettiLinelungo gli assi maggiore e minore del blob, derivati dal rettangolo ruotato di area minima.image.get_enclosing_circle()restituisce unCircleche 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 indraw_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)