Sviluppo e analisi prestazionale di algoritmi di videoanalisi in  tecnologie Open Source
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

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

on

  • 2,174 views

 

Statistics

Views

Total Views
2,174
Views on SlideShare
2,174
Embed Views
0

Actions

Likes
0
Downloads
51
Comments
1

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • grazie mille!!
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

  • 1. UNIVERSITA’ DEGLI STUDI DI FERRARA FACOLTA DI INGEGNERIA CORSO DI LAUREA IN INGEGNERIA DELLINFORMAZIONE COMUNICAZIONI MULTIMEDIALI Sviluppo e analisi prestazionale di algoritmi di videoanalisi in tecnologie Open SourceTesi di Laurea di:Andrea Montanari Relatore: Prof. Ing. Gianluca Mazzini Correlatore: Prof. Ing. Chiara Taddia Anno Accademico: 2010/2011
  • 2. Indice generale1 Elementi di videoanalisi....................................................................................................1 1.1 Che cosè la videoanalisi............................................................................................................1 1.1.1 Un po di storia...................................................................................................................3 1.2 Riconoscimento targhe...............................................................................................................42 Videoanalisi Open Source.................................................................................................5 2.1 OpenCV.....................................................................................................................................5 2.2 Installare OpenCV.....................................................................................................................63 Motion Detect..................................................................................................................... 8 3.1 Lalgoritmo di Lucas-Kanade....................................................................................................8 3.2 Implementazione e miglioramenti...........................................................................................12 3.3 Risultati e considerazioni.........................................................................................................144 Estrazione della targa dallimmagine.............................................................................15 4.1 Haar-training classificatori di Viola e Jones............................................................................16 4.2 Implementazione......................................................................................................................19 4.3 Risultati e statistiche................................................................................................................245 OCR.................................................................................................................................. 25 5.1 Pre-processamento immagine..................................................................................................25 5.2 Risultati con OCR Open Source..............................................................................................27 5.3 Risultati con OCR Shareware..................................................................................................286 Conteggio persone in unarea.........................................................................................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 dellarea........................................................................................................39 6.3 Considerazioni e risultati.........................................................................................................457 Conclusioni....................................................................................................................... 46
  • 3. 1 Elementi di videoanalisiQuesta tesi nasce con lo scopo di implementare e analizzare alcuni algoritmi di videoanalisisfruttando esclusivamente tecnologie open source, nello specifico il rilevamento targhe e ilconteggio delle persone in unarea, 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 chepossiede un centro gestione video con telecamere fisse che puntano su tratti di strade o piazzesparse 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 renderelidentificazione della targa difficile anche ad occhio nudo.1.1 Che cosè la videoanalisiLa videoanalisi (o visione artificiale) è linsieme dei processi che mirano a creare un modelloapprossimato del mondo reale (3D) partendo da immagini bidimensionali (2D). Lo scopo principaledella visione artificiale è quello di riprodurre la vista umana. Vedere è inteso non solo comelacquisizione di una fotografia bidimensionale di unarea ma soprattutto come linterpretazione delcontenuto di quellarea. Linformazione è intesa in questo caso come qualcosa che implica unadecisione automatica.Un problema classico nella videoanalisi è quello di determinare se limmagine contiene o nodeterminati 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 unistanza specifica di una classe. Es. identificazione di un volto, impronta digitale o veicolo specifico. • Detection: limmagine è scandita fino allindividuazione 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 dallintegrazione di componenti ottiche, elettroniche emeccaniche che permettono di acquisire, registrare ed elaborare immagini. Il risultatodellelaborazione è il riconoscimento di determinate caratteristiche dellimmagine per varie finalitàdi controllo, classificazione o selezione. 1
  • 4. Lorganizzazione di un sistema di videoanalisi dipende fortemente dallapplicazione in cui vieneutilizzato. Alcuni sistemi sono semplici applicazioni stand-alone (a sé stanti) che risolvono specificiproblemi di misurazione o individuazione, mentre altri costituiscono un sotto-sistema in un progettopiù 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 immagineUnimmagine digitale è prodotta da vari tipi di sensori come sensori sensibili alla luce, sensori,tomografici, radar, sensori ad ultrasuoni. In base al tipo di sensore limmagine può assumere larappresentazione di unordinaria immagine 2D, un volume in 3D o una sequenza di immagini.PreelaborazionePrima che una immagine venga trattata da un metodo della videoanalisi è solitamente necessarioelaborare 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 dellimmagine sono estratte dai dati dellimmagine[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/segmentazioneIn questo punto del processo viene presa la decisione di quali aree saranno rilevanti per lulterioreelaborazione. Es: • Selezione di un set di punti di interesse • Segmentazione dellimmagine in più regioni che contengono un oggetto di interesseElaborazione di alto livelloA questo punto linput è tipicamente un piccolo insieme di punti o regione di immagine che sipresuppone debba contenere un oggetto specifico. Gli obiettivi del restante processo sono: • Verificare che il modello contenuto nellinput 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. 1.1.1 Un po di storiaSebbene esistono precedenti lavori, gli studi nel settore si sono potuti specializzare solo dopo il1970, grazie allaumento 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 eproprie applicazioni pratiche di questa disciplina, caratterizzate spesso da uno scopo puramentedimostrativo. Negli anni 90 vediamo comparire i primi frame-grabber standard da inserire su PC ei sistemi di visione acquistano maggiore funzionalità e robustezza abbandonando laspettotipicamente sperimentale del decennio precedente, soprattutto in campo industriale si notanonotevoli alti e bassi di questa disciplina caratterizzati da alcune soluzioni funzionali costellati diparecchi insuccessi[2].Ad oggi il campo della videoanalisi può essere descritto come vario ed immaturo perché non esisteuna formulazione standard di come i problemi di visione artificiale vadano risolti. Esistono inveceunabbondanza di metodi atti a risolvere compiti ben definiti della videoanalisi, dove le proceduresono spesso dipendenti dal contesto e raramente possono essere estese ad uno spettro più ampio diapplicazioni. Molti di questi metodi sono ancora a livello di ricerca base, ma molti altri ancorahanno trovato spazio nella produzione commerciale dove fanno parte di grandi sistemi cherisolvono problemi complessi. Nelle applicazioni più pratiche i computer sono pre-addestrati perrisolvere un particolare compito, tuttavia attualmente stanno diventando sempre più comuni imetodi basati sullapprendimento.Un grosso aumento dellutilizzo della videoanalisi negli ultimi anni è stato a causa dellavanzamentotecnologico di strumenti di cattura come videocamere e fotocamere, che forniscono immagini evideo di qualità sempre maggiore, e dellaumento della potenza di calcolo dei processori essenzialeper alcuni procedimenti che hanno unelevata complessità.Al giorno doggi in rete sono presenti molti esempi di funzioni base come ad esempiolindividuazione 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 impiegarecome punto di partenza per un proprio progetto. 3
  • 6. 1.2 Riconoscimento targheLa lettura delle targhe automobilistiche è diventata unesigenza irrinunciabile in numeroseapplicazioni di videosorveglianza. Dal controllo degli accessi nelle aziende alla sorveglianza deidistributori di carburante, lidentificazione inequivocabile della targa dei veicoli è in grado diaggiungere un valore inestimabile alla videoregistrazione di sicurezza.Rendere questo procedimento funzionante in tutti i contesti è però molto complicato perché ci sonodiversi fattori che incidono sulla lettura della targa come la luminosità circostante o linclinazionedella targa. Infatti come detto in precedenza la soluzione investigata e adattata in questa tesifunziona solo su alcune delle telecamere messe a disposizione e con una buona luminosità, peresempio di mattina.Il procedimento seguito è il seguente:Per questo progetto tutta la parte di programmazione è stata svolta sotto sistema sistema operativoLinux distribuzione Ubuntu 10.04. 4
  • 7. 2 Videoanalisi Open SourcePer la realizzazione di questo progetto sono state utilizzate librerie Open Source cioè pezzi dicodice i cui autori hanno permesso il libero studio e lapporto di modifiche da parte diprogrammatori indipendenti. Questo è possibile mediante lapplicazione della licenza duso OpenSource BSD che consente la redistribuzione del software anche in forma proprietaria, purché vengariconosciuto il merito allautore.2.1 OpenCVUna libreria molto potente orientata alla videoanalisi presente sul web è OpenCV (Open SourceComputer Vision).OpenCV è stata originariamente sviluppata da Intel ma attualmente è sotto licenza open source BSDe supportata totalmente dal Willow Garage un laboratorio di ricerca di automazione situato inCalifornia 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 peralcune piattaforme specifiche come i DSP.Wrapper per linguaggi come C#, Python[4], Ruby e Java[5] sono stati sviluppati per incoraggiareladozione di un pubblico più ampio. Tuttavia, sin dalla versione 2.0, OpenCV comprende sia la suainterfaccia tradizionale C, ma anche una nuova interfaccia C++ [6], che cerca di ridurre il numero dirighe di codice necessarie per implementare le funzionalità di visione. La maggior parte dei nuovialgoritmi o miglioramenti di OpenCV sono ora sviluppati con linterfaccia C++. Purtroppo, è moltopiù difficile fornire wrapper per altri linguagggi dal C++, al contrario di quello che accadeva collinguaggio C, quindi i wrapper in genere mancano delle nuove funzionalità contenute in OpenCV2.0.Essendo una libreria multi-piattaforma OpenCV supporta molti sistemi operativi come ad esempioWindows, Linux, Mac OS, Android[7], iOS[8] e Maemo[9]. 5
  • 8. 2.2 Installare OpenCVLe librerie OpenCV possono sono state installate su un server avente le seguenti caratteristiche:CPU AMD CPU AM3 Phenom II X6 1100T BI.E. BoxScheda Madre Gigabyte Scheda Madre SoAM3 GA-89FXA- UD5 (890FX/ATX)RAM DD3 Viper II (Sector 5) PC1600 2x4GB cas 7.9.7.21Hard Disk Seagate HDD 2000GB ST2000DL003 SATA 3.0Scheda Video Asus VGA PCI-E EAH5450 SIlent/DI/512MD2Altre Caratteristiche Scheda PCI-E 2 SATA2Per installarle si può procedere seguendo due diversi metodi. Se non si ha bisogno delle funzioniavanzate contenute nel modulo opencv2, come ad esempio tutte le procedure per il riconoscimentopersonalizzato di oggetti, esistono pacchetti precompilati per il sistema operativo Ubuntu installabilitramite 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 opencvQuesto progetto però utilizza anche le funzioni avanzate per cui è necessario procedere conlinstallazione manuale. Per eseguire la compilazione è indispensabile luso di Cmake per generare ilMakefile. Vediamo i passi per portare a termine correttamente linstallazione: 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. Lultimo comando serve per settare la variabile di sistema LD_LIBRARY_PATH al valore definitosopra. 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 -vUna volta installate le librerie bisogna capire come compilare le applicazioni scritte da noi che leutilizzano. Il comando generale per la compilazione di qualsiasi applicativo è:g++ -ggdb `pkg-config opencv --cflags --libs` main.cpp -o mainSe il sorgente contiene operazioni di cattura video (ad esempio le istruzioni cvCaptureFromAVI,cvCaptureFromFile o cvCaptureFromCAM) che sono procedure facenti parte del primo moduloOpenCV il comando visto precedentemente non è corretto in quanto la compilazione va a buon finema le funzioni sopra citate prima restituiscono sempre valore NULL anche se collegate ad un videoesistente. 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 -omain 7
  • 10. 3 Motion DetectCome prima cosa bisogna riconoscere il passaggio di una vettura e catturare uno o più frame in quelmomento, è 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 unasequenza di frame. Lo scopo delloptical flow è quello di assegnare ad ogni pixel appartenente alframe corrente un vettore che punta verso la posizione dello stesso pixel in un frame di riferimentosuccessivo. Grazie a questi vettori posso capire come loggetto si muove rispetto alla telecamera.3.1 Lalgoritmo di Lucas-KanadeEsistono vari metodi per determinare questo flusso ottico, per questo progetto è stato sceltolalgoritmo di Lucas-Kanade.Il metodo di Lucas-Kanade[10] presuppone che lo spostamento del contenuto dellimmagine tra dueistanti vicini è piccolo e approsimativamente costante in un intorno del punto p in esame. Conquesta ipotesi possiamo assumere lequazione del flusso ottico costante per tutti i punti allinterno diuna finstra centrata a p. Cioè il vettore del flusso ottico locale (Vx,Vy) (in termini di velocità) devesoddisfare:dove q1, q2,..., qn sono i pixel dentro la finestra e Ix(qi), Iy(qi), It(qi) sono le derivate parzialidellimmagine I rispetto alle coordinate x,y e al tempo t valutate nel punto qi e al tempo attuale. 8
  • 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 ottieneuna soluzione applicando lalgorimto di Least Mean Square (LMS). Vale a dire risolvere il sistema2x2: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 lalgoritmo di Least Mean Square sopra dà la stessa importanza a tutti gli n pixelqi della finestra. In pratica però è meglio dare più peso ai pixel più vicini al pixel centrale p. Allorasi utilizza la versione ponderata dellalgolirtmo LMS con la seguente equazione:Dove W è una matrice diagonale [n x n] contenente i pesi Wii = wi da asseganre ai pixel qinellequazione:Di solito i pesi wi sono il risultato di una funzione gaussiana della distanza tra qi e p. 9
  • 12. L’algoritmo di optical flow, nella versione a piramide di Lucas- Kanade, è già implementato inOpenCV dalla funzione cvCalcOpticalFlowPyrLK definita nellheader <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 dellarray è 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, lunica cosa che rimane da trovare sonoi punti (featuresA) su cui agire. Un metodo è quello di trovare gli angoli più determinantiutilizzando la funzione cvGoodFeatureToTrack: 10
  • 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: unaltra 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 cvFindCornerSubPixvoid cvFindCornerSubPix( IplImage* I, CvPoint2D32f* corners, int count, CvSize win, CvSizezeroZone, 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 laccuratezza con la quale si affilano i corner per fermarsi quando si ottiene una certa soglia. Esempio di immagine ottenuta applicando lalgoritmo di Lucas-Kanade. In rosso disegnato il vettore di moto trovato 11
  • 14. 3.2 Implementazione e miglioramentiOra siamo pronti per dare in pasto i frame allalgoritmo di Lucas Kanade il quale ci fornirà i punti diinteresse cioè quelli che si sono spostati.Per ogni punto trovato calcolo quanto si è spostato rispetto al frame precedente in modo da ottenereil modulo del suo vettore di moto. Visto che voglio rilevare movimenti di autovetture un primoscarto è stato quello di prendere in considerazione solo movimenti più forti, quindi con il modulosopra una certa soglia.Come detto in precedenza per questo progetto avevo a disposizione un set di telecamere ma soloalcune di queste si prestavano al rilevamento targhe a causa dellinclinazione con la quale la targaveniva inquadrata.Un altro perfezionamento è stato quello di suddividere la schermata in 15 sotto-quadranti eindividuare quale di questi è il più adatto al nostro scopo cioè dove la targa è più visibile. Questoulteriore 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-quadrantiIl quadrante viene selezionato manualmente dopo la visione di un video quindi è un parametrostatico.Ricapitolando per ogni frame controllo nel quadrante di interesse se cè stato movimento rispetto aquello precedente, se così è stato allora verifico che si tratto di un movimento di una certaimportanza attraverso il modulo del vettore di moto. Se limmagine soddisfa tutte le condizioni lamemorizzo su disco fisso, con il nome della sua posizione temporale dentro al video inmillisecondi.Per eseguire questa ultima operazione ho usato la funzione cvGetCaptureProperty, che definito uncerto stream di cattura (nel nostro caso il video in analisi) ci restituisce una serie di proprietà tra lequali anche la posizione temporale del frame in millisecondi in modo da avere una sorta di time-stamp. 12
  • 15. Miglioramenti:Dopo alcuni test dellalgoritmo su diversi video sono emersi alcuni difetti riguardanti per lo più ilnumero di immagini salvate.Al passaggio di automobile venivano scattate circa 7-8 foto, eccessive visto che in almeno 2-3 diqueste compariva la targa che è il nostro obbiettivo principale. Questa ridondanza è eliminabile inparte andando a controllare il time-stamp dellultima immagine salvata.Praticamente limmagine viene salvata solo se sono passati un certo numero di millisecondidallultimo salvataggio; questo parametro è possibile modificarlo per ogni istanza del programma edè interpretabile come la sensibilità di movimento. Anche questo è un parametro statico per ognivideo, perché varia a seconda della location in quanto un macchina può andare più veloce o meno aseconda 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 dellalgoritmo è data dalla realizzazione di una ricerca più profonda: quandorilevo del movimento nel quadrante selezionato eseguo la procedura anche nei quadranti vicini apatto che esistano. Questa funzione è stata implementata perché in alcuni video non esiste proprioun quadrante ottimale quindi per catturare più targhe può essere utile questa opzione. Questafunzionalità è meglio riservarla per video dove la cattura con i singoli quadranti classici è scadenteperché allunga di molto i tempi di elaborazione di tutto il processo. 13
  • 16. 3.3 Risultati e considerazioniCome detto in precedenza non tutte le foto scattate contengono una targa, bisogna settare iparametri sensibilità e quadrante per ottenere un buon compromesso tra immagini scattate eimmagini contenenti una targa. Dopo varie prove sul campo ho notato che il miglior valore daassegnare al parametro sensibilità era 160, cioè il sistema aspetta minimo 160 millisecondi prima discattare unaltra foto.In questo modo si ottengono circa 2 o 3 foto per ogni macchina che passa e nella maggior partedelle volte almeno una contiene la targa. Ci sono però da aggiungere altri falsi positivi come adesempio macchine dellaltra corsia che invadono il quadrante selezionato, in questo caso vengonoscattate altre foto che naturalmente devono essere scartate.Un sistema intelligente di filtraggio diventa necessario vista la grande quantità di immagini daeliminare.Andando a vedere i dati statistici raccolti su 50 video ho notato che in media in un video passanocirca 20,72 macchine e vengono scattate 64,94 foto il che ci da una media di 3,22 foto scattate permacchina. Bisogna tener conto però che viene scattata una foto per qualsiasi movimento, anche senon è il passaggio di automobile.Mostro come esempio una telecamera che punta su una delle tratte più trafficate. In questo filmatoda 10 minuti sono state scattate 236 foto e analizzando manualmente le immagini ottenute 44 sonopositive. Alcune immagini scattate direttamente dalla telecameraNotare il nome delle immagini che corrispondono al millisecondo nel video in cui quel frame è statopreso. Visto che ad ogni video corrispondono 10 minuti di un certa giornata si può facilmenterisalire allora e la data esatta in cui la macchina è passata. 14
  • 17. 4 Estrazione della targa dallimmagineUna volta ottenute le immagini dal blocco del flusso ottico devo capire in quali di queste è presenteuna 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 ariconoscere un oggetto contenuto in unimmagine qualsiasi.Il riconoscimento di uno o più oggetti noti all’interno di un’immagine (Object Detection) è uncompito difficile. Mentre per noi è facile ad esempio eseguire una “segmentazione semantica” dellascena (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..), unmeccanismo di ricerca automatico soffre di molte limitazioni. Per esempio, possono esservi diversifattori 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 esempidi proprietà fondamentali di un oggetto sono le seguenti: 1. - Simmetria 2. - Colore 3. - Ombre 4. - Angoli e BordiSi noti che le prime tre possono essere fallaci: la simmetria è disturbata dalle trasformazionigeometriche viste sopra, il colore è spesso determinato dalla scena in maniera preponderante, cosìcome le ombre. Altri elementi invece offrono una maggiore affidabilità. Il training, ovverol’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 caratteristichescelte, si tramutano nei parametri del modello e se durante la fase di classificazione si riscontra unfalso positivo, il modello è aggiustato. Illustreremo ora un metodo per la creazione di un modello edil 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. 4.1 Haar-training classificatori di Viola e JonesLe Basi di Haar sono la prima forma di Wavelet (funzioni matematiche usate per trasformareun’immagine nel dominio della frequenza, allo scopo di comprimela o analizzarlasuccessivamente ) proposta dall’omonimo matematico. Usare delle features di Haar vuol direeffettuare un calcolo simile a quello per i coefficienti delle wavelet, usando un loro sottoinsieme perclassificare e definire la “forma” di un oggetto. Le features di Haar comunemente usate sonoriportate nella figura sotto: il valore di una feature è la differenza tra la somma del livello di grigiodei pixel nella zona nera, e la somma del livello di grigio nella zona bianca. Queste somme sicalcolano 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 menolaboriosi 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, scalatiad una dimensione fissa, e convertiti in scala di grigi, che andranno a formare il data set. Su ognunadi 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 sonodefiniti ognuno come 16
  • 19. dove fi è una feature, x è la finestra di ricerca (variabile), θ è una soglia, e pj indica la direzione delladisuguaglianza.Per quanto riguarda il riconoscimento, i passi eseguiti dal metodo Viola-Jones sono illustrati nellafigura sottostante i valori dei classificatori vengono computati su una finestra che si spostaall’interno dell’immagine, e si valutano i risultati di una cascata di weak classifiers. Il voto pesatodato ad ogni classificatore, serve per crearne uno totale, come ad esempio:Uno dei procedimenti più noti è quello chiamato AdaBoost, che sta per “AdaptiveBoost” e consistenel ripetere iterativamente la clusterizzazione del weak classifier, dando un peso maggiore ai falsipositivi presenti nel cluster, e un peso minore (negativo) a tutti gli altri. Alla fine la combinazionelineare dei classificatori darà la clusterizzazione cercata. Ogni iterazione corrisponde a quello chenella guida chiameremo stage. I passi del metodo Viola-Jones 17
  • 20. Il metodo AdaBoost.Dal punto di vista dell’analisi computazionale, sebbene il metodo in esame sia nettamente miglioredi un confronto pixel a pixel, è comunque decisamente oneroso. Supponendo immagini didimensione 320x240, con finestre 24x24 ed una velocità di 15 frame al secondo, si hanno oltre500.000 finestre totali nelle quali computare le features. Moltiplicando il tempo necessario ad unaclassificazione completa per il numero di immagini nel training set, si raggiungono facilmenteordini di grandezza di giorni, su macchine anche molto potenti. Non si hanno più problemi di coloreo luminosità ci si basa sulla forma dell’oggetto. Tuttavia il numero di falsi positivi rischia dirappresentare 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 datidallo sfondo, questo va inserito tra gli esempi negativi, e le altre immagini devono comunquefornire una statistica sufficiente. Resta però la possibilità che qualcosa intorno all’oggetto finiscaper essere riconosciuto come l’oggetto stesso. 18
  • 21. 4.2 ImplementazioneLo scopo di questo procedimento è creare un file XML contenente le caratteristiche delloggetto chevolgiamo riconoscere, nel nostro caso una targa. Questo file verrà poi passato ad opportune funzionimesse a disposizione di OpenCV che produrranno come output una serie di rettangoli identificati dacoordinate, larghezza e lunghezza che indicano dove si trova loggetto dentro limmagine.Come prima cosa dobbiamo ricercare immagini dette “positive” cioè contenenti loggetto chevogliamo riconoscere nel nostro caso una targa. Possiamo scegliere due diverse strade sul formatodi queste immagini: o loggetto in questione è lunica cosa che appare nellimmagine oppureloggetto è contenuto nellintera immagine. Se prendiamo come esempio una targa, o prendiamounimmagine contenente solo questultima o con lintera 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 contenentiloggetto. Trovare queste è meno laborioso: ci sono database di immagini pubblici sul web fattiappositamente, per il mio progetto ho utilizzato questo:http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2008/VOCtrainval_14-Jul-2008.tarSuccessivamente dovremo creare un “file di raccolta” che essenzialmente è un file di testo cheservirà 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 leimmagini 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 comandodi shellfind [image dir] -name *.[image ext] > [description file]Come vantaggio questo procedimento ha la velocità con cui si crea il file di raccolta ma bisognatagliare 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 alloggetto inquestione e utilizzare i frame, però non cè un modo per ottenere il file di raccolta velocemente anzisi deve specificare per ogni immagine quante targhe ci sono, le coordinate del loro punto in alto asinistra, 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 stessalarghezza e lunghezza e memorizzarle in unico file come anteprime. 19
  • 22. OpenCV mette a disposizione un programma chiamato createsamples che viene installato durante lacompilazione di openCV e svolge funzioni differenti utili per il training a seconda dei parametricon cui viene invocato.Sostanzialmente questo programma viene usato per creare i campioni con cui verrà effettuato ilprocesso di training ma può essere utile anche per visualizzare tali campioni o creare immagini ditest.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 statiridimensionati a seconda dei parametri w e h. Questo file è formato da un header contenente ilnumero 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 immagineCome da titolo questa prima funzione permette di creare campioni da una singola immagineapplicando distorsioni. Questa funzione è lanciata quando i parametri img, bg e vec sono specificati.-img è limmagine 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 20Per il mio progetto questa funzione si è dimostrata molto comoda in quanto da una sola immagineapplicando 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 hogenerato 50 campioni diversi per ogni immagine in modo da avere circa 7000 campioni positivi,numero richiesto per un buon training. 20
  • 23. Per rendere il procedimento ancora più veloce ho creato questo semplice script che fa questultimaoperazione in automatico:#!/bin/shfor i in *.png; doecho "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;doneOra però ho un file vec per ogni immagine positiva quindi bisogna lanciare lutility mergevec cheprende come input un file di raccolta vec (che si ottiene facilmente visto che lestensione è semprevec) 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ù immaginiStavolta la creazione del file vec avviene attraverso una moltitudine di campioni quindi va creato unfile di raccolta adatto con indicato dove si trova loggetto dentro limmagine. Nel caso speciale incui si abbiano già alcune migliaia di immagine ritagliate allora cè un metodo semplice per creare unfile di raccolta ad hoc in quanto loggetto 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 diraccolta, e vec che rappresenta il file dove riporre i risultati.Esempio di chiamata:$ createsamples -info samples.dat -vec samples.vec -w 20 -h 20Funzione 3: Creazione immagini di testLe immagini di test non sono altro che foto identificate come negative con incollate sopra immaginipositive in posizione e inclinazione casuale. La creazione di queste immagini può esser molto utileper sperimentare la resa del nostro training atraverso un altro programma fornito da OpenCV che sichiama 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 0il parametro specificato dopo “num” indica il numero di immagini negative da utilizzare. 21
  • 24. Funzione 4: Mostrare il contenuto del file vecQuestultima funzione ci mostra il contenuto del nostro file vec generato e si invoca quando solo ilparametro vec viene specificato.$ createsamples -vec samples.vec -w 20 -h 20 22
  • 25. TrainingOra siamo pronti per usare lutility 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 loggetto in questione non ha una simmetria da destra a sinistracome nel nostro caso, il “-mode ALL” consente si usare tutte le feature ed infine il parametro memindica 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 alloradesideri 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 rilevicome 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 quellodesiderato 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 è dicirca 3 giorni.Una volta finito loutput è una serie di cartelle (una per ogni stage) contenute nella directoryspecificata in precedenza dopo il parametro data.Per creare il file XML che servirà poi al nostro applicativo OpenCV per procedere col detectingusiamo questo comando:convert_cascade –size=”<w>×<h>″ <dir data name> <XML file name> 23
  • 26. 4.3 Risultati e statisticheDopo aver eseguito il training ho sviluppato unapplicazione che rileva e salva le targhe daunimmagine 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 targaElaborando tutte le immagini provenienti dal blocco che si occupa della rilevazione del motoproduco immagini contenenti solo il rettangolo con la targa ove è presente salvandole sempre con laposizione temporale in cui appaiono nel video in millisecondi.Dopo il processo di training molti dei falsi positivi vengono scartati anche se qualcuno rimane. Seper una macchina vengono scattate 2 o più foto che includono la targa il sistema non è in grado diriconoscere 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 videocon 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 lamedia dei falsi positivi al numero di targhe medio rilevato otteniamo il numero di targhe effettiverilevate che è circa 18,28 stavolta minore del numero di macchine che passano che ricordo essere20,72.Tornando allesempio della telecamere del capitolo precedente dove su 236 foto 44 includevano unatarga, 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 cometerzo parametro quando si chiama il programma. Esempio di targhe rilevate 24
  • 27. 5 OCRCome ultimo passo bisogna passare le immagini ottenute ad un sistema OCR (Optical CharacterRecognintion) ma prima di fare questo necessitano di un pre-processamento.5.1 Pre-processamento immaginePer rendere più facile il compito allOCR prima di tutto converto limmagine in scala di grigi eapplico 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 daOpenCV.Il concetto di base del Threshold è quello di confrontare il livello di grigio di ogni pixeldellimmagine e di settarlo a 0 (nero) o a 255 (bianco) a seconda se è minore o maggiore di unacerta soglia.LAdaptive Threshold è une tecnica più raffinata in cui è il valore stesso della soglia a variare. Inquesto caso, la soglia è calcolata in base a una media pesata relativa alle informazioni di unaregione quadrata costruita attorno al pixel considerato a meno di una costante arbitraria. Immagine convertita in scala di grigi e dopo lo Sharpening 25
  • 28. Immagine binarizzataSempre attraverso funzioni fornite da OpenCV trovo i contorni e i rettangoli che li delimitano. Vistoche 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 rettangoloUna volta effettuata la segmentazione della targa faccio processare allOCR ogni carattere uno aduno. 26
  • 29. 5.2 Risultati con OCR Open SourceCome software OCR ho scelto Tesseract di Google in quanto è uno dei più affidabili in rete inoltre èanchesso un progetto open source[14].Tutti i rettangoli corrispondenti trovati vengono salvati come una singola immagine e processati daTessercat attraverso il comandotesseract -psm 10 <nome input> <nome output>Il parametro “psm” indica che limmagine verrà processata come se al suo interno fosse contenutoun singolo carattere.Il risultato è il seguente:D 0 l S 9 L RIl 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 corrispondeal 34,86% della targa.In più in alcune ci sono pezzi della targa, come le bande blu laterali, che a causa di luminosità edimensione vengono scambiate per caratteri generando così un altro tipo di errore. 27
  • 30. 5.3 Risultati con OCR SharewareCome 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 ilcampione 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 destraRicavando le statistiche dai risultati ottenuti si scopre che stavolta lOCR commette in media 3,36errori per targa che sono circa il 48%. 28
  • 31. 6 Conteggio persone in unareaLalgoritmo opera su dei video di 10 minuti ripresi da una telecamera fissa su una piazza e si occupadi contare le persone presenti nel frame e contare quante sono allinterno di unarea definitadallutente e quali no.Il componente chiave per questa applicazione è messo a disposizione dalle librerie ed è chiamatoHOG (Histogram of Oriented Gradient). A grandi linee è un componente dedito al riconoscimentodi pedoni o figure umane in unimmagine.6.1 HOG ProcessingIl metodo si basa sulla valutazione di istogrammi calcolati sulla base dellorientazione dei gradientidellimmagine di input. Lidea è che i margini e le forme di oggetti possono essere ben caratterizzatidallintensità locale dei singoli gradienti[16].La computazione dei singoli istogrammi è ottenuta dividendo limmagine in una griglia di celle eper 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 larisposta dalle condizioni di luminosità dellimmagine, può essere utile normalizzare i singoliblocchi.Dalle informazioni ricavate dai singoli blocchi si ottiene poi un descrittore che verrà utilizzato perla detection.Le precedenti operazioni vengono eseguite su finestre di dimensione finita che analizzanolimmagine a più scale. Per ogni finestra si ottiene quindi un descrittore che viene poi passato ad unclassificatore SVM lineare che fornisce la predizione sulla presenza meno di un pedone. Essendo leprocedure di costruzione dei singoli descrittori indipendenti tra loro, è possibile eseguirlecontemporaneamente con una gestione multithread. In questa modo si ottiene un notevolemiglioramento delle prestazioni.La robustezza dellalgoritmo utilizzato dallSVM e laccurata scansione dellimmagine a più scaleproducono spesso unaddensamento di finestre di detection per ogni singolo pedone, quindi ènecessaria unoperazione di fusione (mean-shift) che porti allindividuazione di ununica finestrafinale.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 iseguenti passi di elaborazione: 1. Computazione dei gradienti dellimmagine; 2. Costruzione degli istogrammi; 3. Normalizzazione dei blocchi; 4. Scansione dellimmagine e costruzione dei descrittori; 5. Classificazione dei descrittori tramite SVM lineare; Le fasi del metodo HOG 29
  • 32. 6.1.1 Computazione dei gradientiIl gradiente di unimmagine può essere semplicemente ottenuto filtrandola con due filtrimonodimensionali: un filtro verticale e un filtro orizzontale. Questa procedura mette in evidenza leregioni dellimmagine in cui si ha una maggiore variazione di luminosità. Esse si trovanogeneralmente in prossimità dei margini di un oggetto e possono essere quindi utilizzati per metter inevidenza la sagoma di una persona.I filtri utilizzati sono i seguenti: 1. Orizzontale: Dx=(-1, 0, 1) 2. Verticale: Dy=(-1, 0, 1)TQuindi data unimmagine I, calcoliamo la derivata rispetto allasse x e allasse y usando leoperazioni di convoluzione: Ix= I * Dx Iy= I * DyPer ogni pixel otteniamo quindi il vettore gradiente:La figura mostra un esempio di applicazione del filtro per lottenimento del gradientedellimmagine. La figura a) mostra limmagine originale, la figura b) mostra il risultatodellapplicazione del filtro orizzontale e la figura c) mostra il risultato dellapplicazione del filtroverticale.I gradienti possono essere considerati con segno o senza segno. Questultimo caso è giustificato dalfatto che la direzione del contrasto non ha importanza. In altre parole, noi avremmo il solitorisultato analizzando un oggetto bianco su sfondo nero o viceversa un oggetto nero su sfondobianco.Il filtro utilizzato per lottenimento dei gradienti è uno dei più semplice ma anche uno dei piùefficaci; esistono tuttavia altre maschere più complesse che possono essere utilizzate per ottenere ilgradiente dellimmagine da analizzare: uno di questi è il filtro di Sobel.Nel caso di immagini a colori viene inoltre effettuata unoperazione preliminare di conversione ascala di grigi; questo per evitare di dover considerare un contributo di intensità diverso per ognipiano di colore (RGB). 30
  • 33. 6.1.2 Costruzione degli istogrammiLa seconda fase della procedure consiste nella costruzione degli istogrammi sulla base dei gradienticalcolati al passo precedente. Innanzi tutto limmagine viene suddivisa in celle. Un cella è definitacome una regione dello spazio che assume una certa forma e dimensione. Le celle possono essererettangolari o circolari e i canali di ogni istogramma sono distribuiti su 0°-180° (gradienti senzasegno).Per ogni cella viene costruito un istogramma accumulando allinterno dei canali i voti dei singoligradienti. Se per esempio vogliamo costruire istogrammi distribuiti su 0°-180° con un numero dicanali pari a 4 la votazione per la costruzione dellistogramma avviene nel seguente modo: ● Tutti i gradienti della cella con angolo compreso nellintervallo [0°-45°) forniscono il loro voto per il primo canale; ● Tutti i gradienti della cella con angolo compreso nellintervallo [45°-90°) forniscono il loro voto per il secondo canale; ● Tutti i gradienti della cella con angolo compreso nellintervallo [90°-135°) forniscono il loro voto per il terzo canale; ● Tutti i gradienti della cella con angolo compreso nellintervallo [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 ilmagnitudo, la sua radice quadrata o il suo quadrato.In questo modo ogni istogramma è calcolato tenendo conto dellimportanza di un gradiente in undeterminato punto. Questo è giustificato dal fatto che un gradiente attorno a un margine di unoggetto in genere è più significativo di un punto in una regione uniforme dellimmagine.Ci aspettiamo quindi che più canali ci sono più dettagliati saranno gli istogrammi. Quando tutti gliistogrammi sono stati creati, possiamo costruire il descrittore dellimmagine concatenando tutti gliistogrammi in un singolo vettore.In ogni caso, a causa di eventuali variazioni di luminosità nellimmagine è opportuno normalizzarele 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. 6.1.3 Normalizzazione dei blocchiCome accennato nella sezione precedente prima di creare i descrittori è necessaria una fase dinormalizzazione, questa a causa delle variazioni di luminosità che ci possono essere inunimmagine.La normalizzazione degli istogrammi è fatta a partire da gruppi di celle detti blocchi. Per ogniblocco viene calcolato un fattore di normalizzazione e tutti gli istogrammi nel blocco sononormalizzati in base a tale fattore. Il descrittore finale è quindi rappresentato dal vettore dellecomponenti 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 diR-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 disovrapposizione (overlap) dei blocchi. Ciò significa che blocchi tra loro adiacenti condividono uncerto 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 overlappari 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. Inquesta caso può sembrare che il descrittore finale contenga informazioni ridondanti ma in realtàlutilizzo delloverlap può in alcuni casi migliorare le prestazioni. 32
  • 35. Schemi di normalizzazionePer effettuare la normalizzazione degli istogrammi nei singoli blocchi possono essere utilizzatidiverse tecniche.Sia v il vettore descrittore normalizzato, ||v||k la sua norma k-esima ed ε una piccola costante cheserve 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 limmagine a più scale utilizzando finestre didimensione finite. Per ogni finestra otteniamo, mediante i passi finora descritti, un descrittore chepuò essere infine classificato da un classificatore SVM lineare. I descrittori sono ottenuti scorrendola relativa finestra dallaltro verso il basso, da sinistra verso destra e accodando gli istogrammi dellesingole 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 predizionesulla presenza o meno di una persona allinterno. Pertanto i descrittori vengono classificatiutilizzando un SVM lineare precedentemente allenato. 33
  • 36. 6.1.5 Classificatore SVMImposto un classificatore SVM (Support Vector Machine) che corrisponde ad un algoritmo diclassificazione mediante apprendimento e supervisione. Per fare in modo che un classificatore“apprenda” cosa deve cercare nellimmagine bisogna prima sottoporlo ad una fase detta di trainingdove gli si danno in input un certo numero di immagini positive e negative (contenenti o nologgetto 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 immaginipositive e due migliaia di negative.6.1.6 Miglioramento prestazioniLe procedure di rilevazione finora analizzate costituiscono unimplementazione base del metodoHOG. Possono infatti essere ulteriormente migliorate le prestazioni con tecniche addizionali.Innanzitutto il metodo base prevede la creazione sequenziale dei vari descrittori quando si scorrelimmagine con la finestra di detection; questa gestione può essere notevolmente migliorataintroducendo 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 dellimmagineportano ad avere molte finestre di rilevazione nelle prossimità di una persona. È quindi necessariauna procedura di fusione per lottenimento di una finestra unica. Lalgoritmo utilizzato è il mean-shift.Lobbiettivo finale della detection è la localizzazione esatta di una persona che appare inunimmagine. Limplementazione di base del metodo HOG prevede una scansione dellimmagine,con una finestra di detection, a tutte le posizione di scale, eseguendo un classificatore SVM per ognifinestra e disegnando un opportuno rettangolo nel caso il classificatore etichetti come positiva lafinestra stessa. Il risultato finale dovrebbe essere quindi dato dalla presenza di una fitta serie direttangoli attorno alloggetto rilevato. È pertanto necessario un metodo di fusione di tali finestremultiple, per questo si usa lalgoritmo di mean-shift[18]. 34
  • 37. 6.2 ImplementazioneQuesta parte di progetto al contrario del rilevamento targhe, è stata realizzata sotto sistemaoperativo Windows 7 perché il componente HOG utilizzato funziona solo se compilato con certelibrerie DLL esterne fornite solo per Windows.Come prima cosa istanzio una variabile facente parte della classe HOGDescriptor che contiene tuttii metodi necessari alle operazione che vogliamo svolgere.cv::HOGDescriptor hog;Imposto un classificatore SVM (Support Vector Machine) che mi mette a disposizione OpenCVhog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());Preparo le strutture dati necessarie dove riporre i risultati del detecting dove res è il frame sul qualeverrà 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 è listruzione che va ad attuare lalgoritmo e si può cambiare qualche parametroper 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 trovatifor( 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 prestareattenzione 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 quantodevo raggruppare perchè può darsi che per una persone trovo più finestra vicine tra loro. 35
  • 38. 6.2.1 Background substractingIl Background-substracting è una tecnica usata per distinguere in una scena le forme in movimentorispetto agli oggetti statici di sfondo.Durante le prime prove di people detect ho notato che venivano rilevati come persone anche figurefacenti parte dello sfondo come paletti o pezzi di lampione. Esempio di errore del detectorCome prima cosa ho provato ad agire sui parametri del metodo detectMultiScale: aumentando lahitThreshold,e quindi diminuisce la hit rate, fino ad eliminare quasi completamente il problema deifalsi positivi ne risente molto anche il riconoscimento di persone: si abbassa notevolmente ildetecting di individui che sono lontane, vicino ad oggetti o confondibili con lo sfondo.Unaltra soluzione meditata era quella di crearsi una statistica frame dopo frame delle aree e dellecoordinate dei rettangoli errore. Questo procedimento ha come ipotesi di base però che questirettangoli 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, laluminosità cambia radicalmente e con lei anche la posizione e le forme dei rettangoli errore. Anchequesta ipotesi, come quella precedente, è da scartare. 36
  • 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 deglioggetti in movimento e lHOG detector agirà su questo. In questo modo posso mantenere una hitrate abbastanza alta in quanto non cè più il problema degli oggetti sullo sfondo.Rimane il problema di trovare unimmagine di sfondo adeguata che è fondamentale affinché ilprocesso vada a buon fine. Il problema principale è che unimmagine statica di sfondo dopo un lassodi 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 dallipotesi che un pixel varia in modo normale da un frame allaltro se fa parte del background. Questo metodo ovvia il fatto dellimmagine di sfondo statica ma è stato scartato in quanto richiede molto tempo di elaborazione per ogni singolo frame (e già ne impiega abbastanza lhog 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 allutente unimmagine di sfondo di partenza e aggiornarla ogni qual volta che lhog detector rileva zero presenze: il problema è che non ci possiamo affidare troppo al detector basterebbe sbagliare una volta e lerrore 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. Lutente poi sceglie quale utilizzare tra quelle trovate. In questo modo il processo non è più completamente automatico in quanto si introduce una scelta che solo unumano può compiere. Se in quei N frame non cè nessuna immagine libera per far da sfondo si può ripetere loperazione 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 limmagine di sfondo invoco la funzione fornita da OpenCV cvAbsDiff cheprende in input due frame, ne fa la differenza assoluta pixel a pixel e pone il risultato un un terzoframe. 37
  • 40. Frame risultante dopo la sottrazione di sfondo 38
  • 41. 6.2.2 Definizione dellareaBisogna anche rendere possibile allutente di definire unarea nella quale ricercare le persone. Ilprogramma poi deve riconoscere per ogni rilevamento se si trova allinterno o allesterno dellareadefinita.Per rendere più facile e intuitiva questa operazione ho impiegato anche luso di funzioni checoinvolgono il mouse. Attraverso la procedura cvSetMouseCallback dichiaro che ad ogni azionecompiuta dal mouse, quale movimento, click o anche il rilascio del tasto, vado a richiamare unfunzione costruita da me.A questa funzione oltre ai parametri da me inseriti, in aggiunta vengono passati in automatico 3interi che rappresentano lascissa e lordinata dellevento nel frame ove è stato fatto, e a quale eventoci stiamo riferendo.Queste le possibili combinazioni:Flag Descrizione evento(è una costante di tipo int)CV_EVENT_MOUSEMOVE Movimento del mouseCV_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 rilasciatoCV_EVENT_RBUTTONUP Tasto destro rilasciatoCV_EVENT_MBUTTONUP Tasto centrale rilasciatoCV_EVENT_LBUTTONDBLCLK Click tasto sinistroCV_EVENT_RBUTTONDBLCLK Click tasto destroCV_EVENT_MBUTTONDBLCLK Click tasto centraleCV_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 SHIFTCV_EVENT_FLAG_ALTKEY Dice se contemporaneamente è premuto anche il tasto ALTTornando al progetto il programma attende 3 click da parte dellutente attraverso i quali definisce 2rette: la prima retta passa tra i primo e il secondo punto, mentre la seconda retta tra il secondo e ilterzo. Definisco anche 4 quadranti virtuali che hanno origine nel secondo punto. 39
  • 42. Ora sapendo le coordinate dei punti allinterno del frame posso calcolarmi lequazione delle 2 rette.Chiamando P1 il primo punto e P1.x,P1,y le sue coordinate lequazione della prima retta si calcolain questo modo:Una volte ricavate le equazioni delle rette ed i 4 quadranti posso calcolare inanzi tutti qualiquadranti fanno completamente parte della mia aerea di ricerca, mentre in quelli dove giace unaretta devo confrontare se il punto trovato sta sopra o sotto facendo una semplice proiezione negliassi. Il punto di un oggetto rilevato che confronto con le rette è langolo in basso a destra delrettangolo trovato dal detector che contorna una persona. Esempio di come lo schermo viene suddiviso in quadranti una volta definite le 2 rette 40
  • 43. Per capire in quale quadrante si trova la persona rilevata devo eseguire un confronto tra lecoordinate dei punti definiti dallutente e quelle rilevate dal mio detector. Ricordo che quando unapersona è rilevata viene contornata da un rettangolo e il punto che poi verrà preso in esame èlangolo in basso a destra di questultimo.Prima di tutto identifico i quadranti con un numero a partire dal quadrante in alto a sinistra comemostrato in nelle figure sottostantiOra gestisco il caso più semplice: quando i tre punti sono uno a destra dellaltro per cui lascissa delpunto 3 sarà maggiore dellascissa del punto 2 che è a sua volta maggiore dellascissa del punto 1.Se il primo punto è più in basso del secondo allora posso affermare che se una persona transita nelprimo quadrante di sicuro non cade nella mia area di ricerca. Se una persona invece viene rilevatanel terzo quadrante devo controllare se è sopra o sotto la linea identificata dal primo e secondopunto. 41
  • 44. Viceversa se il il punto 1 è più in alto del secondo posso dire che tutto il terzo quadrante è validoper la mia definizione di area mentre devo controllare nel primo se sono sopra o sotto la lineadefinita dal punto 1 e 2.Gestiti il primo e terzo quadrante eseguo il controllo duale con il punto 2 e 3 per quanto riguarda ilsecondo e quarto quadrante: 42
  • 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 noperché se i segni sono concordi allora le due rette sono nello stesso quadrante mentre nel caso deisegni discordi le due rette giacciono in due quadranti diversi che sono il primo e il terzo (il secondoe il quarto vengono sempre esclusi). Caso in cui i coeff. hanno segni discordi Caso in cui i coeff. hanno segni concordi 43
  • 46. Qualche screenshot di definizione dellarea presi dal programma in esecuzione: Esempio di area rettangolare Esempio di area a cono 44
  • 47. 6.3 Considerazioni e risultatiCome detto in precedenza utilizzando la tecnica di background-substracting e agendo sui parametridel classificatore SVM lineare, si ottengono ottimi risultati anche se alcuni tipi di errore, come adesempio larea di detecting molto affollata, dove le sagome umane si sovrappongono, persistono.Ricordo inoltre che questo metodo si basa principalmente sulla bontà dellimmagine di sfondorilevata. Trovando unimmagine di sfondo non adeguata infatti si compromette lintero svolgimentodel processo. 45
  • 48. 7 ConclusioniLa prima conclusione che si può trarre è che per quanto riguarda lalgoritmo di ricognizione targheil tasso di caratteri non riconosciuti o errati è troppo alto per essere affidabile, anche nel casomigliore analizzato usando Tesseract.Questo è dovuto sicuramente alle proprietà dellimmagine come la luminosità, la definizione olinclinazione. Prendendo infatti limmagine di una targa più definita e non inclinata tutti e due gliOCR riescono nel loro impiego anche senza segmentare limmagine. ABBY FineReader in azione con una targa più definitaPer migliorare questultima parte del procedimento bisognerebbe agire a livello hardware,sostituendo le telecamere usate con altre più sofisticate, oppure applicare lutilizzo di duetelecamere: una che riprende dallalto per rilevare il moto, e unaltra situata ad altezza targa adibitaesclusivamente a scattare foto tutte le volte che laltro dispositivo rileva del movimento.Per quanto riguarda la prima parte del processo, cioè la Motion Detect e lestrazione della targadallimmagine, sono stati raggiunti risultati soddisfacenti infatti guardando le statistiche ottenute sulcampo si può notare che se in media passano 20,72 in 10 minuti vengono rilevate 18, 28 targhe,cioè l88,22% delle auto che transitano vengono processate correttamente.Riguardo allalgoritmo del conteggio di persone nelle piazze un possibile miglioramento può esserequello di eseguire il tracking delle persone e non solo il detecting, cioè ad ogni pedone rilevatoviene assegnato un identificatore che viene mantenuto frame dopo frame. Questo è possibileattraverso limplementazione del filtro di Kalman[19] applicabile ad un sistema dinamico.In generale dopo questa esperienza posso affermare che la videoanalisi è un mondo molto ampio ecomplicato 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 dellatarga risultato dallhaartraining, 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. AppendiceRileva_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. 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. //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. cvReleaseImage(&pyrB);}//funzione che mostra un frame diviso in quadrati numerati così da poterscegliere quello dinteresse./*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(255,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(255,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. 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 quandranteflag_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. 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. //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. 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 useHaar or LBP features.n" "This classifier can recognize many ~rigid objects, its most knownuse is for faces.n" "Usage:n" "./facedetect [--cascade=<cascade_path> this is the primary trainedclassifier such as frontal face]n" " [--nested-cascade[=nested_cascade_path this an optionalsecondary classifier such as eyes]]n" " [--scale=<image scale greater or equal to 1, try 1.3 forexample>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. 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 nestedobjects" << 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
  • 58. //cerr << "ERROR: Could not load classifier cascade" << endl; //cerr << "Usage: facedetect [--cascade=<cascade_path>]n" // " [--nested-cascade[=nested_cascade_path]]n" // " [--scale[=<image scale>n" // " [filename|camera_index]n" << endl ; return -1; } if( inputName.empty() || (isdigit(inputName.c_str()[0]) && inputName.c_str()[1] == 0) ) { capture = cvCaptureFromAVI("/home/andrea/Scrivania/dai/8.mp4"); int c = inputName.empty() ? 0 : inputName.c_str()[0] - 0 ; //if(!capture) cout << "Capture from CAM " << c << " didnt work" <<endl; //cvWaitKey(0); } else if( inputName.size() ) { image = imread( inputName, 1 ); if( image.empty() ) { capture = cvCaptureFromAVI( inputName.c_str() ); // if(!capture) cout << "Capture from AVI didnt work" << endl; } } else { image = imread( "lena.jpg", 1 ); // if(image.empty()) cout << "Couldnt read lena.jpg" << endl; } //cvNamedWindow( "result", 1 ); if( capture ) { //cout << "In capture ..." << endl; for(;;) { IplImage* iplImg = cvQueryFrame( capture ); frame = iplImg; if( frame.empty() ) break; if( iplImg->origin == IPL_ORIGIN_TL ) frame.copyTo( frameCopy ); else flip( frame, frameCopy, 0 ); detectAndDraw( frameCopy, cascade, nestedCascade, scale ); if( waitKey( 10 ) >= 0 ) goto _cleanup_; } // waitKey(0);_cleanup_: cvReleaseCapture( &capture ); } else 56
  • 59. { //cout << "In image read" << endl; if( !image.empty() ) { detectAndDraw( image, cascade, nestedCascade, scale ); //waitKey(0); } else if( !inputName.empty() ) { /* assume it is a text file containing the list of the image filenames to be processed - one per line */ FILE* f = fopen( inputName.c_str(), "rt" ); if( f ) { char buf[1000+1]; char nomejpg[1000+1]; while( fgets( buf, 1000, f ) ) { int len = (int)strlen(buf), c; while( len > 0 && isspace(buf[len-1]) ) len--; buf[len] = 0; strcpy(nomejpg,buf); int lenjpg = (int)strlen(nomejpg); //char nome[200]=""; char app[5]=""; int con=0; //char a; while (nomejpg[lenjpg-con]!=/){ nome[con]=nomejpg[lenjpg-con-1]; con++; } nome[con]=0; //cout << "file " << buf << endl; image = imread( buf, 1 ); if( !image.empty() ) { reverse(nome,strlen(nome)); detectAndDraw( image, cascade, nestedCascade, scale ); // c = waitKey(0); if( c == 27 || c == q || c == Q ) break; } else { //cerr << "Aw snap, couldnt read image " << buf <<endl; } } fclose(f); } } } //cvDestroyWindow("result"); return 0;} 57
  • 60. int rect_in_frame(cv::Rect r, IplImage* frame){ if((r.tl().x>=0) && (r.tl().x<=frame->width) && (r.tl().y>=0) &&(r.tl().y<=frame->height) && (r.br().x>=0) && (r.br().x<=frame->width) &&(r.br().y>=0) && (r.br().y<=frame->height)) if ((r.width >= 0) && (r.height >= 0) && (r.x < frame->width) && (r.y <frame->height) && (r.x + r.width >= (int)(r.width > 0)) && (r.y + r.height >=(int)(r.height > 0))) { return 1; }else{ return 0; }}void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale){ int i = 0; double t = 0; char s[100]=""; char n[5]=""; vector<Rect> faces; const static Scalar colors[] = { CV_RGB(0,0,255), CV_RGB(0,128,255), CV_RGB(0,255,255), CV_RGB(0,255,0), CV_RGB(255,128,0), CV_RGB(255,255,0), CV_RGB(255,0,0), CV_RGB(255,0,255)} ; Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale),CV_8UC1 ); cvtColor( img, gray, CV_BGR2GRAY ); resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); equalizeHist( smallImg, smallImg ); t = (double)cvGetTickCount(); cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); t = (double)cvGetTickCount() - t; //printf( "detection time = %g msn", t/((double)cvGetTickFrequency()*1000.)); for( vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++,i++ ) { Mat smallImgROI; cv::Rect rt = r[i]; vector<Rect> nestedObjects; Point center; Scalar color = colors[i%8]; //int radius; 58
  • 61. //center.x = cvRound((r->x + r->width*0.5)*scale); //center.y = cvRound((r->y + r->height*0.5)*scale); //radius = cvRound((r->width + r->height)*0.25*scale); //circle( img, center, radius, color, 3, 8, 0 ); //rt.x += cvRound(rt.width*0.1); //arrotondamenti rettangolo //rt.y += cvRound(rt.height*0.1); //rt.width = cvRound(rt.width*0.8); //rt.height = cvRound(rt.height*0.8); rectangle(img, rt.tl()*scale, rt.br(), color, 1, 8, 0); IplImage* img1 = new IplImage(img); if (rect_in_frame(rt, img1)){cvSetImageROI(img1,rt);} IplImage *img2 = cvCreateImage(cvGetSize(img1),img1->depth,img1->nChannels); cvCopy(img1, img2, NULL); cvResetImageROI(img1); strcpy(s,path_img); strcat(s,"targhe"); strcat(s,nome); //sprintf(n,"%d",cc); //strcat(s,n); //strcat(s,".jpg"); if (i==0){cvSaveImage(s,img2); cc++;} if( nestedCascade.empty() ) continue; smallImgROI = smallImg(*r); nestedCascade.detectMultiScale( smallImgROI, nestedObjects, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH //|CV_HAAR_DO_CANNY_PRUNING |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator nr = nestedObjects.begin(); nr !=nestedObjects.end(); nr++ ) { //center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale); //center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale); //radius = cvRound((nr->width + nr->height)*0.25*scale); //circle( img, center, radius, color, 3, 8, 0 ); } } //cv::imshow( "result", img );} 59
  • 62. OCR.cpp#include <cv.h>#include <highgui.h>#include <stdio.h>#include <time.h>#include <stdio.h>#include <stdlib.h>#include <opencv/highgui.h>#include <opencv2/highgui/highgui_c.h>#include <opencv2/imgproc/imgproc_c.h>#include <cxcore.h>#include <string.h>#include <ctype.h>int levels = 78;IplImage* frame_c; //spazio immagine a coloriIplImage* frame; //spazio immagine in scala di grigiIplImage* work; //spazio immagine lavorata (sharp, zoom, threshold)IplImage* framecont; //spazio immagine dappoggioIplImage* panel; //spazio immagine per i contorniIplImage* cnt_img; //spazio immagine dappoggio per la threshold barIplImage* mask;int flag=0; //per sapere se ho lavorato sul threshold con la trackbar o noCvSeq* contours = 0;CvMemStorage *mem=cvCreateMemStorage(0);CvSeq* contorno;CvSeq* contornolow;CvPoint p1,p2;CvRect rect;int cc=0;char* itoa(int val, int base){ static char buf[32] = {0}; int i = 30; for(; val && i ; --i, val /= base) buf[i] = "0123456789abcdef"[val % base]; return &buf[i+1];}void Sharpening( IplImage* img )//funzione di "affilamento" per migliorare la definizione dellimmagine{ int w = img->width; int h = img->height; 60
  • 63. IplImage* gray2 = cvCreateImage( cvGetSize(img) , 32, 1 );cvConvertScale( img, gray2, 1.0 );IplImage* lapl = cvCreateImage( cvGetSize(img) , 32, 1 );CvMat* ker = cvCreateMat( 3, 3, CV_32FC1); cvSet( ker, cvScalarAll( -1.0 ) ); cvSet2D( ker, 1, 1, cvScalarAll( 15.0 ) );cvFilter2D( gray2, lapl, ker );cvReleaseMat( &ker );double maxv = 0.0; double minv = DBL_MAX;cvMinMaxLoc( lapl, &minv, &maxv );for( int i=0; i<w*h; i++ ) { double lap_val = cvGet1D( lapl, i ).val[0]; int v = (int)( (255.0*lap_val/ maxv) +0.5); cvSet1D( img, i, cvScalarAll( v) ); }maxv = 0.0;cvMinMaxLoc( img, &minv, &maxv );for( int i=0; i<w*h; i++ ) { double val = cvGet1D( img, i ).val[0]; int v = (int)( (255.0*val/maxv) + 0.5); cvSet1D( img, i, cvScalarAll( v) ); }cvReleaseImage( &gray2 );}void on_trackbar(int pos){ flag=1; cnt_img = cvCreateImage( cvSize(work->width,work->height), IPL_DEPTH_8U,1 ); cnt_img=cvCloneImage(work); //prendo limmagine lavorata (sharp+zoom) e lacopio su cnt_img 61
  • 64. cvThreshold( cnt_img, cnt_img, levels, 255, CV_THRESH_BINARY ); cvShowImage( "immagine lavorata", cnt_img);}int main( int argc, char** argv ) {char c;float zf=6; //fattore di zoomCvFont font;char s[5];int i=0,g=1;float area;char st[200]="";char stch[200]="";char *n;char command[200]="";char *pa="";char nomin[200]="";char imgbin[200]="";char tpath[200]="";for(g=1;g<51;g++){cc=0;strcpy(command,"mkdir /home/andrea/Scrivania/TESI/ProveOCR/t");pa=itoa(g,10);strcat(command,pa);system(command);cvNamedWindow( "immagine orignale a colori", CV_WINDOW_AUTOSIZE );cvNamedWindow( "immagine scala grigi", CV_WINDOW_AUTOSIZE );pa=itoa(g,10);strcpy(nomin,"/home/andrea/Scrivania/TESI/ProveOCR/t");strcat(nomin,pa);strcat(nomin,".jpg"); frame=cvLoadImage(nomin,0); frame_c=cvLoadImage(nomin,1); work=cvCreateImage(cvSize(zf*(frame->width), zf*(frame->height)),IPL_DEPTH_8U,1); panel=cvCreateImage(cvSize(zf*(frame->width), zf*(frame->height)),IPL_DEPTH_8U,3);mask=cvCreateImage( cvGetSize(work), IPL_DEPTH_32S, 1 );strcpy(tpath,"/home/andrea/Scrivania/TESI/ProveOCR/t");pa=itoa(g,10);strcat(tpath,pa);strcat(tpath,"/");cvInitFont(&font,CV_FONT_HERSHEY_COMPLEX_SMALL, 0.1, 0.1, 0.2, 8);//--- PRE-PROCESSAMENTO IMMAGINE ---////cvThreshold(frame, frame,74, 255, CV_THRESH_BINARY );cvShowImage( "immagine orignale a colori", frame_c);cvShowImage( "immagine scala grigi", frame);//c = cvWaitKey(0);cvNamedWindow( "immagine lavorata", CV_WINDOW_AUTOSIZE );Sharpening(frame);cvResize(frame,work,CV_INTER_LINEAR);Sharpening(work);cvShowImage( "immagine lavorata", work);//c = cvWaitKey(0);cvAdaptiveThreshold(work, work, 255,CV_ADAPTIVE_THRESH_MEAN_C,CV_THRESH_BINARY,75, 10);strcpy(imgbin,"/home/andrea/Scrivania/TESI/ProveOCR/t");pa=itoa(g,10);strcat(imgbin,pa); 62
  • 65. strcat(imgbin,"/bin.jpg");cvSaveImage(imgbin,work);cvShowImage( "immagine lavorata", work);//cvCreateTrackbar( "Threshold", "immagine lavorata", &levels, 255,on_trackbar );//c=cvWaitKey(0);//--- FINE PRE-PRCESSAMENTO IMMAGINE ---////--- INDIVIDUAZIONE E DISEGNO CONTORNI ---//if (flag ? framecont=cvCloneImage(cnt_img) : framecont=cvCloneImage(work));cvFindContours(framecont, mem, &contorno,sizeof(CvContour), CV_RETR_LIST ,CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));contornolow=cvApproxPoly(contorno, sizeof(CvContour),mem, CV_POLY_APPROX_DP,1,1);for( ; contornolow != 0; contornolow = contornolow->h_next ){ rect=cvBoundingRect(contornolow, 0); p1.x=rect.x; p2.x=(rect.x+rect.width); p1.y=rect.y; p2.y=(rect.y+ rect.height); area=cvContourArea(contornolow,CV_WHOLE_SEQ); //printf("area= %fn",area); cvRectangle(panel,p1,p2,CV_RGB(255,255,255),1,8,0); //disegno rettangoli checontengono i contorni if ((area < 5500) && (area > 1000) && (rect.width<rect.height) &&(rect.width>0)){ //printf("%d",i); /*IplImage* app=cvCreateImage(cvSize(rect.width,rect.height),IPL_DEPTH_8U,1); cvNamedWindow( "contorno intermedio", CV_WINDOW_AUTOSIZE ); cvGetRectSubPix(panel, app, cvPoint2D32f((p2.x/2),(p2.y/2))); cvShowImage( "contorno intermedio", app); c = cvWaitKey(0); cvReleaseImage(&app); cvDestroyWindow( "contorno intermedio" );*/ cc++; cvSetImageROI(work,rect); IplImage *app = cvCreateImage(cvGetSize(work),work->depth,work->nChannels); cvCopy(work, app, NULL); cvResetImageROI(work); strcpy(st,"/home/andrea/Scrivania/TESI/ProveOCR/t"); pa=itoa(g,10); strcat(st,pa); strcat(st,"/"); strcpy(stch,"/home/andrea/Scrivania/TESI/ProveOCR/t"); pa=itoa(g,10); strcat(stch,pa); strcat(stch,"/"); n=itoa(cc,10); strcat(st,n); strcat(stch,n); strcat(st,".jpg"); cvSaveImage(st,app); strcpy(command,"tesseract -psm 10 "); strcat(command,st); strcat(command," "); strcat(command,stch); printf("%sn", command); 63
  • 66. system(command); cvDrawContours(panel, contornolow, CV_RGB(255,255,255),CV_RGB(255,255,255), -1,1, 8, cvPoint(0,0)); cvDrawContours(mask, contornolow, CV_RGB(255,255,255),CV_RGB(255,255,255), -1,1, 8, cvPoint(0,0)); } sprintf(s,"%d",i); //cvPutText(panel,s,p1,&font, CV_RGB(255,255,255)); //printf("Contorno %d - esimo area = %fn", i+1, area); i++;}//cvWatershed(work,mask);//cvSaveImage("/home/andrea/Scrivania/provaa.jpg",mask);//printf("nHo trovato %d contornin",i);strcpy(command,"");strcpy(command,"cat ");strcat(command,tpath);strcat(command,"*.txt");system(command);//--- FINE CONTORNI ---////cvFloodFill(panel, cvPoint(0,0), cvScalar(255,255,255,255), cvScalarAll(0),cvScalarAll(0), 0, CV_FLOODFILL_FIXED_RANGE, 0);cvNamedWindow( "contorni", CV_WINDOW_AUTOSIZE );cvShowImage( "contorni", panel);//c = cvWaitKey(0);cvReleaseImage(&panel);cvReleaseImage(&work);cvReleaseImage(&frame_c);cvReleaseImage(&frame);cvDestroyWindow( "immagine lavorata" );cvDestroyWindow( "immagine originale a colori" );cvDestroyWindow( "immagine workata" );cvDestroyWindow( "contorni" );cvDestroyWindow( "Threshold" );}} 64
  • 67. People_detect.cpp// ProjectOpenCV.cpp : definisce il punto di ingresso dellapplicazione console.//#include "stdafx.h"#include "opencv2/imgproc/imgproc.hpp"#include "opencv2/objdetect/objdetect.hpp"#include "opencv2/highgui/highgui.hpp"#include <stdio.h>#include <string.h>#include <ctype.h>#include "cvaux.h"#include "highgui.h"#include <string.h>#include <stdlib.h>#define NFRAME 5#define FRAMESPACING 50CvPoint p1=cvPoint(0,120);CvPoint p2=cvPoint(1220,120);CvPoint p3=cvPoint(582,727);int countline=0;int swap=0;IplImage* cnt_img;int levels=0;void Sharpening( IplImage* img )//funzione di "affilamento" per migliorare la definizione dellimmagine{ int w = img->width; int h = img->height;IplImage* gray2 = cvCreateImage( cvGetSize(img) , 32, 1 );cvConvertScale( img, gray2, 1.0 );IplImage* lapl = cvCreateImage( cvGetSize(img) , 32, 1 );CvMat* ker = cvCreateMat( 3, 3, CV_32FC1); cvSet( ker, cvScalarAll( -1.0 ) ); cvSet2D( ker, 1, 1, cvScalarAll( 15.0 ) );cvFilter2D( gray2, lapl, ker );cvReleaseMat( &ker );double maxv = 0.0; double minv = DBL_MAX; 65
  • 68. cvMinMaxLoc( lapl, &minv, &maxv );for( int i=0; i<w*h; i++ ) { double lap_val = cvGet1D( lapl, i ).val[0]; int v = (int)( (255.0*lap_val/ maxv) +0.5); cvSet1D( img, i, cvScalarAll( v) ); }maxv = 0.0;cvMinMaxLoc( img, &minv, &maxv );for( int i=0; i<w*h; i++ ) { double val = cvGet1D( img, i ).val[0]; int v = (int)( (255.0*val/maxv) + 0.5); cvSet1D( img, i, cvScalarAll( v) ); }cvReleaseImage( &gray2 );}void my_mouse_callback( int event, int x, int y, int flags, void* param ){ IplImage* image = (IplImage*) param; char n[30]=""; char s[30]=""; CvFont font; cvInitFont(&font,CV_FONT_VECTOR0,0.5f,0.5f,0,1); switch( event ){ case CV_EVENT_LBUTTONDOWN: switch(countline){ case 0: p1.x=x; p1.y=y; itoa(x,n,10); strcat(n," , "); itoa(y,s,10); strcat(n,s); cvPutText(image,n,cvPoint(x,y),&font,CV_RGB(255,255,0)); cvShowImage("people detector", image); countline++; break; case 1: p2.x=x; p2.y=y; itoa(x,n,10); strcat(n," , "); 66
  • 69. itoa(y,s,10); strcat(n,s); cvPutText(image,n,cvPoint(x,y),&font,CV_RGB(255,255,0)); cvLine(image, p2, p1, CV_RGB(255,0,0),1,8,0); cvShowImage("people detector", image); countline++; break; case 2: p3.x=x; p3.y=y; itoa(x,n,10); strcat(n," , "); itoa(y,s,10); strcat(n,s); cvPutText(image,n,cvPoint(x,y),&font,CV_RGB(255,255,0)); cvLine(image, p2, p3, CV_RGB(255,0,0),1,8,0); cvShowImage("people detector", image); countline++; break; } }}int rect_in_area (CvRect r, float m1,float m2,float q1,float q2){ float x0,y0,y1; int ret=0; CvPoint app; if ((p1.x<p2.x) && (p2.x<p3.x)){ //tre punti da sinistra verso destra if (p1.y>p2.y){ //escludo il 1° quadrante e applico la regola dellaretta per il terzo if ((r.x<p2.x) && ((r.y+r.height)<p2.y)){ //ho escluso ilprimo quadrante ret=0; } if ((r.x<p2.x) && ((r.y+r.height)>p2.y)){ //sono nel terzoquadrante y0=(m1*r.x)+q1; x0=(r.y+r.height-q2)/m2; if (((r.y+r.height)<y0)){ //(r.x<x0) && ret=0; }else{ret=1;} } }else{//applico la regola della retta nel 1° quadrante mentre il peril 3° va bene tutto if ((r.x<p2.x) && ((r.y+r.height)>p2.y)){ //sono nel terzoquadrante ret=1; } if ((r.x<p2.x) && ((r.y+r.height)<p2.y)){ //sono nel primo y0=(m1*r.x)+q1; x0=(r.y+r.height-q2)/m2; if (((r.y+r.height)<y0)){ //(r.x<x0) && ret=0; }else{ret=1;} } 67
  • 70. } //il processamento del 2°-4° quadrante if (p2.y>p3.y){ //includo tutto il 4° regola retta per il 2 if ((r.x>p2.x) && ((r.y+r.height)>p2.y)){ //sono nel quartoquadrante ret=1; } if ((r.x>p2.x) && ((r.y+r.height)<p2.y)){ //sono nel secondo y0=(m2*r.x)+q2; //x0=(r.y+r.height-q2)/m2; if (((r.y+r.height)<y0)){ //(r.x<x0) && ret=0; }else{ret=1;} } }else{ //escludo il 2° quadrante e regola retta nel 4° if ((r.x>p2.x) && ((r.y+r.height)<p2.y)){ //sono nel secondoquadrante ret=0; } if ((r.x>p2.x) && ((r.y+r.height)>p2.y)){ //sono nel quarto y0=(m2*r.x)+q2; //x0=(r.y+r.height-q2)/m2; if (((r.y+r.height)<y0)){ //(r.x<x0) && ret=0; }else{ret=1;} } } } if (p2.x>p3.x){//escludo il 2 e il 4 regola retta nel 1 e 3 /*if (p3.y<p1.y){ // se il punto 3 è più in alto li scambio inmodo da vere sempre la stessa situazione app=p3; p3=p1; p1=app; swap=1; }*/ if (((m1*m2)>0)){ //caso di due rette nello stesso quadrante //posso essere solo nei quadranti 1 o 3 (perchè come hpiniziale ho che il punto 2 è SEMPRE più a destra //del punto 1-->elimino quad 2-4 e quello in cui non sono (1 o3) if (p3.y<p2.y){ //sono nel quadrante 1 con lerette if ((r.x<p2.x) && ((r.y+r.height)>p2.y) ||(r.x>p2.x)){ //elimino terzo secondo e qarto quadrante ret=0; }else{ y0=(m1*r.x)+q1; y1=(m2*r.x)+q2; if ( ((r.y+r.height)<y0) ||((r.y+r.height)>y1)){// && ((r.y+r.height)>y1) ret=0; }else{ret=1;} } }else{//sono nel quadrante 3 con le rette 68
  • 71. if ((r.x<p2.x) && ((r.y+r.height)<p2.y) ||(r.x>p2.x)){ //ho escluso il primo secondo e qaurto quadrante ret=0; }else{ y0=(m1*r.x)+q1; y1=(m2*r.x)+q2; if ( ((r.y+r.height)<y0) ||((r.y+r.height)>y1)){// && ((r.y+r.height)>y1) ret=0; }else{ret=1;} } } }else{ //caso che hanno pendenze diverse ma il punto 3 èpiu avanti del 2 if ((r.x>p2.x) && ((r.y+r.height)<p2.y)){ //sono nelsecondo quadrante ret=0; } if ((r.x>p2.x) && ((r.y+r.height)>p2.y)){ //sono nelquarto quadrante ret=0; } if ((r.x<p2.x) && ((r.y+r.height)<p2.y)){ //sono nelprimo y0=(m1*r.x)+q1; x0=(r.y+r.height-q2)/m2; if (((r.y+r.height)<y0)){ //(r.x<x0) && ret=0; }else{ret=1;} } if ((r.x<p2.x) && ((r.y+r.height)>p2.y)){ //sono nelterzo quadrante y0=(m2*r.x)+q2; x0=(r.y+r.height-q2)/m2; if (((r.y+r.height)<y0)){ //(r.x<x0) && ret=1; }else{ret=0;} } } } return ret;}void people_detect(char pth_vid[]){ CvFont font; IplImage* img; IplImage* img_b; IplImage* bk; IplImage* a; IplImage* res; CvCapture* capture; int risp=0; 69
  • 72. char pth_bck[50]; char s[30]; char n[30]; int i,c=0,j=1, count_found=0, count_out=0,cbk=0; float m1,dy,dx,m2,q1,q2; CvPoint app; printf("Indica il percorso dellimmagine di backgroundn"); scanf("%s",pth_bck); cvInitFont(&font,CV_FONT_VECTOR0,0.5f,0.5f,0,1); bk=cvLoadImage(pth_bck,0); if (!bk){ printf("Nessuna immagine di backgorund trovata!n"); exit(1); } //Sharpening(bk); res=cvCreateImage(cvSize(bk->width,bk->height),8,1); capture = cvCaptureFromAVI(pth_vid); for(j=0;j<25;j++){img=cvQueryFrame(capture);}//scarto i primi 100 frame img_b=cvCreateImage(cvSize(img->width,img->height),img->depth,1); cvCvtColor(img,img_b,CV_BGR2GRAY); //Sharpening(img_b); cvAbsDiff(bk,img_b,res); //Sharpening(res); cvNamedWindow( "people detector", CV_WINDOW_AUTOSIZE ); cvRectangle(img, cvPoint(50,50), cvPoint(436,96), CV_RGB(255,0,0),CV_FILLED, 8, 0); strcpy(n,"Clicca su 3 punti per definire larea di ricerca."); cvPutText(img,n,cvPoint(55,68),&font,CV_RGB(255,255,255)); strcpy(n,"Poi premi un tasto per iniziare."); cvPutText(img,n,cvPoint(55,88),&font,CV_RGB(255,255,255)); cvShowImage("people detector", img); cvSetMouseCallback( "people detector", my_mouse_callback, (void*) img); cvWaitKey(0); while(capture){ count_found=0; count_out=0; while (j % FRAMESPACING){img=cvQueryFrame(capture); j++;} if (capture){ img=cvQueryFrame(capture); }else{return;} img_b=cvCreateImage(cvSize(img->width,img->height),img->depth,1); cvCvtColor(img,img_b,CV_BGR2GRAY); //Sharpening(img_b); cvAbsDiff(bk,img_b,res); //Sharpening(res); dy=p2.y-p1.y; dx=p2.x-p1.x; m1=dy/dx; q1=p1.y-(m1*p1.x); dy=p2.y-p3.y; dx=p2.x-p3.x; m2=(dy/dx); q2=p3.y-(m2*p3.x);/*cvLine(img,cvPoint(p2.x,p2.y),cvPoint(p2.x,0),CV_RGB(0,0,0),2,8,0); cvLine(img,cvPoint(p2.x,p2.y),cvPoint(p2.x,img->height),CV_RGB(0,0,0),2,8,0); 70
  • 73. cvLine(img,cvPoint(p2.x,p2.y),cvPoint(0,p2.y),CV_RGB(0,0,0),2,8,0); cvLine(img,cvPoint(p2.x,p2.y),cvPoint(img->width,p2.y),CV_RGB(0,0,0),2,8,0);*/ cv::HOGDescriptor hog; hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector()); cv::vector<cv::Rect> found; double t = (double)cv::getTickCount(); // run the detector with default parameters. to get a higher hit-rate // (and more false alarms, respectively), decrease the hitThresholdand // groupThreshold (set groupThreshold to 0 to turn off the groupingcompletely). hog.detectMultiScale(res, found,-0.65, cv::Size(8,8),cv::Size(24,16), 1.05,3); t = (double)cv::getTickCount() - t; printf("Detection time = %gmsn", t*1000./cv::getTickFrequency()); for( int i = 0; i < (int)found.size(); i++ ){ cv::Rect r = found[i]; // the HOG detector returns slightly larger rectangles than thereal objects. //so we slightly shrink the rectangles to get a nicer output. 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); if (rect_in_area(r,m1,m2,q1,q2)){ cvRectangle(img, r.tl(), r.br(), CV_RGB(0,255,0), 1); count_found++; }else{ cvRectangle(img, r.tl(), r.br(), CV_RGB(0,0,255), 1); count_out++; } /*itoa(i,n,10); cvPutText(img,n,cvPoint(r.x,r.y+10),&font,CV_RGB(255,0,0)); //numero delrect itoa(r.x,n,10); strcat(n," , "); itoa(r.y,s,10); strcat(n,s); cvPutText(img,n,cvPoint(r.x,r.y+r.height+13),&font,CV_RGB(255,0,0));//coordinate itoa(r.width,n,10); strcat(n," X "); itoa(r.height,s,10); strcat(n,s); cvPutText(img,n,cvPoint(r.x,r.y+r.height+25),&font,CV_RGB(255,0,0));//area*/ } cv::namedWindow("people detector", 1); cvLine(img, p2, p1, CV_RGB(255,0,0),1,8,0); //prolungamento linee che delimitano larea if (p2.y>p1.y){ cvLine(img, cvPoint((-q1/m1),0), p2,CV_RGB(255,0,0),1,8,0);} 71
  • 74. else{ cvLine(img, cvPoint(((img->height-q1)/m1),img->height), p2,CV_RGB(255,0,0),1,8,0);} if(p2.y>p3.y){ cvLine(img, cvPoint((-q2/m2),0), p2,CV_RGB(255,0,0),1,8,0);} else{ cvLine(img, cvPoint(((img->height-q2)/m2),img->height), p2,CV_RGB(255,0,0),1,8,0);} cvShowImage("people detector", img); strcpy(n,"Frame Numero: "); strcat(n,itoa(j,s,10)); cvPutText(img,n,cvPoint(100,470),&font,CV_RGB(255,255,0)); strcpy(n,"Corrispondeze trovate: "); strcat(n,itoa((int)found.size(),s,10)); cvPutText(img,n,cvPoint(100,500),&font,CV_RGB(255,255,255)); strcpy(n,"Persone dentro larea: "); strcat(n,itoa(count_found,s,10)); cvPutText(img,n,cvPoint(100,520),&font,CV_RGB(255,255,255)); strcpy(n,"Persone fuori dallarea: "); strcat(n,itoa(count_out,s,10)); cvPutText(img,n,cvPoint(100,540),&font,CV_RGB(255,255,255)); cvShowImage("people detector", img); printf("Frame numero: %dn",j); printf("Corrispondenze trovate: %dn",(int)found.size()); printf("Persone dentro larea: %dn",count_found); printf("Persone fuori dallarea: %dn",count_out); printf("---------------------------------------n"); j++; cvWaitKey(20); }}void find_bck(char pth_vid[]){ char s[50]; char n[50]; char pth_dir[50]; int n_im=0,j=0; IplImage* img; CvCapture* capture; printf("Indica la cartella dove le immagini verranno salvate:n"); scanf("%s",pth_dir); printf("Ora scegli quante immagini salvare:n"); scanf("%d",&n_im); capture=cvCaptureFromAVI(pth_vid); //for(j=0;j<100;j++){img=cvQueryFrame(capture);}//scarto i primi 100 frame while((capture) && (n_im)) { while (j % FRAMESPACING){img=cvQueryFrame(capture); j++;} img=cvQueryFrame(capture); cv::HOGDescriptor hog; hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector()); //::getDefaultPeopleDetector()); cv::vector<cv::Rect> found; double t = (double)cv::getTickCount(); // run the detector with default parameters. to get a higher hit-rate // (and more false alarms, respectively), decrease the hitThreshold 72
  • 75. and // groupThreshold (set groupThreshold to 0 to turn off the groupingcompletely). hog.detectMultiScale(img, found, 0.4, cv::Size(8,8),cv::Size(24,16), 1.05, 3); t = (double)cv::getTickCount() - t; printf("Detection time = %gmsn", t*1000./cv::getTickFrequency()); if ((!(int)found.size())){ strcpy(s,pth_dir); itoa(n_im,n,10); strcat(s,n); strcat(s,".jpg"); cvSaveImage(s,img); printf("-----------IMMAGINE SALVATA---------n"); n_im--; } j++; }}int main(int argc, char** argv) { CvCapture* capture; int risp=0; char pth_vid[50]; printf("PEOPLE DETECTORnnn"); printf("Prima di tutto scegli il video da analizzare:"); scanf("%s",pth_vid); capture=cvCaptureFromAVI(pth_vid); if (!capture){ printf("Nessun video trovato!"); return 1; }else{ printf("SORGENTE VIDEO VALIDAnn "); } printf("nSelezionare loperazione da compiere:nn"); printf("1.Esegui people detect su un video (serve unimmagine dibackground)n"); printf("2.Trova limmagine di backgroundn"); scanf("%d",&risp); switch(risp){ case 1: people_detect(pth_vid); case 2: find_bck(pth_vid); printf("Ricerca completata ora recati nella cartella chehai selezionaton"); printf("riavvia il programma e usa unimmagine comesfondo per people detect!n"); printf("Premi un tasto per finire.."); } return 0;} 73
  • 76. Bibliografia[1] E. Roy Davies (2005). Machine Vision : Theory, Algorithms, Practicalities. Morgan Kaufmann.ISBN 0-12-206093-8.[2] http://it.wikipedia.org/wiki/Visione_artificiale[3] OpenCV C interface: http://opencv.willowgarage.com/documentation/c/index.html[4] Python Interface: http://opencv.willowgarage.com/documentation/python/index.html[5] JavaCV http://code.google.com/p/javacv/[6] OpenCV C++ interface: http://opencv.willowgarage.com/documentation/cpp/index.html[7] Android port: http://opencv.willowgarage.com/wiki/AndroidExperimental[8] iPhone port: http://www.eosgarden.com/en/opensource/opencv-ios/overview[9] Maemo port: https://garage.maemo.org/projects/opencv[10] B. D. Lucas and T. Kanade (1981), An iterative image registration technique with anapplication to stereo vision.[11] Paul Viola and Michael J. Jones. Rapid Object Detection using a Boosted Cascade of SimpleFeatures IEEE CVPR, 2001.[12] Rainer Lienhart and Jochen Maydt. An Extended Set of Haar-like Features for Rapid ObjectDetection IEEE ICIP 2002, Vol. 1, pp. 900-903, Sep. 2002[13] HaarTraining doc This document can be obtained from OpenCV/apps/HaarTraining/doc onyour OpenCV install directory[14] code.google.com/p/tesseract-ocr/[15] InriaPerson dataset. http://pascal.inrialpes.fr/data/human/[16] Navneet Dalal and Bill Triggs. Histograms of oriented gradients for human detection. InCordelia Schmid, Stefano Soatto, and Carlo Tomasi, editors,International Conference on ComputerVision and Pattern Recognition, volume 2, pages 886-893, INRIA Rhone-Alpes, ZIRST-655, avo delEurope, Montbonnot-38334, June 2005[17] Navneet Dalal and Bill Triggs. Histograms of oriented gradients for human detection. InCordelia Schmid, Stefano Soatto, and Carlo Tomasi, editors, International Conference on ComputerVision and Pattern Recognition, volume 2, pages 886-893, INRIA Rhone-Alpes,ZIRST-655, avo de lEurope, Montbonnot-38334, June 2005.[18] Dorin Comaniciu and Peter Meer.re space analysis.Mean shift:A robust approach towardfeature IEEE Trans. Pattern Anal. Mach. Intell.,http://dx.doi.org/10.1109/34.1000236[19] A New Approach to Linear Filtering and Prediction Problems, by R. E. Kalman, 1960 74