-

hey viewer, we're moving!

We are currently transitioning to a new web system, so we are not updating this wikisite anymore.

The public part of the new web system is available at http://www.ira.disco.unimib.it


Dodhek (Detector Of Doors Handle and Elevator Keyboard)

From Irawiki

Jump to: navigation, search

People participating to this project

Contents

Introduzione

Computer vision è la trasformazione di dati da una foto o videocamera in una decisione o una nuova rappresentazione. Tutte le trasformazioni sono fatte per ottenere alcuni particolari scopi. I dati di input possono includere qualche informazione contestuale come {}“la camera è montata su un auto”. La decisione potrebbe essere {}“c’è una persona nella scena” o {}“ci sono 14 celle tumorali nella slide”. Una nuova rappresentazione potrebbe significare la conversione di un’immagine a colori in un’immagine a scala di grigi o rimuovere una fotocamera in movimento da una sequenza di immagini. All’inizio si potrebbe pensare che i problemi di computer vision siano banali, perché grazie al nostro cervello e alla nostra vista riusciamo a fare tranquillamente tutte quelle cose che la computer vision si pone come problemi. Ma quanto può essere difficile trovare una macchina quando fissi un’immagine? La nostra intuizione iniziale potrebbe essere quasi ingannata. Il cervello umano divide i segnali visivi in tanti canali, dove scorrono differenti tipi di informazioni. Il nostro cervello ha un sistema di attenzione che identifica importanti parti di un’immagine al fine di esaminarle mentre sta scartando altre aree della stessa immagine. C’è un massiccio feedback negli stream visuali che vengono un po’ compresi. Ci sono una serie di input associativi: dai sensori che controllano i muscoli a tutti gli altri sensi che permettono al cervello di ricavare delle associazioni sviluppate in anni di esperienza. In un sistema di visione, comunque, un computer riceve una griglia di numeri da una camera o da un hard disk e questo è tutto, non esistono pattern di riconoscimento già precostruiti, non esistono sistemi di controllo automatici del focus e apertura e quindi non esiste nessuna associazione ricavata da anni di esperienza. Per questi motivi i sistemi di visione sono ancora abbastanza ingenui.

Framework utilizzati

Per poter gestire in modo efficiente un robot è conveniente usare un middleware, cioè un software che si interponga tra il sistema operativo del computer e il robot stesso, e che si occupi dell’interazione tra i vari componenti sollevando il robotico da molti dettagli implementativi. E’ però fondamentale scegliere un middleware che corrisponda alle proprie aspettative e soddisfi i requisiti di progetto.

Per poter effettuare questa scelta si è reso necessario ragionare sulle singole componenti che dovessero interagire nel progetto. A seguito di questa analisi è stato effettuato un lavoro di ricerca e catalogazione dei middleware più conosciuti, tra cui Ros, Orocos, Dctd, Aria, Carmen e Bricks, che ha portato a scegliere Ros, Robot operating system. La maggior parte dei middleware con una versione stabile rilasciata avevano caratteristiche tecniche soddisfacenti e la scelta è stata basata pertanto su altri criteri: la disponibilità di packages già sviluppati, la documentazione e l’attività degli sviluppatori e della community.

Ros si è rivelato una buona scelta anche se la documentazione, che in prima analisi era sembrata completa con numerosi tutorial ed esempi, è risultata scarna in alcuni punti ed entrava poco nel merito delle scelte progettuali effettuate dagli sviluppatori, sia per la struttura di base che per i packages forniti.

Per la parte di computer vision è stata utilizzato in primis Matlab e poi OpenCV, che viene sviluppato dalla stessa azienda che sviluppa Ros. Anche questo all’inizio sembrava una buona scelta, almeno per i lavori che rimangono nel basilare, per cose più approfondite lascia poca o quasi nulla elasticità e c’è una scarsa documentazione su come accedere e modificare gli oggetti del framework, infatti alcune parti sono state rifatte da zero soprattutto per questi limiti.

OpenCV

OpenCV è una libreria open source di computer vision. Originariamente è stata sviluppata da Intel, mentre attualmente è sotto licenza open source BSD. La libreria è scritta in C e C++ e funziona su Linux, Windows e Mac OS X. C’è uno sviluppo attivo su interfacce come Python, Ruby, Matlab e altri linguaggi. OpenCV è stato progettato per essere computazionalmente efficiente con un forte focus su applicazione real-time. E’ scritto in C ottimizzato e può prendere vantaggio su processori multicore. Uno degli obiettivi di OpenCV è fornire un’ infrastruttura di computer vision facile da usare che può aiutare a compilare sofisticate applicazioni di visione in modo celere. La libreria di OpenCV contiene oltre 500 funzioni che spaziano in diverse aree di visione, includendo ispezione dei prodotti di una fabbrica, medical imaging, sicurezza, user interface, calibrazione della camera, visione stereo e robotica. Visto che computer vision e machine learning sono due argomenti strettamente collegati, OpenCV contiene anche una Machine Learning Library (MLL). Questa sottolibreria è incentrata su pattern di riconoscimento con approccio statistico e clustering. MLL è molto utile per i problemi di visione artificiale che sono il cuore della mission di OpenCV, ma anche in generale viene usata per ogni problema di machine learning.

Ros

La struttura di Ros si basa sul Master, un’entità che si occupa di gestire la comunicazione tra tutti i componenti e fornire i servizi di base per il funzionamento delle parti sottostanti. Alcuni di questi servizi, che vengono avviati automaticamente insieme al master, sono il parameter server, che si occupa del salvataggio e della distribuzione di parametri per i diversi componenti di controllo, e il prezioso tf, che gestisce tutti i sistemi di riferimento definiti e permette di effettuare trasformazioni tra di essi.

Sostanzialmente il master è un server e tutti i componenti, chiamati nodi, sono dei client che effettuano richieste su tcp: questa configurazione si presta molto bene alla distribuzione dell’architettura su più calcolatori, ed è stata sperimentata per distribuire complessità di calcolo ed ottenere prestazioni migliori.

I nodi, che sono processi a se stanti, possono comunicare tra di loro attraverso il master secondo i paradigmi classici publisher and subscriber e request/response per scambiarsi messaggi di vario genere. Esistono numerosi tipi di messaggi già definiti, come laserscan, pose, velocity commands, che lasciano intuire quanto sia vasto il progetto Ros, ma è comunque possibile creare nuovi tipi secondo le proprie esigenze.

I packages disponibili sono numerosi e spaziano dalla navigazione ai driver per laser, camere e sensori fino a modellazione del robot e ambienti di simulazione e visualizzazione. Ci sono anche varie utilities che supportano lo sviluppo permettendo di registrare le comunicazioni tra nodi o visualizzare nodi attivi e le relazioni tra di essi, le frequenze di pubblicazione.

Ros è supportato su linux e si integra nella shell con alcuni comandi per navigare nel filesystem e per facilitare la compilazione e l’esecuzione dei nodi che possono essere scritti in c++ o, alternativamente, in python.

L’impressione globale di Ros è molto positiva anche se abbiamo avuto qualche difficoltà nell’utilizzare i package di base poiché, essendo pensati per un utilizzo generico su qualsiasi robot, risultano molto complessi. Inoltre per la scarsa documentazione sulla loro progettazione è arduo riuscire a comprenderne a fondo il funzionamento per adattarli alle proprie esigenze quindi abbiamo preferito, in alcuni casi, riscrivere da zero un componente.

Per una comprensione di base di Ros rimandiamo alla documentazione e specialmente ai tutorial che tramite esempi mostrano il funzionamento della maggior parte delle componenti disponibili.

Pre-Elaborazione

La fase di pre-elaborazione è stata sviluppata in due modi diversi (chiamati per l’appunto primo e secondo metodo), per questo sono stati specificati entrambi i metodi. Il programma prende come input o una cartella contenente delle immagini o un video e analizza ogni singolo file o frame. Le immagini del dataset di test sono state fatte nei piani dell’U14, sia dall’interno degli uffici che dal corridoio. Inizialmente è stata applicata una correzione gamma con gamma = 1.2 e poi l’immagine a colori è stata trasformata in un’immagine binaria. Una volta ottenuta l’immagine binaria viene data in input al metodo scelto in fase di compilazione.

Gamma correction

Introduzione

Correzione di gamma, non linearità del gamma, codifica gamma, o, spesso, semplicemente, gamma è il nome di una trasformazione non lineare usata per codificare e decodificare la luminanza o i valori del tristimolo in un sistema video o fotografico. La correzione di gamma, è, nel caso più semplice, definita dalla seguente legge di potenza: V_{out}=AV_{in}^{\gamma}, dove A è una costante e i valori di input e output sono valori reali non negativi. In molti casi A = 1 e gli input ed output sono tipicamente nel range 0-1. Un valore di gamma con γ < 1 è, a volte, chiamato compressione gamma. Viceversa con un valore gamma γ > 1 è chiamato gamma decodificante e l’applicazione di tale non linearità mediante semplice legge di potenza espansiva è chiamata espansione gamma.

Spiegazione

La compressione gamma di immagini è richiesta per compensare le proprietà della vista umana, per massimizzare l’uso dei bit o le larghezze di banda relative per come gli uomini percepiscono la luce e i colori. La vista umana, sotto usuali condizioni di illuminazioni, (ne il caso in cui ci sia buio o un’illuminazione troppo intensa) segue una gamma approssimata o una funzione di potenza. Se le immagini non fossero gamma compresse, allocheremmo molti bit nei particolari che la vista umana non differenzia e pochi bit verso valori di ombra verso cui invece la vista è molto sensibile. Quindi le zone di ombra richiedono un maggior numero di bit per mantenere la stessa qualità visiva. Compressione gamma su immagini con valori a virgola mobile non è necessaria perché il formato a virgola mobile è già provvisto di una compressione pseudo-logaritmica.

