5.13. Filtri lineari e di vicinato

Le operazioni di matematica sui pixel viste in precedenza nel capitolo combinavano due immagini punto per punto. I filtri svolgono un lavoro analogo in modo diverso: calcolano il valore di ogni pixel di uscita a partire da un piccolo vicinato di pixel di ingresso che circondano la posizione corrispondente. L’uscita in (x, y) è una qualche statistica – la media, la mediana, il valore più frequente – dei pixel di ingresso contenuti in un piccolo riquadro centrato su (x, y).

Quel piccolo cambio di prospettiva – passare da un pixel alla volta a una finestra di pixel alla volta – è ciò che fa funzionare un’intera famiglia di operazioni utili. Una semplice media su una piccola finestra leviga il rumore del sensore. La mediana sulla stessa finestra rimuove i puntini isolati senza ammorbidire i bordi quanto fa la media. Una media bilaterale si rifiuta di levigare attraverso i confini ad alto contrasto, preservando i bordi degli oggetti mentre ripulisce le texture al loro interno. Il vicinato è l’unità di lavoro; la scelta della statistica decide cosa fa il filtro.

5.13.1. La dimensione del kernel

Ogni filtro di vicinato accetta un parametro size che imposta il raggio della finestra in pixel. La finestra stessa è quadrata e copre (2 * size + 1) pixel per lato – quindi size=1 indica un vicinato 3 per 3, size=2 indica 5 per 5, size=3 indica 7 per 7, e così via.

Una piccola griglia di immagine con una sotto-griglia 3 per 3 evidenziata che rappresenta il vicinato del filtro. Una freccia mostra il vicinato che scorre di un pixel verso destra. Una seconda freccia lo mostra scorrere verso il basso alla riga successiva alla fine della riga. Il pixel di uscita per ogni posizione è disegnato sotto il vicinato, con una piccola nota che dice che l'uscita è una qualche statistica del vicinato di ingresso.

Il vicinato scorre lungo l’immagine un pixel alla volta, da in alto a sinistra a in basso a destra. Ogni pixel di uscita è il risultato dell’applicazione della statistica del filtro al vicinato di ingresso centrato su di esso.

Dimensioni maggiori significano vicinati più grandi, il che significa un filtraggio più levigato (o più aggressivo). Il costo cresce con l’area della finestra, quindi un filtro size=3 svolge circa nove volte il lavoro per pixel rispetto a un filtro size=1. Il valore predefinito pratico per la maggior parte del lavoro di pulizia è size=1 o size=2; ricorri a dimensioni maggiori solo quando i vicinati piccoli non sono sufficienti a sopprimere la caratteristica che l’applicazione cerca di sopprimere.

5.13.2. Il filtro media

mean() sostituisce ogni pixel con la media aritmetica del suo vicinato. Il risultato leviga la variazione da pixel a pixel sulla dimensione della finestra, il che lo rende il modo più economico per sopprimere i puntini di rumore del sensore: la variazione ad alta frequenza si media via, il contenuto a bassa frequenza sopravvive.

Il compromesso è che anche i bordi e altre caratteristiche nette vengono mediati. Un bordo luminoso largo un pixel prima del filtro diventa largo due o tre pixel dopo un filtro media size=1, con la luminosità che si attenua ai lati. Per la pura riduzione del rumore su un’immagine povera di texture (un muro pulito, l’interno di un marcatore colorato) il compromesso va bene. Per una scena affollata in cui i bordi contano, uno dei filtri seguenti è di solito più adatto.

img.mean(1)        # 3x3 box average -- fast, gentle smoothing
img.mean(2)        # 5x5 box average -- stronger, slower

5.13.3. Mediana, moda, punto medio

Gli altri tre filtri statistici di vicinato barattano la semplice media aritmetica con qualcosa di più robusto contro i valori anomali.

median() restituisce la mediana del vicinato – il valore che finisce al centro dell’elenco ordinato dei pixel della finestra. Un singolo pixel molto luminoso o molto scuro nella finestra non sposta la mediana; diventa semplicemente uno degli estremi scartati. L’effetto pratico è che il filtro mediana rimuove i puntini isolati e il rumore sale e pepe senza ammorbidire i bordi nel modo in cui fa mean. Il costo è un maggiore calcolo per pixel – ordinare una finestra è più lento che mediarla – e il risultato non è strettamente una media, il che a volte conta per i calcoli a valle.

Un parametro percentile (predefinito 0.5) sposta il valore scelto rispetto alla mediana stretta. percentile=0.0 restituisce il minimo del vicinato, percentile=1.0 il massimo; i valori intermedi scelgono proporzionalmente tra di essi nella finestra ordinata. Questo conferisce a median la capacità di enfatizzare le parti scure o luminose del vicinato senza perdere la robustezza ai valori anomali della statistica d’ordine.

