Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

Sviluppo e analisi prestazionale di algoritmi di videoanalisi in tecnologie Open Source

Weitere Verwandte Inhalte

Ähnliche Bücher

Kostenlos mit einer 30-tägigen Testversion von Scribd

Alle anzeigen

Ähnliche Hörbücher

Kostenlos mit einer 30-tägigen Testversion von Scribd

Alle anzeigen

Sviluppo e analisi prestazionale di algoritmi di videoanalisi in tecnologie Open Source

  1. 1. UNIVERSITA’ DEGLI STUDI DI FERRARA FACOLTA' DI INGEGNERIA CORSO DI LAUREA IN INGEGNERIA DELL'INFORMAZIONE COMUNICAZIONI MULTIMEDIALI Sviluppo e analisi prestazionale di algoritmi di videoanalisi in tecnologie Open Source Tesi di Laurea di: Andrea Montanari Relatore: Prof. Ing. Gianluca Mazzini Correlatore: Prof. Ing. Chiara Taddia Anno Accademico: 2010/2011
  2. 2. Indice generale 1 Elementi di videoanalisi....................................................................................................1 1.1 Che cos'è la videoanalisi............................................................................................................1 1.1.1 Un po' di storia...................................................................................................................3 1.2 Riconoscimento targhe...............................................................................................................4 2 Videoanalisi Open Source.................................................................................................5 2.1 OpenCV.....................................................................................................................................5 2.2 Installare OpenCV.....................................................................................................................6 3 Motion Detect..................................................................................................................... 8 3.1 L'algoritmo di Lucas-Kanade....................................................................................................8 3.2 Implementazione e miglioramenti...........................................................................................12 3.3 Risultati e considerazioni.........................................................................................................14 4 Estrazione della targa dall'immagine.............................................................................15 4.1 Haar-training classificatori di Viola e Jones............................................................................16 4.2 Implementazione......................................................................................................................19 4.3 Risultati e statistiche................................................................................................................24 5 OCR.................................................................................................................................. 25 5.1 Pre-processamento immagine..................................................................................................25 5.2 Risultati con OCR Open Source..............................................................................................27 5.3 Risultati con OCR Shareware..................................................................................................28 6 Conteggio persone in un'area.........................................................................................29 6.1 HOG Processing......................................................................................................................29 6.1.1 Computazione dei gradienti.............................................................................................30 6.1.2 Costruzione degli istogrammi..........................................................................................31 6.1.3 Normalizzazione dei blocchi............................................................................................32 6.1.4 Costruzione dei descrittori (Detection Window)..............................................................33 6.1.5 Classificatore SVM..........................................................................................................34 6.1.6 Miglioramento prestazioni...............................................................................................34 6.2 Implementazione......................................................................................................................35 6.2.1 Background substracting..................................................................................................36 6.2.2 Definizione dell'area........................................................................................................39 6.3 Considerazioni e risultati.........................................................................................................45 7 Conclusioni....................................................................................................................... 46
  3. 3. 1 Elementi di videoanalisi Questa tesi nasce con lo scopo di implementare e analizzare alcuni algoritmi di videoanalisi sfruttando esclusivamente tecnologie open source, nello specifico il rilevamento targhe e il conteggio delle persone in un'area, operanti su filmati registrati con codifica H.264. Tutti i video su cui sono stati testati gli algoritmi sono stati forniti dalla ditta Lepida Spa che possiede un centro gestione video con telecamere fisse che puntano su tratti di strade o piazze sparse nella regione. Per la scrittura di questa tesi ho utilizzato video provenienti solo da alcune telecamere, perché alcune di queste riprendevano la strada da un angolazione o con una luminosità tale da rendere l'identificazione della targa difficile anche ad occhio nudo. 1.1 Che cos'è la videoanalisi La videoanalisi (o visione artificiale) è l'insieme dei processi che mirano a creare un modello approssimato del mondo reale (3D) partendo da immagini bidimensionali (2D). Lo scopo principale della visione artificiale è quello di riprodurre la vista umana. Vedere è inteso non solo come l'acquisizione di una fotografia bidimensionale di un'area ma soprattutto come l'interpretazione del contenuto di quell'area. L'informazione è intesa in questo caso come qualcosa che implica una decisione automatica. Un problema classico nella videoanalisi è quello di determinare se l'immagine contiene o no determinati oggetti. Nella letteratura troviamo differenti varietà del problema: • Recognition: uno o più oggetti specificati o memorizzati possono essere ricondotti a classi generiche usualmente insieme alla loro posizione 2D o 3D nella scena. • Identification: viene individuata un'istanza specifica di una classe. Es. identificazione di un volto, impronta digitale o veicolo specifico. • Detection: l'immagine è scandita fino all'individuazione di una condizione specifica. Es. individuazione di possibili cellule anormali o tessuti nelle immagini mediche. • Optical character recognition (OCR): individuazione di caratteri in immagini di testo o scritti a mano per poi codificarli in un formato di più facile gestione (Es. ASCII). • Lettura di codici in 2d come codici a barre. Un sistema di visione artificiale è costituito dall'integrazione di componenti ottiche, elettroniche e meccaniche che permettono di acquisire, registrare ed elaborare immagini. Il risultato dell'elaborazione è il riconoscimento di determinate caratteristiche dell'immagine per varie finalità di controllo, classificazione o selezione. 1
  4. 4. L'organizzazione di un sistema di videoanalisi dipende fortemente dall'applicazione in cui viene utilizzato. Alcuni sistemi sono semplici applicazioni stand-alone (a sé stanti) che risolvono specifici problemi di misurazione o individuazione, mentre altri costituiscono un sotto-sistema in un progetto più grande, che per esempio contiene anche altri sottosistemi per il controllo di attuatori meccanici. Ci sono tuttavia delle funzioni tipiche presenti nella maggior parte dei sistemi di visone artificiale: Acquisizione immagine Un'immagine digitale è prodotta da vari tipi di sensori come sensori sensibili alla luce, sensori, tomografici, radar, sensori ad ultrasuoni. In base al tipo di sensore l'immagine può assumere la rappresentazione di un'ordinaria immagine 2D, un volume in 3D o una sequenza di immagini. Preelaborazione Prima che una immagine venga trattata da un metodo della videoanalisi è solitamente necessario elaborare i dati in modo da verificare che questi soddisfino specifiche regole necessarie al metodo. Per esempio: • Ricampionare per assicurarsi che le informazioni non siano ridondanti o insufficienti. • Togliere eventuale rumore che introduce informazioni false. • Modificare il contrasto per assicurarsi che le informazioni rilevanti vengano individuate (Thresholding). Estrazione proprietà Le caratteristiche dell'immagine sono estratte dai dati dell'immagine[1] ad esempio: • Linee e bordi • Punti di interesse locale (Es: angoli) Caratteristiche più complesse possono essere il riconoscimento di movimento, forme e texture. Individuazione/segmentazione In questo punto del processo viene presa la decisione di quali aree saranno rilevanti per l'ulteriore elaborazione. Es: • Selezione di un set di punti di interesse • Segmentazione dell'immagine in più regioni che contengono un oggetto di interesse Elaborazione di alto livello A questo punto l'input è tipicamente un piccolo insieme di punti o regione di immagine che si presuppone debba contenere un oggetto specifico. Gli obiettivi del restante processo sono: • Verificare che il modello contenuto nell'input possieda le specifiche del modello base o della classe • Stimare i parametri specifici tipo la posizione o la dimensione • Classificare oggetti in più categorie 2
  5. 5. 1.1.1 Un po' di storia Sebbene esistono precedenti lavori, gli studi nel settore si sono potuti specializzare solo dopo il 1970, grazie all'aumento delle prestazioni dei computer che hanno potuto elaborare grandi quantità di informazioni come le immagini. Dobbiamo aspettare gli anni '80 per vedere le prime vere e proprie applicazioni pratiche di questa disciplina, caratterizzate spesso da uno scopo puramente dimostrativo. Negli anni '90 vediamo comparire i primi frame-grabber standard da inserire su PC e i sistemi di visione acquistano maggiore funzionalità e robustezza abbandonando l'aspetto tipicamente sperimentale del decennio precedente, soprattutto in campo industriale si notano notevoli alti e bassi di questa disciplina caratterizzati da alcune soluzioni funzionali costellati di parecchi insuccessi[2]. Ad oggi il campo della videoanalisi può essere descritto come vario ed immaturo perché non esiste una formulazione standard di come i problemi di visione artificiale vadano risolti. Esistono invece un'abbondanza di metodi atti a risolvere compiti ben definiti della videoanalisi, dove le procedure sono spesso dipendenti dal contesto e raramente possono essere estese ad uno spettro più ampio di applicazioni. Molti di questi metodi sono ancora a livello di ricerca base, ma molti altri ancora hanno trovato spazio nella produzione commerciale dove fanno parte di grandi sistemi che risolvono problemi complessi. Nelle applicazioni più pratiche i computer sono pre-addestrati per risolvere un particolare compito, tuttavia attualmente stanno diventando sempre più comuni i metodi basati sull'apprendimento. Un grosso aumento dell'utilizzo della videoanalisi negli ultimi anni è stato a causa dell'avanzamento tecnologico di strumenti di cattura come videocamere e fotocamere, che forniscono immagini e video di qualità sempre maggiore, e dell'aumento della potenza di calcolo dei processori essenziale per alcuni procedimenti che hanno un'elevata complessità. Al giorno d'oggi in rete sono presenti molti esempi di funzioni base come ad esempio l'individuazione di contorni (edge detection) o di moto (motion detect) perlopiù scritti in C++ utilizzando le librerie open source OpenCV, di cui parleremo in seguito, molto utili da impiegare come punto di partenza per un proprio progetto. 3
  6. 6. 1.2 Riconoscimento targhe La lettura delle targhe automobilistiche è diventata un'esigenza irrinunciabile in numerose applicazioni di videosorveglianza. Dal controllo degli accessi nelle aziende alla sorveglianza dei distributori di carburante, l'identificazione inequivocabile della targa dei veicoli è in grado di aggiungere un valore inestimabile alla videoregistrazione di sicurezza. Rendere questo procedimento funzionante in tutti i contesti è però molto complicato perché ci sono diversi fattori che incidono sulla lettura della targa come la luminosità circostante o l'inclinazione della targa. Infatti come detto in precedenza la soluzione investigata e adattata in questa tesi funziona solo su alcune delle telecamere messe a disposizione e con una buona luminosità, per esempio di mattina. Il procedimento seguito è il seguente: Per questo progetto tutta la parte di programmazione è stata svolta sotto sistema sistema operativo Linux distribuzione Ubuntu 10.04. 4
  7. 7. 2 Videoanalisi Open Source Per la realizzazione di questo progetto sono state utilizzate librerie Open Source cioè pezzi di codice i cui autori hanno permesso il libero studio e l'apporto di modifiche da parte di programmatori indipendenti. Questo è possibile mediante l'applicazione della licenza d'uso Open Source BSD che consente la redistribuzione del software anche in forma proprietaria, purché venga riconosciuto il merito all'autore. 2.1 OpenCV Una libreria molto potente orientata alla videoanalisi presente sul web è OpenCV (Open Source Computer Vision). OpenCV è stata originariamente sviluppata da Intel ma attualmente è sotto licenza open source BSD e supportata totalmente dal Willow Garage un laboratorio di ricerca di automazione situato in California che si occupa di hardware e software open source per applicazioni robotiche. Il sito di referimento è http://opencv.willowgarage.com/wiki/. La libreria è stata originariamente scritta in C [3] e questa interfaccia rende OpenCV portatile per alcune piattaforme specifiche come i DSP. Wrapper per linguaggi come C#, Python[4], Ruby e Java[5] sono stati sviluppati per incoraggiare l'adozione di un pubblico più ampio. Tuttavia, sin dalla versione 2.0, OpenCV comprende sia la sua interfaccia tradizionale C, ma anche una nuova interfaccia C++ [6], che cerca di ridurre il numero di righe di codice necessarie per implementare le funzionalità di visione. La maggior parte dei nuovi algoritmi o miglioramenti di OpenCV sono ora sviluppati con l'interfaccia C++. Purtroppo, è molto più difficile fornire wrapper per altri linguagggi dal C++, al contrario di quello che accadeva col linguaggio C, quindi i wrapper in genere mancano delle nuove funzionalità contenute in OpenCV 2.0. Essendo una libreria multi-piattaforma OpenCV supporta molti sistemi operativi come ad esempio Windows, Linux, Mac OS, Android[7], iOS[8] e Maemo[9]. 5
  8. 8. 2.2 Installare OpenCV Le librerie OpenCV possono sono state installate su un server avente le seguenti caratteristiche: CPU AMD CPU AM3 Phenom II X6 1100T BI.E. Box Scheda Madre Gigabyte Scheda Madre SoAM3 GA-89FXA- UD5 (890FX/ATX) RAM DD3 Viper II (Sector 5) PC1600 2x4GB cas 7.9.7.21 Hard Disk Seagate HDD 2000GB ST2000DL003 SATA 3.0 Scheda Video Asus VGA PCI-E EAH5450 SIlent/DI/512MD2 Altre Caratteristiche Scheda PCI-E 2 SATA2 Per installarle si può procedere seguendo due diversi metodi. Se non si ha bisogno delle funzioni avanzate contenute nel modulo opencv2, come ad esempio tutte le procedure per il riconoscimento personalizzato di oggetti, esistono pacchetti precompilati per il sistema operativo Ubuntu installabili tramite il classico comando apt-get install aggiungendo prima un repository specifico al sistema. Ecco la lista di istruzioni da eseguire nella shell: $ sudo add-apt-repository ppa:gijzelaar/opencv2 $ sudo apt-get update $ sudo apt-get install opencv Questo progetto però utilizza anche le funzioni avanzate per cui è necessario procedere con l'installazione manuale. Per eseguire la compilazione è indispensabile l'uso di Cmake per generare il Makefile. Vediamo i passi per portare a termine correttamente l'installazione: 1. Scaricare il sorgente da http://sourceforge.net/projects/opencvlibrary 2. Creare una cartella (per esempio nella home) con $ mkdir /home/username/CMAKEdir 3. Scompattare i sorgenti di OpenCV con $ tar jxf OpenCV-2.3.1a.tar.bz2 4. Installare Cmake tramite $ sudo apt-get install cmake 5. Intallare pkg-config sudo $ apt-get install pkg-config 6. Entrare nella directory creata precedentemente $ cd /home/username/CMAKEdir/ 7. $ cmake /home/username/OpenCV-2.3.1 8. $ make 9. $ sudo make install 10. $ export LD_LIBRARY_PATH=/usr/local/lib 6
  9. 9. L'ultimo comando serve per settare la variabile di sistema LD_LIBRARY_PATH al valore definito sopra. Si è riscontrato però che per le chiamate da broswer web tale variabile non rimane impostata, per fare in modo che rimanga al valore voluto stabilmente bisogna eseguire le seguenti istruzioni: 1. $ cd /etc/ld.so.conf.d 2. nano new_file.conf creo una file miavariabile.conf che contiene la riga LD_LIBRARY_PATH=/usr/local/lib 3. $ sudo ldconfig -v Una volta installate le librerie bisogna capire come compilare le applicazioni scritte da noi che le utilizzano. Il comando generale per la compilazione di qualsiasi applicativo è: g++ -ggdb `pkg-config opencv --cflags --libs` main.cpp -o main Se il sorgente contiene operazioni di cattura video (ad esempio le istruzioni cvCaptureFromAVI, cvCaptureFromFile o cvCaptureFromCAM) che sono procedure facenti parte del primo modulo OpenCV il comando visto precedentemente non è corretto in quanto la compilazione va a buon fine ma le funzioni sopra citate prima restituiscono sempre valore NULL anche se collegate ad un video esistente. Bisogna quindi usare questo nuovo comando: g++ -g -I/usr/local/include/opencv -I/usr/include/opencv -I/usr/local/include/opencv2 -L/usr/lib /usr/lib/libcvaux.so /usr/lib/libcv.so /usr/lib/libcxcore.so -lhighgui -lopencv_objdetect main.cpp -o main 7
  10. 10. 3 Motion Detect Come prima cosa bisogna riconoscere il passaggio di una vettura e catturare uno o più frame in quel momento, è necessario implementare quindi un rilevatore di movimento. Il moto in una sequenza di frame è descritto dal concetto di Optical Flow (Flusso Ottico). Tipicamente il moto è rappresentato come un vettore che si origina da un pixel ad esempio in una sequenza di frame. Lo scopo dell'optical flow è quello di assegnare ad ogni pixel appartenente al frame corrente un vettore che punta verso la posizione dello stesso pixel in un frame di riferimento successivo. Grazie a questi vettori posso capire come l'oggetto si muove rispetto alla telecamera. 3.1 L'algoritmo di Lucas-Kanade Esistono vari metodi per determinare questo flusso ottico, per questo progetto è stato scelto l'algoritmo di Lucas-Kanade. Il metodo di Lucas-Kanade[10] presuppone che lo spostamento del contenuto dell'immagine tra due istanti vicini è piccolo e approsimativamente costante in un intorno del punto p in esame. Con questa ipotesi possiamo assumere l'equazione del flusso ottico costante per tutti i punti all'interno di una finstra centrata a p. Cioè il vettore del flusso ottico locale (Vx,Vy) (in termini di velocità) deve soddisfare: dove q1, q2,..., qn sono i pixel dentro la finestra e Ix(qi), Iy(qi), It(qi) sono le derivate parziali dell'immagine I rispetto alle coordinate x,y e al tempo t valutate nel punto qi e al tempo attuale. 8
  11. 11. Queste equazioni possono essere scritte in forma matriciale Av = b, dove: Il sistema ha più equazioni che incognite e quindi di solito è sovradeterminato. Il sistema ottiene una soluzione applicando l'algorimto di Least Mean Square (LMS). Vale a dire risolvere il sistema 2x2: Dove AT è la matrice trasposta di A. Ora posso calcolare il vettore di moto: Con le somme che vanno da i ad n. La soluzione con l'algoritmo di Least Mean Square sopra dà la stessa importanza a tutti gli n pixel qi della finestra. In pratica però è meglio dare più peso ai pixel più vicini al pixel centrale p. Allora si utilizza la versione ponderata dell'algolirtmo LMS con la seguente equazione: Dove W è una matrice diagonale [n x n] contenente i pesi Wii = wi da asseganre ai pixel qi nell'equazione: Di solito i pesi wi sono il risultato di una funzione gaussiana della distanza tra qi e p. 9
  12. 12. L’algoritmo di optical flow, nella versione a piramide di Lucas- Kanade, è già implementato in OpenCV dalla funzione cvCalcOpticalFlowPyrLK definita nell'header <cv.h>. Il suo prototipo è il seguente: void cvCalcOpticalFlowPyrLK( const CvArr* imgA, const CvArr* imgB, CvArr* pyrA, CvArr* pyrB, CvPoint2D32f* featuresA, CvPoint2D32f* featuresB, int count, CvSize winSize, int level, char* status, float* error, CvTermCriteria criteria , int flags ); • imgA: frame corrente al tempo t • imgB: frame successivo al tempo t+dt • pyrA: buffer piramidale per il primo frame. Se il puntatore è diverso da NULL, il buffer deve avere una capienza sufficiente per contenere la piramide in tutti i suoi livelli; la size ( imgSize.width +8)* imgSize.height /3 bytes è sufficiente • pyrB: simile a pyrA ma per il secondo frame • featuresA: vettore di punti per i quali devo trovare il flusso • featuresB: vettore di punti dove sono state calcolate le nuove posizioni dei punti di input • count: numero di punti trovati. • WinSize: Size della finestra di ricerca per ogni livello di piramide. • level: massimo numero di livelli di piramide, se è 0 ci sarà solo un livello (piramidi non usate) se è 1 ci saranno 2 livelli e così via. • status: ogni componenete dell'array è settato a 1 se per il corrispondente elemento è stato calcolato il nuovo punto nel frame successivo, 0 altrimenti. • error: vettore di elementi double che contiene la differenza tra i percorsi dei punti originali e nuovi. • criteria : specifica a quale livello di piramide dovrei fermarmi una volta trovato il nuovo punto • flags: ci sono vari flag in cui posso specificare se pre-calcolare la piramide per il primo frame prima della chiamata. I frame li catturiamo direttamente dal video volta per volta, l'unica cosa che rimane da trovare sono i punti (featuresA) su cui agire. Un metodo è quello di trovare gli angoli più determinanti utilizzando la funzione cvGoodFeatureToTrack: 10
  13. 13. void cvGoodFeaturesToTrack( IplImage* image, IplImage* eigImage, IplImage* tempImage, CvPoint2D32f* corners, int* cornerCount, double qualityLevel, double minDistance ); • image: immagine a singolo canale a 8bit o floating point 32-bit • eigImage: immagine temporanea a 32-bit della stessa dimensione di image • TempImage: un'altra immagine temporanea della stessa dimensione e formato di eigImage • corners: parametro di output sono i corner trovati. • cornerCount: numero dei corner trovati. • QualityLevel: specifico la qualità minima per i corner torvati • minDistance: specifica la distanza minima che possono avere 2 corner (è usata la distanza Euclidiana. Una volta ottenuti gli angoli posso raffinarli attraverso la funzione cvFindCornerSubPix void cvFindCornerSubPix( IplImage* I, CvPoint2D32f* corners, int count, CvSize win, CvSize zeroZone, CvTermCriteria criteria ); • I: immagine di input. • corners: sono le coordinate iniziale che varranno poi cambiate se ce n'è bisogno. • count: numero degli elementi. • Win: ci va la dimensione dimezzata della search windows usata precedentemente. • ZeroZone: dimensione dimezzata nella zona morta in mezzo alla regione di ricerca. Se è settata a (-1,-1) non c'è nessuna regione. • Criteria: serve per definire l'accuratezza con la quale si affilano i corner per fermarsi quando si ottiene una certa soglia. Esempio di immagine ottenuta applicando l'algoritmo di Lucas-Kanade. In rosso disegnato il vettore di moto trovato 11
  14. 14. 3.2 Implementazione e miglioramenti Ora siamo pronti per dare in pasto i frame all'algoritmo di Lucas Kanade il quale ci fornirà i punti di interesse cioè quelli che si sono spostati. Per ogni punto trovato calcolo quanto si è spostato rispetto al frame precedente in modo da ottenere il modulo del suo vettore di moto. Visto che voglio rilevare movimenti di autovetture un primo scarto è stato quello di prendere in considerazione solo movimenti più forti, quindi con il modulo sopra una certa soglia. Come detto in precedenza per questo progetto avevo a disposizione un set di telecamere ma solo alcune di queste si prestavano al rilevamento targhe a causa dell'inclinazione con la quale la targa veniva inquadrata. Un altro perfezionamento è stato quello di suddividere la schermata in 15 sotto-quadranti e individuare quale di questi è il più adatto al nostro scopo cioè dove la targa è più visibile. Questo ulteriore miglioramento ha permesso di risparmiare risorse in quanto il rilevamento del movimento è solo su 1/15 di frame e non su tutta la schermata. Nella figura viene mostrato come lo schermo viene diviso in sotto-quadranti Il quadrante viene selezionato manualmente dopo la visione di un video quindi è un parametro statico. Ricapitolando per ogni frame controllo nel quadrante di interesse se c'è stato movimento rispetto a quello precedente, se così è stato allora verifico che si tratto di un movimento di una certa importanza attraverso il modulo del vettore di moto. Se l'immagine soddisfa tutte le condizioni la memorizzo su disco fisso, con il nome della sua posizione temporale dentro al video in millisecondi. Per eseguire questa ultima operazione ho usato la funzione cvGetCaptureProperty, che definito un certo stream di cattura (nel nostro caso il video in analisi) ci restituisce una serie di proprietà tra le quali anche la posizione temporale del frame in millisecondi in modo da avere una sorta di time- stamp. 12
  15. 15. Miglioramenti: Dopo alcuni test dell'algoritmo su diversi video sono emersi alcuni difetti riguardanti per lo più il numero di immagini salvate. Al passaggio di automobile venivano scattate circa 7-8 foto, eccessive visto che in almeno 2-3 di queste compariva la targa che è il nostro obbiettivo principale. Questa ridondanza è eliminabile in parte andando a controllare il time-stamp dell'ultima immagine salvata. Praticamente l'immagine viene salvata solo se sono passati un certo numero di millisecondi dall'ultimo salvataggio; questo parametro è possibile modificarlo per ogni istanza del programma ed è interpretabile come la sensibilità di movimento. Anche questo è un parametro statico per ogni video, perché varia a seconda della location in quanto un macchina può andare più veloce o meno a seconda se la telecamera si trovi su un rettilineo o una curva. Aumentando questo parametro la sensibilità diminuisce quindi vengono scattate meno immagini, adattandosi ad una vettura che si muove più lentamente; viceversa diminuendolo la sensibilità aumenta. Un altra ottimizzazione dell'algoritmo è data dalla realizzazione di una ricerca più profonda: quando rilevo del movimento nel quadrante selezionato eseguo la procedura anche nei quadranti vicini a patto che esistano. Questa funzione è stata implementata perché in alcuni video non esiste proprio un quadrante ottimale quindi per catturare più targhe può essere utile questa opzione. Questa funzionalità è meglio riservarla per video dove la cattura con i singoli quadranti classici è scadente perché allunga di molto i tempi di elaborazione di tutto il processo. 13
  16. 16. 3.3 Risultati e considerazioni Come detto in precedenza non tutte le foto scattate contengono una targa, bisogna settare i parametri sensibilità e quadrante per ottenere un buon compromesso tra immagini scattate e immagini contenenti una targa. Dopo varie prove sul campo ho notato che il miglior valore da assegnare al parametro sensibilità era 160, cioè il sistema aspetta minimo 160 millisecondi prima di scattare un'altra foto. In questo modo si ottengono circa 2 o 3 foto per ogni macchina che passa e nella maggior parte delle volte almeno una contiene la targa. Ci sono però da aggiungere altri falsi positivi come ad esempio macchine dell'altra corsia che invadono il quadrante selezionato, in questo caso vengono scattate altre foto che naturalmente devono essere scartate. Un sistema intelligente di filtraggio diventa necessario vista la grande quantità di immagini da eliminare. Andando a vedere i dati statistici raccolti su 50 video ho notato che in media in un video passano circa 20,72 macchine e vengono scattate 64,94 foto il che ci da una media di 3,22 foto scattate per macchina. Bisogna tener conto però che viene scattata una foto per qualsiasi movimento, anche se non è il passaggio di automobile. Mostro come esempio una telecamera che punta su una delle tratte più trafficate. In questo filmato da 10 minuti sono state scattate 236 foto e analizzando manualmente le immagini ottenute 44 sono positive. Alcune immagini scattate direttamente dalla telecamera Notare il nome delle immagini che corrispondono al millisecondo nel video in cui quel frame è stato preso. Visto che ad ogni video corrispondono 10 minuti di un certa giornata si può facilmente risalire all'ora e la data esatta in cui la macchina è passata. 14
  17. 17. 4 Estrazione della targa dall'immagine Una volta ottenute le immagini dal blocco del flusso ottico devo capire in quali di queste è presente una targa o meno. Per far questo c'è bisogno di un processo di learning chiamato haartraining. OpenCV mette a disposizione funzioni e procedure per “addestrare” il proprio programma a riconoscere un oggetto contenuto in un'immagine qualsiasi. Il riconoscimento di uno o più oggetti noti all’interno di un’immagine (Object Detection) è un compito difficile. Mentre per noi è facile ad esempio eseguire una “segmentazione semantica” della scena (valutare cosa accade, cosa è di nostro interesse, ecc..) e di uno o più oggetti in particolare (notare se è formato da più parti, che relazione ha con il mondo, cosa ci ricorda, ecc..), un meccanismo di ricerca automatico soffre di molte limitazioni. Per esempio, possono esservi diversi fattori che contribuiscono a rendere l’oggetto irriconoscibile, e spesso si tratta di uno di questi: 1. - luminosità variabile; 2. - rumore 3. - roto-traslazioni 2D e rotazioni 3D; 4. - “gap” (parti del bordo non distinguibili); 5. - occlusioni (parti dell’oggetto non visibili). L’approccio comunemente usato consiste nella creazione di un modello dell’oggetto in questione, estraendo alcune features, “caratteristiche” peculiari che lo distinguono da altre cose. Alcuni esempi di proprietà fondamentali di un oggetto sono le seguenti: 1. - Simmetria 2. - Colore 3. - Ombre 4. - Angoli e Bordi Si noti che le prime tre possono essere fallaci: la simmetria è disturbata dalle trasformazioni geometriche viste sopra, il colore è spesso determinato dalla scena in maniera preponderante, così come le ombre. Altri elementi invece offrono una maggiore affidabilità. Il training, ovvero l’addestramento necessario per creare il modello, si avvale di esempi positivi, contenenti l’oggetto, ed eventualmente di esempi negativi (sfondi o altro). Dai primi si estraggono le caratteristiche scelte, si tramutano nei parametri del modello e se durante la fase di classificazione si riscontra un falso positivo, il modello è aggiustato. Illustreremo ora un metodo per la creazione di un modello ed il riconoscimento di un oggetto che si basa sulle cosiddette features di Haar [11][12], delle quali verrà illustrato l’utilizzo a titolo di esempio di metodi per il riconoscimento di oggetti. 15
  18. 18. 4.1 Haar-training classificatori di Viola e Jones Le Basi di Haar sono la prima forma di Wavelet (funzioni matematiche usate per trasformare un’immagine nel dominio della frequenza, allo scopo di comprimela o analizzarla successivamente ) proposta dall’omonimo matematico. Usare delle features di Haar vuol dire effettuare un calcolo simile a quello per i coefficienti delle wavelet, usando un loro sottoinsieme per classificare e definire la “forma” di un oggetto. Le features di Haar comunemente usate sono riportate nella figura sotto: il valore di una feature è la differenza tra la somma del livello di grigio dei pixel nella zona nera, e la somma del livello di grigio nella zona bianca. Queste somme si calcolano facilmente se si rappresenta l’immagine in maniera integrale: dove ii(x,y) è l’immagine integrale, e i(x,y) quella originale. Questo rende i calcoli molto meno laboriosi che non un confronto pixel a pixel. Le features di Haar comunemente usate. Per il training, anzitutto vengono collezionate alcune migliaia di esempi positivi e negativi, scalati ad una dimensione fissa, e convertiti in scala di grigi, che andranno a formare il data set. Su ognuna di esse si calcolano i valori delle feature, che costituiranno gli input di alcuni classificatori di base, detti “weak classifiers”, il cui numero è variabile e personalizzabile. Questi weak classifier sono definiti ognuno come 16
  19. 19. dove fi è una feature, x è la finestra di ricerca (variabile), θ è una soglia, e pj indica la direzione della disuguaglianza. Per quanto riguarda il riconoscimento, i passi eseguiti dal metodo Viola-Jones sono illustrati nella figura sottostante i valori dei classificatori vengono computati su una finestra che si sposta all’interno dell’immagine, e si valutano i risultati di una cascata di weak classifiers. Il voto pesato dato ad ogni classificatore, serve per crearne uno totale, come ad esempio: Uno dei procedimenti più noti è quello chiamato AdaBoost, che sta per “AdaptiveBoost” e consiste nel ripetere iterativamente la clusterizzazione del weak classifier, dando un peso maggiore ai falsi positivi presenti nel cluster, e un peso minore (negativo) a tutti gli altri. Alla fine la combinazione lineare dei classificatori darà la clusterizzazione cercata. Ogni iterazione corrisponde a quello che nella guida chiameremo stage. I passi del metodo Viola-Jones 17
  20. 20. Il metodo AdaBoost. Dal punto di vista dell’analisi computazionale, sebbene il metodo in esame sia nettamente migliore di un confronto pixel a pixel, è comunque decisamente oneroso. Supponendo immagini di dimensione 320x240, con finestre 24x24 ed una velocità di 15 frame al secondo, si hanno oltre 500.000 finestre totali nelle quali computare le features. Moltiplicando il tempo necessario ad una classificazione completa per il numero di immagini nel training set, si raggiungono facilmente ordini di grandezza di giorni, su macchine anche molto potenti. Non si hanno più problemi di colore o luminosità ci si basa sulla forma dell’oggetto. Tuttavia il numero di falsi positivi rischia di rappresentare un grosso disturbo al tracciamento. La loro presenza dipende anche, ma non solo, dalla scelta delle immagini nel training set: per evitare un numero eccessivo di falsi positivi dati dallo sfondo, questo va inserito tra gli esempi negativi, e le altre immagini devono comunque fornire una statistica sufficiente. Resta però la possibilità che qualcosa intorno all’oggetto finisca per essere riconosciuto come l’oggetto stesso. 18
  21. 21. 4.2 Implementazione Lo scopo di questo procedimento è creare un file XML contenente le caratteristiche dell'oggetto che volgiamo riconoscere, nel nostro caso una targa. Questo file verrà poi passato ad opportune funzioni messe a disposizione di OpenCV che produrranno come output una serie di rettangoli identificati da coordinate, larghezza e lunghezza che indicano dove si trova l'oggetto dentro l'immagine. Come prima cosa dobbiamo ricercare immagini dette “positive” cioè contenenti l'oggetto che vogliamo riconoscere nel nostro caso una targa. Possiamo scegliere due diverse strade sul formato di queste immagini: o l'oggetto in questione è l'unica cosa che appare nell'immagine oppure l'oggetto è contenuto nell'intera immagine. Se prendiamo come esempio una targa, o prendiamo un'immagine contenente solo quest'ultima o con l'intera macchina e il paesaggio dietro. Per eseguire un buon training servono alcune migliaia di immagini positive (5000 – 7000). Fatto questo dobbiamo avere anche alcune migliaia di immagini “negative” cioè non contenenti l'oggetto. Trovare queste è meno laborioso: ci sono database di immagini pubblici sul web fatti appositamente, per il mio progetto ho utilizzato questo: http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2008/VOCtrainval_14-Jul-2008.tar Successivamente dovremo creare un “file di raccolta” che essenzialmente è un file di testo che servirà alle funzioni che useremo contenente il percorso di ogni immagine. Questo file deve avere un formato specifico che cambia a seconda di come abbiamo scelto le immagini positive[13]: 1)Se abbiamo immagini contenenti solo la targa deve avere questo formato: [filename] [filename] [filename] ... che si ottiene facilmente se abbiamo riposto tutte le immagini dentro la stessa cartella col comando di shell find [image dir] -name '*.[image ext]' > [description file] Come vantaggio questo procedimento ha la velocità con cui si crea il file di raccolta ma bisogna tagliare a mano tutte le immagini una ad una. 2)Nel secondo caso abbiamo immagini con anche lo sfondo il formato del file di raccolta cambia: [filename] [# of objects] [[x y width height] [... 2nd object ...] [filename] [# of objects] [[x y width height] [... 2nd object ...] [filename] [# of objects] [[x y width height] [... 2nd object ...] Stavolta è più veloce ottenere immagini positive in quanto possiamo fare un video all'oggetto in questione e utilizzare i frame, però non c'è un modo per ottenere il file di raccolta velocemente anzi si deve specificare per ogni immagine quante targhe ci sono, le coordinate del loro punto in alto a sinistra, la loro larghezza e la loro altezza. Anche questo risulta un procedimento molto laborioso. Per iniziare il processo di training bisogna uniformare le varie immagini positive ad una stessa larghezza e lunghezza e memorizzarle in unico file come anteprime. 19
  22. 22. OpenCV mette a disposizione un programma chiamato createsamples che viene installato durante la compilazione di openCV e svolge funzioni differenti utili per il training a seconda dei parametri con cui viene invocato. Sostanzialmente questo programma viene usato per creare i campioni con cui verrà effettuato il processo di training ma può essere utile anche per visualizzare tali campioni o creare immagini di test. Vediamo ora tutti i parametri che può avere il programma createsamples[13]: Usage: ./createsamples [-info <description_file_name>] [-img <image_file_name>] [-vec <vec_file_name>] [-bg <background_file_name>] [-num <number_of_samples = 1000>] [-bgcolor <background_color = 0>] [-inv] [-randinv] [-bgthresh <background_color_threshold = 80>] [-maxidev <max_intensity_deviation = 40>] [-maxxangle <max_x_rotation_angle = 1.100000>] [-maxyangle <max_y_rotation_angle = 1.100000>] [-maxzangle <max_z_rotation_angle = 0.500000>] [-show [<scale = 4.000000>]] [-w <sample_width = 24>] [-h <sample_height = 24>] Tutti i campioni positivi costruiti verranno salvati in un file con estensione .vec dopo essere stati ridimensionati a seconda dei parametri w e h. Questo file è formato da un header contenente il numero di immagini positive, larghezza e lunghezza dei campioni con tutte le anteprime di seguito. Visto che la targa per sua natura ha una forma rettangolare ho scelto w=35 e h=15. Funzione 1: Creazione di campioni da una sola immagine Come da titolo questa prima funzione permette di creare campioni da una singola immagine applicando distorsioni. Questa funzione è lanciata quando i parametri img, bg e vec sono specificati. -img è l'immagine scelta come positiva -bg è il file di raccolta delle immagini negative -vec e il nome del file (con estensione vec) che verrà creato contenente i campioni generati $ createsamples -img face.png -num 10 -bg negatives.dat -vec samples.vec -maxxangle 0.6 -maxyangle 0 -maxzangle 0.3 -maxidev 100 -bgcolor 0 -bgthresh 0 -w 20 -h 20 Per il mio progetto questa funzione si è dimostrata molto comoda in quanto da una sola immagine applicando distorsioni geometriche e di luminosità si possono creare più campioni. Ho ritagliato a mano circa 140 targhe dai frame dei video e attraverso questa applicazione ho generato 50 campioni diversi per ogni immagine in modo da avere circa 7000 campioni positivi, numero richiesto per un buon training. 20
  23. 23. Per rendere il procedimento ancora più veloce ho creato questo semplice script che fa quest'ultima operazione in automatico: #!/bin/sh for i in *.png; do echo "processo $i" opencv_createsamples -img $i -num 50 -bg /home/andrea/Scrivania/haartraining/negativi.dat -vec $i.vec -maxxangle 0.2 -maxyangle 0.2 -maxzangle 0.2 -maxidev 100 -bgcolor 0 -bgthresh 0 -w 35 -h 15; done Ora però ho un file vec per ogni immagine positiva quindi bisogna lanciare l'utility mergevec che prende come input un file di raccolta vec (che si ottiene facilmente visto che l'estensione è sempre vec) e in output ci restituisce il file vec totale. $ mergevec <collection_file_of_vecs> <output_vec_file_name> Funzione 2: Creazione dei campioni da più immagini Stavolta la creazione del file vec avviene attraverso una moltitudine di campioni quindi va creato un file di raccolta adatto con indicato dove si trova l'oggetto dentro l'immagine. Nel caso speciale in cui si abbiano già alcune migliaia di immagine ritagliate allora c'è un metodo semplice per creare un file di raccolta ad hoc in quanto l'oggetto si trova sempre nelle coordinate (0,0): find <dir> -name '*.<ext>' -exec identify -format '%i 1 0 0 %w %h' {} ; > <description_file> Questa funzione viene invocata quando sono specificati i parametri info, che rappresenta il file di raccolta, e vec che rappresenta il file dove riporre i risultati. Esempio di chiamata: $ createsamples -info samples.dat -vec samples.vec -w 20 -h 20 Funzione 3: Creazione immagini di test Le immagini di test non sono altro che foto identificate come negative con incollate sopra immagini positive in posizione e inclinazione casuale. La creazione di queste immagini può esser molto utile per sperimentare la resa del nostro training atraverso un altro programma fornito da OpenCV che si chiama performance. Esempio di chiamata: $ createsamples -img face.png -num 10 -bg negatives.dat -info test.dat -maxxangle 0.6 -maxyangle 0 -maxzangle 0.3 -maxidev 100 -bgcolor 0 -bgthresh 0 il parametro specificato dopo “num” indica il numero di immagini negative da utilizzare. 21
  24. 24. Funzione 4: Mostrare il contenuto del file vec Quest'ultima funzione ci mostra il contenuto del nostro file vec generato e si invoca quando solo il parametro vec viene specificato. $ createsamples -vec samples.vec -w 20 -h 20 22
  25. 25. Training Ora siamo pronti per usare l'utility che fa partire il training vero e proprio. Usage: ./haartraining -data <dir_name> -vec <vec_file_name> -bg <background_file_name> [-npos <number_of_positive_samples = 2000>] [-nneg <number_of_negative_samples = 2000>] [-nstages <number_of_stages = 14>] [-nsplits <number_of_splits = 1>] [-mem <memory_in_MB = 200>] [-sym (default)] [-nonsym] [-minhitrate <min_hit_rate = 0.995000>] [-maxfalsealarm <max_false_alarm_rate = 0.500000>] [-weighttrimming <weight_trimming = 0.950000>] [-eqw] [-mode <BASIC (default) | CORE | ALL>] [-w <sample_width = 24>] [-h <sample_height = 24>] [-bt <DAB | RAB | LB | GAB (default)>] [-err <misclass (default) | gini | entropy>] [-maxtreesplits <max_number_of_splits_in_tree_cascade = 0>] [-minpos <min_number_of_positive_samples_per_cluster = 500>] Il parametro nonsym si usa quando l'oggetto in questione non ha una simmetria da destra a sinistra come nel nostro caso, il “-mode ALL” consente si usare tutte le feature ed infine il parametro mem indica quanta memoria RAM dedicare al sistema[12]. Due parametri molto importanti sono minhitrate e maxfalsealarm. Se per esempio abbiamo 1000 campioni positivi e vuoi che il sistemi ne rilevi almeno 900 allora desideri una hitrate = 900/1000 = 0.9. Di solito si imposta hitrate=0.999. Se invece abbiamo 1000 immagini negative e quini non vogliamo che il nostro sistema le rilevi come oggetti in questione, ma il sistema ne rileva comunque 500 allora false alarm = 500/1000 = 0.5. Di solito si imposta maxfalsealarm=0.5. É consigliabile usare 20 stage visto che se si raggiunge un livello di falsi allarmi superiore a quello desiderato il training viene fermato anche con qualche stage di anticipo. Con un maxfalsealarm=0,5 minhitrate=0,999 e 20 stage mi aspetto un falsealarm rate=0,5^20=9.6e- 07 ed una hitrate=0.999^20=0.98. Il tempo di durata del training su una macchina con le caratteristiche indicate in precedenza è di circa 3 giorni. Una volta finito l'output è una serie di cartelle (una per ogni stage) contenute nella directory specificata in precedenza dopo il parametro data. Per creare il file XML che servirà poi al nostro applicativo OpenCV per procedere col detecting usiamo questo comando: convert_cascade –size=”<w>×<h>″ <dir data name> <XML file name> 23
  26. 26. 4.3 Risultati e statistiche Dopo aver eseguito il training ho sviluppato un'applicazione che rileva e salva le targhe da un'immagine che prende come input questi parametri: 1. percorso del file XML 2. file di raccolta delle immagini (vedi procedimento haar-training) 3. percorso di salvataggio delle immagini contenenti la targa Elaborando tutte le immagini provenienti dal blocco che si occupa della rilevazione del moto produco immagini contenenti solo il rettangolo con la targa ove è presente salvandole sempre con la posizione temporale in cui appaiono nel video in millisecondi. Dopo il processo di training molti dei falsi positivi vengono scartati anche se qualcuno rimane. Se per una macchina vengono scattate 2 o più foto che includono la targa il sistema non è in grado di riconoscere se sono uguali o meno per cui si può aggiungere questo tipo di ridondanza. I dati statistici raccolti su 50 video dicono che ci sono in media 24,14 targhe rilevate per ogni video con 5,86 falsi positivi ogni volta. I falsi positivi sono quindi il 24% delle targhe trovate. Da notare che in media in un video sono di più le targhe rilevate rispetto alle automobili passate, questo proprio a causa dei falsi positivi che vanno ad aggiungersi al totale. Infatti togliendo la media dei falsi positivi al numero di targhe medio rilevato otteniamo il numero di targhe effettive rilevate che è circa 18,28 stavolta minore del numero di macchine che passano che ricordo essere 20,72. Tornando all'esempio della telecamere del capitolo precedente dove su 236 foto 44 includevano una targa, dopo questo blocco ho ottenuto un totale di 57 immagini con 11 falsi positivi. Le immagini vengono salvate dentro una cartella chiamata “targhe” nella directory specificata come terzo parametro quando si chiama il programma. Esempio di targhe rilevate 24
  27. 27. 5 OCR Come ultimo passo bisogna passare le immagini ottenute ad un sistema OCR (Optical Character Recognintion) ma prima di fare questo necessitano di un pre-processamento. 5.1 Pre-processamento immagine Per rendere più facile il compito all'OCR prima di tutto converto l'immagine in scala di grigi e applico un algoritmo di Sharpening cioè di “affilatura” dei bordi per renderla più definita, dopodiché binarizzo il tutto con un funzione di Adaptive Threshold messa a disposizione da OpenCV. Il concetto di base del Threshold è quello di confrontare il livello di grigio di ogni pixel dell'immagine e di settarlo a 0 (nero) o a 255 (bianco) a seconda se è minore o maggiore di una certa soglia. L'Adaptive Threshold è une tecnica più raffinata in cui è il valore stesso della soglia a variare. In questo caso, la soglia è calcolata in base a una media pesata relativa alle informazioni di una regione quadrata costruita attorno al pixel considerato a meno di una costante arbitraria. Immagine convertita in scala di grigi e dopo lo Sharpening 25
  28. 28. Immagine binarizzata Sempre attraverso funzioni fornite da OpenCV trovo i contorni e i rettangoli che li delimitano. Visto che la distanza tra la targa e la telecamera è sempre approssimativamente uguale, e un carattere sarà sempre più largo che alto, posso ricavare i rettangoli relativi ai caratteri della targa. Contorni dei caratteri delimitati da un rettangolo Una volta effettuata la segmentazione della targa faccio processare all'OCR ogni carattere uno ad uno. 26
  29. 29. 5.2 Risultati con OCR Open Source Come software OCR ho scelto Tesseract di Google in quanto è uno dei più affidabili in rete inoltre è anch'esso un progetto open source[14]. Tutti i rettangoli corrispondenti trovati vengono salvati come una singola immagine e processati da Tessercat attraverso il comando tesseract -psm 10 <nome input> <nome output> Il parametro “psm” indica che l'immagine verrà processata come se al suo interno fosse contenuto un singolo carattere. Il risultato è il seguente: D 0 l S 9 L R Il programma ha sbagliato 3 caratteri su 7. Facendo prove su altre 50 targhe si nota che la media degli errori è di 2,44 caratteri che corrisponde al 34,86% della targa. In più in alcune ci sono pezzi della targa, come le bande blu laterali, che a causa di luminosità e dimensione vengono scambiate per caratteri generando così un altro tipo di errore. 27
  30. 30. 5.3 Risultati con OCR Shareware Come software OCR shareware ho scelto ABBYY FineReader 9.0 Pro. Il pre-processamento per ogni targa è identico a quello descritto nella sezione precedente e anche il campione di 50 targhe è sempre lo stesso per avere un confronto valido. ABBYY FineReader in esecuzione su due caratteri: quello di sinitra viene riconosciuto al contrario di quello di destra Ricavando le statistiche dai risultati ottenuti si scopre che stavolta l'OCR commette in media 3,36 errori per targa che sono circa il 48%. 28
  31. 31. 6 Conteggio persone in un'area L'algoritmo opera su dei video di 10 minuti ripresi da una telecamera fissa su una piazza e si occupa di contare le persone presenti nel frame e contare quante sono all'interno di un'area definita dall'utente e quali no. Il componente chiave per questa applicazione è messo a disposizione dalle librerie ed è chiamato HOG (Histogram of Oriented Gradient). A grandi linee è un componente dedito al riconoscimento di pedoni o figure umane in un'immagine. 6.1 HOG Processing Il metodo si basa sulla valutazione di istogrammi calcolati sulla base dell'orientazione dei gradienti dell'immagine di input. L'idea è che i margini e le forme di oggetti possono essere ben caratterizzati dall'intensità locale dei singoli gradienti[16]. La computazione dei singoli istogrammi è ottenuta dividendo l'immagine in una griglia di celle e per ognuna di esse viene elaborato un istogramma relativo ai gradienti dei singoli pixel. Successivamente le celle sono raggruppate in regioni denominate blocchi. Inoltre, per svincolare la risposta dalle condizioni di luminosità dell'immagine, può essere utile normalizzare i singoli blocchi. Dalle informazioni ricavate dai singoli blocchi si ottiene poi un descrittore che verrà utilizzato per la detection. Le precedenti operazioni vengono eseguite su finestre di dimensione finita che analizzano l'immagine a più scale. Per ogni finestra si ottiene quindi un descrittore che viene poi passato ad un classificatore SVM lineare che fornisce la predizione sulla presenza meno di un pedone. Essendo le procedure di costruzione dei singoli descrittori indipendenti tra loro, è possibile eseguirle contemporaneamente con una gestione multithread. In questa modo si ottiene un notevole miglioramento delle prestazioni. La robustezza dell'algoritmo utilizzato dall'SVM e l'accurata scansione dell'immagine a più scale producono spesso un'addensamento di finestre di detection per ogni singolo pedone, quindi è necessaria un'operazione di fusione (mean-shift) che porti all'individuazione di un'unica finestra finale. Nel settore del object recognition, l' uso di istogrammi di gradienti orientati è molto popolare [17]. Usando questo metodo al fine di ottenere i descrittori di una singola immagine, vengono effettuati i seguenti passi di elaborazione: 1. Computazione dei gradienti dell'immagine; 2. Costruzione degli istogrammi; 3. Normalizzazione dei blocchi; 4. Scansione dell'immagine e costruzione dei descrittori; 5. Classificazione dei descrittori tramite SVM lineare; Le fasi del metodo HOG 29
  32. 32. 6.1.1 Computazione dei gradienti Il gradiente di un'immagine può essere semplicemente ottenuto filtrandola con due filtri monodimensionali: un filtro verticale e un filtro orizzontale. Questa procedura mette in evidenza le regioni dell'immagine in cui si ha una maggiore variazione di luminosità. Esse si trovano generalmente in prossimità dei margini di un oggetto e possono essere quindi utilizzati per metter in evidenza la sagoma di una persona. I filtri utilizzati sono i seguenti: 1. Orizzontale: Dx=(-1, 0, 1) 2. Verticale: Dy=(-1, 0, 1)T Quindi data un'immagine I, calcoliamo la derivata rispetto all'asse x e all'asse y usando le operazioni di convoluzione: Ix= I * Dx Iy= I * Dy Per ogni pixel otteniamo quindi il vettore gradiente: La figura mostra un esempio di applicazione del filtro per l'ottenimento del gradiente dell'immagine. La figura a) mostra l'immagine originale, la figura b) mostra il risultato dell'applicazione del filtro orizzontale e la figura c) mostra il risultato dell'applicazione del filtro verticale. I gradienti possono essere considerati con segno o senza segno. Quest'ultimo caso è giustificato dal fatto che la direzione del contrasto non ha importanza. In altre parole, noi avremmo il solito risultato analizzando un oggetto bianco su sfondo nero o viceversa un oggetto nero su sfondo bianco. Il filtro utilizzato per l'ottenimento dei gradienti è uno dei più semplice ma anche uno dei più efficaci; esistono tuttavia altre maschere più complesse che possono essere utilizzate per ottenere il gradiente dell'immagine da analizzare: uno di questi è il filtro di Sobel. Nel caso di immagini a colori viene inoltre effettuata un'operazione preliminare di conversione a scala di grigi; questo per evitare di dover considerare un contributo di intensità diverso per ogni piano di colore (RGB). 30
  33. 33. 6.1.2 Costruzione degli istogrammi La seconda fase della procedure consiste nella costruzione degli istogrammi sulla base dei gradienti calcolati al passo precedente. Innanzi tutto l'immagine viene suddivisa in celle. Un cella è definita come una regione dello spazio che assume una certa forma e dimensione. Le celle possono essere rettangolari o circolari e i canali di ogni istogramma sono distribuiti su 0°-180° (gradienti senza segno). Per ogni cella viene costruito un istogramma accumulando all'interno dei canali i voti dei singoli gradienti. Se per esempio vogliamo costruire istogrammi distribuiti su 0°-180° con un numero di canali pari a 4 la votazione per la costruzione dell'istogramma avviene nel seguente modo: ● Tutti i gradienti della cella con angolo compreso nell'intervallo [0°-45°) forniscono il loro voto per il primo canale; ● Tutti i gradienti della cella con angolo compreso nell'intervallo [45°-90°) forniscono il loro voto per il secondo canale; ● Tutti i gradienti della cella con angolo compreso nell'intervallo [90°-135°) forniscono il loro voto per il terzo canale; ● Tutti i gradienti della cella con angolo compreso nell'intervallo [135°-180°) forniscono il loro voto per il quarto canale; La procedura di votazione è una funzione che, per ogni gradiente, assegna un determinato peso. Tale peso può essere unitario o dipendere dal magnitudo stesso. Valori spesso utilizzati sono il magnitudo, la sua radice quadrata o il suo quadrato. In questo modo ogni istogramma è calcolato tenendo conto dell'importanza di un gradiente in un determinato punto. Questo è giustificato dal fatto che un gradiente attorno a un margine di un oggetto in genere è più significativo di un punto in una regione uniforme dell'immagine. Ci aspettiamo quindi che più canali ci sono più dettagliati saranno gli istogrammi. Quando tutti gli istogrammi sono stati creati, possiamo costruire il descrittore dell'immagine concatenando tutti gli istogrammi in un singolo vettore. In ogni caso, a causa di eventuali variazioni di luminosità nell'immagine è opportuno normalizzare le celle. La procedura di normalizzazione è descritta nel capitolo successivo. La figura mostra un esempio di possibile istogramma calcolato per 4 canali (sinistra), 8 canali (centro) e 16 canali (destra). 31
  34. 34. 6.1.3 Normalizzazione dei blocchi Come accennato nella sezione precedente prima di creare i descrittori è necessaria una fase di normalizzazione, questa a causa delle variazioni di luminosità che ci possono essere in un'immagine. La normalizzazione degli istogrammi è fatta a partire da gruppi di celle detti blocchi. Per ogni blocco viene calcolato un fattore di normalizzazione e tutti gli istogrammi nel blocco sono normalizzati in base a tale fattore. Il descrittore finale è quindi rappresentato dal vettore delle componenti di tutte le celle dopo che sono state normalizzate raggruppandole per blocchi. Se i blocchi hanno una forma quadrata o rettangolare e le celle la medesima forma allora si parla di R-HOG. Gli R-HOG sono composti da [n x n] celle di [m x m] pixel, ognuna contenente C canali, dove n, m, C sono parametri. Per migliorare ulteriormente la qualità dei descrittori può essere utile introdurre il concetto di sovrapposizione (overlap) dei blocchi. Ciò significa che blocchi tra loro adiacenti condividono un certo numero di celle che dipende ovviamente dal parametro di overlap. La figura mostra un esempio di creazione di blocchi di 2x2 celle utilizzando un fattore di overlap pari a 1. Da notare che nel caso di blocchi costruiti con overlap, un istogramma di una determinata cella può appartenere a diversi blocchi e, quindi, può contribuire alla normalizzazione di diversi blocchi. In questa caso può sembrare che il descrittore finale contenga informazioni ridondanti ma in realtà l'utilizzo dell'overlap può in alcuni casi migliorare le prestazioni. 32
  35. 35. Schemi di normalizzazione Per effettuare la normalizzazione degli istogrammi nei singoli blocchi possono essere utilizzati diverse tecniche. Sia v il vettore descrittore normalizzato, ||v||k la sua norma k-esima ed ε una piccola costante che serve nel caso siano valutati gradienti nulli, gli schemi di normalizzazione sono allora i seguenti: 6.1.4 Costruzione dei descrittori (Detection Window) Il nostro obbiettivo è adesso quello di scorrere l'immagine a più scale utilizzando finestre di dimensione finite. Per ogni finestra otteniamo, mediante i passi finora descritti, un descrittore che può essere infine classificato da un classificatore SVM lineare. I descrittori sono ottenuti scorrendo la relativa finestra dall'altro verso il basso, da sinistra verso destra e accodando gli istogrammi delle singole celle ad un vettore finale. Supponiamo di avere: ● finestre di dimensione 64x128 ● celle di 8x8 pixel (in totale si hanno 8x16 celle) ● istogrammi da 9 canali ● blocchi di 2x2 celle senza overlap (in totale si hanno 4x8 blocchi) si ottiene un descrittore finale di dimensione (4 x 8) x (2 x 2) x 9 = 1152. Una volta ottenuti i descrittori relativi ad ogni singola finestra, deve essere effettuata una predizione sulla presenza o meno di una persona all'interno. Pertanto i descrittori vengono classificati utilizzando un SVM lineare precedentemente allenato. 33
  36. 36. 6.1.5 Classificatore SVM Imposto un classificatore SVM (Support Vector Machine) che corrisponde ad un algoritmo di classificazione mediante apprendimento e supervisione. Per fare in modo che un classificatore “apprenda” cosa deve cercare nell'immagine bisogna prima sottoporlo ad una fase detta di training dove gli si danno in input un certo numero di immagini positive e negative (contenenti o no l'oggetto in questione). OpenCV mette a disposizione il Default People Detector che è stato già allenato sul database di immagini “INRIA Person” [15] composto da circa un migliaio di immagini positive e due migliaia di negative. 6.1.6 Miglioramento prestazioni Le procedure di rilevazione finora analizzate costituiscono un'implementazione base del metodo HOG. Possono infatti essere ulteriormente migliorate le prestazioni con tecniche addizionali. Innanzitutto il metodo base prevede la creazione sequenziale dei vari descrittori quando si scorre l'immagine con la finestra di detection; questa gestione può essere notevolmente migliorata introducendo una creazione multithreading dei descrittori. Utilizzando pertanto ca1colatori con più CPU i tempi di esecuzione migliorano notevolmente. In secondo luogo la robustezza del classificatore utilizzato e la densa scansione dell'immagine portano ad avere molte finestre di rilevazione nelle prossimità di una persona. È quindi necessaria una procedura di fusione per l'ottenimento di una finestra unica. L'algoritmo utilizzato è il mean- shift. L'obbiettivo finale della detection è la localizzazione esatta di una persona che appare in un'immagine. L'implementazione di base del metodo HOG prevede una scansione dell'immagine, con una finestra di detection, a tutte le posizione di scale, eseguendo un classificatore SVM per ogni finestra e disegnando un opportuno rettangolo nel caso il classificatore etichetti come positiva la finestra stessa. Il risultato finale dovrebbe essere quindi dato dalla presenza di una fitta serie di rettangoli attorno all'oggetto rilevato. È pertanto necessario un metodo di fusione di tali finestre multiple, per questo si usa l'algoritmo di mean-shift[18]. 34
  37. 37. 6.2 Implementazione Questa parte di progetto al contrario del rilevamento targhe, è stata realizzata sotto sistema operativo Windows 7 perché il componente HOG utilizzato funziona solo se compilato con certe librerie DLL esterne fornite solo per Windows. Come prima cosa istanzio una variabile facente parte della classe HOGDescriptor che contiene tutti i metodi necessari alle operazione che vogliamo svolgere. cv::HOGDescriptor hog; Imposto un classificatore SVM (Support Vector Machine) che mi mette a disposizione OpenCV hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector()); Preparo le strutture dati necessarie dove riporre i risultati del detecting dove res è il frame sul quale verrà effettuato il detecting mentre found è un vettore di strutture cvRect (descritti da coordinate, altezza e larghezza) che contornano ogni area positiva trovata. cv::vector<cv::Rect> found; IplImage* res; DetectMultiScale è l'istruzione che va ad attuare l'algoritmo e si può cambiare qualche parametro per avere risultati differenti. hog.detectMultiScale(res, found, -0.85, cv::Size(8,8), cv::Size(24,16), 1.05, 5); Infine il ciclo che stampa a video tutti i rettangoli trovati for( int i = 0; i < (int)found.size(); i++ ){ cv::Rect r = found[i]; r.x += cvRound(r.width*0.1); r.y += cvRound(r.height*0.1); r.width = cvRound(r.width*0.8); r.height = cvRound(r.height*0.8); cvRectangle(img, r.tl(), r.br(), CV_RGB(0,255,0), 1); } ma vediamo il prototipo del metodo detectMultiScale: void cv::HOGDescriptor::detectMultiScale (const cv::mat &img, std::vector<> &foundLocation, double hitThreshold, CvSize winStripe, CvSize padding, double scale, double finalThreshold) decrementando hitThreshold aumentiamo la hit rate cioè la sensibiltà del detector,ma c'è da prestare attenzione perché scegliendo un valore troppo basso si possono rilevare troppi falsi allarmi. WinStripe è la dimensione della sotto finestra di ricerca . Un altro parametro impostante è finalThreshold che opera sul raggruppamento finestre, dice quanto devo raggruppare perchè può darsi che per una persone trovo più finestra vicine tra loro. 35
  38. 38. 6.2.1 Background substracting Il Background-substracting è una tecnica usata per distinguere in una scena le forme in movimento rispetto agli oggetti statici di sfondo. Durante le prime prove di people detect ho notato che venivano rilevati come persone anche figure facenti parte dello sfondo come paletti o pezzi di lampione. Esempio di errore del detector Come prima cosa ho provato ad agire sui parametri del metodo detectMultiScale: aumentando la hitThreshold,e quindi diminuisce la hit rate, fino ad eliminare quasi completamente il problema dei falsi positivi ne risente molto anche il riconoscimento di persone: si abbassa notevolmente il detecting di individui che sono lontane, vicino ad oggetti o confondibili con lo sfondo. Un'altra soluzione meditata era quella di crearsi una statistica frame dopo frame delle aree e delle coordinate dei rettangoli errore. Questo procedimento ha come ipotesi di base però che questi rettangoli si modifichino di poco andando avanti nel video e sopratutto che siano sempre li stessi. Procedendo con le sperimentazioni notai che esaminando video di diverse ora del giorno, la luminosità cambia radicalmente e con lei anche la posizione e le forme dei rettangoli errore. Anche questa ipotesi, come quella precedente, è da scartare. 36
  39. 39. Qui nasce la necessità di applicare la tecnica di sottrazione di sfondo. Una volta identificato lo sfondo adatto, il risultato sarà un frame contenente solo le sagome degli oggetti in movimento e l'HOG detector agirà su questo. In questo modo posso mantenere una hit rate abbastanza alta in quanto non c'è più il problema degli oggetti sullo sfondo. Rimane il problema di trovare un'immagine di sfondo adeguata che è fondamentale affinché il processo vada a buon fine. Il problema principale è che un'immagine statica di sfondo dopo un lasso di tempo “scade” perché la luminosità della giornata cambia e la sottrazione non è più bilanciata. Sono stati valutati vari metodi per ottenere tale immagine: ● Si può usare un modello gaussiano (presente nei samples di OpenCV) dove frame dopo frame si effettua il background-sub. partendo dall'ipotesi che un pixel varia in modo normale da un frame all'altro se fa parte del background. Questo metodo ovvia il fatto dell'immagine di sfondo statica ma è stato scartato in quanto richiede molto tempo di elaborazione per ogni singolo frame (e già ne impiega abbastanza l'hog processing) e soprattutto ricavo delle sagome che hanno poco a che fare con una forma umana e la percentuale di rilevamento diventa troppo bassa. ● Si può fornire all'utente un'immagine di sfondo di partenza e aggiornarla ogni qual volta che l'hog detector rileva zero presenze: il problema è che non ci possiamo affidare troppo al detector basterebbe sbagliare una volta e l'errore si propagherebbe poi anche se rilevo zero persone ma ci sono altri oggetti che possono sparire dopo qualche frame (animali, macchine) non ci sarebbe una calibrazione esatta dello sfondo. ● Come ultima opzione ho deciso di processare prima tutto il video ed estrarre tutti i primi N frame dove rilevo 0 persone, con la hit rate abbastanza bassa per avere meno falsi allarmi possibili. L'utente poi sceglie quale utilizzare tra quelle trovate. In questo modo il processo non è più completamente automatico in quanto si introduce una scelta che solo un'umano può compiere. Se in quei N frame non c'è nessuna immagine libera per far da sfondo si può ripetere l'operazione con M > N. Questo metodo ha come problema la luminosità in quanto col passare del tempo aumenta (o diminuisce) ma visto che i video sono di 10 minuti rimane stabile. Una volta ottenuta l'immagine di sfondo invoco la funzione fornita da OpenCV cvAbsDiff che prende in input due frame, ne fa la differenza assoluta pixel a pixel e pone il risultato un un terzo frame. 37
  40. 40. Frame risultante dopo la sottrazione di sfondo 38
  41. 41. 6.2.2 Definizione dell'area Bisogna anche rendere possibile all'utente di definire un'area nella quale ricercare le persone. Il programma poi deve riconoscere per ogni rilevamento se si trova all'interno o all'esterno dell'area definita. Per rendere più facile e intuitiva questa operazione ho impiegato anche l'uso di funzioni che coinvolgono il mouse. Attraverso la procedura cvSetMouseCallback dichiaro che ad ogni azione compiuta dal mouse, quale movimento, click o anche il rilascio del tasto, vado a richiamare un funzione costruita da me. A questa funzione oltre ai parametri da me inseriti, in aggiunta vengono passati in automatico 3 interi che rappresentano l'ascissa e l'ordinata dell'evento nel frame ove è stato fatto, e a quale evento ci stiamo riferendo. Queste le possibili combinazioni: Flag Descrizione evento (è una costante di tipo int) CV_EVENT_MOUSEMOVE Movimento del mouse CV_EVENT_LBUTTONDOWN Tasto sinistro premuto (e non per forza rilasciato) CV_EVENT_RBUTTONDOWN Tasto destro premuto (e non per forza rilasciato) CV_EVENT_MBUTTONDOWN Tasto centrale premuto (e non per forza rilasciato) CV_EVENT_LBUTTONUP Tasto sinistro rilasciato CV_EVENT_RBUTTONUP Tasto destro rilasciato CV_EVENT_MBUTTONUP Tasto centrale rilasciato CV_EVENT_LBUTTONDBLCLK Click tasto sinistro CV_EVENT_RBUTTONDBLCLK Click tasto destro CV_EVENT_MBUTTONDBLCLK Click tasto centrale CV_EVENT_FLAG_CTRLKEY Dice se contemporaneamente è premuto anche il tasto CTRL (serve per implementare più funzioni) CV_EVENT_FLAG_SHIFTKEY Dice se contemporaneamente è premuto anche il tasto SHIFT CV_EVENT_FLAG_ALTKEY Dice se contemporaneamente è premuto anche il tasto ALT Tornando al progetto il programma attende 3 click da parte dell'utente attraverso i quali definisce 2 rette: la prima retta passa tra i primo e il secondo punto, mentre la seconda retta tra il secondo e il terzo. Definisco anche 4 quadranti virtuali che hanno origine nel secondo punto. 39
  42. 42. Ora sapendo le coordinate dei punti all'interno del frame posso calcolarmi l'equazione delle 2 rette. Chiamando P1 il primo punto e P1.x,P1,y le sue coordinate l'equazione della prima retta si calcola in questo modo: Una volte ricavate le equazioni delle rette ed i 4 quadranti posso calcolare inanzi tutti quali quadranti fanno completamente parte della mia aerea di ricerca, mentre in quelli dove giace una retta devo confrontare se il punto trovato sta sopra o sotto facendo una semplice proiezione negli assi. Il punto di un oggetto rilevato che confronto con le rette è l'angolo in basso a destra del rettangolo trovato dal detector che contorna una persona. Esempio di come lo schermo viene suddiviso in quadranti una volta definite le 2 rette 40
  43. 43. Per capire in quale quadrante si trova la persona rilevata devo eseguire un confronto tra le coordinate dei punti definiti dall'utente e quelle rilevate dal mio detector. Ricordo che quando una persona è rilevata viene contornata da un rettangolo e il punto che poi verrà preso in esame è l'angolo in basso a destra di quest'ultimo. Prima di tutto identifico i quadranti con un numero a partire dal quadrante in alto a sinistra come mostrato in nelle figure sottostanti Ora gestisco il caso più semplice: quando i tre punti sono uno a destra dell'altro per cui l'ascissa del punto 3 sarà maggiore dell'ascissa del punto 2 che è a sua volta maggiore dell'ascissa del punto 1. Se il primo punto è più in basso del secondo allora posso affermare che se una persona transita nel primo quadrante di sicuro non cade nella mia area di ricerca. Se una persona invece viene rilevata nel terzo quadrante devo controllare se è sopra o sotto la linea identificata dal primo e secondo punto. 41
  44. 44. Viceversa se il il punto 1 è più in alto del secondo posso dire che tutto il terzo quadrante è valido per la mia definizione di area mentre devo controllare nel primo se sono sopra o sotto la linea definita dal punto 1 e 2. Gestiti il primo e terzo quadrante eseguo il controllo duale con il punto 2 e 3 per quanto riguarda il secondo e quarto quadrante: 42
  45. 45. Il caso rimasto da gestire è quando sia il punto 1 e il punto 3 rimangono a sinistra del punto 2. Stavolta devo vedere se le rette formatasi hanno il coefficiente angolare con lo stesso segno o no perché se i segni sono concordi allora le due rette sono nello stesso quadrante mentre nel caso dei segni discordi le due rette giacciono in due quadranti diversi che sono il primo e il terzo (il secondo e il quarto vengono sempre esclusi). Caso in cui i coeff. hanno segni discordi Caso in cui i coeff. hanno segni concordi 43
  46. 46. Qualche screenshot di definizione dell'area presi dal programma in esecuzione: Esempio di area rettangolare Esempio di area a cono 44
  47. 47. 6.3 Considerazioni e risultati Come detto in precedenza utilizzando la tecnica di background-substracting e agendo sui parametri del classificatore SVM lineare, si ottengono ottimi risultati anche se alcuni tipi di errore, come ad esempio l'area di detecting molto affollata, dove le sagome umane si sovrappongono, persistono. Ricordo inoltre che questo metodo si basa principalmente sulla bontà dell'immagine di sfondo rilevata. Trovando un'immagine di sfondo non adeguata infatti si compromette l'intero svolgimento del processo. 45
  48. 48. 7 Conclusioni La prima conclusione che si può trarre è che per quanto riguarda l'algoritmo di ricognizione targhe il tasso di caratteri non riconosciuti o errati è troppo alto per essere affidabile, anche nel caso migliore analizzato usando Tesseract. Questo è dovuto sicuramente alle proprietà dell'immagine come la luminosità, la definizione o l'inclinazione. Prendendo infatti l'immagine di una targa più definita e non inclinata tutti e due gli OCR riescono nel loro impiego anche senza segmentare l'immagine. ABBY FineReader in azione con una targa più definita Per migliorare quest'ultima parte del procedimento bisognerebbe agire a livello hardware, sostituendo le telecamere usate con altre più sofisticate, oppure applicare l'utilizzo di due telecamere: una che riprende dall'alto per rilevare il moto, e un'altra situata ad altezza targa adibita esclusivamente a scattare foto tutte le volte che l'altro dispositivo rileva del movimento. Per quanto riguarda la prima parte del processo, cioè la Motion Detect e l'estrazione della targa dall'immagine, sono stati raggiunti risultati soddisfacenti infatti guardando le statistiche ottenute sul campo si può notare che se in media passano 20,72 in 10 minuti vengono rilevate 18, 28 targhe, cioè l'88,22% delle auto che transitano vengono processate correttamente. Riguardo all'algoritmo del conteggio di persone nelle piazze un possibile miglioramento può essere quello di eseguire il tracking delle persone e non solo il detecting, cioè ad ogni pedone rilevato viene assegnato un identificatore che viene mantenuto frame dopo frame. Questo è possibile attraverso l'implementazione del filtro di Kalman[19] applicabile ad un sistema dinamico. In generale dopo questa esperienza posso affermare che la videoanalisi è un mondo molto ampio e complicato in cui i problemi difficilmente possono essere risolti indipendentemente dal contesto. Prendendo il mio lavoro come esempio, per ottenere il file XML contenente la descrizione della targa risultato dall'haartraining, ho utilizzato immagini provenienti dalle telecamere a disposizione, quindi è molto probabile che questo file vada bene solo per questo set di telecamere e non per altre. 46
  49. 49. Appendice Rileva_targhe.cpp #include <stdio.h> #include <stdlib.h> #include "cv.h" #include <opencv/highgui.h> #include <highgui.h> //#include <opencv2/highgui/highgui_c.h> //#include <cvblobs/BlobResult.h> #include <cxcore.h> #include <string.h> const int MAX_CORNERS = 100; CvSize imgSize; const float x[]={0.0, 1/5.0, 2/5.0, 3/5.0, 4/5.0, 1.0}; const float y[]={0.0, 1/3.0, 2/3.0, 1.0}; int msec=0, msecprec=0; int flagsec=0; int sens; //int c=0; void salva_frame(IplImage *frame,char* path_img) { int l; //memset(path_img, '0', 500); char datetime[80]; //memset(datetime, '0', 80); char app[100]=""; char buf[20]; time_t rawtime; struct tm * timeinfo; time ( &rawtime ); timeinfo = localtime ( &rawtime ); strcpy(app,path_img); l=strlen(path_img); if (path_img[l-1]=='/'){ //strcat(app, "Img_"); }else{ strcat(app, "/"); } //strftime (datetime,80,"%x-%X",timeinfo); //strftime (datetime,80,"%Y%m%d-%H:%M:%S",timeinfo); //strcat(app, datetime); //strcat(app,"--"); //sprintf(buf,"%d",c); //strcat(app,buf); //strcat(app,"@"); sprintf(buf,"%d",msec); strcat(app,buf); strcat(app, ".jpg"); /* IplImage *l=cvCreateImage(imgSize, 47
  50. 50. IPL_DEPTH_8U, 1); cvCvtColor(frame,l,CV_RGB2GRAY); Sharpening(l); cvSaveImage( filename, l ); cvReleaseImage(&l); */ cvSaveImage(app,frame); //c++; } int distanza (CvPoint a, CvPoint b) { return sqrt(pow((float)(a.x - b.x),2)+pow((float)(a.y - b.y),2)); } void processa(IplImage* imgA, IplImage* imgB, IplImage* imgC, int* k, int col, int riga, char* path_img, CvCapture* capture) { // Initialize, load two images from the file system, and // allocate the images and other structures we will need for // results. // //IplImage* imgA = cvLoadImage("image0.jpg",CV_LOAD_IMAGE_GRAYSCALE); //IplImage* imgB = cvLoadImage("image1.jpg",CV_LOAD_IMAGE_GRAYSCALE); //char nomeFile[30]; CvSize img_sz = cvGetSize( imgA ); int win_size = 10; //IplImage* imgC = cvLoadImage("image1.jpg",CV_LOAD_IMAGE_UNCHANGED); // The first thing we need to do is get the features // we want to track. // IplImage* eig_image = cvCreateImage( img_sz, IPL_DEPTH_32F, 1 ); IplImage* tmp_image = cvCreateImage( img_sz, IPL_DEPTH_32F, 1 ); int corner_count = MAX_CORNERS; CvPoint2D32f* cornersA = new CvPoint2D32f[MAX_CORNERS]; cvGoodFeaturesToTrack(imgA, eig_image, tmp_image, cornersA, &corner_count, 0.01, 5.0, 0, 3, 0, 0.04); cvFindCornerSubPix( imgA, cornersA, corner_count, cvSize(win_size,win_size), cvSize(-1,-1), cvTermCriteria(CV_TERMCRIT_ITER| CV_TERMCRIT_EPS,20,0.03)); // Call the Lucas Kanade algorithm // char features_found[ MAX_CORNERS ]; float feature_errors[ MAX_CORNERS ]; CvSize pyr_sz = cvSize( imgA->width+8, imgB->height/3 ); IplImage* pyrA= cvCreateImage( pyr_sz, IPL_DEPTH_32F, 1 ); IplImage* pyrB= cvCreateImage( pyr_sz, IPL_DEPTH_32F, 1 ); CvPoint2D32f* cornersB = new CvPoint2D32f[ MAX_CORNERS ]; cvCalcOpticalFlowPyrLK(imgA, imgB, pyrA, pyrB, cornersA, cornersB, corner_count, cvSize( win_size,win_size ), 5, features_found, feature_errors, cvTermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, .3 ), 0); // Now make some image of what we are looking at: // for( int i=0; i<corner_count; i++ ) { if( features_found[i]==0|| feature_errors[i]>550 ) { 48
  51. 51. //printf("Error is %f/n",feature_errors[i]); continue; } //printf("Got it/n"); CvPoint p0 = cvPoint( cvRound( cornersA[i].x ), cvRound( cornersA[i].y )); CvPoint p1 = cvPoint( cvRound( cornersB[i].x ), cvRound( cornersB[i].y )); int d=distanza(p0,p1); if (d>20) { if((p0.x>=imgSize.width*x[col] || p1.x>=imgSize.width*x[col]) && (p0.x<=imgSize.width*x[col+1] || p1.x<=imgSize.width*x[col+1])) if((p0.y>=imgSize.height*y[riga] || p1.y>=imgSize.height*y[riga]) && (p0.y<=imgSize.height*y[riga+1] || p1.y<=imgSize.height*y[riga+1])) { //cvLine( imgC, p0, p1, CV_RGB(255,0,0),2 ); cvRectangle(imgC,cvPoint(imgSize.width*x[col],imgSize.height*y[riga]), cvPoint(imgSize.width*x[col+1],imgSize.height*y[riga+1]),cvScalar(255,0,0),1); /* IplImage* dst = cvCreateImage( imgSize, IPL_DEPTH_8U, 1); cvCvtColor(imgC,imgCgray,CV_RGB2GRAY); cvThreshold(imgCgray,dst,128,255,CV_THRESH_OTSU); */ IplImage *img = cvCreateImage(cvSize(imgC- >width/5,imgC->height/3), IPL_DEPTH_8U, 3); cvGetRectSubPix(imgC,img,cvPoint2D32f(imgC- >width*(col*2+1)/10,imgC->height*(riga*2+1)/6)); msec=(int)cvGetCaptureProperty(capture, CV_CAP_PROP_POS_MSEC); if ((msec-msecprec)>sens){ flagsec=1; msecprec=msec; salva_frame(img, path_img); } (*k)++; } //cvShowImage("titolo", imgC); //printf("Distanza:%02dn",d); } } cvReleaseImage(&eig_image); cvReleaseImage(&tmp_image); cvReleaseImage(&pyrA); 49
  52. 52. cvReleaseImage(&pyrB); } //funzione che mostra un frame diviso in quadrati numerati così da poter scegliere quello d'interesse. /*int mostraQuadranti(IplImage *frame ) { int q,i; CvFont font;; cvNamedWindow("Window",0); for(i=1;i<5;i++) cvLine(frame, cvPoint(imgSize.width*i/5,0),cvPoint(imgSize.width*i/5,imgSize.height),CV_RGB(25 5,0,0),2); cvLine(frame, cvPoint(0,imgSize.height/3),cvPoint(imgSize.width,imgSize.height/3),CV_RGB(255,0 ,0),2); cvLine(frame, cvPoint(0,imgSize.height*2/3),cvPoint(imgSize.width,imgSize.height*2/3),CV_RGB(2 55,0,0),2); cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, 5.0, 5.0, 0, 5); char s[5]; for (i=0;i<6;i++) { sprintf(s,"%d",i+1); cvPutText(frame, s,cvPoint(imgSize.width*i/5,imgSize.height/6),&font, CV_RGB(255,0,0)); } for (i=0;i<6;i++) { sprintf(s,"%d",i+6); cvPutText(frame, s,cvPoint(imgSize.width*i/5,imgSize.height/2),&font, CV_RGB(255,0,0)); } for (i=0;i<6;i++) { sprintf(s,"%d",i+11); cvPutText(frame, s,cvPoint(imgSize.width*i/5,imgSize.height*5/6),&font, CV_RGB(255,0,0)); } //while (cvWaitKey(33)!=27) cvShowImage("Window",frame); printf("Scegliere il quadrante: n"); scanf("%d",&q); cvDestroyWindow("Window"); if( q>0 && q<16) return q; else return 1; }*/ int in_quad(int colonna, int riga){ if ((colonna>=0) && (colonna<=4) && (riga>=0) && (riga<=2)){ return 1; }else{return 0;} } 50
  53. 53. int main(int argc, char** argv) { CvCapture *capture; char path_img[100]; char input_name[200]; int quadrante=14, k=0, search; IplImage* frame, *frameA, *frameB; IplImage* frameAgray, *frameBgray; //cattura video //printf("Path assoluto del video:n"); //scanf("%s",input_name); if (argc<5){ printf("Pochi argomenti:n"); printf("Uso: ./motion percorso_video cartella_immagini quandrante flag_ricerca_vicina *sensibilità(facoltativo)n"); return 1; } if (argc==6){sens=atoi(argv[5]);} else {sens=160;} strcpy(input_name,argv[1]); capture = cvCaptureFromAVI( input_name ); if (!capture){ printf("Nessun video trovaton"); return 1; } //printf("Percorso dove verranno salvate le immagini:n"); //scanf("%s",path_img); strcpy(path_img,argv[2]); //cvNamedWindow("titolo",1); for (int i=0;i<100;i++) frame = cvQueryFrame( capture ); //imgSize=cvSize(frame->width/5,frame->height/3); imgSize=cvSize(frame->width,frame->height); //quadrante=mostraQuadranti(frame); //printf("Selezionare il quadrante:n"); //scanf("%d",&quadrante); quadrante=atoi(argv[3]); search=atoi(argv[4]); int col, riga; switch(quadrante) { case 1: col=0; riga=0; break; case 2: col=1; riga=0; break; case 3: col=2; riga=0; break; case 4: col=3; riga=0; break; case 5: col=4; riga=0; break; case 6: col=0; riga=1;break; case 7: col=1; riga=1;break; 51
  54. 54. case 8: col=2; riga=1;break; case 9: col=3; riga=1;break; case 10: col=4; riga=1;break; case 11: col=0; riga=2; break; case 12: col=1; riga=2; break; case 13: col=2; riga=2; break; case 14: col=3; riga=2; break; case 15: col=4; riga=2; break; default: col=0; riga=0; break; } printf("width: %d, height: %dn",imgSize.width, imgSize.height); frameA = cvCreateImage(imgSize, IPL_DEPTH_8U, 3); frameB = cvCreateImage(imgSize, IPL_DEPTH_8U, 3); frameAgray = cvCreateImage(imgSize, IPL_DEPTH_8U, 1); frameBgray = cvCreateImage(imgSize, IPL_DEPTH_8U, 1); do{ cvCvtColor(frameA, frameAgray, CV_RGB2GRAY); frameB = cvQueryFrame( capture ); cvCvtColor(frameB, frameBgray, CV_RGB2GRAY); if (!search){processa(frameAgray, frameBgray, frameB, &k, col, riga, path_img, capture);} else{ processa(frameAgray, frameBgray, frameB, &k, col, riga, path_img, capture); if (in_quad(col-1,riga-1)){processa(frameAgray, frameBgray, frameB, &k, col-1, riga-1, path_img, capture);} if (in_quad(col-1,riga)){processa(frameAgray, frameBgray, frameB, &k, col-1, riga, path_img, capture);} if (in_quad(col,riga-1)){processa(frameAgray, frameBgray, frameB, &k, col, riga-1, path_img, capture);} if (in_quad(col+1,riga+1)){processa(frameAgray, frameBgray, frameB, &k, col+1, riga+1, path_img, capture);} if (in_quad(col+1,riga)){processa(frameAgray, frameBgray, frameB, &k, col+1, riga, path_img, capture);} if (in_quad(col,riga+1)){processa(frameAgray, frameBgray, frameB, &k, col, riga+1, path_img, capture);} if (in_quad(col-1,riga+1)){processa(frameAgray, frameBgray, frameB, &k, col-1, riga+1, path_img, capture);} if (in_quad(col+1,riga-1)){processa(frameAgray, frameBgray, frameB, &k, col+1, riga-1, path_img, capture);} } 52
  55. 55. //if (cvWaitKey(10)==27) break; }while((frameA=cvQueryFrame(capture)) != NULL); cvReleaseCapture( &capture ); cvReleaseImage(&frameA); cvReleaseImage(&frameAgray); cvReleaseImage(&frameB); cvReleaseImage(&frameBgray); cvReleaseImage(&frame); return 0; } 53
  56. 56. Estrai_targhe.cpp #include "opencv2/objdetect/objdetect.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace std; using namespace cv; int cc=0; char path_img[200]; char nome[200]; void reverse(char array[], int N) { int i, x = N-1; int tmp; for(i=0; i<N/2; i++) { tmp = array[i]; array[i] = array[x]; array[x] = tmp; x--; } } void help() { cout << "nThis program demonstrates the cascade recognizer. Now you can use Haar or LBP features.n" "This classifier can recognize many ~rigid objects, it's most known use is for faces.n" "Usage:n" "./facedetect [--cascade=<cascade_path> this is the primary trained classifier such as frontal face]n" " [--nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]n" " [--scale=<image scale greater or equal to 1, try 1.3 for example>n" " [filename|camera_index]nn" "see facedetect.cmd for one call:n" "./facedetect --cascade="../../data/haarcascades/haarcascade_frontalface_alt.xml" --nested- cascade="../../data/haarcascades/haarcascade_eye.xml" --scale=1.3 n" "Hit any key to quit.n" "Using OpenCV version " << CV_VERSION << "n" << endl; } void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale); 54
  57. 57. String cascadeName = ""; String nestedCascadeName = "../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml"; int main( int argc, const char** argv ) { CvCapture* capture = 0; Mat frame, frameCopy, image; const String scaleOpt = "--scale="; size_t scaleOptLen = scaleOpt.length(); const String cascadeOpt = "--cascade="; size_t cascadeOptLen = cascadeOpt.length(); const String nestedCascadeOpt = "--nested-cascade"; size_t nestedCascadeOptLen = nestedCascadeOpt.length(); String inputName; //help(); strcpy(path_img,argv[argc-1]); CascadeClassifier cascade, nestedCascade; double scale = 1; for( int i = 1; i < argc; i++ ) { //cout << "Processing " << i << " " << argv[i-1] << endl; if( cascadeOpt.compare( 0, cascadeOptLen, argv[i-1], cascadeOptLen ) == 0 ) { cascadeName.assign( argv[i-1] + cascadeOptLen ); //cout << " from which we have cascadeName= " << cascadeName << endl; } else if( nestedCascadeOpt.compare( 0, nestedCascadeOptLen, argv[i-1], nestedCascadeOptLen ) == 0 ) { if( argv[i-1][nestedCascadeOpt.length()] == '=' ) nestedCascadeName.assign( argv[i-1] + nestedCascadeOpt.length() + 1 ); // if( !nestedCascade.load( nestedCascadeName ) ) // cerr << "WARNING: Could not load classifier cascade for nested objects" << endl; } else if( scaleOpt.compare( 0, scaleOptLen, argv[i-1], scaleOptLen ) == 0 ) { if( !sscanf( argv[i-1] + scaleOpt.length(), "%lf", &scale ) || scale < 1 ) scale = 1; //cout << " from which we read scale = " << scale << endl; } else if( argv[i-1][0] == '-' ) { //cerr << "WARNING: Unknown option %s" << argv[i-1] << endl; } else inputName.assign( argv[i-1] ); } if( !cascade.load( cascadeName ) ) { 55

×