Gamma generalizzato

Il concetto di gamma può essere applicato a qualsiasi relazione non lineare. Per la relazione della legge di potenza V_{out}=V_{in}^{\gamma}, il fattore gamma è uguale a: \gamma=\frac{dlog(V_{out})}{dlog(V_{in})}.

In altri termini il fattore gamma indica l’inclinazione della curva rappresentativa della relazione (in termini logaritmici) tra segnale e risposta.

Convoluzione

La convoluzione di due segnali continui x(t) e h(t) è definita come

Failed to parse (unknown function\intop): y(t)=h(t)\cdot x(t)\triangleq\intop_{-\infty}^{\infty}x(\tau)h(t-\tau)d\tau=\intop_{-\infty}^{\infty}h(\tau)x(t-\tau)d\tau=x(t)*h(t)

quindi la convoluzione è commutativa. La convoluzione è anche associativa:

h\cdot(g\cdot x)=(h\cdot g)\cdot x

Tipicamente, y(t) è l’output di un sistema caratterizzato da una funzione a risposta di impulsi h(t) con input x(t)

{Esempio di convoluzione}

La forma discreta della convoluzione è

y(n)=\sum_{m=-\infty}^{\infty}x(n-m)h(m)=\sum_{m=-\infty}^{\infty}h(n-m)x(m)

Se h(m)è finita, per esempio

h(m)=\begin{cases}
h(m) & |m|\leq k\\
0 & |m|>k
\end{cases}

la convoluzione diventa

y(n)=\sum_{m=-k}^{k}x(n-m)h(m)

Se il sistema in questione è un sistema causale nel dominio del tempo h[n) = h[n]u[n] cioè h[n] = 0 se n < 0 così che diventa

y(n)=\sum_{m=0}^{k}x(n-m)h(m)

Comunque, in image processing, spesso consideriamo la convoluzione nel dominio spaziale dove la causalità non può essere applicata. Se h(m) è simmetrico (spesso è vero in image processing), cioè

h[ − m] = h[m]

la convoluzione diventa come calcolare la correlazione tra due funzioni:

y[n]=\sum_{m=-k}^{k}x[n+m]h[m]

La correlazione può essere considerata come un modello computazionale per le reti neurali nei sistemi visuali biologici.

Se l’input x(m) è finito (questo è sempre vero nella realtà) cioè

x[m]=\begin{cases}
x[m] & 0\leq m<N\\
0 & altrimenti
\end{cases}

il suo indice n + m nella convoluzione deve soddisfare per x per essere nel range non-zero valido:

0\leq n+m\leq N-1

o in corrispondenza, l’indice n dell’output y[n] deve soddisfare:

-m\leq n\leq N-m-1

Quando la variabile di indice m nella convoluzione è uguale a k, l’indice dell’output y[n] raggiunge il suo limite più basso in n = − k; quando m = − k l’indice di y[n] raggiunge il suo limite massimo in n = N + k − 1. In altre parole, ci sono N + 2k elementi validi (non-zero) nell’output: y[n], (-k\leq n\leq N+k-1).

La convoluzione digitale può essere meglio compresa graficamente (quando gli indici di y[n] sono riorganizzati).

{La convoluzione digitale}

Assumiamo che la dimensionalità del segnale di input x sia N e che quella del kernel h sia M = 2k + 1 (di solito è un numero dispari), allora la dimensionalità della convoluzione risultante y=x\cdot h sarà N + M − 1. Tuttavia è di solito desiderabile per l’output y avere la stessa dimensionalità dell’input x e per arrivare a questo k componenti ad ogni fine di y vengono scartati.

{}File:Img/img35{Pezzo di codice che mostra una convoluzione a una dimensione y=x\cdot h}

In particolare, se gli elementi del kernel sono tutti uguali (una media o filtro passa-basso), possiamo velocizzare il processo di convoluzione mentre scorriamo il kernel sul segnale di input avendo cura di usare soltanto gli ultimi due elementi del kernel.

In image processing la convoluzione a una dimensione viene generalizzata in due dimensioni, e h è chiamato kernel della convoluzione o maschera.

y[m,n]=\sum_{i=-k}^{k}\sum_{j=-k}^{k}x[m+i][n+j]h[i,j]

{Convoluzione digitale a due dimensioni di un’immagine}

Edge Detection

L’edge detection o meglio il riconoscimento dei bordi, è un fondamentale problema in image processing e computer vision, particolarmente nelle aree di estrazione e riconoscimento di feature, che ha lo scopo di identificare i punti in un’immagine digitale in cui l’intensità luminosa cambia bruscamente. Questi cambiamenti possono essere, ad esempio, discontinuità della profondità, discontinuità dell’orientamento delle superfici, modifica delle proprietà dei materiali e variazioni dell’illuminazione proveniente dall’ambiente circostante.

L’operazione di riconoscimento dei contorni genera immagini contenenti molte meno informazioni rispetto a quelle originali, poiché elimina la maggior parte dei dettagli non rilevanti al fine di individuare dei contorni, conservando invece le informazioni essenziali per descrivere la forma e le caratteristiche strutturali e geometriche degli oggetti rappresentati. Esistono molti metodi per riconoscere i contorni, ma la maggior parte può essere raggruppata in due categorie: metodi basati sulla ricerca (search-based) e metodi basati sull’attraversamento dello zero (zero-crossing). I metodi basati sulla ricerca riconoscono i contorni cercando i massimi ed i minimi della derivata del primo ordine dell’immagine, di solito cercando la direzione in cui si ha il massimo gradiente locale. I metodi zero-crossing cercano i punti in cui la derivata del secondo ordine passa per lo zero, solitamente la funzione laplaciana o un’espressione differenziale di una funzione non-lineare.

Proprietà dei contorni

I contorni possono essere dipendenti dal punto di osservazione, infatti, possono variare al cambiare del punto di osservazione, rispecchiando la disposizione e la configurazione geometrica dell’ambiente circostante. Un esempio sono oggetti che si frappongono l’un l’altro. I contorni possono essere indipendenti dal punto di osservazione, quando rispecchiano proprietà intrinseche degli oggetti stessi, come ad esempio segni tracciati sulle superfici oppure forme geometriche delle superfici degli oggetti. In due o più dimensioni bisogna tener presenti gli effetti della prospettiva. Un tipico contorno potrebbe essere il confine fra un’area di colore rosso ed una gialla, oppure una linea con spessore di pochi pixel e di colore diverso rispetto ad uno sfondo uniformemente colorato. In quest’ultimo caso i contorni sono per l’esattezza due, uno per ciascun lato della linea. I contorni giocano un ruolo fondamentale in molte applicazioni di computer vision, anche se, in tempi recenti, in questo campo sono stati fatti sostanziali progressi utilizzando anche altri approcci, che non usano il riconoscimento dei contorni come step preliminare dell’elaborazione.

Tipologia dei contorni

I contorni si possono manifestare sotto forma di diverse variazioni di intensità

  • a gradino (ideale) quando la variazione è localizzata
  • a rampa quando la variazione è graduale

In un’immagine digitale reale non avremo mai un contorno a gradino.

Il concetto di variazione locale di intensità può essere catturato da quello di derivata, per questo i sistemi più semplici per effettuare una edge detection sono i filtri derivativi.

Sono molto sensibili ai rumori quindi sarebbe meglio applicarli dopo un filtro di Smoothing.

Degli esempi di filtri derivativi sono i filtri di Roberts, Prewitt e Sobel.

Primo metodo di pre-elaborazione

Nel primo metodo applico all’immagine (che chiameremo image) una maschera mask (cioè viene effettuata una convoluzione tra image e mask) ed effettuo una sogliatura. Ho come risultato dst1. A image applico l’opposto di mask, soglio e ho come risultato dst2. Infine il risultato della pre-elaborazione sarà result = dst1 + dst2. La maschera mask non è altro che un filtro di Prewitt o di Sobel.

Filtri di Prewitt

Il filtro di Prewitt o operatore di Prewitt è usato in image processing, particolarmente con algoritmi di edge detection. Tecnicamente è un operatore discreto di differenze finite, che calcola un’approssimazione del gradiente della funzione di intensità dell’immagine.

Ad ogni punto dell’immagine, il risultato dell’operatore di Prewitt non è altro che il corrispondete vettore gradiente o la norma di questo vettore. L’operatore di Prewitt è basato su una convoluzione dell’immagine con un piccolo, separabile, filtro a valori interi in direzione orizzontale o verticale e pertanto è relativamente poco oneroso in termini di tempi computazionali. Tuttavia, l’approssimazione del gradiente è relativamente grezza per variazioni di alte frequenze nell’immagine.

Matematicamente, l’operatore usa due maschere 3x3 che vengono convolute con l’immagine originale per calcolare l’approssimazione delle derivate, una per i cambiamenti orizzontali e una per quelli verticali. Se definiamo A come l’immagine originale e GX e Gy sono due immagini dove in ogni punto contengono l’approssimazione della derivata orizzontale e verticale e vengono calcolate in questo modo:

G_{x}=\left[\begin{array}{ccc}
-1 & 0 & +1\\
-1 & 0 & +1\\
-1 & 0 & +1
\end{array}\right]\cdot A

G_{y}=\left[\begin{array}{ccc}
-1 & -1 & -1\\
0 & 0 & 0\\
+1 & +1 & +1
\end{array}\right]\cdot A

