Sei sulla pagina 1di 10

Corso di

Elaborazione ed Interpretazione delle


Immagini Digitali
A.A. 2014/2015

N2:
Video-Analysis: Circle Detection

Autore:

Valerio Colamatteo

Sommario:

Lesercitazione N 2 richiede lideazione, in ambiente di programmazione


C++/OpenCV, di un meccanismo di tracking video delle traiettorie di alcune
figure geometriche. richiesta inoltre la determinazione in tempo reale dei
relativi centroidi e la stampa a video di alcune informazioni specifiche. Linput
video di riferimento il flusso webcam.

Descrizione dellesercitazione
Questa seconda esercitazione, come immediatamente evidente dallintestazione, evidenzia
un netto scollamento teorico/didattico dalla precedente muovendo dalle tematiche di pi diretta e
naturale argomentazione del corso di insegnamento afferente. Onde abbracciare dunque spazi di pi
ampia operativit, la stessa si sofferma su tematiche di approfondimento collaterali per la materia in
esame ma che, stante lindubbia componente ludica di cui risultano intrinsecamente portatrici, si
dimostrano comunque tali da suscitare, nei pi, vivo apprezzamento e rappresentare cos fonte di
curiosit didattica 1.

Lesercitazione richiedeva, come suggerito dallintitolazione della stessa, di effettuare il tracking


video di oggetti circolari rappresentati, contestualmente ad altre figure geometriche, su una stampa
appositamente ideata e fornita dal docente come riferimento. La fonte video designata la webcam
del proprio PC.
Operativamente il problema si risolto come segue.
La prima cosa che si fatto stata quella di convertire il flusso delle immagini acquisite in
input dalla webcam da rgb a scala di grigio 2. Limmagine appena convertita stata poi binarizzata3
sfruttando lalgoritmo di Otsu, il quale determina il valore di soglia dellimmagine dallanalisi
distributiva dei livelli di grigio allinterno delle varie componenti di sfondo e di oggetto della stessa
immagine.
Dopodich si usata la funzione findContours() 4 per lindividuazione dei contorni
dellimmagine binarizzata.
Con lindividuazione degli stessi e la loro conseguente allocazione in una struttura apposita di tipo
vector 5 si chiude la parte iniziale dellesercitazione 6.

Di riflesso, dunque, stante la diversa architettura teorica di base, il taglio argomentativo caratterizzante la prima di
questo ciclo di relazioni dedicate agli approfondimenti didattici non trover, in questa sede, uguale accoglimento,
ritenendo pleonastico per le finalit della presente fornire gli approfondimenti teorici altrimenti dovuti. Ci detto, la
trattazione evolver ugualmente in maniera da potersi ritenere certamente non eccessivamente liquidatoria di alcuni
degli aspetti pi delicati per loggetto di analisi.
2

Prototipo della funzione utilizzata:

dstCn=0);

