5.30. Template matching¶
I rilevatori trattati finora rispondono a domande sul contenuto di un singolo frame: dove si trovano i blob, dove vanno le linee, cosa dice un codice stampato. Una diversa categoria di domande confronta un’immagine con un’altra. Questa regione del frame catturato assomiglia al patch di riferimento che ho memorizzato al momento della calibrazione? I metodi di matching rispondono a questa domanda.
L’analisi tonale e statistica ha introdotto get_similarity() per la domanda correlata – quanto sono simili nel complesso queste due immagini delle stesse dimensioni? – con SSIM come metrica sottostante. La restante domanda di matching è quella di localizzazione: non «quanto sono simili queste due immagini» ma «dove all’interno di questa immagine più grande compare quel patch più piccolo?» Lo strumento giusto per la domanda di localizzazione è il template matching.
5.30.1. La chiamata di base¶
find_template() cerca il primo punto in cui una piccola immagine template compare all’interno del frame catturato. L’implementazione usa la cross-correlazione normalizzata (NCC): il template scorre sul frame, il punteggio di corrispondenza per ogni posizione viene calcolato dalla correlazione tra i pixel del template e i pixel sottostanti del frame (normalizzata rispetto alle medie e varianze locali, in modo che le variazioni di guadagno non ingannino la corrispondenza), e viene restituita come bounding box la prima posizione il cui punteggio supera threshold:
template = image.Image("/sdcard/template.bmp", copy_to_fb=False)
template.to_grayscale()
match = img.find_template(template, threshold=0.7,
search=image.SEARCH_DS)
if match is not None:
img.draw_rectangle(match, color=(255, 0, 0))
Il metodo funziona solo su immagini in scala di grigi. Cattura in scala di grigi (la scelta naturale per qualsiasi camera senza sensore a colori), oppure converti sul posto tramite to_grayscale() prima della chiamata. Lo stesso vale per il template caricato dal disco: un template a colori viene convertito con lo stesso metodo, e il risultato è ciò che il matcher si aspetta.
threshold è un valore float da 0.0 a 1.0. Un valore di 1.0 richiede una corrispondenza perfetta pixel per pixel (cosa che non accade mai con immagini reali catturate), 0.0 accetta qualsiasi cosa, e i valori tra 0.6 e 0.8 coprono il caso comune in cui il template è stato catturato con un’illuminazione simile e la scena non è cambiata in modo drastico. Aumenta la soglia per sopprimere i falsi positivi; abbassala per accettare corrispondenze più rumorose al costo di un maggior numero di rilevamenti spuri.
5.30.2. Strategia di ricerca¶
search permette di scegliere tra due strategie. image.SEARCH_EX è la ricerca esaustiva: il template scorre attraverso ogni posizione a passi di step pixel nel frame e restituisce la prima corrispondenza sopra la soglia. image.SEARCH_DS è la ricerca a diamante: il matcher campiona prima in modo grossolano, poi affina intorno al punteggio migliore, il che è notevolmente più veloce ma può mancare una corrispondenza reale se il passaggio grossolano è capitato vicino a un massimo locale che batte quello globale. Per una pipeline in tempo reale in cui il template è ben definito e difficilmente confondibile, SEARCH_DS è il valore predefinito giusto; per una calibrazione one-shot in cui il costo di una mancata corrispondenza è più alto del costo di una scansione più lenta, SEARCH_EX è più sicuro.
step controlla il salto in pixel durante il passaggio esaustivo (la ricerca a diamante gestisce il proprio passo). Valori di step più grandi velocizzano la scansione a scapito della precisione sub-pixel. roi limita la ricerca a una regione del frame, restringendo sia ciò che il matcher considera sia la quantità di lavoro.
Il valore restituito è una tupla bounding box (x, y, w, h) che identifica la corrispondenza migliore, oppure None se nessuna posizione ha superato la soglia. La bounding box si inserisce direttamente in draw_rectangle() o crop() per la fase successiva di elaborazione.
5.30.3. La trappola della scala e della rotazione¶
L’insidia classica del template matching è la sensibilità a scala e rotazione. Il matcher confronta il template con il frame pixel per pixel; un template catturato a una certa distanza non corrisponde allo stesso oggetto catturato a una distanza diversa, e un template catturato frontalmente non corrisponde allo stesso oggetto visto da un’angolazione diversa. La soglia scende silenziosamente al di sotto del livello di corrispondenza anche quando l’oggetto è chiaramente visibile a un occhio umano, e il metodo restituisce None.
Esistono alcune soluzioni alternative per i casi semplici. L’applicazione può catturare più template a scale diverse ed eseguire find_template() per ciascuno in sequenza, accettando il primo che supera la soglia; il costo cresce con il numero di template. L’applicazione può pre-elaborare il frame con rotation_corr() o la trasformazione polare (Trasformazioni geometriche) per rimuovere la rotazione problematica prima che venga eseguita la corrispondenza; il template corrispondente deve comunque corrispondere alla geometria corretta.
Un idioma utile per le pipeline di ispezione QA abbina il template matcher al calcolatore di similarità introdotto dall’analisi tonale e statistica: find_template() individua la parte nel frame catturato e la bounding box restituita viene ritagliata e passata a get_similarity() rispetto al patch di riferimento. Il passo di template matching decide dove si trova la parte; il passo di punteggio di similarità decide se la parte è accettabile. I due passi vengono eseguiti a ogni frame, la soglia su mean è il criterio pass/fail, e la bounding box corrispondente ridisegnata nel frame è l’anteprima dell’IDE che l’operatore osserva.