File:Img/300px-Bikesgray File:Img/300px-Bikesgray prewitt

{Esempio di applicazione dell’operatore di Prewitt}

Filtri di Sobel

Il filtro di Sobel o operatore di Sobel è usato in image processing, in particolare con algoritmi di edge detection. Tecnicamente è un operatore discreto di differenze finite, che calcola un’approssimazione del gradiente della funzione di intensità dell’immagine.

Ad ogni punto dell’immagine, il risultato dell’operatore di Sobel non è altro che il corrispondete vettore gradiente o la norma di questo vettore. L’operatore di Sobel è basato su una convoluzione dell’immagine con un piccolo, separabile, filtro a valori interi in direzione orizzontale o verticale e pertanto è relativamente poco oneroso in termini di tempi computazionali. Tuttavia, l’approssimazione del gradiente è relativamente grezza per variazioni di alte frequenze nell’immagine.

Matematicamente, l’operatore usa due maschere 3x3 che vengono convolute con l’immagine originale per calcolare l’approssimazione delle derivate, una per i cambiamenti orizzontali e una per quelli verticali. Se definiamo A come l’immagine originale e GX e Gy sono due immagini dove in ogni punto contengono l’approssimazione della derivata orizzontale e verticale e vengono calcolate in questo modo:

G_{x}=\left[\begin{array}{ccc}
-1 & 0 & +1\\
-2 & 0 & +2\\
-1 & 0 & +1
\end{array}\right]\cdot A

G_{y}=\left[\begin{array}{ccc}
-1 & -2 & -1\\
0 & 0 & 0\\
+1 & +2 & +1
\end{array}\right]\cdot A

File:Img/300px-Valve original (1) File:Img/300px-Valve sobel

{Esempio di applicazione dell’operatore di Sobel}

Secondo metodo di pre-elaborazione

Nel secondo metodo applico all’immagine un filtro Gaussiano e poi effettuo una edge detection con Canny.

Canny Edge Detection

Il Canny Edge detector è un approccio analitico per l’edge detection:

  • Usa un modello matematico per edge e rumore
  • Formalizza criteri di ottimalità per l’edge detection
  • Determina un filtro lineare ottimale

Costituisce una delle tecniche più utilizzate nella computer vision.

Il modello di edge utilizzato è un edge monodimensionale che viene modellato come una funzione a gradino di altezza hE situato nell’origine.

Il modello di rumore utilizzato è un rumore gaussiano bianco con deviazione σn.

Il filtro di edge deve essere tale da avere:

  • Buona Rilevazione: La probabilità di non individuare un edge presente e la probabilità di individuare falsi edge deve essere minima
  • Buona Localizzazione: La distanza fra il punto di edge individuato e la sua effettiva posizione deve essere minima
  • Risposta Singola: Per un edge ci deve essere un’unica risposta

Canny ha determinato una funzione analitica dei tre criteri e li ha combinati massimizzando la risposta finale del filtro. Il risultato può essere approssimato alla derivata della Gaussiana (soluzione adottata nella maggior parte delle implementazioni)

Canny introduce alcune euristiche per l’estensione al dominio bidimensionale delle immagini reali

  • gli edge sono i massimi locali del gradiente
  • uso di due soglie: una per individuare gli edge più netti e un’altra per gli edge più deboli
  • analisi degli edge per avere una risposta singola

Alla fine l’algoritmo è

  1. Smooth dell’immagine con un filtro Gaussiano di parametro σ per ridurre rumori e dettagli e texture non voluti. g(m,n)=G_{\sigma}(m,n)\cdot f(m,n)
dove G_{\sigma}=\frac{1}{\sqrt{2\pi\sigma^{2}}}exp[-\frac{m^{2}+n^{2}}{2\sigma^{2}}]
  2. Calcolo del gradiente di g(m,n) usando uno degli operatori (Roberts, Sobel, Prewitt, etc) per avere: M(m,n)=\sqrt{g_{m}^{2}(m,n)+g_{n}^{2}(m,n)}
e θ(m,n) = tan − 1[gn(m,n) / gm(m,n)]
  3. Identificazione dei possibili edge (non-maximum-suppression): Per ogni punto non uguale a zero di M(m,n) controllo se è più grande di due suoi punti vicini in direzione normale dell’edge (per l’appunto θ(m,n)). Se è vero allora è un massimo locale e rimane M(m,n) altrimenti viene settato a 0.
  4. Rimozione degli edge non rilevanti (isteresi) mediante analisi a doppia soglia (a,b)

{Esempio di Isteresi}

Isteresi

L’isteresi è la fase finale del Canny Edge Detector che divide tutti i contorni in tre classi (nell’esempio vengono scelti i colori per definire le classi) in base a due soglie (nell’esempio sono a e b). I contorni legati alla prima classe (nell’esempio è il rosso) che hanno una magnitudine minore della prima soglia vengono eliminati immediatamente. Si parte ad ispezionare i contorni della seconda classe (arancione) e se sono collegati topologicamente con dei contorni della terza classe (verde) rimangono, altrimenti vengono eliminati.

Scelta del metodo di pre-elaborazione

Per i vari detector è stato scelto di utilizzare il secondo metodo di pre-elaborazione, che si è dimostrato essere migliore del primo.

File:Img/preelab-1 File:Img/preelab-2

{Esempio di risultato della pre-elaborazione nel primo metodo (prima immagine) e nel secondo metodo (seconda immagine)}

Door Detector

Dopo aver effettuato una edge detection sull’immagine con l’algoritmo di Canny, estraggo i contorni con la funzione findContours. Per cercare se l’i-esimo contorno combacia con una porta, calcolo il suo perimetro e controllo se sia sopra ad una certa soglia scelta a priori. Il contorno viene approssimato poligonalmente con la funzione approxCurve ed infine viene calcolato il suo inviluppo convesso con la funzione convexHull.

{Schema della struttura del riconoscimento della porta}

Estrazione dei bordi

La funzione findContours implementa l’algoritmo di Suzuki: l’immagine viene scansionata in avanti e all’indietro da due maschere (a e b presenti in figura) e viene assegnata una label provvisoria ad ogni pixel.

{a) Forward scan mask b) backward scan mask c) 8-connected neighborhood }

Suppongo che l’immagine binaria b(x,y) abbia dei pixel con valori Fo (che indicano gli oggetti) e FB (che indica lo sfondo) e la label provvisoria m inizializzata a 1.

Nella prima scansione si assegna una label provvisoria a ogni pixel nella posizione di «e» che soddisfa la seguente equazione di ricorrenza:

Failed to parse (lexing error): g(x,y)=\begin{cases} F_{B} & se\: b(x,y)=F_{B}\\ m,\:(m=m+1) & se\:\forall\{i,j\epsilon M_{s}\}\: g(x-i,y-j)=F_{B}\\ T_{min}(x,y) & altrimenti \end{cases}


T_{min}(x,y)=min\left\{ T\left[g(x-i,y-j)\right]/i,j\epsilon M_{s}\right\}

Dove in g(x,y) è memorizzata la label provvisoria, T{[}m{]} è la tabella delle equivalenze e Ms è la regione della maschera eccetto il pixel in oggetto. Una tabella di equivalenza descrive quali label appartengono allo stesso contorno. Anch’essa viene aggiornata nello stesso momento di g(x,y) soddisfando la seguente equazione:

Failed to parse (lexing error): \begin{cases} no-operation & se\: b(x,y)=F_{B}\\ T[m]=m & se\:\forall\{i,j\epsilon M_{s}\}\: g(x-i,y-j)=F_{B}\\ T\left[g(x-i,y-j)\right]=T_{min}(x,y) & se\: g(x-i,y-j)\neq F_{B} \end{cases}


Nelle scansioni successive, le scansioni in avanti e all’indietro sono eseguite alternativamente e le tabelle sono aggiornate secondo le seguenti equazioni:

Failed to parse (lexing error): g(x,y)=\begin{cases} F_{B} & se\: g(x,y)=F_{B}\\ T_{min}(x,y) & altrimenti \end{cases}


T_{min}(x,y)=min\left\{ T\left[g(x-i,y-j)\right]/i,j\epsilon M_{s}\right\}

Failed to parse (lexing error): \begin{cases} no-operation & se\: b(x,y)=F_{B}\\ T\left[g(x-i,y-j)\right]=T_{min}(x,y) & se\: g(x-i,y-j)\neq F_{B} \end{cases}


Le scansioni vengono eseguite finché rimane soddisfatta la seguente condizione

Failed to parse (lexing error): g(x-i,y-j)\neq T_{min}(x,y)\; se\: g(x-i,y-j)\neq F_{B}


cioè finché per ogni pixel nell’intorno di un pixel (x,y), che non sia sfondo, non sia assegnata la label Tmin(x,y). Questo significa riconoscere il fatto che un pixel vicino appartenga allo stesso contorno. Finché ciò non vale per tutti i pixel di uno stesso contorno, per tutti i contorni, l’algoritmo continua ad elaborare.

Approssimazione poligonale

L’approssimazione poligonale è fatta con la funzione approxCurve che implementa l’algoritmo di Douglas-Peucker . L’idea è, data una curva, di trovare una curva simile con meno punti. L’algoritmo definisce le differenze basandosi sulla massima distanza della curva originale e la curva semplificata (che consiste in un sottogruppo di punti della curva originale). Ovviamente l’algoritmo può essere esteso a forme più complesse di una curva. L’algoritmo:

  1. Ricorsivamente divido le linee, li ordino e setto un ε>0
  2. Inizialmente «marco» il primo e l’ultimo
  3. Traccio un segmento tra i due punti
  4. Trovo il punto più lontano dal segmento con una distanza > ε
  5. «marco» il nuovo punto e comincio da 3 scambiando questo punto col primo
  6. Continuo finché non ho più punti

