1986. A soli 23 anni, l'australiano John F. Canny pubblica uno degli articoli più importanti per la Computer Vision: A Computational Approach to Edge Detection. Per Edge Detection si intende il riconoscimento all'interno di un'immagine di punti in cui la luminosità cambia in maniera repentina. Ottimisticamente, punti di questo tipo definiscono i contorni degli oggetti all'interno dell'immagine. Ottimisticamente, perchè un cambiamento improvviso del valore di luminosità può essere generato anche da rumore o da una illuminazione non omogenea della scena.
L'algoritmo proposto da Canny si basa su tre criteri di ottimalità per il riconoscimento dei punti di edge, i quali possono essere così riassunti:
- gli edge principali in un'immagine devono essere riconosciuti;
- i bordi individuati devono approssimare al meglio possibili i reali contorni;
- ogni contorno deve deve essere individuato una e una volta sola.
La funzione matematica che esprime i tre criteri è molto simile alla derivata prima di una funzione gaussiana. Risulta scontato parlare di derivate prime in questo contesto: stiamo cercando punti dell'immagine in cui la funzione della luminosità cambia improvvisamente, che possono essere anche intesi come i punti dove la derivata prima della suddetta funzione è massima.
Un filtro basato unicamente sulla derivata prima tuttavia non è sufficiente da solo a raggiungere lo scopo poiché condizionato dalla presenza di rumore.
Per questo motivo, l'algoritmo iterativo presentato da Canny si compone di 4 passi:
1) Smoothing dell'immagine: un filtro gaussiano di smoothing viene applicato all'immagine per sopprimere gli effetti dati dal rumore.
2) Differenziazione: dall'immagine così ottenuta è calcolata la derivata prima della funzione luminosità (per esempio con una maschera di Sobel).
3) Soppressione dei non-massimi: trovata la frequenza del cambiamento di luminosità, i punti di edge devono essere localizzati in corrispondenza dei punti di massimo; questa operazione è performata eliminando i punti che non sono di massimo tramite un procedimento iterativo che confronta i valori della derivata prima tra pixel all'interno di un neighborhood.
4) Edge Thresholding: i valori dei punti di massimo trovati in precedenza sono confrontati con due valori di soglia, una alta ed una bassa; tutti i pixel di valore più basso della soglia minore sono scartati, mentre quelli con valore più alto della soglia superiore sono identificati come punti di edge. I pixel compresi fra i due valori sono etichettati come punti edge solo se adiacenti ad altri pixel di valore maggiore della soglia alta. Questo tipo di thresholding è conosciuto col nome di sogliatura adattiva tramite isteresi.
Grazie all'isteresi, il risultato ottenuto presenta molte meno linee spezzate rispetto ai metodi di sogliatura singola: può capitare che pixel non siano marcati come edge perchè il valore della loro derivata prima è di poco minore rispetto alla soglia, per cui anche se fanno parte dei bordi di un oggetto non appariranno nel risultato finale.
Sebbene siano passati 26 anni, questa procedura è tutt'oggi utilizzata per la ricerca di punti di edge all'interno di un'immagine. Nella libreria OpenCV l'algoritmo di Canny è implementato all'interno della funzione cvCanny().
void cvCanny(const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3);
image: puntatore all'immagine sulla quale si vuole applicare l'algoritmo;
edges: puntatore all'immagine che conterrà il risultato dell'applicazione dell'algortimo;
threshold1: valore della soglia inferiore;
threshold2: valore della soglia superiore;
aperture_size: parametro che serve per la differenziazione tramite maschera di Sobel.
Nel seguente breve codice di esempio sarà presentato come utilizzare la funzione cvCanny. I file header da includere sono cv.h e highgui.h. Alcuni pezzi sono stati già visti ne primi tutorial per OpenCV, ma li ripetiamo anche adesso.
int main(int argc, char** argv) { IplImage* src = cvLoadImage("image.jpg");
il puntatore src punterà all'immagine passata con il suo path alla funzione cvLoadImage().
IplImage* dst = cvCreateImage(cvGetSize(src), 8, 1);
dst invece punta all'immagine che rappresenta il risultato dell'applicazione dell'algoritmo di Canny. La funzione cvCreateImage() ha bisogno delle dimensioni dell'immagine, della profondità dell'immagine (8 bit in questo caso) e del numero di canali (1 poiché l'immagine deve necessariamente essere in scala di grigio).
cvCanny(src, dst, 50, 200, 3);
è applicato l'algoritmo di Canny.
cvNamedWindow("Source", 1); cvShowImage("Source", src); cvNamedWindow("After Canny", 1); cvShowImage("After Canny", dst);
le due funzione servono rispettivamente per creare la finestra di visualizzazione e per associare a questa l'immagine da visualizzare.
cvWaitKey(0); }
il programma si mette in attesa fino a che non viene premuto un tasto.
Segue il codice completo:
include "opencv/cv.h" include "opencv/highgui.h // ATTENZIONE: gli include potrebbero cambiare a seconda // della versione di OpenCV e del sistema operativo int main(int argc, char** argv) { IplImage* src = cvLoadImage("image.jpg"); IplImage* dst = cvCreateImage(cvGetSize(src), 8, 1); cvCanny(src, dst, 50, 200, 3);cvNamedWindow("Source", 1); cvShowImage("Source", src); cvNamedWindow("After Canny", 1); cvShowImage("After Canny", dst); cvWaitKey(0); }
Fonti:
- Appunti del corso di Analisi di Immagini e Video, Ingegneria Informatica, UniFi, 2011-12
- Linux PRO, N. 188, Rivista
- "A Computational Approach to Edge Detection", J. Canny, 1986, Articolo