void cvtColor(InputArray src, OutputArray dst, int code, int

Prototipo della funzione utilizzata: double cvThreshold(const CvArr* src, CvArr* dst, double threshold,
double max_value, int threshold_type);

Prototipo della funzione utilizzata: void findContours(InputOutputArray image, OutputArrayOfArrays


contours, OutputArray hierarchy, int mode, int method, Point offset=Point());

Tale funzione opera solo su immagini binarie di input che abbiano le cui componenti connesse siano bianche su sfondo
nero, ci ha allora indotto, previa corretto utilizzo di tale funzione, la generazione preventiva del negativo della stessa
immagine.
5
Maggiori e pi puntali riferimenti, come al solito, sono rimandati alla sezione tre della relazione o direttamente al
codice sorgente in allegato.
6
In questa prima parte si deliberatamente deciso di non fornire ulteriori e pi profondi riferimenti a quanto fatto
poich tali primi passaggi sono in parte da ritenersi frutto sia di un comune convenire di idee e proposte in aula con il
docente, sia in parte frutto delle preziose linee di indirizzamento fornite in allegato al testo dellesercitazione. Gli aspetti
ritenuti invece core dellesercitazione sono quelli immediatamente seguenti, alla cui, si spera esaustiva, argomentazione
si dedicher tutta la parte rimanente di tale lavoro.

Oggetto di intensa attivit elaborativa stata, in questa fase, lideazione di un metodo efficace di
discernimento dei contorni delle diverse forme geometriche raffigurate sulla apposita stampa
campione.
Nella successiva sezione si analizzeranno invece i risultati effettivamente ottenuti.
Le specifiche dellesercitazione richiedevano esplicitamente solo lidentificazione della figura di
forma circolare. Una volta ottenuto il tracciamento della stessa, si cercato di trovare una
metodologia unica che permettesse di identificare correttamente anche le altre figure polinomiali
della stampa campione. In merito a ci la soluzione tentata stata quella di affidare
lidentificazione della figura al geometrica al numero di vertici da essa posseduta (le immagini sulla
stampa hanno tutte un diverso numero di vertici e questo un elemento di semplificazione a mio
parere 7). Per identificare il numero di vertici si fatto della funzione approxPolyDP() 8. Essa, in
realt, risulta implementata allinterno della libreria OpenCV con finalit differenti da quelle con cui
stata invece impiegata in tale lavoro. Essa sostanzialmente si occupa di approssimare i contorni di
una immagine a quelli di una figura polinomiale secondo un margine di tolleranza prefissabile. I
contorni approssimati vengono salvati in una struttura dati vector (approx) appositamente creata.
Allora, inserendo tale funzione in un ciclo for( ) e ciclandone lazione in base al numero di
contorni individuati, si riesce a determinare il numero di contorni di volta in volta approssimati
dalla funzione semplicemente ispezionando le dimensioni di approx (approx.size( )).
Successivamente si pu procedere allidentificazione del centroide e alla stampa delle informazioni
richieste dalla traccia, cercando comunque di mantenere sempre degli elementi di differenziazione
per la figura circolare. Tale discorso verr ripreso e ultimato nelle seguenti sezioni.

Ulteriore problema a questo punto stata la caratterizzazione del centroide dei vari poligoni. A tal
proposito si sfruttata la funzione (suggerita dal testo dellesercitazione) boundingRect() per
trovare il rettangolo che meglio circoscrivesse di volta in volta i vari poligoni. La stessa funzione
restituisce un oggetto di classe Rect. possibile accedere alle informazioni specifiche di tale
oggetto attraverso gli appositi metodi boundingRect(.).x e boundingRect(.).y. Con ci
vengono, rispettivamente restituite in uscita le coordinate x e y dellestremo superiore sinistro del
rettangolo. A questo punto, attraverso i metodi boundingRect(.).windth
e
boundingRect(.).height si ricavano anche le lunghezze delle due dimensioni del rettangolo. Le
stesse allora, se divise per 2 e sommate alle coordinate dellestremo superiore sinistro del
rettangolo, consentono di determinare univocamente le coordinate del punto centrale per ogni
poligono che venga circoscritto dal rettangolo di boundingRect(.).

In caso di pi poligoni con lo stesso numero di vertici si sarebbe dovuto probabilmente implementare un meccanismo
di valutazione degli angoli interni di ogni figura, aumentando cos non di poco la complessit analitica del tutto.
8
Prototipo della funzione utilizzata : void approxPolyDP ( InputArray curve, OutputArray approxCurve, double epsilon,
bool closed );

Descrizione dei risultati


Veniamo ora allanalisi dei risultati ottenuti.
Facendo partire il programma si intuisce innanzitutto come effettivamente la strategia preposta di
identificazione delle diverse forme poligonali abbia funzionato. Le figure vengono riconosciute in
maniera distinta come previsto dal programma. Ci che per appare ancor pi evidente come in
realt laccuratezza determinativa dei vari centroidi e conseguentemente la stampa delle
informazioni necessarie siano non correttamente distribuite sulla superfice video. Il punto in cui di
fatto il sistema ideato in parte fallisce a mio parere legato allerrore di approssimazione della
funzione approxPolyDP(). Si cercato, a tal proposito di legare il margine di errore al profilo
perimetrale di ogni specifico poligono e lo si fatto in questo modo:
-

Utilizzando una apposita funziona per la determinazione del perimetro di una curva chiusa
(quale sono considerabili in effetti i poligoni in questione). La funzione di specie la
seguente: double arcLength(InputArray curve, bool closed);

Il valore restituito da arcLength() stato poi ponderato per un fattore di riduzione <0. Il
prodotto dei due termini costituisce allora nellesercitazione il parametro 3 cercato.

Il parametro 3 rappresenta, di fatto, la massima distanza tra la curva originale e la sua


approssimazione. In particolare si scelto di applicare un margine di errore pari all0.1% della
lunghezza perimetrale di ogni poligono.
I risultati, ad ogni modo, non soddisfano le aspettative 9.

Vista la preannunciata alternanza di una esercitazione a carattere prettamente teorico e una a carattere pi
specificatamente volta allimplementazione di meccanismi di inseguimento di traiettorie video, certamente si ritorner
su tali problematiche, proponendo sperabilmente, a tempo debito, nuove pi efficienti soluzioni.

Listati dei programmi

Listato numero 1: funzione circleFinder()

cv::Mat eiid::p2::circleFinder(const cv::Mat& frame) throw (eiid::failure)


{
// create output image
cv::Mat out = frame;
cv::cvtColor(frame,out,CV_RGB2GRAY);

// converto l'immagine acquisita dalla webcam


in una a scala di grigio

cv::threshold(out, out, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);


// binarizzazione dell'immagine con algoritmo di OTSU
out=255-out;

// genero il negativo dellimmagine per poterla passare findContours()

// devo copiare l'immagine prima di passarla a findcountours


cv::Mat out_contours;
out.copyTo(out_contours);
cv::findContours(out_contours,countours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);
cv::Mat out2;
frame.copyTo(out2);
std::vector<cv::Point> contoursOUT;
std::vector<cv::Point> approx;
float t=0.05; double t2=(double) t;
// ciclo for() in cui applico approxPolyDP per ogni contorno
for (int i = 0; i < countours.size(); i++){
cv::approxPolyDP(countours[i], approx, arcLength(countours[i], true)*, true);
if (approx.size() == 3){
printf("TRIANgolo\n");
cv::drawContours(out2,countours,i,cv::Scalar(255,0,0),2);
char s5[20];
sprintf(s5," TRIANG.");
eiid::p2::Draw(out2,countours,i,s5);
if (approx.size() == 5){
printf("Pentagono\n");
cv::drawContours(out2,countours,i,cv::Scalar(255,0,0),2);

char s5[20];
sprintf(s5," PENT.");
eiid::p2::Draw(out2,countours,i,s5);
}
if (approx.size() == 4){
printf("QUADRATO\n");
cv::drawContours(out2,countours,i,cv::Scalar(255,0,0),2);
char s5[20];
sprintf(s5," QUADR.");
eiid::p2::Draw(out2,countours,i,s5);
}

if (approx.size() > 10){


printf("STELLA....\n");
cv::drawContours(out2,countours,i,cv::Scalar(255,0,0),2); // Triangles
char s5[20];
sprintf(s5," STELLA");
eiid::p2::Draw(out2,countours,i,s5);

else{
double area = cv::contourArea(countours[i]);
cv::Rect r = cv::boundingRect(countours[i]);
int radius = r.width / 2;

if (std::abs(1 - (area / (CV_PI * std::pow(radius, 2)))) <= t2){


printf("CERCHIO\n");
cv::drawContours(out2,countours,i,cv::Scalar(255,255,0),3);
char s5[20];
sprintf(s5," CERCHIO.");
eiid::p2::Draw(out2,countours,i,s5);
}
}
}

return out2;

Praticamente quello che faccio semplicemente, ad ogni passo del ciclo for( ), controllare il numero
di vertici approssimati. Ci mi consente di poter gestire individualmente i vari poligoni,
richiamando una ulteriore funzione, appositamente definita, che sar commentata nel prossimo
listato. Allinterno delle varie sezioni (if( )) di codice dedicate ai singoli poligoni, si disegnano
opportunamente i contorni (drawContours() 10) e viene poi definita una stringa di caratteri da passare
alla funzione draw() che sar poi visualizzata in real-time sullo schermo nei pressi del relativo
centroide. La procedura differisce leggermente solo per il cerchio, per il quale il controllo prima se
il raggio dei due poligoni (quello del contorno originale e quello approssimato) coincidono a meno
dellerrore 3. In particolare per il calcolo dellarea si usato lapposita funzione contourArea() 11.
Infine il listato della funzione Draw() e, con esso, si si conclude anche questa seconda relazione.

Listato numero 2: funzione Draw()


void eiid::p2::Draw(cv::Mat m,std::vector<std::vector<cv::Point>>countours,int i,char* s)
throw (eiid::failure){
double area = cv::contourArea(countours[i]);
int alt=(cv::boundingRect(countours[i]).height/2);
int larg=(cv::boundingRect(countours[i]).width/2);
int
int
int
int

pointX=cv::boundingRect(countours[i]).x;
pointY=cv::boundingRect(countours[i]).y;
sumX=alt+pointX;
sumY=alt+pointY;
cv::Point pt5;
pt5.x=sumX;
pt5.y=sumY;

if(std::strcmp(s," CERCHIO.")){
cv::circle(m,pt5,1,cv::Scalar(0,255,0),8);
sprintf(s+strlen(s),", area= %d",(int)area);
cv::putText(m,s, pt5, 2, 0.4, cv::Scalar(0,255,0), 1);
}
else {
cv::circle(m,pt5,1,cv::Scalar(255,0,0),8);
sprintf(s+strlen(s),", area= %d",(int)area);
cv::putText(m,s, pt5, 2, 0.4, cv::Scalar(255,0,0), 1);
}
}

Prototipo della funzione utilizzata : void drawContours(InputOutputArray image, InputArrayOfArrays


contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray
hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point());
10

11

Prototipo della funzione utilizzata : double contourArea( InputArray contour, bool oriented=false );

La funzione riceve in ingresso limmagine video su cui operare, il vettore dei contorni, lindice
relativo al contorno di interesse e un puntatore alla stringa da stampare a video.
Per prima cosa, allinterno di essa, mi determino le coordinate del centroide secondo le modalit gi
ispezionate nelle precedenti sezioni, e poi, a seconda della stringa ricevuta in ingresso, stampo le
informazioni richieste dallesercitazione (area della figura) in maniera particolareggiata (per colore)
unicamente se si tratta del cerchio. Tale funzione viene continuamente richiamata ad ogni avvenuta
individuazione di uno dei poligoni ricercati.