{Esempio di applicazione dell’algoritmo di Douglas-Pecker}

Inviluppo Convesso

In matematica si definisce inviluppo convesso (o talvolta involucro convesso) di un qualsiasi insieme I l’intersezione di tutti gli insiemi convessi che contengono I.

{Esempio di Inviluppo Convesso}

Il calcolo dell’inviluppo convesso viene fatto con la funzione convexHull che implementa l’algoritmo Sklansky:

  1. Trovo un vertice convesso p0
  2. Diamo un nome agli n-1 vertici rimanenti, in senso orario, partendo da p0
  3. Metto tre monete su p0, p1 e p2 e le chiamo «dietro», «centro» e «davanti»
  4. Se le tre monete formano una curva destra
    1. Posiziono «dietro» sul vertice davanti a «davanti»
    2. Rinomino: «dietro» diventa «davanti», «davanti» diventa «centro» e «centro» diventa «dietro»
  1. Altrimenti
    1. Prendo «centro» e lo metto nel vertice dietro a «dietro»
    2. Rinomino: «centro» diventa «dietro» e viceversa
  1. Continuo finché «davanti» non è su p0 e le tre monete formano una curva destra

Algoritmo

L’inviluppo convesso di una porta deve avere solamente 4 punti. Ci sono vari casi dipendenti anche dalle angolazioni, dalle luci e dalle ombre, in cui il suo inviluppo convesso ha più punti. Per questo vengono usati due metodi per filtrare i punti rimanenti: un controllo se tre punti si trovano sulla stessa retta (o quasi) e una sogliatura sulla distanza fra i punti.

Vengono visitati i punti a coppie di tre e viene calcolato l’angolazione della retta passante tra il primo e secondo punto, che chiameremo θ1 e l’angolazione θ2 della retta passante tra il secondo e il terzo punto. Dove θ soddisfa l’equazione della retta r = ysin(θ) + xcos(θ) e l’angolazione tra due punti p1 e p2 viene calcolata in questo modo:

\theta=\begin{cases}
\pi/2 & p_{1}.y=p_{2}.y\\
atan(|p_{1}.y-p_{2}.y|/|p_{1}.x-p_{2}.x|) & altrimenti
\end{cases}

Se \theta_{1}\backsimeq\theta_{2} allora i tre punti poggiano sulla stessa retta, quindi viene eliminato il secondo punto che è il punto in mezzo della tripla, perché i punti ammessi sono solo il punto iniziale e quello finale di ogni segmento.

Una volta effettuata la sogliatura, vengono rivisitati tutti i punti e viene calcolato se la distanza col successivo è maggiore di una soglia chiamata sizeThresh, scelta a priori. Nel caso fosse minore, il punto successivo viene eliminato dalla lista e si ricalcola la distanza dal punto i-esimo al punto (i+1)-esimo (che prima dell’eliminazione era il punto (i+2)-esimo). Si continua così finché non si finisce di visitare tutti i punti. Viene fatto anche un controllo della distanza fra l’ultimo e il primo a priori, nelle stesse modalità. La formula per la distanza utilizzata è la city-block: presi due punti p1 e p2 allora d = | p1.xp2.x | + | p1.yp2.y | .

Finite queste due scremature dei punti, se il contorno ha 4 punti viene preso in considerazione altrimenti viene scartato.

Nel caso in cui venga preso in considerazione classifichiamo i segmenti del contorno in verticali o orizzontali:

Failed to parse (lexing error): tipo\: di\: segmento=\begin{cases} orizzontale & 0.9\leq\theta\leq1.6\\ verticale & \theta\leq0.61 \end{cases}


I valori descritti sono stati trovati empiricamente, dopo molte prove. La nostra porta ideale deve avere una forma ad U rovesciata, per questo, nel caso un segmento non sia né verticale né orizzontale il contorno viene scartato. Nel caso in cui esistano due segmenti orizzontali e due verticali dobbiamo trovare il segmento orizzontale che non è realmente presente (altrimenti troveremo anche oggetti che hanno la forma di un quadrato). Per questo viene calcolato il punto medio in ogni segmento orizzontale e viene controllato se la distanza tra il contorno, non ancora filtrato e quindi contenente ancora tutti i punti, e il punto medio è minore di una soglia chiamata distanzaThresh, nella situazione in cui solo uno dei due segmenti sia riconosciuto come {}“lato non esistente” allora riconosceremo il contorno come il contorno di una porta.

Una volta avvenuto il riconoscimento, viene calcolata la ROI (region of interest) dove verrà cercata la maniglia. Per questo viene passata a un livello successivo a cascata che controlla l’esistenza o meno di una maniglia (non è detto che tutti i contorni a U rovesciata siano porte, infatti questo algoritmo serve a ridurre l’area di ricerca della maniglia).

Risultati sperimentali

I risultati sperimentali su un dataset di 204 immagini hanno dimostrato una percentuale dell’73,6% di veri positivi e 26,4% di falsi positivi.

File:Img/matched/IMG 0476 File:Img/matched/IMG 0479
File:Img/matched/IMG 0482 File:Img/matched/IMG 0494

{Esempi di porte riconosciute}

File:Img/matched/IMG 0477 File:Img/matched/IMG 0533
File:Img/matched/IMG 0536 File:Img/matched/IMG 0480

{Esempi di falsi positivi}

File:Img/matched/IMG 0500 File:Img/component/IMG 0500
File:Img/matched/IMG 0503 File:Img/component/IMG 0503

{Esempi di porte non rilevate}

Handle Detector

La ricerca della maniglia si effettua in due modi: tramite fast template matching e tramite trasformata di Hough. Nel primo caso è un approccio basato sul template matching di immagine binarie di contorni. Sono stati definiti alcuni template ed è stato implementato un algoritmo di fast template matching. Tramite Normalized Correlation Coefficient (che è un metodo di template matching), si fa passare ogni template sulla regione di interesse al fine di trovare la maniglia. Nel secondo caso si ricercano tutte le linee parallele all’angolazione del lato superiore della porta e se ne esistono due che hanno delle buone qualità per essere una maniglia (distanza piccola fra loro, vicinanza al bordo e inizio e fine di entrambi i segmenti molto vicini). Si è scelto di utilizzare il secondo metodo se e solo se il primo metodo fallisca e non trovi nessun risultato.

{Schema della struttura del riconoscimento della maniglia}

Introduzione al Template matching

Template Matching è uno dei metodi più semplici di feature detection. L’idea è di far scorrere un’immagine template (in forma binaria o un pattern a livelli di grigio) sull’immagine a mano. E’ una ricerca 2D, per vedere se un’immagine dell’oggetto che combacia con il template può essere trovata da qualche parte nell’immagine. L’immagine template può essere salvata in un libreria con un array 2D t(i,j)=(i=-m/2,\ldots,m/2,j=-n/2,\ldots,n/2) dove m x n è la dimensione dell’oggetto.

Nello specifico, noi definiamo una distanza tra il template e la sottoimmagine

D(k,l)=\sum_{i=-m/2}^{m/2}\sum_{j=-n/2}^{n/2}[t(i,j)-f(k+i,k+j)]^{2}

dove MxN è la dimensione dell’immagine, e k=m/2,\ldots M-1-m/2,l=n/2\ldots,N-1-n/2. Se la distanza è più piccola di una soglia predeterminata D(k,l) < Td allora un oggetto si può dire localizzato alla posizione (k,l).

Somma di assolute differenze (SAD)

Un metodo basilare di template matching usa un template, su misura per una specifica feature di un’immagine in cui cerchiamo l’oggetto che vogliamo trovare. Questa tecnica può essere facilmente eseguita su immagini a livello di grigio o contorni di un’immagine. L’output della convoluzione tra l’immagine e il template sarà più alto dove la struttura dell’immagine combacia con la struttura della maschera, dove grandi valori dell’immagine vengono moltiplicati da grandi valori del template.

Chiameremo l’immagine in cui ricerchiamo S(x,y), dove (x,y) rappresentano le coordinate di ogni suo pixel e chiameremo il template T(i,j) dove (i,j) rappresentano le coordinate di ogni suo pixel. Per comparare useremo l’intensità dei pixel, usando la misura SAD.

SAD(x,y)=\sum_{i=0}^{T_{rows}}\sum_{j=0}^{T_{cols}}|S(x+i,y+j)-T(i,j)|

File:Img/SIMG+File:Img/Temp2=File:Img/SIMGo

{Esempio di SAD}

Somma di differenze quadratiche (SSD)

Chiameremo l’immagine in cui ricerchiamo S(x,y), dove (x,y) rappresentano le coordinate di ogni suo pixel e chiameremo il template T(i,j) dove (i,j) rappresentano le coordinate di ogni suo pixel. Per comparare useremo l’intensità dei pixel, usando la misura SSD. La misura SSD si basa sulla distanza euclidea mentre la misura SAD sulla distanza city-block.

SSD(x,y)=\sum_{i=0}^{T_{rows}}\sum_{j=0}^{T_{cols}}\left(S(x+i,y+j)-T(i,j)\right)^{2}

Fast Normalized Cross-Correlation

Introduzione