mode() restituisce il valore più frequente nel vicinato. Utile quando il modello di rumore è «la maggior parte dei pixel è corretta, alcuni sono stati corrotti in varia misura», in cui la risposta giusta è il valore che compare più spesso – cosa che la mediana può mancare quando i valori corrotti si accumulano su un lato della finestra ordinata.

midpoint() restituisce una combinazione ponderata del minimo e del massimo del vicinato – bias=0.5 dà il punto medio tra di essi, bias=0.0 dà il minimo, bias=1.0 dà il massimo. Meno usato degli altri, ma vale la pena conoscerlo quando l’obiettivo è specificamente estrarre caratteristiche scure o luminose.

5.13.4. Bilaterale, la versione che preserva i bordi

bilateral() è il filtro di vicinato che più vale la pena comprendere bene. Produce l’effetto di levigatura di mean(), ma con un vincolo aggiuntivo: più un pixel del vicinato differisce dal pixel centrale, meno conta nella media. Il risultato leviga l’interno di ogni regione uniforme senza sbavare attraverso i bordi che le separano, che è esattamente ciò che la maggior parte delle applicazioni desidera davvero.

Due parametri controllano quanto aggressivamente il filtro penalizza i pixel:

  • color_sigma decide come la differenza di colore influisce sulla ponderazione. Valori più piccoli significano che il filtro è più severo nel penalizzare i pixel che differiscono dal centro.

  • space_sigma decide come la distanza spaziale influisce sulla ponderazione. Valori più piccoli danno più peso ai pixel vicini al centro.

I valori predefiniti (color_sigma=0.1, space_sigma=1.0) sono ragionevoli punti di partenza; la loro messa a punto è di solito una questione di eseguire il filtro su un frame di esempio e regolare finché i bordi non sono nitidi e gli interni puliti.

Il filtro bilaterale è più costoso di median() e significativamente più costoso di mean(), quindi vale la pena ricorrervi solo quando il comportamento che preserva i bordi è ciò di cui l’applicazione ha bisogno.

5.13.5. Sogliatura adattiva

I filtri media, mediana, moda e punto medio portano tutti la stessa coppia di argomenti per parola chiave che trasformano la loro uscita in una soglia binaria:

  • threshold=True commuta il filtro in modalità di sogliatura.

  • offset=N sposta il valore di taglio locale di N unità prima del confronto.

Il meccanismo si basa direttamente sul comportamento ordinario del filtro. Senza threshold=True, il filtro calcola la sua statistica sul vicinato e scrive quella statistica nel pixel di uscita. Con threshold=True, il filtro calcola la stessa statistica, poi confronta il pixel sorgente nella stessa posizione con la statistica più l’offset, e scrive il valore massimo del formato se la sorgente è maggiore, zero altrimenti.

Il risultato è un’immagine binaria il cui valore di taglio si muove con la luminosità locale lungo il frame. Le regioni luminose ottengono un valore di taglio alto, le regioni scure un valore di taglio basso, e un pixel di primo piano localmente più luminoso dei suoi vicini corrisponde sia che si trovi in una regione luminosa sia in una scura – che è esattamente il comportamento che una singola soglia globale non potrebbe produrre su un’immagine illuminata in modo non uniforme.

img.mean(3, threshold=True, offset=5)

Il parametro offset è il punto in cui l’applicazione controlla quanto è severo il test. Un piccolo offset positivo richiede che il pixel sorgente sia misurabilmente più luminoso dei suoi vicini prima di contare come corrispondenza, il che sopprime i falsi positivi del rumore del sensore a costo di perdere il primo piano debole. Un piccolo offset negativo cattura il primo piano debole a costo di lasciar passare un po” di rumore. La scelta dipende da cosa farà il resto della pipeline con l’uscita binaria.

Tre pannelli di immagine in fila. Il primo è un frame in scala di grigi di ingresso con un gradiente di luminosità e marcature di primo piano sparse a un'oscurità uniforme. Il secondo pannello mostra una soglia globale applicata ad esso: il primo piano è classificato correttamente sul lato luminoso, ma l'intero lato scuro risulta come primo piano perché la pagina e il primo piano cadono entrambi sotto il valore di taglio. Il terzo pannello mostra una soglia adattiva applicata allo stesso ingresso: il primo piano è classificato correttamente su tutto il frame.

Sotto un’illuminazione non uniforme, una singola soglia globale non può descrivere il primo piano in ogni posizione. Un filtro di vicinato eseguito con threshold=True produce un valore di taglio che si muove con la luminosità locale e classifica correttamente il primo piano su tutto il frame.

La famiglia di filtri esegue la soglia adattiva, quindi scegliere il filtro giusto conta: mean() per la soglia adattiva più economica, median() quando l’ingresso ha rumore sale e pepe che il filtro dovrebbe rifiutare prima di calcolare il valore di taglio locale.