5.27. Trovare cerchi e rettangoli¶
Le linee e i segmenti coprono i bordi diritti nel frame catturato, ma molte caratteristiche del mondo reale che la camera cerca non sono diritte. Una moneta appoggiata su una scrivania è un cerchio. Un’etichetta stampata, un foglietto adesivo, la parte superiore di una scatola vista da una angolazione obliqua è un quadrilatero. Il modulo image espone un rilevatore dedicato per ciascuno: una ricerca di tipo Hough per i cerchi e una ricerca derivata da AprilTag per le forme a quattro lati.
Entrambi i metodi seguono lo stesso schema dei rilevatori di linee: threshold controlla quanti voti necessita un rilevamento, roi restringe la ricerca e gli oggetti restituiti portano con sé sia una posizione sia un valore di confidenza. Tuttavia gli spazi dei parametri e i valori predefiniti corretti differiscono abbastanza da meritare una trattazione esplicita.
5.27.1. Cerchi di Hough¶
find_circles() esegue la variante circolare della trasformata di Hough. Ogni pixel di bordo derivato dal pre-passaggio Sobel vota per ogni cerchio che potrebbe attraversarlo; i cerchi che raccolgono voti sufficienti vengono restituiti.
circles = img.find_circles(threshold=3500,
x_margin=10, y_margin=10, r_margin=10,
r_min=10, r_max=80, r_step=2)
for c in circles:
img.draw_circle((c.x, c.y, c.r), color=(255, 0, 0))
threshold è la somma minima delle magnitudini dei bordi Sobel lungo il cerchio candidato. I cerchi più grandi tracciano più pixel e quindi necessitano di soglie più alte per essere accettati; un valore che trova una moneta con raggio di 20 pixel scatterà anche sul rumore attorno a un bordo di 100 pixel, mentre un valore tarato sulla moneta grande mancherà quella piccola. Quando l’intervallo del raggio target è noto, la soglia corretta scala con la circonferenza: approssimativamente threshold = 50 * 2 * pi * r offre un punto di partenza ragionevole e il valore corretto deriva da un breve passaggio di taratura.
r_min, r_max e r_step impostano la ricerca del raggio. Senza limiti, il rilevatore cercherebbe ogni raggio da pochi pixel fino alla metà della larghezza dell’immagine, cosa che è sia lenta sia una ricetta per i falsi positivi. Impostare r_min e r_max in modo da racchiudere la dimensione target attesa con un margine generoso (ad esempio r_min=15, r_max=25 per una moneta che si sa essere di circa 20 pixel) riduce sostanzialmente il lavoro e migliora il rapporto segnale-rumore dei voti. r_step controlla la granularità della ricerca; passi più grandi sono più veloci e possono mancare un cerchio il cui raggio reale ricade tra due valori campionati. Il valore predefinito r_step=2 è un compromesso ragionevole.
x_margin, y_margin e r_margin controllano la fusione dei rilevamenti vicini, allo stesso modo in cui theta_margin e rho_margin lo fanno per il rilevamento delle linee. Un singolo cerchio fisico nell’immagine vota per un raggruppamento di cerchi candidati i cui centri e raggi concordano entro pochi pixel; i margini collassano ciascun raggruppamento al suo picco prima che venga costruita la lista dei risultati. Margini più grandi restituiscono rilevamenti meno numerosi ma più affidabili; margini più piccoli restituiscono più rilevamenti con possibili quasi-duplicati.
x_stride e y_stride regolano il passo della scansione di voto, allo stesso modo in cui fanno negli altri rilevatori. I valori predefiniti di 2 e 1 vanno bene per la maggior parte delle immagini; portarli entrambi a 4 è il classico compromesso di velocità per un’immagine che si sa contenere cerchi grandi.
Ogni Circle restituito porta con sé x, y, r (il centro e il raggio) e magnitude (il totale dei voti, utile come punteggio di confidenza per l’ordinamento o il filtraggio). Disegnare il rilevamento di nuovo nel frame è una singola chiamata: draw_circle() accetta la tripletta (x, y, r), disponibile come (c.x, c.y, c.r) direttamente dal risultato.
5.27.2. Rettangoli¶
find_rects() prende in prestito il rilevatore di quadrilateri dalla pipeline di AprilTag: la stessa routine che individua il quadrato nero attorno a un tag viene esposta autonomamente come rilevatore di rettangoli generico.
rects = img.find_rects(threshold=12000)
for r in rects:
img.draw_rectangle(r.rect, color=(0, 255, 0))
for corner in r.corners:
img.draw_circle((corner[0], corner[1], 4),
color=(0, 255, 0))
threshold è la somma minima delle magnitudini dei bordi attorno al perimetro del rettangolo. Un rettangolo nero su bianco stampato in un frame ben illuminato supera facilmente 10000; un rettangolo tenue su uno sfondo strutturato può dover scendere a 2000, scambiando i falsi positivi con la sensibilità. Come per il rilevatore di cerchi, il valore corretto deriva da un rapido passaggio di taratura con i target previsti in vista.
Il rilevatore è proiettivo: trova quadrilateri i cui lati sono diritti ma non necessariamente paralleli o allineati agli assi. Un’etichetta vista da un’angolazione obliqua appare come un trapezio nell’immagine, e il rilevatore di rettangoli la trova correttamente; un rettangolo allineato agli assi è semplicemente il caso degenere in cui i quattro angoli formano una scatola ad angoli retti. roi restringe la ricerca; il resto degli argomenti keyword assume i valori predefiniti dalla pipeline di AprilTag e raramente necessita di taratura.
Ogni Rect restituito porta con sé il bounding box allineato agli assi – x, y, w, h, oltre alla quadrupla rect che draw_rectangle() si aspetta – e i quattro angoli rilevati come corners. Il bounding box è ciò che l’applicazione usa per una posizione e una dimensione approssimative; gli angoli descrivono il quadrilatero proiettivo stesso. Quando la camera inquadra un target piatto da una angolazione e l’applicazione deve annullare l’effetto keystone – leggere il testo su un’etichetta, campionare il colore da una zona piatta – gli angoli alimentano direttamente rotation_corr() con il keyword corners= (vedi correzione di lente e prospettiva), e l’output è il rettangolo rettificato pronto per qualunque analisi venga successivamente.
Avvertimento
Poiché il rilevatore è tarato per ciò di cui la pipeline di AprilTag ha bisogno – quadrilateri con bordi forti e ad alto contrasto, come il contorno nero di un tag su carta bianca – non è un passaggio che trova ogni rettangolo. I rettangoli con contrasto debole, bordi strutturati o circostanti affollati possono rimanere del tutto non rilevati. Quanto bene funziona dipende dalla situazione: testalo presto sui target reali, prima di costruire una pipeline attorno a esso.
5.27.3. Quando il rilevatore sbaglia¶
I cerchi in particolare traggono beneficio da un pre-filtro sull’input. Un frame rumoroso produce molti pixel di bordo spuri che votano tutti, e lo spazio di Hough risultante presenta picchi ampi e poco definiti che il modulo di fusione fatica a separare. Un passaggio gaussian() o mean() prima di find_circles() attenua il rumore lasciando intatti i bordi reali; il rilevatore restituisce picchi più puliti in meno tempo.
Per i rettangoli, la modalità di errore comune è l’opposta: un basso contrasto tra il rettangolo e il suo sfondo significa che la somma delle magnitudini dei bordi non supera mai threshold. Un passaggio histeq() per ridistribuire l’intervallo di luminosità sull’intera estensione da 0 a 255 ripristina il contrasto di cui il rilevatore ha bisogno. (Il contrasto deve esistere da qualche parte nell’immagine; l’equalizzazione dell’istogramma può solo amplificare ciò che già c’è.)