La correlazione fra due segnali (cross-correlazione) è un approccio standard per il riconoscimento di feature usato come componente di tecniche più sofisticate (per esempio ). Libri che parlano di correlazione presentano la teoria della convoluzione e la possibilità di avere la correlazione in modo computazionalmente efficiente, nel dominio delle frequenze, usando la FFT (Fast Fourier transform). Purtroppo la forma normalizzata della correlazione (correlazione di coefficienti), preferita nel template matching, non ha una corrispettiva espressione nel dominio delle frequenze semplice e veloce. Per questa ragione la NCC (Normalized Cross Correlation) viene calcolata nel dominio spaziale . Preso in considerazione il costo computazionale della convoluzione nel dominio spaziale, si hanno diverse inesattezze ma sono stati sviluppati veloci metodi di comparazione nel dominio spaziale .

Template Matching by Cross-Correlation

L’uso di cross-correlazione per template matching è motivato dalla misura della distanza (distanza Euclidea quadratica):

d_{f,t}^{2}\left(u,v\right)=\sum_{x,y}\left[f\left(x,y\right)-t\left(x-u,y-v\right)\right]^{2}

(dove f è l’immagine e la somma fino a x,y sotto la finestra contente la feature t posizionata a u,v). Si espande d2

d_{f,t}^{2}\left(u,v\right)=\sum_{x,y}\left[f^{2}\left(x,y\right)-2f\left(x,y\right)t\left(x-u,y-v\right)-t^{2}\left(x-u,y-v\right)\right]

il termine \sum t^{2}\left(x-u,y-v\right) è costante. Se il termine \sum f^{2}\left(x,y\right) è approssimativamente costante allora rimane il termine di cross-correlazione

c\left(u,v\right)=\sum_{x,y}f(x,y)t\left(x-u,y-v\right)

è la misura di similarità tra l’immagine e la feature.

Esistono diversi svantaggi nell’usare (1) per il template matching:

  • Se l’energia dell’immagine \sum f^{2}\left(x,y\right) varia con la posizione, il matching può fallire.
  • Se il range di c(u,v) è dipendente dalla dimensione della feature.
  • L’equazione (1) non è invariante ai cambiamenti dell’ampiezza dell’immagine causate dal cambiamento di luce in una sequenza di immagini

Il metodo Correlation Coefficient, basato sulla correlazione di coefficienti, è invariante per variazioni delle ampiezze dovute al cambiamento di luce. La normalizzazione della funzione correlazione rispetto a cambi di scala e rotazioni richiede un significativo incremento del numero di calcoli da effettuare. Nel nostro caso si potrebbe affrontare il problema cercando di scalare la regione di interesse (o parti di essa) in modo tale che siano grandi quanto la dimensione del template che si sta utilizzando così risolvendo il problema della scala.

Coefficiente di correlazione

Il coefficiente di correlazione è un importante strumento statistico che consente di stabilire il grado di relazione lineare tra due serie di dati. Tale coefficiente, noto come coefficiente di Pearson, viene definito come il rapporto tra la sommatoria dei prodotti dei vari punteggi standard delle due variabili ed i gradi di libertà; esso può variare da un massimo di +1,00 (perfetta correlazione positiva) ad un minimo di -1,00 (perfetta correlazione negativa). Il coefficiente di correlazione può essere rappresentato graficamente come il coseno dell’angolo formato tra due vettori associati alle variabili X ed Y. Calcolando, infatti, il prodotto scalare tra due vettori (X, Y) si può risalire all’angolo θ ed è pertanto possibile rappresentare nel piano due serie di dati.

X\cdot Y=\parallel X\parallel\parallel Y\parallel cos\theta

Coseno di similarità

Il coseno di similitudine, o cosine similarity, è una tecnica euristica per la misurazione della similitudine tra due vettori effettuata calcolando il coseno tra di loro, usata generalmente per il confronto di testi nel Data mining e nell’analisi del testo.

Dati due vettori di attributi numerici, A e B, il livello di similarità tra di loro è espresso utilizzando la formula

similarity=cos\left(\theta\right)=\frac{A\cdot B}{||A||\cdot||B||}=\frac{\sum_{k=1}^{n}A(k)B(k)}{\sqrt{\sum_{k=1}^{n}A(k)^{2}}\sqrt{\sum_{k=1}^{n}B(k)^{2}}}

dove la funzione cos\left(\theta\right) non va confusa con il coseno tra vettori, perché, in questo caso, si applica una normalizzazione grazie alla magnitudine.

Metodi di template matching

Nei capitoli precedenti sono stati citati SSD (Sum of Squared Difference) e NCC (Normalized Cross Correlation), questi sono metodi disponibili nella libreria di OpenCV con la funzione matchTemplate . La funzione fa scivolare il template sull’immagine in questione utilizzando uno specifico metodo scelto a priori. Qui ci sono le formule dei metodi di comparazione disponibili (I denota l’immagine, T denota il template e R il template). Le sommatorie sono eseguite sul template e/o l’immagine che è sovrapposta: x' = 0...w − 1,y' = 0...h − 1.

  • metodo Sum of Squared Difference:
R(x,y) = (T(x',y') − I(x + x',y + y'))2
x',y'

  • metodo Sum of Normalized Squared Difference: R(x,y)=\frac{\sum_{x',y'}(T(x',y')-I(x+x',y+y'))^{2}}{\sqrt{\sum_{x',y'}T(x',y')^{2}\cdot\sum_{x',y'}I(x+x',y+y')^{2}}}
  • metodo di Cross Correlation: R(x,y)=\sum_{x',y'}(T(x',y')\cdot I(x+x',y+y'))
  • metodo di Normalized Cross Correlation: R(x,y)=\frac{\sum_{x',y'}(T(x',y')\cdot I'(x+x',y+y'))}{\sqrt{\sum_{x',y'}T(x',y')^{2}\cdot\sum_{x',y'}I(x+x',y+y')^{2}}}
  • metodo di Correlation Coefficient: R(x,y)=\sum_{x',y'}(T'(x',y')\cdot I(x+x',y+y'))
dove \begin{array}{l}
T'(x',y')=T(x',y')-1/(w\cdot h)\cdot\sum_{x'',y''}T(x'',y'')\\
I'(x+x',y+y')=I(x+x',y+y')-1/(w\cdot h)\cdot\sum_{x'',y''}I(x+x'',y+y'')
\end{array}
  • metodo di Normalized Correlation Coefficient: R(x,y)=\frac{\sum_{x',y'}(T'(x',y')\cdot I'(x+x',y+y'))}{\sqrt{\sum_{x',y'}T'(x',y')^{2}\cdot\sum_{x',y'}I'(x+x',y+y')^{2}}}

Nel nostro caso utilizziamo il metodo di Normalized Correlation Coefficient (NCCOE) perché si è dimostrato migliore degli altri come risultati. I risultati del NCCOE hanno un valore tra 0 e 1 (oppure nel nostro caso è stato moltiplicato per 100 e si utilizza la percentuale).

Fast Template Matching

Il metodo di fast template matching implementato, oltre ad avere in input template T e immagine I, ha i seguenti parametri:

  • matchPercentage, la percentuale minima per cui un massimo sia preso in considerazione
  • findMultipleTargets, un booleano da settare a vero se si vuole trovare più di un bersaglio
  • numMaxima, il numero massimo di locazioni da cercare prima di uscire
  • searchExpansion, una volta selezionate le locazioni (vedasi grafico), per settare la regione di interesse (ROI) si va in +/- searchExpansion sia per x che per y rispetto alla locazione scelta
  • minConfidence, confidenza minima per cui un pixel deve essere considerato.
  • templateMethod, metodo di template utilizzato

Inizialmente viene fatto passare il template sull’immagine e calcolata la matrice risultante result secondo il templateMethod (nel nostro caso usiamo Normalized Cross Correlation Coefficent) passato alla funzione matchTemplate. Nella matrice result ricerchiamo al massimo numMaxima massimi locali che vengono salvati in un array ordinato in modo decrescente. Per ogni pixel controlliamo se è maggiore del valore di minConfidence e se è maggiore di uno dei massimi già salvati. Nel caso in cui fosse vero, prima facciamo slittare di una posizione tutti i massimi più piccoli e inseriamo nell’array il nuovo massimo.

Per ogni massimo locale \left(x,y\right) creiamo una regione di interesse ROI quadrata con lato 2 * searchExpansion centrata nel massimo. Effettuiamo una ricerca, nuovamente grazie alla funzione matchtemplate, e questa volta ricerchiamo il massimo o minimo globale all’interno della ROI (dipende dal tipo di templateMethod utilizzato). Se il massimo è maggiore a matchPercentage, allora abbiamo trovato una feature. Nel caso in cui findMultipleTargets fosse false, finiamo la ricerca e usciamo, in caso contrario continuiamo finché non abbiamo ispezionato tutti i massimi locali trovati precedentemente.

{Schema della struttura del fast template matching}

Trasformata di Hough

Una linea nello spazio 2D può essere espressa con due variabili, per esempio:

  • Nel sistema di coordinate cartesiano con i parametri (m,q)
  • Nel sistema di coordinate polari con i parametri (r,θ)

{english}

{italian}Per la trasformata di Hough, le linee vengono espresse nel sistema Polare, quindi l’equazione di una linea può essere scritta come:

y=\left(\frac{-cos\theta}{sin\theta}\right)x+\left(\frac{r}{sin\theta}\right)

Sistemando i termini si ha

r = cosθx + sinθy

In generale per ogni punto (x0,y0) possiamo definire una famiglia di linee che passano per esso:

rθ = cosθx0 + sinθy0

Vuol dire che ogni coppia (rθ,θ) rappresenta una linea che passa per (x0,y0). Quindi viene creata una matrice chiamata accumulatore A, in cui ogni elemento è inizializzato a 0. Il numero di righe e di colonne dipende dal numero di discretizzazioni dei parametri reali e continui r e θ. Per ogni coppia (r,θ) trovata per un punto (x,y) viene incrementato di 1 A(r,θ) e infine vengono considerate solamente le linee che hanno una coppia (r,θ) con un numero maggiore di punti maggiore di una soglia scelta a priori.

Caricamento dei template

Il caricamento dei template viene fatto considerando che i template utilizzati sono solamente delle maniglie e quindi rappresentano un sotto-caso speciale del riconoscimento della maniglia senza template matching (infatti i grafici sono molto simili), solo che in questo caso già sappiamo che l’immagine passata è una maniglia e quindi non c’è bisogno di applicare filtri e soglie. Quando i template vengono caricati, per ognuno viene calcolato il punto di presa (x,y,θ). Se nell’immagine si trova il template i-esimo sappiamo già dove prendere la maniglia e come approcciarci, ovviamente le x e y sono relative al template quindi verranno poi sommate rispetto a un offset che descrive dove è la regione che combacia col template nella ROI e un altro offset che ci dice dove si trova la ROI nell’immagine iniziale.

File:Img/template/IMG 0477.JPG File:Img/template/IMG 0481 File:Img/template/IMG 0495 File:Img/template/IMG 0499.JPG
File:Img/template/template1 File:Img/template/template2 File:Img/template/template3 File:Img/template/template4
File:Img/template/template5 File:Img/template/template6 File:Img/template/template7

{I template scelti in Dodhek}

{Schema della struttura del caricamento dei template}

Calcolo del punto di presa

Per calcolare il punto di presa (x,y,θ) calcoliamo la trasformata di Hough probabilistica per le linee con la funzione HoughLinesP che per ogni segmento trova (x0,y0,x1,y1) e per ogni segmento orizzontale calcoliamo (r,θ).

Se il numero di segmenti è maggiore di 2 (la maniglia dovrebbe avere solamente due segmenti paralleli) prendo in considerazione i due segmenti paralleli più distanti (questa scelta parte dal presupposto che i template siano immagini binarie già lavorate, dove c’è solamente una maniglia).

Per calcolare il punto di presa verranno calcolati i punti medi di entrambi i segmenti e verrà tracciata la linea passante da questi due punti e verrà preso in considerazione il punto medio. Per calcolare θ viene fatta semplicemente una media tra i θ dei due segmenti. Una volta trovati i tre parametri vengono memorizzati in una classe di utilità e utilizzati in stadi successivi.

Riconoscimento della maniglia basato sulla trasformata di Hough

Precedentemente nell’oggetto porta è stata salvata l’angolazione θlsp del lato superiore della porta perché c’è buona probabilità che la maniglia abbia la stessa angolazione. Per questo, utilizziamo lo stesso metodo utilizzato per calcolare il punto di presa quando i template vengono caricati con due accortezze.

La prima accortezza è quella di controllare se i segmenti orizzontali hanno un angolazione θ che è circa l’angolazione del lato superiore della porta (\theta\backsimeq\theta_{lsp}). Nel caso in cui non fosse così il segmento viene eliminato. La seconda è quella di verificare la distanza tra due segmenti paralleli.

E’ ovvio che se fossero orizzontali e che se tutti avessero un \theta\backsimeq\theta_{lsp} sarebbero tutti paralleli fra di loro. Per questo, dobbiamo eliminare quei casi in cui due segmenti paralleli siano troppo distanti e scegliere quale fra le coppie rimaste sia la migliore coppia nell’ordine di angolazione e distanza.

Risultati Sperimentali

I risultati sperimentali su un dataset di 204 immagini hanno dimostrato una percentuale dell’80,6% di veri positivi e 19,4% di falsi positivi per quanto riguarda il metodo di template matching. Per il metodo basato sulla trasformata di Hough i risultati si abbassano drasticamente con una percentuale del 56% di veri positivi e 44% falsi positivi. Per questo è stato scelto di utilizzare metodo basato sulla trasformata di Hough solo nel caso in cui il primo metodo fallisca.

File:Img/matched/IMG 0513 File:Img/matched/IMG 0484 File:Img/matched/IMG 0485 File:Img/matched/IMG 0534 {Errori nel riconoscimento della maniglia con fast template matching}
File:Img/matched/IMG 0478 File:Img/matched/IMG 0481
File:Img/matched/IMG 0486 File:Img/matched/IMG 0489
File:Img/matched/test-4 File:Img/matched/IMG 0495
File:Img/matched/IMG 0493 File:Img/matched/IMG 0518

{Esempi di maniglie riconosciute con fast template matching}

File:Img/match no template/IMG 0494 File:Img/match no template/IMG 0498 File:Img/match no template/IMG 0529 File:Img/match no template/IMG 0543 {Esempi di maniglie riconosciute senza fast template matching}

Haar-like features

Avevo già avuto un po’ di esperienze in elaborazione immagini e in computer vision, volevo utilizzare qualche tecnica avanzata per il riconoscimento di maniglie. Facendo un’accurata ricerca avevo trovato due metodi che sembravano quasi infallibili: ISM (Implicit Shape Model) e Haar. Pensavo di poter creare un detector efficiente, veloce ed affidabile. La prima si basa su BoW (Bag of Words) e la seconda tecnica si basa su Machine Learning, che ho scoperto essere un enorme branca dell’informatica, infatti nel web questo argomento è facilmente rintracciabile. Ho letto tanti metodi su paper, li ho provati ed alla fine, dopo giorni, non funzionavano. Non metto in dubbio che, magari, tra un po’ di anni rileggendo questa tesi, la troverei banale, trovando facilmente risolvibili i problemi insormontabili che trovo adesso. Alla fine ho scelto di iniziare ad utilizzare Haar e di utilizzare le api già predisposte da OpenCV.

Classificatore Haar

L’algoritmo di Viola e Jones proposto nel 2001 introduce tre elementi di novità rispetto agli algoritmi di ricerca di feature in immagini statiche precedentemente sviluppati:

  • Il primo consiste nell’utilizzo delle Haar-like features sulle regioni che compongono un’immagine in combinazione con una nuova rappresentazione in forma matriciale dell’immagine stessa, detta Integral Image. Le feature utilizzate, grazie alla loro semplicità, presentano un basso costo computazionale, il calcolo della nuova struttura dati impiega un tempo quadrato e permette di effettuare l’analisi in tempo costante indipendentemente dalla dimensione delle regioni analizzate.
  • In secondo luogo viene presentato un metodo per la costruzione di classificatori basato sulla selezione di un basso numero di feature di Haar attraverso l’algoritmo AdaBoost di Freud e Schapire del 1995 . Questa strategia permette di eliminare la maggior parte delle feature di scarsa capacità discriminante e selezionare solo quelle più efficaci per la costruzione del classificatore.
  • Il terzo maggiore contributo consiste nell’introdurre una nuova strategia di analisi dell’immagine basata su una struttura a cascata dove ogni livello della cascata è un classificatore creato con procedura AdaBoost. La complessità dei livelli cresce man mano che si procede verso la fine della cascata. In questo modo le regioni di facile analisi vengono scartate velocemente ai primi stadi, mentre quelle di più difficile interpretazione sono sottoposte a più livelli di verifica. Qualora una sotto regione completi la cascata, ovvero superi tutti gli stadi di classificazione con successo, viene etichettata come regione contenente una feature.

Feature di Haar e Integral Image

Come anticipato, il metodo ideato da Viola e Jones classifica le regioni di un’immagine basandosi su valori calcolati attraverso semplici operatori chiamati Haar-like features. In particolare ve ne sono di tre tipologie:

  • two-rectangle features: coppia di rettangoli affiancati verticalmente o orizzontalmente (A e B)
  • three-rectangle features: formate da tre rettangoli affiancati orizzontalmente (C)
  • four-rectangle features: composte da quattro rettangoli disposti a scacchiera (D)

{Haar-like Features}

Le feature, oltre al tipo, si differenziano tra loro anche per dimensione e posizione all’interno della detection window (la finestrella di ricerca che scansiona l’immagine in analisi). Il valore di una feature, una volta applicata su una regione dell’immagine, è dato dalla differenza (normalizzata) tra i livelli di grigio dei pixel appartenenti ai rettangoli bianchi e a quelli neri.

Le motivazioni a favore dell’utilizzo di questo tipo di feature sono molteplici. In primo luogo, considerando che i volti hanno una struttura regolare (ad esempio si noti che l’asse degli occhi e la posizione centrale del naso sono caratteristiche comune a tutte le facce), è intuibile come tali caratteristiche possano contribuire a discriminare gli esempi positivi da quelli negativi in maniera netta.

In seconda battuta, grazie all’utilizzo dell’immagine integrale, l’operazione di accesso ai pixel dell’immagine risulta computazionalmente molto efficiente (il costo è costante indipendentemente dall’estensione in pixel del rettangolo). L’immagine integrale, che ha le stesse dimensioni dell’immagine originale, nella posizione di indice (x,y) contiene la somma dei livelli di grigio di tutti i pixel superiori e a sinistra del punto (x,y) incluso. Utilizzando l’immagine integrale, la somma dei livelli di grigio di una qualsiasi area rettangolare in un’immagine può essere calcolata attraverso pochi riferimenti alla matrice.

Lienhart and Maydt introducono il concetto di Haar-like feature inclinata (45{}). Questa viene usata per incrementare la dimensionalità del set di feature per tentare di migliorare il riconoscimento di oggetti nell’immagine.

Messom and Barczak estendono l’idea ad una Haar-like feature genericamente ruotata. Malgrado l’idea suoni matematicamente giusta, nei problemi pratici viene prevenuto l’uso di Haar-like features di ogni angolazione. Per essere più veloci, gli algoritmi di riconoscimento usano immagini a bassa risoluzione, causando errori di arrotondamento. Per questa ragione, feature Haar-like non vengono comunemente usate.

Integrazione in OpenCV

In OpenCV viene divisa la fase di training dalla fase di detection, come in ogni algoritmo di Machine Learning. La fase di training ha come risultato un file XML che descrive le Haar-like features e la struttura a cascata creata. Mentre la fase di detection analizza un’immagine in input con l’xml e cerca le feature nell’immagine. Inizialmente è stata testata la fase di detection dell’algoritmo utilizzando il file xml, che è il risultato del training per un face detector (ogni volta che viene ricercato qualcosa per Haar-like viene sempre legato al face detector inizialmente implementato da Viola e Jones e poi portato avanti da altri). Visto che funziona benissimo per le faccie avevo pensato di utilizzarlo anche per maniglie e tasti dell’ascensore. L’integrazione con OpenCV e poi con Ros sembrava funzionare perfettamente, Ros mi permetteva di leggere le immagini del Kinect attraverso il master e facevo una veloce detection e la maggior parte delle volte riconosceva la faccia. Quindi ho iniziato a studiare come potesse funzionare la fase di training, per capire come creare un xml che mi permettesse poi di riconoscere maniglie e tasti.

Risultato XML di Haar

Viene inoltre dimostrato, in modo empirico , che la configurazione più efficiente è quella in cui ogni stadio di classificazione è una combinazione lineare di classificatori deboli di tipo CART (Classification and Regression Tree) con due split (ramificazioni) di decisione. I CART sono addestrati con una variante dell’algoritmo AdaBoost originale. Tale variante, che utilizza un diverso schema di aggiornamento dei pesi durante le iterazioni, è attualmente la procedura di apprendimento che garantisce i migliori risultati nella realizzazione di rilevatori di feature. Nell’implementazione di OpenCV (di cui Lienhart è uno dei maggiori artefici), il classificatore è presentato sotto forma di file XML. Il classificatore {}haarcascade_frontalface_alt2.xml{} (presente nel pacchetto OpenCV) costituisce il rilevatore di volti frontali. Tale classificatore è composto da 20 stadi di classificazione di complessità sempre crescente: il numero di classificatori deboli presenti negli stadi aumenta tendenzialmente lungo la cascata andando da tre, per il primo stadio, fino a centonove nel ventesimo livello. Ogni CART è composto da due nodi di decisione; il primo è chiamato nodo radice ed è il nodo di ingresso all’albero. Quando un albero ha un solo nodo di decisione (quindi una sola feature da testare) l’albero è di tipo Stump; graficamente è composto solo da un nodo radice e due foglie terminanti (evidenziati in rosso nella figura seguente).

{CART e XML}

Come detto, in precedenza, ad ogni nodo di decisione corrisponde l’utilizzo di una feature di Haar con relativa soglia di valutazione. A seconda dell’esito del confronto tra la soglia e il responso dato dalla feature è possibile proseguire l’esplorazione del secondo livello dell’albero CART oppure terminare subito l’analisi; in entrambi i casi, il nodo terminante (in verde nella figura sopra) restituisce un valore che esprime in termini numerici la bontà della risposta del classificatore debole applicato alla regione in esame.

Tutti i contributi di ogni singolo classificatore debole all’interno di uno stadio della cascata vengono sommati tra loro per formare il contributo totale che la combinazione lineare di classificatori lineari restituisce; tale valore viene infine confrontato rispetto alla soglia di livello per valutare se la regione in esame può essere scartata (non faccia) oppure deve essere promossa al successivo stadio di valutazione così come previsto dall’algoritmo di Viola e Jones.

Training parallelizzato: OpenMP

Dopo aver seguito alla lettera come fare il training per Haar (in internet si trova sempre la stessa documentazione, sembra essere l’unica fonte disponibile) e provato a metterlo in atto ed ho visto che non funzionava e si bloccava varie volte. Per ovviare al problema ho staccato il modulo da OpenCV e ho creato un progetto a parte per avere miglior controllo su tutto. La prima cosa che mi ha stupito è che utilizzava ancora OpenMP che OpenCV aveva dichiarato deprecato e stava dismettendo pian piano in favore di Intel TBB. Qui una breve descrizione di entrambi i framework prima di continuare.

OpenMP

OpenMP (Open Multi-Processing) è un API (application programming interface) che supporta la programmazione parallela in C, C++, and Fortran, su molte architetture e sistemi operativi, incluso Linux, Unix, AIX, Solaris, Mac OS X, e piattaforme Microsoft Windows. Consiste in un set di direttive per il compilatore, routine di libreria e variabili d’ambiente che possono influenzare il comportamento del programma a run-time.

OpenMP è gestito dal consorzio tecnologico no-profit OpenMP Architecture Review Board (o OpenMP ARB), definito insieme da un gruppo delle maggiori case venditrici di hardware e software come AMD, IBM, Intel, Cray, HP, Fujitsu, NVIDIA, NEC, Microsoft, Texas Instruments, VMware, Oracle Corporation e altre.

Intel TBB

Intel Threading Building Blocks (anche conosciuta come TBB) è una libreria a template in C++ sviluppata da Intel Corporation per scrivere software che prendono vantaggio da processori multi-core. La libreria consiste in strutture dati e algoritmi che permettono ad un programmatore di eliminare alcune complicazioni. Queste si hanno quando si usano pacchetti nativi che gestiscono thread quali POSIX threads, Windows threads o i portabili Boost Threads, in cui i thread individuali di esecuzione sono creati, sincronizzati e terminati manualmente. Invece, la libreria in questione, astrae l’accesso ai processori multi-core, permettendo delle operazioni che avvengono ad alto livello. L’allocazione a core individuali viene fatta dinamicamente dal runtime engine interno alla libreria dove c’è un uso efficiente e automatico della cache. Questo approccio racchiude TBB nella famiglia di soluzioni di programmazione parallela che ha lo scopo di disacoppiare la programmazione con i particolati della macchina sottostante.

Boost di Haar

Dalla spiegazione fatta sopra dei due framework si nota le ragioni della scelta di OpenCV di abbandonare un vecchio stile di programmazione come è in uso in OpenMP. All’inizio ho pensato che stessero dismettendo il supporto OpenMP facendolo bene pezzo per pezzo, non togliendolo completamente e lasciando così lì dismessa una cosa non funzionate. Quindi seguendo i pochi consigli trovati su internet sono riuscito a compilare il codice che avevo riunito ed a farlo andare in parallelo. Ma ancora aveva dei problemi che non riuscivo a risolvere. Sotto consiglio del mio relatore, ho utilizzato un profiler per capire dove perdesse la maggior parte del tempo di esecuzione. Era comunque un codice di diecimila righe, quasi per nulla commentato che faceva molte cose dinamiche e richiami di funzioni. Dopo varie prove sono riuscito ad avere un risultato dal profiler e a capire che la maggior parte del tempo la sprecava in QuickSort che non era stranamente parallelizzato. Quindi la mia ricerca si è spostata sul trovare un modo per parallelizzare anche questa procedura.

Cambio di approccio dopo vari fallimenti

Dopo uno studio sulla parallelizzazione del quicksort e qualche implementazione veloce, prima di iniziare mi sono chiesto se nessun altro aveva avuto il mio stesso problema. Dopo varie ore di ricerca trovai che uno sviluppatore di TBB aveva scritto che haartraining (l’utility che stavo utilizzando) doveva essere ormai fuori uso e doveva essere eliminata dal trunk di SVN. E che consigliava l’utilizzo di traincascade che non solo supportava Haar, ma supportava LBP (Local Binary Pattern). Di fatto su quella utility non esiste documentazione, anche se è stata rilasciata da quasi un anno, tutti i tutorial in giro consigliano di usare haartraining, poi ho scoperto di non essere l’unico ad avere questo tipo di problemi. E quei pochi che avevano avuto risultati avevano utilizzato una versione molto vecchia di OpenCV, quando ancora Haar era la tecnica più famosa. Infatti dopo un po’ di settimane ho scoperto che traincascade era ancora molto sperimentale, supportava meglio LBP di Haar e poi il riconoscimento effettivo basato sull’XML non si capiva se era funzionante o meno. Notando il numero ecessivo di problemi, ho deciso di cambiare approccio.

Elevator Keyboard Detector

Dopo aver effettuato una edge detection sull’immagine con l’algoritmo di Canny, estraggo i contorni con la funzione findContours.

Riconoscimento dei tasti dell’ascensore

Per ogni contorno controllo se è un quadrato, calcolo il suo perimetro p e area a e controllo se p/4\backsimeq\sqrt{a}. Se è vero, calcolo il cerchio minore che racchiude il mio contorno e controllo se il raggio di questo cerchio è maggiore di una soglia scelta a priori per eliminare contorni piccoli. L’osservatore deve essere più o meno all’altezza della pulsantiera perchè funzioni l’euristica del quadrato e non venga distorto dalla prospettiva. Ogni volta che un contorno viene riconosciuto come un tasto viene messo in una lista. Una volta finito il controllo di tutti i contorni, nel caso in cui siano stati rilevati tasti e soprattutto non sia stata rilevata una porta, allora controlliamo se i tasti rilevati appartengono davvero a una pulsantiera di un ipotetico ascensore.

Per primo dividiamo i bottoni per cluster in base al loro raggio (che è stato calcolato prima). I cluster non sono altro che insiemi di elementi dove un elemento è il rappresentante per certe proprietà. In questo caso i cluster sono divisi a seconda del raggio, ogni volta che prendiamo un ipotetico tasto dalla lista dobbiamo decidere se metterlo in un cluster già esistente o crearne uno nuovo dove l’elemento sarà il suo nuovo rappresentante. Per decidere questo vediamo se il raggio dell’elemento scelto è circa uguale al raggio del rappresentate del cluster i-esimo che prendiamo in considerazione. Se si, l’elemento diventa parte del cluster e viene ricalcolato un nuovo rappresentante che meglio rappresenterà l’insieme, altrimenti si passa al successivo. Questo avviene finché la lista non è stata ispezionata del tutto. Se l’elemento non è stato assegnato a nessun cluster, allora verrà creata un nuovo cluster che avrà esso come rappresentante.

Una volta creati gli insiemi di elementi, questi vengono controllati da un classificatore di minima distanza che decide se un elemento è troppo lontano dagli altri. Nel caso in cui fosse vero, verrebbe espulso dall’insieme. Il classificatore a minima distanza controlla se due elementi (in questo caso sono cerchi) siano innestati (un esempio è il tasto dove viene inserita la chiave nelle immagini sottostanti). In questo caso viene eliminato il cerchio con raggio minore.

Per ogni elemento i-esimo di ogni gruppo, si vede quale angolazione ha rispetto agli altri elementi, se risulta essere non {}“verticale” per ciascuno degli altri elementi, allora l’elemento viene eliminato.

Una volta finito il filtraggio degli elementi, prenderemo in considerazione tra tutte i gruppi di tasti, con numero di tasti maggiore di uno (altrimenti non è una tastiera), l’insieme con più tasti. Questa scelta parte dalla considerazione che difficilmente ce’è più di una tastiera dell’ascensore nella stessa immagine. Nel caso in cui sia stata rilevata vengono salvate le coordinate (x,y) e raggio r per ogni tasto.

{Schema della struttura del riconoscimento dei tasti dell’ascensore}

Risultati sperimentali

I risultati sperimentali su un dataset di 80 immagini hanno dimostrato una percentuale dell’74,4% di veri positivi e 25,6% di falsi positivi.

File:Img/matched/test-238 File:Img/component/test-238.ppm
File:Img/matched/test-84 File:Img/component/test-84.ppm

{Errori nel riconoscimento di tasti}

File:Img/matched/test-76 File:Img/matched/test-81
File:Img/matched/test-149 File:Img/matched/test-169
File:Img/matched/test-189 File:Img/matched/test-208
File:Img/matched/test-212 File:Img/matched/test-218

{Esempi di tasti rilevati}

Dodhek

Introduzione

E’ stato implementato un esempio di una possibile integrazione dei vari detector. Dopo aver effettuato una edge detection sull’immagine con l’algoritmo di Canny, estraggo i contorni con la funzione findContours, per ogni contorno controllo prima che sia una porta. Se non è una porta controllo se è un tasto dell’ascensore. Nel caso sia una porta verrà messa nella lista doors, nel caso sia un tasto dell’ascensore verrà messa nella lista elevatorButton. Infine se non sono state rilevata porte, si cercherà di capire se esiste una pulsantiera nell’immagine.

{Schema della struttura ipotetica di Dodhek}

Conclusioni e sviluppi futuri

Dodhek è un insieme di detector ognuno con le sue caratteristiche, potrebbe essere quasi presentato come un modulo indipendente. Ci sono problemi, per quanto riguarda le maniglie, che dipendono dalla scala e dalla rotazione, per risolverli in modo quasi definitivo bisogna utilizzare metodi come SIFT o SURF, un po’ troppo accademici, oppure metodi basati su tecniche avanzate come Haar, ISM, LBP e OpenTLD, che sembrano avere più riscontro nella vita reale, ma come presentato nella sezione su Haar hanno tantissimi problemi legati a aspetti implementativi per quanto rigurada la fase di training. Manca la parte software di {}“alto” livello che decide quando utilizzare uno o più detector di Dodhek, è stata presentata una bozza di una possibile integrazione, ma ci potrebbero essere altre molteplici architetture. Lo stesso {}“modulo” potrebbe essere ampliato e non solo permettere la scelta di quale detector utilizzare ma anche di quale approccio utilizzare (un esempio è il detector delle maniglie che utilizza due metodi interni) oppure specificare attraverso un funtore come procedere col matching, anche se risulterebbe troppo complicato da generalizzare.

Ore di lavoro

giorno inizio fine n. ore descrizione attività svolta
19/06/11 14:00 19:00 5 creazione pagina wiki personale, lettura tesi Sacchi e improving Ros
26/06/11 14:00 19:00 5 Improving Ros, Rviz e vari pacchetti in ros
22/07/11 10:00 18:00 8 Ros, Opencv e PCL e brainstorming
28/07/11 14:00 19:00 5 Lettura di alcuni capitoli di Computer Vision: A Modern Approach
29/07/11 09:00 18:00 8 Riguardato Hook Finder e fatto varie modifiche a quello e altri programmi trovati in giro
30/07/11 09:00 13:00 4 Studio di PCA e visione del paper: Real-time Door Detection based on AdaBoost Learning Algorithm
31/07/11 10:00 19:00 8 Sistemazione script roboupdate.sh, compilazione da lina di comando di Opencv, Prime prove con AdaBoost
04/08/11 09:00 18:00 8 Scelta troppo complicata, ho fatto una serie di infiniti tutorial per capire meglio come funziona Opencv
06/08/11 13:00 17:00 4 OpenCV non riconosceva la webcam, alla fine ci sono riuscito..
13/08/11 15:00 19:00 4 Morfologia matematica & Classificazione
01/09/11 09:00 18:00 8 Riflessioni, lettura paper su Implicit Shape Model e compilazione di libpmk-2.5
02/09/11 09:00 15:00 5 Chiaccherata con Furlan, prove con ISM e Haar, spulciato tesi Furlan
01/10/11 21:00 02:00 5 Rifatto piano di lavoro e messo references
03/10/11 10:00 18:30 8 Fatti task [1] [2] [3] [4] [5] [6] [7] [8] [10] [15]
04/10/11 09:00 18:30 8 Fatti task [16] [17] [19]
05/10/11 10:00 19:00 8 Fatti task [18] e mille prove con haar training. Creato account nella macchina di Sacchi e installato di nuovo tutta la roba per lavorare in remoto
05/10/11 21:00 01:00 4 Rimplementato da 0 haartraining, adesso va meglio ed è più facile da debuggare e task [11]
06/10/11 09:00 18:00 8 Modifiche al programma principale di haartraining, fatto in modo tale che scrive su file invece che in bash, risistemato la cartella dividendo gli script con i file dat, log e vec e fatti vari training, adesso funziona meglio, si debugga tranquillamente e va bene anche in parallelo
07/10/11 10:00 16:30 6 Ampliato dataset di immagini (task [21]), sia con foto che con task [9], e aggiunte le direttive per il profiling nel make di haartraining
09/10/11 10:00 13:00 3 Fatto l'ennesimo training andato male, rifatto ripartire con dei limiti più stringenti e visionato il file di output del profiling
09/10/11 15:00 18:00 3 Dopo la nefasta scoperta (https://groups.google.com/group/android-opencv/browse_thread/thread/c2070d2fe3df1a93?pli=1#), cambiato haartraing con traincascade e ricompilato a mano intel tbb 4.0 sia sulla mia macchina che sulla macchina di Sacchi e rifatto ripartire training
10/10/11 9:30 13:00 4 Sistemato Hook Finder
11/10/11 9:30 19:00 9 Trasposto Hook Finder in OpenCV
11/10/11 21:00 01:00 4 Trasposto Hook Finder in OpenCV
12/10/11 10:00 18:30 8 Varie modifiche è prove sul detector
13/10/11 09:30 18:30 9 Aiutato gli altri al cart e aggiunta il gamma correction
14/10/11 09:30 18:30 9 Altre prove e inizio scrittura tesi
15/10/11 13:30 18:30 5 Altre prove (flusso ottico) e inizio scrittura tesi
16/10/11 17:30 19:30 2 Altre prove (flusso ottico) e scrittura tesi
17/10/11 09:30 19:30 9 Altre prove (flusso ottico), installazione prosilica, scrittura tesi
18/10/11 11:00 18:30 6 Sistemato il codice e incapsulato un pò per benino, fatto altre prove con la prosilica -.- trovato il problema almeno, non è colpa di ROS, ma dell'SDK
18/10/11 20:00 00:00 4 Door Detection con trasformata di Hough
19/10/11 09:30 18:30 8 Door Detection e smanettamento con prosilica
20/10/11 09:00 17:00 7 Door Detector
20/10/11 18:00 20:00 2 Door Detection, findCountours, approxPolyDp, COnvexHull
21/10/11 10:30 18:30 7 penso sia finito il Door Detection
22/10/11 16:30 19:00 3 sistemazione e arrangiamento di Dodhek in un solo programma
23/10/11 14:30 18:30 4 Calcolo (x,y,theta) del punto di presa
24/10/11 08:30 18:30 9 Aggiunta del detect dei tasti e della pulsantiera dell'ascensore se non c'è una porta, sistemazione vari bug
25/10/11 10:30 18:30 7 Aumentato dataset, casi di test, sistemato template matching in alcuni casi e altri piccoli bug, valutazioni prestazioni
ORE TOTALI: 241
Personal tools