1
UNIVERSITA’ DEGLI STUDI DI PARMAFacoltà di Ingegneria
Corso di laurea in Ingegneria elettronicaAnno accademico 2000 / 2001
Progettazione e Realizzazione di un
Sistema per Analisi del Moto di
Oggetti in Scene Stradali
Relatore: Correlatore:Chiarissimo Prof. Ing. Giovanni Adorni Chiarissimo Prof. Ing. Stefano Cagnoni
Tesi di laurea di: Pier Paolo Ampollini
2
Indice
1 INTRODUZIONE 4
2 FLUSSO OTTICO E STIMA DEL MOVIMENTO 7
2.1. Stime basate sul Flusso Ottico 82.1.1. Pro e contro 14
2.2. Stime basate su corrispondenze discrete 152.2.1. Pro e contro 19
2.3. L’algoritmo di Gabriele Maris e lo sviluppo successivo 202.3.1. La prima stesura 212.3.2. Primi risultati ed alcuni esempi 282.3.3. Time to Impact 302.3.4. L’algoritmo sviluppato 332.3.5. Test e risultati 392.3.6. Preparazione alla tesi 402.3.7. Ultimi test e conclusioni 42
3 IL SISTEMA APACHE 44
3.1. Come funziona 45
3.2. Problemi e possibili sviluppi 47
4 FLUSSO FOR LINUX 49
4.1. Uno sguardo d’insieme 504.1.1. Gli elementi del sistema 51
4.2. L’algoritmo sviluppato 514.2.1. La thread ScriviOnline 534.2.2. La procedura videoplay 554.2.3. La procedura Flusso ed i suoi moduli 56
3
4.2.3.1. grabba_scaled 574.2.3.2. acquisisci_sfondo 584.2.3.3. elaborazione_immagine_ppmgs 604.2.3.4. reset_of 614.2.3.5. operatore_ppmgs 614.2.3.6. combinaflussiottici e visualizzaflussoottico 62
4.3. L’elaborazione delle immagini 654.3.1. gradient 664.3.2. estrai_auto 684.3.3. binary 704.3.4. CodSoft_ppmgs 73
4.4. L’estrazione del flusso ottico 75
4.5. Prove effettuate e risultati ottenuti 80
4.6. Conclusioni 84
APPENDICE A - MANUALE UTENTE 86
A.1 Pulsantiera 86
A.2 Le finestre 88
APPENDICE B - LISTATI 90
B.1 graphics.h 90
B.2 flusso_d.h 91
B.3 flusso4l.h 92
B.4 opt_flow.h 95
BIBLIOGRAFIA 96
4
Capitolo 1
Introduzione
Trovandosi di fronte ad un amico incapace di vedere qualcosa di apparentemente ovvio e banale,
chiunque esclamerebbe “Ma apri gli occhi!”, e solo il più paziente ed imperturbabile dei lettori
riuscirebbe a trattenersi. Bisogna riconoscere, comunque che, vedere un’ovvietà è cosa ben più
complessa di quanto comunemente si creda, e in casi di cecità “inspiegabile” si farebbe meglio a
consigliare di usare il cervello, cioè di pensare, piuttosto che di guardare. Il guaio di tale consiglio è
che sarebbe spesso considerato paradossale o incomprensibile. Vi è infatti l’opinione diffusa
secondo cui sussiste una rigida dicotomia tra il vedere ed il pensare, che implicitamente nega che sia
possibile qualsiasi influenza del secondo sul primo. Non è inoltre chiaro che cosa precisamente
sarebbe necessario pensare in ogni singola circostanza dal momento che si è di solito tanto
interiormente inconsapevoli degli essenziali processi sottostanti alla visione, quanto incapaci di far
loro appello coscientemente.
D’altra parte , gli addetti alla computer vision, sono costretti ad affrontare tali problemi
direttamente, poiché i programmi vedono nella misura in cui i programmatori sono in grado di
rendere quella connessione funzionalmente esplicita. La presente questione è enormemente
complessa, e anche il solo compito di rendere una macchina capace di riconoscere in un’ampia
gamma di casi semplici oggetti è un problema complicato.
L’inseguimento di oggetti non rigidi e la classificazione del loro moto è un problema che presenta
numerose sfide. L’importanza che questi problemi rivestono nel modo moderno è sottolineata dal
crescente interesse, che negli ultimi anni, li ha investiti. Soluzioni efficaci a queste tipologie di problemi
potrebbero condurre all’apertura di “nuovi mercati” in settori quali la sorveglianza video, l’analisi del
moto, le interfacce della realtà virtuale, processi di riconoscimento e navigazione per robot. Vediamo
alcuni esempi in dettaglio.
Immagini come quelle acquisite da telecamere a circuito chiuso, sono normalmente utilizzate, anche
se di bassa qualità e difficili da interpretare, in sistemi di sicurezza. Le procedure di visione artificiale
5
forniscono metodi per migliorare la qualità delle immagini, interpretare degli eventi e monitorare delle
scene complesse.
§ Monitoraggio anti-intrusione - Telecamere che sorvegliano delle zone ampie, quali ad
esempio i perimetri delle prigioni, sono normalmente collegate al sistema d’allarme. Se
l’allarme viene fatto scattare delle immagini sono subito immagazzinate per permettere
l’identificazione della causa. Molti falsi allarmi sono prodotti da animali selvatici ed il
processo di ricostruzione degli eventi si rileva una perdita di tempo. Un sistema per
localizzare e identificare la causa degli allarmi può essere utilizzato per minimizzare il
numero di falsi allarmi.
§ Identificazione dei numeri di targa – Le tecniche di visione artificiale hanno reso
possibile la lettura dei numeri di registrazione dei veicoli attraverso immagini video.
Questa tecnologia, che può essere utilizzata in innumerevoli applicazioni, risulta molto
meno invasiva rispetto a quelle tecniche impieganti dispositivi quali ad esempio i
transponders o i sistemi GPS.
§ Inseguimento di persone – In alcune situazioni i movimenti delle persone possono
essere interessanti, ad esempio il monitoraggio di centri commerciali. Questi sistemi sono
sviluppati per sorvegliare una zona imparando i percorsi abituali utilizzati dagli individui
che la attraversano, qualunque deviazione rispetto ai percorsi abituali viene segnalata
come anomalia e può far scattare un allarme.
Le tecniche di visione artificiale trovano impiego in molti altri campi, quali il monitoraggio del traffico
sia terrestre che aereo, sistemi automatici di ispezione industriale, eccetera e non basterebbe un
singolo capitolo di questa tesi per descriverli tutti in modo accurato.
In questo lavoro affronterò le problematiche legate alla progettazione di un dispositivo per l’analisi
del moto in scene stradali. Cercherò di vedere se è possibile utilizzare il flusso ottico, risultato
prodotto dall’analisi del moto, come dispositivo di segnalazione dell’avvenuto passaggio di un
oggetto in movimento entro una determinata zona.
Nel capitolo 2 affronterò le tematiche legate alla determinazione del flusso ottico, soffermandomi
sull’algoritmo sviluppato da G. Maris e sulle modifiche da me introdotte.
Nel capitolo 3 darò una breve descrizione del sistema APACHE evidenziando i problemi che hanno
dato vita a questo lavoro.
6
Nel capitolo 4 affronterò il nucleo di questo lavoro, darò una descrizione del codice sviluppato,
parlerò dei risultati ottenuti e delle conclusioni alle quali sono giunto.
7
Capitolo 2
Flusso Ottico e stima del movimento
“Ogni organismo vivente sfrutta la rilevazione del moto come metodo fondamentale per la
percezione del proprio movimento, della struttura dell’ambiente in cui si trova come pure del
movimento di altri organismi presenti nell’ambiente che lo circonda.”
Partendo da questa assunzione si può affermare che l’evoluzione temporale dell’ambiente e della
scena osservata, porta una grande quantità di informazione necessaria alla percezione naturale ed
artificiale, degli “eventi” che hanno luogo nel campo visivo.
Anche quando la scena è stazionaria, gli oggetti presenti in essa possono apparire in movimento a
causa del moto dell’osservatore o degli occhi o comunque dei sensori che analizzano la scena stessa:
anzi il moto relativo tra oggetti e sensori è proprio un metodo fondamentale d’indagine per la
comprensione della struttura spaziale dell’ambiente in esame.
Sebbene i sistemi di visione biologici siano discreti, la quantizzazione da loro prodotta è talmente fine
da essere in grado di produrre un uscita essenzialmente di tipo continua. Questa uscita così ottenuta
è in grado di riprodurre il flusso persistente che il mondo osservato imprime sulla retina. La sequenza
di informazioni così ottenute viene chiamata Flusso Ottico o in inglese Optical Flow.
Il Flusso Ottico, detto anche campo di velocità istantanea, assegna ad ogni punto in movimento
all’interno del campo visivo una “velocità retinica” bidimensionale, tramite l’analisi di questa struttura
dati è possibile ricostruire l’ambiente osservato.
I sensori di tipo non-biologico funzionano, essenzialmente, nello stesso modo quantizzando più o
meno finemente la variabile temporale che caratterizza l’evoluzione dell’ambiente. Agendo secondo
questa modalità generano una sequenza di immagini che riprodotte possono dare la sensazione di un
flusso continuo di avvenimenti in funzione dell’intervallo di tempo tra un’immagine e quella seguente.
Da un punto di vista informatico il problema del calcolo del flusso ottico si riduce all’acquisizione di
immagini dell’ambiente in esame ed alla loro elaborazione in tempi sufficientemente rapidi per poter
8
apprezzare le variazioni “ambientali” in esse contenute.
L’analisi dei cambiamenti nel tempo della struttura e della distribuzione dei livelli di grigio (o di
colore) registrati dai sensori e immagazzinati in una sequenza di immagini, implica l’elaborazione di
segnali a tre ed eventualmente a quattro dimensioni, producendo quindi una quantità di dati non
trascurabile. Questo è uno dei problemi principali della visione a basso livello : la necessità di gestire
enormi volumi di dati in tempi relativamente brevi per consentire l’impiego dei risultati ottenuti in
situazioni reali. Situazioni che possono comprendere l’inseguimento di bersagli, la guida di robot e la
navigazione di veicoli autonomi ed altre problematiche in cui il tempo riveste un’importanza
determinante.
In generale, la stima del movimento 3D a partire dalle sue proiezioni 2D in una sequenza di immagini
comprende tecniche di diverso livello raggruppabili a seconda della “filosofia” che ne sta alla base.
Come prima distinzione possiamo evidenziare due categorie fondamentali: la stima basata sul flusso
ottico e la stima basata sulle corrispondenze discrete. All’interno di queste due metodologie è
possibile effettuare un’ulteriore separazione delle tecniche in esse contenute a seconda che siano o
non siano dipendenti dal dominio, cioè che si basino o meno su conoscenze a priori dell’ambiente in
esame.
Quello che mi preme fare notare, però, è l’appartenenza di questi quesiti alla categoria dei problemi
inversi o mal condizionati, per i quali non è garantita l’esistenza e l’unicità della soluzione, la quale
risulta comunque molto se non troppo “sensibile” ad errori e approssimazioni nei dati di partenza.
Passiamo ora ad analizzare più in dettaglio queste tecniche, in modo da poterne evidenziare le
caratteristiche principali, sia in positivo che in negativo, per essere poi in grado di spiegare le scelte
effettuate nell’ambito di questa tesi.
2.1. Stime basate sul Flusso Ottico
Una tra le due classi di metodi di stima del moto 3D è basato sul calcolo del cosiddetto flusso ottico
(“optical flow”), ovvero sulle variazioni nel tempo di valori risultanti dall’applicazione di operatori
locali all’immagine. Il risultato di questo metodo viene rappresentato tramite un insieme, se non più
propriamente una matrice, di vettori rappresentanti i movimenti dei punti appartenenti agli oggetti
presenti nell’immagine. In poche parole il flusso ottico rappresenta la traiettoria seguita dai punti
9
degli oggetti inquadrati durante il loro movimento rispetto al sensore. Consideriamo quindi un oggetto
in moto relativo rispetto ad un sensore, ad esempio una telecamera: indicando con P0 e P1 un punto
dell’oggetto ed il corrispondente punto sul piano immagine ad un certo istante, se P0 possiede una
velocità v0 relativa alla telecamera, al suo spostamento v0δt nell’intervallo di tempo δt corrisponderà
un analogo spostamento del punto P1 nel piano immagine di valore v1δt. Dove v0 e v1 sono i vettori
rappresentanti la derivata temporale dei vettori che congiungono il centro ottico O del sistema con i
punti P0 e P1, ossia gli spostamenti che questi due punti effettuano nell’intervallo di tempo δt. Come
rappresentato nella figura 2.1.
Usando il legame proiettivo tra i due segmenti OP0 e OP1 risulta assegnato un vettore spostamento
ad ogni punto Pi dell’immagine. L’insieme di questi vettori costituisce il campo di spostamenti che,
considerandoli nell’unità di tempo, δt piccola a piacere, mi permette di ricavare un campo di
velocità. Il flusso ottico è definito come il campo vettoriale di tali velocità.
In realtà l’informazione disponibile risulta essere solamente la variazione temporale della distribuzione
di luminosità presente nel piano immagine. Come immediata conseguenza il flusso ottico può essere
estratto dal calcolo delle velocità, o degli spostamenti, nel piano immagine delle variazioni di intensità.
Figura 2.1
10
Sia f(x, y, t) la funzione rappresentante l’intensità luminosa nell’immagine nel punto di coordinate (x,
y) all’istante di tempo t. Assumendo che tale intensità vari solamente a causa del moto 3D1 che si
proietta sul piano immagine, e che quindi sia la medesima al tempo t + δt nel punto (x + δx, y +
δy), si può scrivere:
1 L’assunzione appena effettuata ricade nei “vincoli aggiuntivi” di cui parlerò verso la fine del paragrafo.
11
f(x + δx, y + δy, t + δt) = f(x, y, t) (1.1.1)
dove δx = uδt e δy = vδt, essendo u e v le componenti della velocità, cioè del
vettore del flusso ottico applicato in questo punto, nelle direzioni x e y:
u = dx/dt e v = dy/dt (1.1.2)
Considerando le variazioni temporali e spaziali piccole a “piacere” si può espandere
il primo membro della (1.1.1) in serie di Taylor ottenendo la seguente relazione:
f(x, y, t) + fxδx + fyδy +ftδt + ε = f(x, y, t) (1.1.3)
dove fx, fy e ft indicano le derivate parziali dell’intensità rispetto alle variabili spaziali e
temporale, ed ε contiene i termini di ordine superiore (trascurabili). Dividendo per dt
tendente anche a zero si ricava la cosiddetta “equazione di vincolo del flusso ottico”:
fxu + fyv + f t = 0 (1.1.4)
La (1.1.4) non permette di calcolare le due incognite u e v:
v . grad(f) = - f t (1.1.5)
prodotto scalare tra il vettore gradiente della luminosità e il vettore velocità.
Quest’ultima relazione implica che sono soluzioni della (1.1.4) tutti i vettori v che la
soddisfano e quindi fornisce l’informazione relativa alla componente del flusso ottico
12
nella direzione del gradiente (che vale ft / (fx2 + fy
2)½), ma non in quella
perpendicolare, cioè lungo le linee a intensità luminosa costante. Inoltre v non può
essere calcolato nei punti in cui il gradiente è nullo. Queste ultime affermazioni si
rifanno a quanto accennato in precedenza sui problemi inversi o mal condizionati.
Per ricavare il flusso ottico è quindi necessario introdurre delle ipotesi aggiuntive
derivate da conoscenze a priori, in questo modo ci si sposta verso un approccio al
problema di tipo “dipendente dal dominio”, oppure tramite l’imposizione di vincoli
aggiuntivi quali possono essere la continuità della funzione luminosità o,
analogamente, del colore all’interno dell’immagine, sempre in funzione del tempo.
Una possibile soluzione è ottenuta minimizzando contemporaneamente lo
scostamento da zero del primo membro della (1.1.4) e le variazioni del flusso ottico
misurate dal suo gradiente. In poche parole si arriva alla soluzione minimizzando,
rispetto a u e v la seguente relazione:
??{(fxu + fyv + f t)2 + λ [(ux2 + uy
2) + (vx2 + vy
2)]}dx dy (1.1.6)
La minimizzazione viene effettuata tramite un processo iterativo dopo aver posto in
forma discreta gli operatori richiesti. Vengono utilizzati degli stimatori discreti delle
derivate spaziali e di quella temporale, limitandoli, tipicamente, alle differenze finite
del primo ordine, e il doppio integrale viene sostituito da una doppia sommatoria. Per
questa possibile soluzione è stata assunta la regolarità del flusso ottico e la continuità
della luminosità dell’immagine. Se il flusso presenta delle discontinuità, come accade
in corrispondenza dei bordi degli oggetti, occorre evitare che il metodo tenti di
estendere con regolarità la soluzione da una regione all’altra. Ciò non è semplice
poiché queste discontinuità permetterebbero la segmentazione dell’immagine e lo
13
sfruttamento di queste informazioni fornirebbe una stima migliore del flusso ottico.
Una soluzione possibile prevede l’introduzione della segmentazione nell’algoritmo
iterativo di stima, individuando le zone dove il flusso varia bruscamente ed
impedendo all’iterazione successiva di collegare con continuità la soluzione attraverso
la discontinuità.
Proseguendo nell’analisi si potrebbero portare alla luce altre limitazioni, sempre
risolvibili mediante l’introduzione di vincoli aggiuntivi o di operazioni di elaborazione
di altre caratteristiche delle immagini in esame. Questo però ci farebbe perdere molto
tempo, considerando il fatto che sarebbe necessario proporre con i dovuti dettagli
anche i numerosi metodi alternativi di calcolo del flusso ottico. Quello che mi preme
sottolineare è come ogni metodo utilizzato propone un problema che necessita
dell’introduzione di vincoli per l’ottenimento della soluzione, vincoli che si possono
raggruppare nelle due categorie elencate in precedenza: indipendenza dal dominio e
dipendenza dal dominio.
Nella prima possiamo evidenziare i due principi di continuità della luminosità e del
colore (non subiscono variazioni brusche passando da un’immagine a quella
successiva), come pure possiamo introdurre gli operatori che ci permettono di
estrarre informazioni caratterizzanti le componenti dell’immagine. Vedi ad esempio la
segmentazione come pure la cosiddetta features extraction (estrazione delle
caratteristiche) nelle loro molteplici versioni: estrazioni dei contorni, delle regioni,
delle textures, dei punti dominanti nelle curve, ecc.
Nella seconda categoria possiamo raggruppare quei vincoli che dipendono dal
dominio da un punto di vista della “comprensione logica” di quanto può avvenire
nell’ambiente che si sta analizzando. Tra questi possiamo includere: la struttura degli
oggetti presenti, la velocità massima alla quale si possono muovere i soggetti
14
dell’analisi, la direzione preferenziale del movimento (moto comune, moto conosciuto
o piccoli cambiamenti nella direzione della velocità), il cosiddetto consistent match
(due punti appartenenti ad una stessa immagine, normalmente, non si associano con il
medesimo punto di un’altra immagine), ecc.
2.1.1. Pro e contro
Proseguiamo ora con una breve analisi dei pro e dei contro della stima basata sul
flusso ottico. La disponibilità del campo vettoriale di velocità o di spostamento in due
dimensioni permette di stimare il movimento e la struttura spaziale degli oggetti in
moto, quindi dare una rappresentazione dell’ambiente osservato, mediante
un’opportuna formulazione delle relazioni tra i punti delle superfici componenti gli
oggetti nello spazio e quelli nel piano immagine, e tra le associate velocità. Tali
relazioni sono indotte dalla proiezione prospettica e richiedono alcune assunzioni,
tipicamente la variazione “dolce” del flusso ottico e la regolarità della superficie 3D,
per poterne troncare ai primi termini lo sviluppo in serie di Taylor. Le equazioni finali
sono, nella forma generale, piuttosto complesse, permettono però, dopo aver
introdotto ulteriori vincoli, di ottenere un flusso ottico abbastanza preciso. Questa
precisione viene pagata in termini computazionali, infatti la maggior parte se non la
totalità degli algoritmi che si basano su queste tecniche sono di tipo iterativo e
richiedono la soluzione di equazioni simili alla (1.1.6) per ogni pixel appartenente
all’immagine o alla regione dell’immagine interessata. Quindi massimizzazione o
minimizzazione di funzioni errore che possono richiedere numerosi cicli di calcolo
prima di fornire il risultato cercato. Se a tutto questo aggiungiamo che le funzioni
errore richiedono l’utilizzo di operatori opportuni per il calcolo dei loro termini, che
normalmente gestiscono strutture dati di tipo float (virgola mobile), si comprende
15
come queste tecniche tendono a non essere utilizzate o comunque poco sfruttate in
situazioni reali. Nei casi in cui esse vengono adottate è possibile osservare l’impiego
di una serie di drastiche limitazioni per quanto riguarda l’ambiente percepibile oppure
l’utilizzo di macchine di calcolo molto potenti se non addirittura costruite ad hoc per il
problema specifico.
2.2. Stime basate su corrispondenze discrete
Il movimento relativo tra gli oggetti della scena ed il sensore produce uno
spostamento di parti delle immagini formatesi nel piano focale del sensore stesso.
Spostamento che può essere caratterizzato con il moto di un insieme discreto di
attributi o elementi significativi (“features”). Questo moto può essere ricavato
dall’estrazione delle componenti osservabili in un’immagine della sequenza e dalla
successiva determinazione della corrispondenza nelle immagini successive.
La situazione può essere schematizzata come in figura 2.2, dove le “qualità
specifiche” sono i punti Pi individuati sul piano immagine (X, Y) ad un certo istante
come la proiezione dei punti pi dell’oggetto (in coordinate x, y, z): all’istante
successivo i punti si sono spostati rispettivamente in P’i e p’i. Queste variazioni di
posizione dei punti corrispondenti in (X, Y) sono i dati disponibili per tentare di
ricavare il movimento di punti pi in (x, y, z) tra i due istanti, sempre che il problema
ammetta soluzione.
16
Il problema oltre ad essere mal condizionato è anche intrinsecamente ambiguo: la
soluzione, quando esiste, può essere ottenuta solo a meno di una costante,
tipicamente un fattore di scala sulla traslazione. Ad esempio, se consideriamo due
oggetti, uno di date dimensioni e l’altro di dimensioni doppie, non è possibile
distinguere i seguenti due moti: il primo oggetto che subisce una certa rototraslazione
alla distanza d dal sensore ed il secondo che si muove con velocità di traslazione
doppia e di rotazione identica al precedente ma ad una distanza 2d dal sensore.
La differenza sostanziale rispetto al paragrafo precedente risiede nel numero elevato
e nella densità del flusso ottico, qui assunto disponibile in pochi punti dell’immagine.
Nell’esempio citato in figura si fa riferimento ai punti che compongono l’oggetto,
nell’insieme dei metodi di calcolo affrontabili in questo paragrafo, è possibile
utilizzare come elementi significativi altre caratteristiche rappresentative, quali ad
esempio vertici, linee di separazione tra superfici, regioni ecc. Per questo motivo
risulta difficile descrivere in modo sistematico e sintetico i possibili metodi di
soluzione, che andrebbero classificati secondo una varietà di parametri tra i quali
possiamo citare:
Figura 2.2
17
ü il tipo di osservabili considerato (punti, linee, angoli, …)
ü eventuali vincoli spaziali tra gli osservabili (la complanarità per esempio)
ü il tipo di proiezione impiegata
ü i parametri del moto ricercati
ü la linearità o meno del sistema di equazioni
ü la rigidità o meno del moto
ü la limitazione o meno a piccoli spostamenti, e analogamente l’uso di un
numero limitato di viste o di una lunga sequenza
L’ipotesi su cui si basano i metodi di stima del movimento da osservabili estratti in
un’immagine e poi “inseguiti” nella sequenza è che siano innanzitutto individuati
elementi caratteristici in una vista, e che sia stata stabilita la corrispondenza tra di essi
nella sequenza. Stabilire e mantenere le corrispondenze interframe può essere
tutt’altro che semplice, sia perché gli elementi da inseguire non permangono
necessariamente in tutte le immagini costituenti la sequenza (possibili occlusioni), sia
perché è proprio il movimento una delle proprietà che permettono di individuare le
corrispondenze.
“Nel sistema visivo umano i processi di corrispondenza e quelli di struttura e
movimento sono strettamente connessi.”
In ogni caso, le tecniche più comuni adottate per individuare gli osservabili sono
basate sull’analisi delle caratteristiche locali della distribuzione di luminosità, ad
esempio: la tessitura (texture), gli angoli, tratti di contorno rettilineo, intersezioni di
linee. Per trovare la soluzione al problema delle corrispondenze vengono utilizzate
18
delle misure di somiglianza opportunamente formulate: la correlazione locale, le
misure di somiglianza strutturale. Anche nell’applicazione di queste metodologie è
importante far notare come vengano impiegati dei vincoli aggiuntivi simili a quelli
affrontati nel paragrafo precedente, si parla anche in questo caso di indipendenza o
dipendenza dal dominio. Andiamo a vedere alcune di queste procedure nel dettaglio.
Consideriamo inizialmente la situazione descritta dalla figura 2.2, gli osservabili sono
dei punti, dove (x, y, z) sono le coordinate di un punto generico p appartenente alla
superficie di un oggetto in moto rigido relativo al sensore, e (X, Y) quelle della
proiezione P sul piano immagine. Le relazioni che descrivono la proiezione di tipo
prospettica sono:
X = F · x/z Y = F · y/z
(1.2.1)
X’ = F · x’/z’ Y’ = F · y’/z’
Dove F = OO’ è la lunghezza focale, che per semplicità viene assunta unitaria. La
relazione che regola il moto di un corpo rigido, che possiede sei gradi di libertà, è
data dalla:
p’ = R · p + T (1.2.2)
dove p’ e p sono vettori di componenti (x’, y’, z’) e (x, y, z), R è una matrice di
rotazione ortonormale di dimensioni 3x3 e con elementi funzione di tre parametri
indipendenti (quali due coseni direttori di un asse e l’angolo di rotazione attorno a
questo) e T vettore traslazione di componenti Tx, Ty e Tz. A partire dalla situazione di
19
base descritta dalle (1.2.1) e (1.2.2) si ramificano una varietà di tecniche possibili,
che conducono a equazioni lineari o non lineari, in incognite che sono o le posizioni
successive dei punti p o i parametri del moto come R e T.
Introducendo nelle (1.2.1) anche le variazioni di posizione e di orientamento del
sensore si ottiene un sistema composto da almeno 18 equazioni non lineari in 18
incognite, senza garanzia teorica sull’unicità della soluzione. Siamo in presenza di una
notevole mole di calcoli, soprattutto se pensata per applicazioni real time,
considerando anche il fatto che la soluzione per questo tipo di sistemi viene ricercata
con algoritmi iterativi ai minimi quadrati.
2.2.1. Pro e contro
Esistono vincoli aggiuntivi e metodi per affrontare il problema che permettono di
ridurre il numero di equazioni e di incognite semplificando notevolmente il problema.
Non è mia intenzione affrontarne la descrizione in questa sede per non appesantire
troppo il lavoro. Mi preme sottolineare il fatto che questo tipo di soluzione presenta
dei vantaggi, in termini di costi di elaborazione, nei confronti della stima del
movimento mediante il flusso ottico. Infatti tramite l’applicazione di maschere
opportunamente studiate, alla sequenza di immagini in esame e precedentemente
elaborata, è possibile ottenere una valutazione del movimento ed in particolare del
flusso ottico qualitativamente accurata in tempi di calcolo sufficientemente brevi.
Soprattutto se si pensa che la maggior parte, se non la totalità, di queste operazioni
utilizzano dei dati di tipo intero. Sarà questa la strada che seguirò nel proseguimento
di questo lavoro. Un altro vantaggio non trascurabile, che si può rivelare anche
svantaggioso in taluni casi, è il fatto che utilizzando le tecniche descritte in questo
paragrafo il calcolo del movimento si può facilmente concentrare su alcuni oggetti
20
della scena trascurando il resto, situazione importante nei casi di inseguimento e/o di
catalogazione degli enti presenti o che ci si aspetta siano presenti nella scena.
L’obbiezione più importante che può essere sollevata nei riguardi delle procedure
evidenziate è la loro “inesattezza” formale, dovuta alle semplificazioni richieste per
poter essere in grado di ottenere una soluzione senza dover affrontare una quantità di
calcoli “mostruosa”.
2.3. L’algoritmo di Gabriele Maris e lo svilupposuccessivo
Un metodo di calcolo del flusso ottico degno di attenzione è stato presentato, nel suo
lavoro di tesi presso l’università di Parma, dall’ingegnere Gabriele Maris. Questo
algoritmo si basa sulle metodiche viste nel paragrafo 1.2, cioè stima il flusso ottico e
conseguentemente il movimento tramite le corrispondenze discrete, ossia dopo aver
effettuato una prima elaborazione sulle immagini costituenti la sequenza per estrarne i
cosiddetti osservabili, ne ricerca le affinità nelle varie immagini. Nato per architetture
di tipo parallelo, per la precisione fu sviluppato su una macchina CAM 8, a causa
della quantità di calcoli necessari per l’ottenimento della soluzione al problema posto
e della velocità di calcolo elevata di queste tipologie di calcolatori. In seconda battuta
fu convertito e modificato, dal sottoscritto, nell’ambito dell’esame per il corso di
Intelligenza Artificiale, per adattarsi a processori sequenziali in ambiente Unix/Linux.
Analizziamo ora le varie parti nelle quali può essere suddiviso evidenziando le scelte
effettuate a livello di stesura del codice. Nei sottoparagrafi 1.3.1 e 1.3.2 parlerò della
prima conversione effettuata fornendo alcuni risultati di esempio, nel 1.3.3 e nel 1.3.5
21
ragionerò sul time to impact e sulla sua introduzione nell’algoritmo, nel 1.3.6 e nel
1.3.7 descriverò infine le modifiche apportate in preparazione della mia tesi.
2.3.1. La prima stesura
Evitando un’analisi dettagliata delle scelte effettuate da Maris per l’ottenimento
dell’algoritmo suddetto, peraltro ampiamente trattate nella sua tesi, passiamo
immediatamente allo studio delle quattro parti fondamentali nelle quali può essere
suddiviso:
a) il Filtro Quantizzatore
b) l’Estrattore di Roberts
c) il Codificatore Soft 3 x 3 a 4 bit/pixel
d) l’Operatore di Matching
a) Dalle osservazioni svolte durante il suo lavoro Maris afferma che il rumore
aleatorio presente in una immagine, dovuto alle più svariate cause quali difetti
nelle lenti, rumori termici nella matrice CCD, piccole oscillazioni della telecamera,
ecc., può essere eliminato, in parte se non del tutto, tramite la soppressione di un
certo numero dei bit meno significativi di ogni pixel. Il Filtro Quantizzatore si
occupa di operare questa soppressione mediante la traslazione a destra di un
certo numero di posizioni del contenuto informativo di ogni pixel. Il numero di
posizioni è stato impostato a 3 tenendo presente che ad ogni pixel sono associati
8 bit di informazione (immagini con 256 toni di grigio). La realizzazione di questa
funzione fu immediata.
A B
C D
Figura 2.3
22
b) In seguito all’operazione di riduzione del rumore, il calcolo del flusso ottico
richiede l’estrazione delle features delle quali analizzare le corrispondenze. In
questo algoritmo si è scelto di rilevare i contorni degli oggetti presenti
nell’immagine. Questa operazione viene eseguita mediante il calcolo del
gradiente dei livelli di grigio di un certo intorno del punto in questione. Se il
valore ottenuto supera una soglia prefissata il punto viene marcato come
contorno. La scelta di Maris è caduta sull’estrattore di Roberts, semplice da
implementare e veloce nell’esecuzione dato che necessita di un numero
esiguo di operazioni. È composto da due fasi distinte: calcolo del gradiente e
confronto con la soglia. L’intorno utilizzato per il calcolo del gradiente non è
simmetrico rispetto al punto in esame, infatti siano A, B, C e D quattro pixel
appartenenti all’immagine disposti secondo la figura 2.3. Il pixel A è quello
per il quale si vuole verificare se si tratta di un punto di contorno oppure no.
Si ha quindi il seguente algoritmo
GradRob = max {abs(A – D) ; abs(B – C)}
Se (GradRob > Soglia) allora {A è un punto di contorno}
Il codice per questo operatore risulta abbastanza semplice, si è pensato di
utilizzare una matrice immagine locale per la memorizzazione del valore logico
(abs(A – D) > Soglia)
23
Come conseguenza l’elaborazione dei contorni avviene mediante due
“passate” dell’immagine, nella prima si verifica se abs(A – D) è maggiore
della soglia, nella seconda si effettua il medesimo controllo per abs(B – C), in
questo modo se una delle due condizioni si verifica il pixel in esame viene
marcato come contorno. Infine sono utilizzati due cicli for per annullare il
contenuto dell’ultima colonna e dell’ultima riga che, non venendo elaborate
dall’estrattore di Roberts, contengono valori “non corretti” per la codifica
soft successiva.
c) A questo punto l’immagine risulta essere binarizzata e contenente le linee di
contorno degli oggetti presenti più eventuali distorsioni e/o disturbi, i
cosiddetti artefatti. La presenza di artefatti rende problematica l’operazione
di matching tra due immagini consecutive appartenenti alla sequenza in
esame. Per questo motivo, G. Maris ha pensato di effettuare una codifica
dell’immagine binarizzata tramite l’applicazione di una maschera 3x3 in modo
da semplificare quest’azione e tentare di correggere gli errori che si possono
avere a causa della presenza di disturbi. L’idea di questa soluzione nasce da
una serie di osservazioni ambientali discusse con la dovuta ricchezza di
particolari nel paragrafo della tesi di Maris attinente alla codifica geometrica
degli oggetti. Passando alla conversione della funzione in linguaggio C mi
sono accorto che il codice utilizzato per la codifica, il cui intento principale
era quello di poter correggere un errore (distanza di Hamming uguale a 3),
presentava delle ambiguità, cioè era dotato di una distanza di Hamming
minima uguale a 2. Ho contattato Maris, il quale mi ha spiegato che il
problema era stato risolto analizzando i risultati di uno studio di tipo statistico,
24
sulla distribuzione dei costrutti 3x3 all’interno dell’immagine. Come
conseguenza risulta sufficiente e necessario definire, una volta per tutte,
l’insieme di regole di assegnazione delle parole di codice. In questo modo le
ambiguità vengono eliminate in modo brusco (senza rispettare la teoria di
Hamming), dividendo le parole di codice in ingresso in insiemi separati a cui
corrispondono regole di associazione distinte. A livello di linguaggio C, ciò
viene fatto tramite una sequenza di istruzioni di confronto che si escludono a
vicenda, ossia se una fornisce un risultato positivo quelle seguenti non
vengono neppure analizzate. Ciò è stato ottenuto mediante una serie di
istruzioni if contenenti un’operazione logica su di una variabile di controllo
(flag) in grado di inibire l’esecuzione del codice associato a quel particolare
if, per quanto riguarda la ricerca di una codifica a distanza uno. La codifica a
distanza zero, ovviamente, non presenta questo tipo di problematiche. Ultimi
due punti da evidenziare nel sorgente dell’operatore sono:
§ l’utilizzo di una matrice locale nella quale memorizzare gli offset
rispetto al pixel in esame, per eseguire la codifica della struttura
3x3 in esame. Scelta operata in base a criteri di velocità,
comodità e modificabilità eventuale del codice.
§ l’utilizzo di una matrice immagine temporanea nella quale
immagazzinare la codifica dell’immagine per poi trasferirla nella
matrice immagine fornita in ingresso.
25
d) Dopo aver applicato i punti a), b) e c) a due immagini consecutive
appartenenti alla medesima sequenza si è pronti ad utilizzare un operatore di
matching in grado di rilevare le corrispondenze tra gli osservabili che si sono
“mossi” calcolando gli spostamenti relativi dei punti costituenti. All’interno
della propria tesi, G. Maris, ha sviluppato un certo numero di operatori, per
la precisione 9, caratterizzati dalle differenti dimensioni della griglia da
sovrapporre all’immagine, e da come ci si “muove” all’interno di questa
griglia. È importante notare che esistono due modi fondamentali di spostarsi,
per indice crescente o per indice decrescente. Assumendo come indice zero
il punto del quale si vuole verificare il movimento relativo, all’atto della
conversione in linguaggio C ho preferito considerare unicamente il modo per
indice crescente, poiché, trovato un match valido, la scansione della griglia
Figura 2.4
26
viene interrotta, mentre quella per indice decrescente continua fino all’indice
zero, e quindi, in linea di massima, la scelta effettuata risulta più veloce. Altra
caratteristica da evidenziare, prima di passare ad un’analisi più in profondità
dei singoli operatori, è la capacità, da parte di questi operatori, di rilevare
spostamenti minimi di un numero di pixel per frame variabile e definibile
dall’utente. Maris ha considerato come rumore e quindi assimilati a
spostamento nullo quelli di 1 pixel per frame, di conseguenza non mi sono
sentito di cambiare questa impostazione. Sempre pensando alle
problematiche di compatibilità, nel caso specifico con maschere di tipo
differente, e tenendo conto delle diverse velocità di esecuzione, tutti gli
operatori sono stati costruiti per essere contenuti completamente nella
regione significativa dell’immagine, vedi figura 2.4 nella quale viene
evidenziata in grigio quell’area dell’immagine non considerata per la ricerca
delle corrispondenze nei casi specifici di operatori laterali destri come ad
esempio il 3x21R. Si ha quindi una perdita di informazione rispetto ad una
cornice di pixel vicino ai bordi dell’immagine di dimensioni dipendenti
dall’operatore utilizzato. Anche in questo caso, come per il codificatore soft,
ho deciso di utilizzare una matrice locale nella quale ho memorizzato gli
spostamenti relativi al punto in analisi (indice “zero”). Ciò mi ha permesso di
scrivere un codice più compatto e riutilizzabile, tramite piccole variazioni, per
un sottoinsieme di operatori. Negli operatori 3x21L e 3x21R il codice è lo
stesso cambia unicamente il file header a cui fanno riferimento, analogamente
per il sottoinsieme formato dal 5x12L e dal 5x12R. L’operatore 3x21B è di
tipo bilaterale simmetrico, ricerca il match in due colonne, una a destra e una
a sinistra del punto in esame, contemporaneamente. Ciò ha reso necessario
27
l’utilizzo di un flag (RFF) per mantenere memoria del ritrovamento di un
match e in quale colonna (destra o sinistra), inibire ulteriori ricerche, inibire
ulteriori ricerche in quella colonna, annullare il flusso ottico nel caso si abbia
una ambiguità (match valido in tutte e due le colonne) oppure nel caso in cui
non si abbia alcun match. Per gli operatori 5x11B e Extended Cocle la
situazione cambia radicalmente. Non cambia per quanto riguarda l’utilizzo
della matrice “op” (contenente gli offset), tramite la quale è stato possibile
“riutilizzare” il codice, basta osservare le analogie tra i due operatori normali
e i due a settori per rendersene conto. Osservando la struttura delle griglie
utilizzate si nota la presenza di due regioni distinte al loro interno, ecco dove
si ha il cambiamento. La regione più esterna, le ultime 6 colonne per il 5x11B
e le ultime 2 per l’Extended Cocle, viene scandita normalmente in modalità
bilaterale simmetrica, come per il 3x21B. La regione più interna, le prime 5
colonne per il 5x11B e le prime 7 per l’Extended Cocle, viene scandita
secondo un andamento a chiocciola di tipo normale o a settori. La presenza
di queste due zone viene trasferita all’interno del codice mediante l’utilizzo di
due cicli, uno while e uno for, ben distinti. La scansione della chiocciola
viene effettuata tramite il ciclo while, in esso il flag RFF serve sempre a
marcare l’avvenuta individuazione di un match valido, la variabile ia serve per
contare gli anelli della chiocciola mentre z per conteggiare gli elementi
presenti in ogni anello. Per quanto riguarda la ricerca a settori risulta
necessario mantenere memoria del settore di lavoro e/o di riscontro del
match, per fare questo ho semplicemente modificato la matrice “op”
introducendovi un valore rappresentante il settore e ho deciso di evidenziare
l’ottenimento di un match positivo tramite la memorizzazione del settore di
28
ritrovamento in RFF. L’ultima istruzione di tipo if serve per annullare il flusso
ottico nel caso non venga ritrovato alcun match. Nella figura 2.5 sono
riportati gli operatori utilizzati.
2.3.2. Primi risultati ed alcuni esempi
Questa prima stesura del programma fu provata su alcune sequenze di immagini, tra
le varie prove effettuate una fra le più significative fu quella riportante una pianta in
movimento relativo verso destra rispetto alla telecamera. Questa sequenza, come si
può vedere dalla figura 2.6 nella quale sono riportate la prima e l’ultima immagine
componenti la sequenza stessa, contiene una quantità enorme di strutture estraibili
secondo l’algoritmo sviluppato.
Figura 2.5
29
Per questo motivo è stata considerata un test importante per verificare il
funzionamento del programma. Nella figura 2.7 viene riportato il flusso ottico di una
coppia consecutiva di immagini di questa sequenza, si nota immediatamente l’alta
densità di vettori concordanti nella direzione del movimento rilevato, ed anche se
l’immagine non è facilmente interpretabile ad occhio nudo si può affermare che il
flusso ottico viene calcolato correttamente, almeno da un punto di vista qualitativo.
Come conclusione il programma, nella sua prima stesura, funziona.
Figura 2.6
Figura 2.7
30
2.3.3. Time to Impact
Con il nome di time to impact (time to collision, time to contact …) si identifica una
caratteristica, per non dire capacità, comune ad ogni essere vivente dotato di sensori
“visivi” di tipo passivo. Avvalendosi unicamente di misure di tipo ottico, il flusso
ottico in particolare, e non conoscendo la propria velocità o la distanza da una
superficie, o comunque da un oggetto, è possibile determinare il momento in cui si
verificherà il contatto con questa superficie. L’importanza di questa capacità si
intuisce osservando il ruolo che riveste nel regolare il movimento degli appartenenti al
regno animale. Di conseguenza risulta un argomento fondamentale della branca
scientifica denominata Intelligenza Artificiale per quanto riguarda le problematiche
legate a quelle operazioni automatiche che possono essere raccolte sotto il nome di
navigazione e manipolazione di oggetti.
In questo sottoparagrafo parlerò della teoria alla base del time to impact, per poi
concentrarmi, nei sottoparagrafi successivi, sullo sviluppo di un algoritmo in grado di
ricavarlo partendo dal flusso ottico. In figura 2.8 si può osservare la geometria ottica
rappresentante il problema del calcolo del time to impact.
31
Un punto di interesse P alle coordinate (X, Y, Z) viene riprodotto attraverso il fuoco
di proiezione centrato nell’origine del sistema di coordinate (0, 0, 0). P è fisso nello
spazio fisico e non si muove. L’origine o fuoco di proiezione si muove con una
velocità dZ/dt. Se il sensore, nel nostro caso una telecamera, è rivolto nella
medesima direzione del moto, allora questa direzione è comunemente conosciuta con
il nome di “fuoco di espansione” (FOE, Focus Of Expansion), dato che si tratta del
punto dal quale i vettori del flusso ottico divergono. Considerando il caso di un robot
mobile si può ragionevolmente assumere che la direzione della telecamera sia la
medesima del moto, non è quindi necessario risolvere il caso generale di
“egomotion”2 per poter ottenere dei risultati utili. Il piano immagine è posto ad una
distanza z davanti all’origine; per convenienza si pone z = 1. Il valore attuale di z
dipende da più fattori tra i quali la distanza focale della telecamera, per comodità si
2 Situazione in cui il sensore possiede, contemporaneamente, una velocità di traslazione e una dirotazione, quest’ultima descrive l’egomotion.
Figura 2.8
32
2ZZYZ
Yy &&& ⋅−=
cerca di modificare questi fattori per portarsi nella situazione desiderata. Il piano
immagine si muove assieme all’origine. P viene proiettato, su questo piano, nel punto
p. Al movimento di avvicinamento del piano immagine a P corrisponde una
variazione della posizione di p sempre sul piano immagine.
Usando una relazione tra triangoli equilateri possiamo scrivere:
(1.3.1)
derivando rispetto al tempo si ottiene:
(1.3.2)
Dato che il punto P è, per ipotesi, immobile nello spazio possiamo affermare che:
(1.3.3)
Introducendo la (1.3.3) nella (1.3.2) e sostituendo a Y il valore y · Z, sempre nella
(1.3.2), possiamo ottenere la seguente espressione:
(1.3.4)
Riscrivendola otteniamo la relazione finale sulla quale si baserà il lavoro svolto
successivamente:
(1.3.5)
0=Y&
ZZyy && ⋅−=
τ=−=Z
Zy
y&&
ZYy
zy == 1
33
Quantità conosciuta con il nome di tempo di impatto. Si nota immediatamente che la
parte a primo membro (all’estrema sinistra) è interamente composta da grandezze di
tipo ottico e che quindi ci fornisce un metodo per il calcolo del time to contact basato
sulle immagini registrate dal sensore. Considerando una telecamera indirizzata nella
stessa direzione del FOE è sufficiente prendere un punto nell’immagine e dividerne la
distanza dal FOE per la propria divergenza dal FOE stesso; divergenza che risulta
essere il modulo del flusso ottico di quel punto. Come si ricava dalla (1.3.5)
considerata insieme alla figura 2.8.
2.3.4. L’algoritmo sviluppato
La scelta dell’algoritmo da utilizzare e le modifiche da apportare per calcolare il time
to contact, sempre sfruttando gli operatori di G. Maris per ottenere il flusso ottico, è
stato il passo successivo. Questa operazione si è rivelata più difficile del previsto a
causa della carenza di materiale a disposizione. Purtroppo questo argomento risulta
poco trattato anche a livello mondiale. La mia scelta è quindi caduta sul primo
metodo di calcolo reperito che fosse accompagnato da una accurata descrizione, per
la precisione il lavoro svolto da Ted Camus [1]. Innanzi tutto sottolineerei il fatto che
il lavoro di Camus è stato effettuato su porzioni di immagini 256 x 256 estratte da
immagini 512 x 512 e successivamente sottocampionate in un formato 64 x 64. Ciò
presenta una evidente differenza con il mio compito teso ad una verifica del ttc in
condizioni reali, senza l’impiego di preelaborazioni esasperate delle immagini. Inoltre
la velocità di movimento della telecamera è stata fissata in modo da ottenere un
frame per cm di spostamento, determinando, così, delle velocità relative in pixel per
frame molto basse, un’ulteriore vincolo che ho cercato di eliminare sempre per
34
tendere all’obbiettivo di applicazioni in campo reale. Il risultato è stato una sequenza
composta da approssimativamente 140 immagini.
La prima operazione da compiere è il calcolo delle coordinate del FOE all’interno
delle immagini della sequenza. Per determinare questi due valori si può sfruttare il
fatto che i vettori del flusso ottico possono essere rappresentati utilizzando le
componenti X e Y che li caratterizzano. Nel caso di un movimento in avanti e di un
solo oggetto occupante l’intero campo visivo, si può calcolare il FOE mediando i
fattori X e Y di tutti i vettori del flusso ottico, assegnando ad ogni componente il peso
equivalente ad un pixel trascurandone il valore effettivo, mantenendone il segno, e
trattando la media ottenuta come un offset rispetto alla linea di vista, la cui direzione è
stata assunta come perpendicolare al piano immagine e quindi coincidente con la
direzione del movimento (si proietta sul piano immagine nel punto centrale).
Le restrizioni imposte per quanto riguarda il moto ed il numero di oggetti inseguibili
all’interno del campo visivo, riducono le possibili applicazioni ma permettono di
semplificare la geometria del sistema. Considerando il fatto che lo scopo principale di
questo lavoro sarebbe un’eventuale realizzazione nell’ambito della robotica per
quanto concerne la navigazione, si può affermare che queste ultime restrizioni non
sono critiche.
Dalla (1.3.5) ricavata in precedenza per il ttc e dalla figura 2.8, che ne descrive la
geometria si osserva che la divergenza prevista di ogni punto appartenente ad una
circonferenza di un dato raggio centrata nel FOE dovrebbe essere sempre uguale
(per esempio quella del punto A è uguale a quella del punto P). È quindi giustificato
mediare le misure del flusso ottico lungo circonferenze di dato raggio per ottenere il
valore della divergenza da introdurre nell’equazione suddetta, cercando in questo
35
modo di limitare gli eventuali errori introdotti dal rumore o comunque da fenomeni di
tipo aleatorio.
Il numero di misure singole, sulle quali effettuare la media, associate ad ogni
circonferenza, è uguale a quattro volte il raggio in pixel della circonferenza stessa, ed
ogni misura singola è la media pesata tra i quattro pixel più vicini al punto, espresso in
coordinate reali, appartenente alla circonferenza. Infine la variabile y della (1.3.5)
viene assimilata al raggio, espresso in pixel, della particolare circonferenza usata.
Per ottenere un valore più preciso e meno sensibile ai fenomeni aleatori sempre
presenti in natura, è possibile eseguire il calcolo di t su più circonferenze
contemporaneamente ed effettuare la media sui risultati ottenuti.
Per una misura ancora più accurata, e sempre più stabile, è possibile mantenere in
memoria i valori istantanei calcolati nei t istanti di tempo precedenti, in modo da
calcolare una media tra t + 1 valori istantanei di t .
Da quanto appena detto si evince l’enorme importanza della parte dell’algoritmo
riguardante il calcolo delle coordinate del Fuoco di Espansione. Ho effettuato un
primo tentativo determinando le coordinate secondo l’algoritmo di Camus senza
utilizzare alcun accorgimento.
Utilizzando come test una sequenza composta da 20 immagini prelevate dal robot
Nomad in movimento di avvicinamento verso una parete, si è osservato uno
sbilanciamento del flusso ottico.
Questo sbilanciamento ha dato origine
a delle oscillazioni troppo accentuate e
non regolari del FOE rispetto al LOS
(Line of Sight, linea di vista). Nella
tabella qui di seguito sono riportati i
Figura 2.9
36
valori calcolati dalla procedura precedente per il caso appena descritto. Nella figura
2.9 viene riportata una delle immagini appartenente alla sequenza utilizzata con lo
scopo di far comprendere l’ambiente di lavoro nel quale sono stati sviluppati gli
algoritmi.
X Y
1-2 167.0 -128.0
2-3 54.0 -17.0
3-4 -74.0 16.0
4-5 70.0 20.0
5-6 138.0 -12.0
6-7 148.0 -123.0
7-8 160.0 14.0
8-9 171.0 36.0
9-10 238.0 -8.0
10-11 120.0 -13.0
11-12 -146.0 -37.0
12-13 -341.0 6.0
13-14 -1.0 15.0
14-15 -279.0 -22.0
15-16 -105.0 -70.0
16-17 -66.0 2.0
17-18 -96.0 17.0
18-19 -231.0 -16.0
19-20 -36.0 -65.0
Si può facilmente osservare come in alcuni casi le coordinate del FOE escono
addirittura dall’immagine, ciò, ovviamente, non va bene. Successivamente ho operato
un secondo tentativo limitando la superficie sulla quale effettuare il calcolo del FOE
ad una sottoimmagine 64 x 64 centrata nel
LOS. Mi sono basato sul fatto che a noi
interessa il calcolo del ttc rispetto al muro
davanti al Nomad e quindi i dati di
movimento generati dalle pareti che
passano a fianco del robot possono essere
Figura 2.10
37
considerati come rumore e quindi eliminati. La tabella seguente riporta i risultati degli
offset tenendo conto di questa limitazione sempre per la medesima sequenza utilizzata
in precedenza.
X Y
1-2 1.0 -15.0
2-3 17.0 -10.0
3-4 8.0 2.0
4-5 2.0 4.0
5-6 12.0 -5.0
6-7 12.0 -20.0
7-8 7.0 2.0
8-9 26.0 -1.0
9-10 68.0 -17.0
10-11 44.0 -7.0
11-12 -40.0 0.0
12-13 -47.0 11.0
13-14 38.0 21.0
14-15 -144.0 13.0
15-16 12.0 -5.0
16-17 40.0 27.0
17-18 50.0 43.0
18-19 -119.0 -16.0
19-20 47.0 -51.0
38
Nella figura 2.10 è riportato uno dei flussi ottici risultanti dalla sequenza in questione. In esso si
possono facilmente vedere i vettori risultanti dal movimento relativo delle pareti laterali del corridoio
evidenziati dai due ellissoidi. Purtroppo anche questa soluzione ha prodotto, come si osserva dalla
tabella, delle oscillazioni troppo ampie del FOE rispetto al LOS per poter essere considerata buona.
Tutti questi risultati sono comprensibili alla luce dei forti vincoli imposti da Camus nel suo lavoro.
Come conseguenza ho pensato di considerare le coordinate del FOE e quelle del LOS coincidenti,
approssimazione valida nel mio caso di lavoro. Ho quindi eliminato dal mio algoritmo il calcolo degli
offset del FOE.
Dopo aver affrontato il problema del FOE mi sono concentrato sul cuore dell’algoritmo ossia il
modo di calcolare il ttc. Tutto il lavoro svolto fino ad ora, incluso quello riguardante il flusso ottico, è
stato raggruppato in un programma completo (ttcextn e ttcexts) in grado di elaborare sequenze di
immagini almeno di 64 x 64 e di estrarne il time to impact.
Il programma funziona nel seguente modo. Innanzi tutto viene allocato lo spazio per la matrice
tridimensionale contenente le coordinate dei punti appartenenti alle circonferenze, scelta effettuata
per contenere in un’unica struttura tutti i dati necessari alle operazioni sulle circonferenze utilizzate. In
questo modo non sono stato vincolato a scegliere le circonferenze una volta per tutte ma queste
possono essere impostate al momento dell’esecuzione del programma, ciò equivale ad
un’allocazione di tipo dinamico. Insieme all’inizializzazione delle variabili usate vengono caricate in
memoria le circonferenze. Successivamente viene prelevata la prima immagine della sequenza per
elaborarla applicando nell’ordine il filtro quantizzatore, l’estrattore di Roberts e il codificatore soft,
questa diviene l’immagine old. A questo punto si entra nel ciclo principale del programma. In esso
viene caricata l’immagine successiva, definita come new, elaborandola come sopra , poi la si
confronta con la precedente per ottenere il flusso ottico. Dai vettori del flusso ottico si ricava il valore
istantaneo del time to impact riguardante queste due immagini, che poi verrà mediato con i valori
calcolati per le 7 coppie di immagini precedenti. Questa situazione richiede che l’algoritmo arrivi a
regime, cioè prima di avere dei risultati attendibili è necessario che abbia elaborato almeno 7 coppie
di immagini.
Arrivati in questa fase si “butta via” la vecchia immagine (old) si trasforma la nuova in vecchia, si
carica il prossimo frame della sequenza e si ripetono le operazioni appartenenti al ciclo principale fino
ad esaurimento della sequenza.
39
Dalla tipologia del nostro problema, ossia la ricerca del ttc in un movimento ortogonale diretto verso
una superficie, si comprende la necessità di utilizzare degli operatori bilaterali per poter ricavare un
flusso ottico il più “realistico” possibile. Eliminando il 3x21B (perdita di informazioni, operatore
troppo “basso”, ha il lato corto di soli 3 pixel), ho preferito concentrare la mia attenzione
all’Extended Cocle rispetto al 5x11B a causa della più spiccata simmetria puntuale del primo rispetto
al secondo. Come primo programma ho utilizzato l’operatore con la scansione della chiocciola
secondo la modalità normale, ecco il perché del nomi ttcextn e ttcexts (Extended Cocle a settori)
per l’eseguibile. Oltre a quanto detto in precedenza mi preme sottolineare il fatto che il programma
visualizza sullo schermo i valori del time to contact e li salva contemporaneamente nel file di testo
risultatinofoe.txt.
2.3.5. Test e risultati
Il primo passo fu quello di effettuare una serie di test utilizzando una sequenza di immagini artificiale,
costruita ad hoc, rappresentante un quadrato in espansione. Lo scopo di questa prova era quello di
permettere il debugging del programma, sia da un punto di vista sintattico che dal punto di vista della
coerenza dei risultati prodotti. Terminata la fase di debugging mi sono rivolto ad una sequenza reale,
prelevata sul campo tramite la telecamera del robot Nomad. La sequenza rappresenta il robot in
movimento lungo un corridoio diretto verso il muro terminale del corridoio stesso. Il muro è stato
“marcato” tramite l’apposizione, ad altezza telecamera, di un poster contenente segni di riferimento.
La prima prova, effettuata con il programma completo con il calcolo del FOE secondo la teoria di
Camus, ha dato origine a risultati disastrosi. Infatti ritornando alle tabelle presentate nel paragrafo
1.3.4 si vede che già nella prima coppia di immagini le coordinate del FOE escono dal piano
immagine. Oltre a quanto detto in precedenza mi preme far notare il fatto che aver scelto una
sequenza con pochi frame, 1 ogni 20 cm circa di movimento, ha prodotto un campo di vettori di
flusso ottico molto numeroso con velocità maggiori, anche di molto, di 1 pixel per frame. Come si
può osservare dalla figura 2.10, più ci si allontana dal punto centrale dell’immagine (LOS e FOE,
nella nostra assunzione), e più i vettori del flusso ottico aumentano d’ampiezza. È stato per questo
motivo che ho pensato di considerare un area limitata per il calcolo del time to contact (circonferenze
di raggio massimo uguale a 31 pixel) e del FOE per cercare di limitare le forti oscillazioni rilevate.
Questa scelta si rivelò profondamente errata, non solo in relazione ai risultati ottenuti, più coerenti
40
rispetto ai precedenti ma comunque inesatti, ma soprattutto nei riguardi della teoria del flusso ottico e
nelle assunzioni fatte per giungere a questo risultato.
Approfondendo le conoscenze sull’argomento in questione, e di questo devo ringraziare l’ing.
Tistarelli dell’università di Genova [2] e [3], sono riuscito a comprendere l’errore fatto. Nei due
lavori citati vengono analizzati i metodi principali di calcolo del flusso ottico evidenziando gli errori
che possono nascere durante le operazioni sia da un punto di vista qualitativo che quantitativo.
Fattore di grande interesse risulta essere la direzione scelta per il sensore in rapporto a come si
svolge il moto all’interno del campo visivo di quest’ultimo. Impostare il punto rappresentante la linea
di vista della telecamera come coincidente con il fuoco di espansione del campo vettoriale del flusso
ottico e considerarne un intorno per il calcolo del tempo di impatto è errato. Infatti i vettori che si
generano in prossimità di queste coordinate sono “piccoli” in modulo, di conseguenza gli errori che
possono insorgere nel loro calcolo a causa del rumore presente nel dispositivo di acquisizione hanno
un peso maggiore rispetto a vettori lontani che presentano un modulo “grande”. Questo problema
viene maggiormente esaltato se la “velocità” media in pixel per frame, presente all’interno della
sequenza, eccede di molto i valori di 2, 3 pixel per frame. Concludendo il metodo proposto da
Camus [1] presenta dei vincoli troppo restrittivi per il suo utilizzo, senza operare delle modifiche
importanti, in situazioni reali. O comunque può essere applicato in condizioni particolari di
sorveglianza nelle quali la LOS ed il FOE non coincidono.
2.3.6. Preparazione alla tesi
Alla luce di queste “nuove” conoscenze ed in preparazione del lavoro da svolgere per l’esame di tesi
mi sono dedicato al consolidamento del programma basato sugli algoritmi di Maris e Camus. Come
primo passo ho definito una libreria per la gestione della struttura dati contenente le coordinate dei
punti costituenti i cerchi di cui sopra. Questo modulo fa riferimento ad un’altra libreria che si occupa
della creazione, distruzione, visualizzazione e inizializzazione di strutture dati di tipo matriciale, per la
precisione:
• matrici 2D di char (immagini e flusso ottico)
• matrici 3D frastagliate nelle righe (seconda dimensione) di float (circonferenze)
• matrici 2D di float pensate per una possibile applicazione a colori con metodi di
calcolo del flusso ottico appartenenti alla prima macrocategoria descritta
41
(paragrafo 2.1, è stata abbandonata per motivi di calcolo, se ne parlerà nel
prossimo capitolo)
A questa libreria si lega anche quella per la gestione della struttura dati contenente il flusso ottico
calcolato creata per facilitarne l’utilizzo e rendere più comprensibile il codice che lo riguarda,
soprattutto il modulo destinato al calcolo del time to impact.
Per semplificare la visualizzazione del flusso ottico ho definito una libreria che permette di effettuarne
il salvataggio secondo un formato utilizzabile dal programma MatLab (le immagini 2.7 e 2.12 sono un
esempio di applicazione). Purtroppo questo programma, che permette di riprodurre facilmente il
campo di vettori del flusso ottico non funziona in ambiente Unix/Linux, quindi per poter verificare in
tempi brevi i risultati prodotti ho definito una libreria includente una funzione per il disegno di questi
vettori in maniera semplice veloce, e spero comprensibile (un esempio è la figura 2.10). Sfruttando
queste librerie ho definito un modulo contenente alcune procedure che si occupano della gestione
delle uscite prodotte dal programma, ossia il loro salvataggio su hard disk:
• immagini in formato PGM per il risultato del filtro quantizzatore, dell’estrattore di
Roberts e del codificatore soft
• flusso ottico ricavato esportabile secondo tre formati: immagine di tipo PGM,
matrice delle componenti (x, y) dei vettori, matrice per MatLab.
La prima stesura presentava un grosso difetto, il programma poteva utilizzare un solo operatore, la
scelta del quale veniva effettuata durante la fase di compilazione. Questo difetto è stato eliminato
ridefinendo i moduli contenenti i vari operatori, nella parte di codice contenete la definizione della
struttura della matrice di matching, in modo tale da poter essere inglobati nel programma tutti
insieme.
Ho introdotto un nuovo algoritmo di calcolo delle coordinate del fuoco di espansione, adesso si
hanno due possibili metodi:
• metodo sviluppato da Camus
• calcolo delle coordinate del punto di intersezione dei vettori del flusso ottico
Per quanto riguarda il secondo punto non è stata implementata, per motivi di tempo, la possibilità di
definire una soglia per il modulo del vettore sotto la quale non considerare il vettore in esame nel
computo totale. Logica conseguenza di quanto detto in precedenza riguardo all’errore presente nei
vettori di modulo piccolo [2] e [3].
42
Un altro difetto della prima stesura riguarda il passaggio al programma dei parametri necessari al suo
funzionamento, ad esempio la sequenza di immagini da utilizzare, mediante riga di comando. Avendo
introdotto le seguenti possibilità di scelta:
• l’operatore di matching
• il tipo di algoritmo per il calcolo del FOE
• quali e quante circonferenze
• quanti valori istantanei di TTI per il calcolo del valore medio
• le uscite disponibili dal programma
• la sequenza di immagini
per semplificare questo procedimento ho definito un modulo contenete il codice riguardante la fase di
inizializzazione del programma facente riferimento ad un file di configurazione contenente tutti i
parametri necessari e definibili per il corretto funzionamento del programma. Da questo momento in
poi l’eseguibile ha cambiato nome in timetocontact_new.
Tutte queste modifiche sono servite successivamente per permettere un facile trasferimento delle
componenti di codice necessarie allo sviluppo del programma principale di questa tesi, come si vedrà
nel prossimo capitolo.
2.3.7. Ultimi test e conclusioni
Il codice ottenuto alla fine delle modifiche descritte nel paragrafo 2.3.6 è stato testato su di una serie
di sequenze gentilmente fornite dall’ing. Tistarelli. I risultati sono stati confortanti soprattutto per
quanto riguarda il tti, migliorato di molto rispetto alla prima stesura. In queste prove si è osservato un
comportamento qualitativamente corretto a fronte di una serie di valori quantitativamente non
accurati. Quest’ultimo problema è da imputare al fatto che l’algoritmo di calcolo del flusso ottico non
è molto sofisticato e che funziona abbastanza bene in situazioni che presentano un unico oggetto in
moto all’interno del campo visivo. Inoltre è necessario per poter calcolare il time to impact che le
coordinate del fuoco di espansione siano contenute all’interno dell’immagine in misura tale da
permettere l’utilizzo corretto delle circonferenze scelte. Di seguito riporto i dati estratti dalla sequenza
caratterizzata dalle immagini di figura 2.11 rappresentanti la prima e l’ultima rispettivamente, mentre
in figura 2.12 viene presentato il flusso ottico ricavato per una coppia di immagini consecutive della
medesima sequenza.Figura 2.11
43
FOE - X FOE - Y TTI mediato
1-2 157.338196 140.395584 1.673028
2-3 150.354599 126.923409 3.998635
3-4 146.991806 140.691589 6.734520
4-5 145.765656 144.553696 8.294420
5-6 144.484070 122.739380 10.116780
6-7 151.914444 130.757111 11.982764
7-8 145.707367 129.678604 17.415642
8-9 142.179886 141.869949 23.548075
9-10 145.736267 152.909378 26.456690
10-11 133.802216 132.794006 25.369814
11-12 150.218384 157.254730 23.460073
12-13 141.765076 153.773041 20.929058
13-14 149.690948 157.523331 19.361246
N.B. le coordinate del FOE sono calcolate come intersezione dei vettori di flusso ottico.
Si osserva facilmente che dopo il periodo necessario per arrivare a regime, 7 valori istantanei in
questo caso, il tempo di impatto si stabilizza e comincia a diminuire definendo un avvicinamento agli
oggetti presenti nell’immagine.
Figura 2.12
44
Capitolo 3
Il sistema APACHE
In questo capitolo darò una breve descrizione del sistema APACHE evidenziandone le relazioni con
il presente lavoro di tesi.
In ambito scientifico è ormai assoldato che lo sviluppo di supporti intelligenti per i veicoli e per il
controllo del traffico abbia assunto una dimensione ed un’importanza di livello mondiale (Shibata and
French 1997). È possibile identificare due campi principali di sviluppo: la produzione di infrastrutture
intelligenti e la produzione di veicoli intelligenti. Una recente ricerca di tipo comparativo ha
evidenziato che l’interesse per problematiche di questo tipo si è spostato al di fuori dell’Unione
Europea, gli Stati Uniti d’America ed il Giappone, ed altre nazioni minori, sono ora interessate allo
sviluppo ed all’acquisizione di tecnologie di questo tipo. La visione artificiale mediante supporto
computerizzato gioca un ruolo fondamentale nel creare sistemi intelligenti mirati al miglioramento della
gestione, della sicurezza e del controllo del traffico. Questo tipo di sistemi può essere facilmente
raggruppato in due categorie: “dispositivi on board”, che implementano i veicoli intelligenti, e
“dispositivi on the road”, che implementano le infrastrutture intelligenti.
Il sistema nato dal progetto chiamato APACHE – Automatic PArking CHEck – è stato pensato per
essere un dispositivo di tipo “on the road” fornendo allo stesso tempo una soluzione a basso costo
per il problema del controllo degli accessi ad aree circoscritte. Implementa una architettura di
sorveglianza in grado di leggere le targhe dei veicoli che accedono all’area controllata in modo
automatico. Il processo di riconoscimento è completamente automatizzato e solamente nel caso in
cui il livello di affidabilità delle targhe riconosciute scenda al di sotto di una certa soglia, un segnale
d’allarme può essere inviato ad un operatore umano insieme all’immagine che ha causato il problema
per permettere una lettura di tipo manuale. Molti sistemi funzionalmente simili ad APACHE sono stati
sviluppati utilizzando varie tecniche per stabilire l’istante in cui avviare la procedura di
riconoscimento, per segmentare l’immagine catturata e per effettuare il riconoscimento dei caratteri
45
costituenti la targa. Alcuni di questi sistemi si trovano ancora in uno stato di prototipo mentre altri
sono prodotti completi ed immessi sul mercato. Oltre alla telecamera, molti di questi impianti
richiedono dei dispositivi addizionali per poter operare correttamente, come ad esempio sensori
all’infrarosso o di movimento. Nella fase di pianificazione del sistema APACHE è stato scelto di
adottare un’architettura basata esclusivamente sulla visione. Per questa ragione il sistema non
necessita di hardware particolare per funzionare, ma utilizzando dispositivi facilmente reperibili “negli
scaffali” dei negozi di elettronica si dimostra molto competitivo dal punto di vista del rapporto
prezzo/prestazioni.
La presenza di un sensore ottico, quale può essere una telecamera, risulta essere la richiesta minima
per un sistema di sorveglianza il cui compito sia quello di registrare quanto accade nell’area sotto il
suo controllo.
L’utilizzo, per il controllo ambientale, di un’architettura basata unicamente su procedure di visione
presenta alcuni vantaggi rispetto ad altre soluzioni come ad esempio il sistema Telepass adottato
dalla Società Autostrade Italiana. L’impatto di tipo architettonico e di “inquinamento visivo” sono
parametri di vitale importanza. Un sistema del tipo utilizzato per APACHE è paragonabile ad una
comunissima installazione di tipo semaforica piuttosto che ad un portale speciale come quello
richiesto dal sistema Telepass, inoltre la flessibilità e la gestione a livello economico rappresentano
altri due parametri molto importanti da tenere in considerazione. La presenza della sola telecamera
elimina la necessità di carte speciali o di equipaggiamenti basati su microprocessori da installare sul
veicolo per fornirgli l’accesso alle aree controllate, in questo modo si riduco i costi per l’utente,
eliminando il problema della ricarica periodica della carta e/o della manutenzione dei dispositivi
installati sul mezzo di trasporto. Inoltre utilizzando unicamente dei sensori di tipo passivo che si
basano sulle radiazioni luminose presenti nell’ambiente, si riduce drasticamente l’inquinamento
elettromagnetico, che, come sappiamo, è diventato molto importante in quest’ultimo periodo. Infine è
molto più semplice gestire l’accesso temporaneo alle zone controllate, basta infatti aggiornare un
database centrale che contiene le targhe dei veicoli abilitati.
3.1. Come funziona
46
Il sistema si appoggia unicamente su procedure di “computer vision” ed è disegnato come un set di
moduli in cascata, ognuno dei quali è responsabile di un singolo passo nella sequenza di operazioni
che compongono il processo di riconoscimento delle targhe. APACHE è suddivisibile in tre moduli: il
modulo di segmentazione delle targhe, che si occupa del rilevamento della posizione della targa del
veicolo all’interno dell’immagine in ingresso e della seguente estrazione; il modulo di segmentazione
dei simboli, che estrae i simboli presenti nella targa uno ad uno; ed il modulo di riconoscimento dei
simboli, che classifica i simboli estratti caratterizzando così la targa. I primi due moduli combinano
tecniche di elaborazione d’immagini e conoscenze a priori sulla topologia delle targhe, mentre il terzo
modulo impiega un approccio a rete neurale.
APACHE è un sistema basato su un software interamente sviluppato in C e C++, codificato
inizialmente in ambiente DOS è stato poi riscritto e compilato per funzionare anche in ambiente
Linux. Le immagini, riprese da posizione fissa (vedi figura 3.1), vengono elaborate da un PC dotato
di una scheda di acquisizione video specifica e, in uscita sul video, viene fornita una visualizzazione
della targa letta. L'uso principale per il quale fu dato il via allo sviluppo del software riguardava,
come accennato in precedenza, i caselli autostradali. Questo tipo di applicazione, già oggi utilizzata,
funge da supporto ai tradizionali sistemi Telepass e Viacard. In un casello in cui non funzioni
correttamente uno dei sistemi previsti, entra in gioco la telecamera che aiuta un operatore ad
identificare l'auto ''non riconosciuta''. Da questa applicazione deriva l'idea per un'altra, del tutto
sperimentale al momento, che possa regolamentare l'accesso ai centri storici: il sistema deve
riconoscere le auto che accedono a zone riservate per verificarne i permessi. Fattore fondamentale,
in questo caso, è che la strada non può più essere ''strutturata'', cioè non è più possibile sistemare
Figura 3.1
47
dispositivi che rilevino la presenza dell'auto o che ne regolino il flusso (sbarre, guardrail, spire
annegate nell’asfalto, ecc.) e che comunque prevedano la posa di sensori nelle immediate vicinanze
del passaggio. L'unico sensore di APACHE è una ''semplice'' telecamera posta ad una certa altezza
(circa 4 m), perché non sia facilmente raggiungibile, e ad una distanza di circa 10 m perché possa
inquadrare la targa posteriore dell'auto in modo agevole. Tutto il sistema non deve richiedere la
presenza sul posto di un operatore che, invece, può controllare il corretto funzionamento di più
installazioni da una postazione remota. Un'altra possibile espansione dovrà riguardare la capacità di
leggere le targhe dei ciclomotori in quanto mezzi fondamentali, non tanto nelle autostrade dalle quali
sono esclusi, quanto nei centri storici.
3.2. Problemi e possibili sviluppi
Da quanto visto nel paragrafo precedente, il tipo di sensore utilizzabile per fornire ad APACHE
l’informazione temporale di quando scattare il fermo immagine sul quale effettuare le elaborazioni di
ricerca della targa, riveste una importanza critica, soprattutto se si vuole installare questo dispositivo
in determinati ambienti. Riflettendo sulla conformazione tipica dei centri storici delle città presenti in
Italia risulta molto complesso se non impossibile predisporre l’utilizzo di sbarre, spire annegate nel
manto stradale, ecc. Bisogna quindi trovare una strada alternativa poiché, l’estrazione corretta della
targa è molto sensibile alla “qualità” dell’immagine fornita al sistema. Qualità che dipende non solo
dalla risoluzione con cui la telecamera può osservare l’ambiente controllato, ma soprattutto da due
fattori estranei al concetto standard di qualità dell’immagine stessa. Il primo è che il veicolo da
“analizzare” deve essere presente all’interno dell’immagine, se non fosse presente APACHE
perderebbe del tempo a ricercarne la targa con la possibilità di mancare il passaggio di uno o più
veicoli successivi, quest’ultimo problema dipende dai tempi di elaborazione. Il secondo punto
dipende da una situazione che si è verificata durante gli ultimi test effettuati presso la palazzina 1 del
dipartimento di Ingegneria. Acquisendo le immagini direttamente dalla telecamera, è stato osservato
che la probabilità di riuscita della ricerca della targa, e della conseguente estrazione dei simboli, viene
influenzata dalla posizione della targa all’interno dell’immagine stessa. A questo aggiungiamo che in
taluni casi, dipendenti dal colore e dalla forma dell’automobile, la telecamera presenta un notevole
48
problema di saturazione quando l’oggetto dell’indagine tende ad occupare almeno il 90% del campo
visivo, livello di zoom necessario per poter riconoscere i caratteri della targa. Inoltre questa
situazione tende a presentarsi in istanti molto vicini a quello di fermo immagine “ideale”. Possiamo
concludere affermando che il tempo riveste una importanza fondamentale e le possibili soluzioni per
eliminare la spira ne dovranno tenere conto.
49
Capitolo 4
Flusso for Linux
Compreso che cos’è APACHE e quali sono le sue limitazioni viene spontaneo porsi la seguente
domanda:
“È possibile determinare l’istante migliore nel quale scattare il fermo immagine, dal quale
estrarre la targa del veicolo, che sia a due o a quattro ruote, senza utilizzare un sensore di
tipo invasivo come ad esempio una spira annegata nell’asfalto?”
Ragionando su questo interrogativo e riconsiderando quanto detto nel capitolo 2 sulle informazioni
che sarebbe possibile estrarre da un’analisi dell’ambiente soggetto ad indagine, una prima risposta
sarebbe sì. Lo scopo di APACHE, come detto nel capitolo 3, è quello di sorvegliare una porzione di
strada per identificare le targhe dei veicoli che la percorrono. Per poter scegliere il momento in cui
acquisire l’immagine da elaborare è necessario poter caratterizzare in qualche modo il moto dei
veicoli che attraversano il campo visivo della telecamera. Viene quindi effettuata una elaborazione di
tipo “cognitiva” ricadendo in quella branca dell’ingegneria chiamata Intelligenza Artificiale. Uno degli
strumenti resi disponibili da quest’ultima per caratterizzare il movimento di oggetti acquisiti tramite
una telecamera è l’estrazione del loro flusso ottico. Quindi, come conseguenza immediata, si
potrebbe pensare di sostituire la spira utilizzata fino ad ora, o qualunque altro tipo di sensore
invasivo, con un sistema informatico in grado di calcolarlo e valutarlo per stabilire quando ottenere
l’immagine da passare ad APACHE per le elaborazioni del caso. Da un punto di vista puramente
teorico tutto ciò è possibile, da un punto di vista pratico cosa si può dire?
Nel presente lavoro di tesi cercherò di fornire una risposta a questo interrogativo la più esauriente
possibile.
50
4.1. Uno sguardo d’insieme
Partendo da questi presupposti ho cercato di ottenere un programma in grado di ricavare il flusso
ottico da una sequenza di immagini prelevata dalla telecamera. Ho scelto per il programma il nome
flusso4l ovvia storpiatura di flusso for linux, ed è con questo nome che lo indicherò nel proseguo di
questo lavoro.
Flusso4l è basato su di un software sviluppato interamente in C e C++ ovviamente in ambiente
Linux. La GUI (Graphics User Interface) è stata implementata utilizzando le librerie grafiche QT,
scelta imposta dalla necessità di far girare il programma sullo stesso computer predisposto per
APACHE. Su questa macchina erano state installate solo queste librerie.
Il sistema sviluppato funziona nel seguente modo. Una telecamera situata in posizione fissa, installata
su di un palo “semaforico” al di fuori della palazzina 1 del dipartimento di Ingegneria ad un’altezza
approssimativa di 4 m, sorveglia un tratto di strada del parcheggio antistante. Le immagini riprese
vengono elaborate da un PC dotato di scheda di acquisizione video. Confrontando il risultato di
queste elaborazioni per coppie di immagini temporalmente consecutive, il sistema è in grado di
estrarre il flusso ottico degli oggetti in movimento (autovetture, biciclette, motocicli e pedoni) presenti
all’interno del campo visivo della telecamera.
Il dispositivo funziona correttamente in tutte le condizioni meteo nelle quali è stato possibile testarlo,
tra queste sono escluse la presenza di nebbia e di neve poiché non erano “disponibili” nelle giornate
dedicate alle prove “su strada”. Al momento il flusso ottico rilevato è di tipo monolaterale, viene cioè
privilegiata una direzione di ricerca specifica impostandola al momento di avviare il sistema. Ciò si
realizza tramite l’utilizzo di una serie di maschere, dette di matching, che vedremo in dettaglio più
avanti, che vengono sovrapposte alle immagini elaborate determinando così la direzione di ricerca del
flusso ottico. Questo è un discorso che può sembrare limitativo ma se si pensa che lungo una strada i
veicoli la percorrono lungo due sensi ben precisi appartenenti alla medesima direzione, la
semplificazione effettuata non solo ha un senso logico ma ricade in quei vincoli di conoscenza a
priori, di cui ho discusso nel secondo capitolo, che permettono di alleggerire il problema che si sta
tentando di risolvere. Comunque sono in fase di sviluppo una serie di maschere di tipo bilaterale che
permetterebbero l’estrazione del flusso ottico lungo i due sensi appartenenti alla medesima direzione.
Queste maschere presentano alcuni problemi di cui parlerò più avanti.
51
4.1.1. Gli elementi del sistema
Il sistema che oggi rappresenta flusso4l è costituito da un PC basato su un processore Pentium Intel
II a 600 MHz, con 128 MB di RAM, da una scheda di acquisizione video Matrox Comet a 16 MB
di RAM PCI con ingresso video composito e da una telecamera a CCD.
In fase di test la telecamera è stata sostituita con un videoregistratore in modo da poter effettuare le
stime dei tempi di elaborazione sulla stessa sequenza di immagini, ottenendo così dei risultati coerenti.
Il VCR è stato utilizzato anche nella fase di estrazione dei risultati da salvare su hard disk. Si è
rilevato un ottimo strumento di supporto.
Il monitor del PC permette, tramite l’interfaccia utente, il controllo di tutte le funzioni del sistema e
risulta utile come dispositivo di verifica del corretto funzionamento del programma.
Per quanto riguarda il PC, al momento della stesura di questo testo, il modello rappresentante l’entry
level è basato su di un processore con frequenza di clock intorno agli 800 MHz se non addirittura
1000 MHz. È chiaro come la capacità computazione del PC utilizzato in sede di sviluppo di questa
tesi risulti inferiore rispetto a queste macchine, soprattutto se consideriamo che passando ad un 1000
MHz le operazioni su numeri interi, che rappresentano le fondamenta di questo lavoro, come
vedremo più avanti, vengono eseguite, approssimativamente, in metà tempo.
La scheda di acquisizione sulla quale si basa l’intero sistema è in grado di importare immagini PAL e
NTSC a piena risoluzione (768 x 576 pixel per lo standard PAL) attraverso un ingresso Y/C in cui li
segnale di luminanza (Y) e quello di crominanza (C) sono separati. Le limitazioni maggiori di questo
hardware risiedono nella memoria a disposizione, in quantità e velocità, nel BUS di comunicazione
utilizzato, PCI invece che il più veloce AGP, e nella velocità del processore che la governa.
4.2. L’algoritmo sviluppato
Il programma che gestisce l’estrazione del flusso ottico è fondamentalmente basato su di un ciclo
infinito nel quale viene catturata una coppia di immagini consecutive, queste vengono elaborate
52
singolarmente per estrarne le caratteristiche salienti, nel nostro caso gli oggetti in movimento, e poi
confrontate tramite opportuni operatori di matching per estrarne il campo di vettori del flusso ottico.
Al di fuori del ciclo rimangono unicamente le parti di inizializzazione e terminazione delle varie funzioni
nonché le procedure di gestione delle variazioni dei parametri utilizzati nelle operazioni svolte. Nella
figura 4.1 è raffigurato il diagramma di flusso del main o nucleo centrale del programma. In esso si
possono riconoscere tre blocchi principali:
§ il primo effettua l’inizializzazione dell’oggetto grafico di tipo QT che rappresenta
l’interfaccia utente e delle strutture dati delle due thread utilizzate; dopo l’inizializzazione
le thread vengono attivate e si esce da questo primo blocco.
§ il secondo ed i terzo gestiscono le due thread create; uno si occupa del calcolo del flusso
ottico “off line” (thread ScriviOffline), cioè lo estrae da sequenze di immagini statiche
salvate su hard disk; l’altro controlla l’estrazione del flusso ottico “on line” (thread
ScriviOnline), utilizzando le immagini prelevate direttamente dalla telecamera.
L’utilizzo delle thread, peraltro non necessario in questo contesto specifico, è stato pensato come
esercizio di programmazione finalizzato all’integrazione delle procedure sviluppate in questo lavoro
con il sistema APACHE. Quando queste due realizzazioni saranno integrate il modulo di APACHE
rimarrà in attesa di un segnale che il dispositivo di calcolo del flusso ottico dovrà inviargli quando un
oggetto rilevabile si presenterà nella posizione migliore per analizzarlo. Una struttura di questo tipo
può essere facilmente “riprodotta” mediante l’utilizzo di due processi concomitanti, uno che funziona
continuativamente e l’altro che “dorme” fino a quando non si verifica una situazione particolare. A
Figura 4.1
53
livello software si ottiene tutto ciò mediante l’impiego delle thread. Le due procedure si devono
escludere a vicenda, non potendo essere attive contemporaneamente, verificando e modificando
delle opportune variabili ambientali. Ambedue contengono un ciclo infinito dal quale usciranno
solamente quando il programma verrà terminato tramite la pressione del pulsante di uscita. Tra le due
la più importante è sicuramente quella che si occupa dell’acquisizione diretta dalla telecamera e sarà
quella descritta nel proseguo di questo lavoro. La seconda è stata sviluppata con il solo intento di
poter avere a disposizione uno strumento per il debugging e per l’estrazione di risultati senza
influenzare il funzionamento normale del programma, questi concetti verranno espansi nel paragrafo
dedicato alle prove effettuate.
4.2.1. La thread ScriviOnline
Dedichiamoci ora a dare una breve spiegazione di questo blocco del programma. In figura 4.2 sono
riportati il diagramma di flusso ed il codice della thread.
void *ScriviOnline(Flusso4L *data){ data->videoOFF = true; data->flowOFF = true; for ( ; ; ) { while (!data->videoOFF) { //start_time(); data->videoplay(); if ((!data->flowOFF) && (data->op_loaded)) {
//preprocedure();data->Flusso();//afterprocedure(0);
} //printresult(); } }
Figura 4.2
54
La fase di inizializzazione, che lega la thread all’oggetto grafico QT chiamato Flusso4L, è definita
dall’assegnamento del valore true a due variabili globali che regolano l’attività della scheda di
acquisizione video (videoOFF) e l’attivazione della parte di codice per il calcolo del flusso ottico
(flowOFF). Il ciclo “infinito” è suddiviso in due parti, l’accesso ad ognuna delle due viene regolato
tramite l’impiego di due relazioni booleane basate sulle variabili di cui sopra, più una terza che viene
definita e modificata in un’altra parte del programma (op_loaded) e di cui parlerò più avanti. In
poche parole quando il sistema viene attivato tramite la pressione del pulsante “PLAY”, presente
nell’interfaccia utente, videoOFF viene posta a false e la scheda video comincia ad acquisire
immagini e a visualizzarle sul monitor del PC. Una volta impostati i parametri che influenzano il
calcolo del flusso ottico, è in questa fase che la variabile op_loaded può essere modificata, e
regolata la posizione dell’obbiettivo della telecamera, premendo il pulsante “Flusso ON”, sempre
presente nell’interfaccia utente, anche la variabile flowOFF viene posta a false ed il sistema
comincia a calcolare il flusso ottico.
55
4.2.2. La procedura videoplay
Questa procedura si occupa dell’acquisizione delle immagini tramite la scheda Matrox, della
conseguente visualizzazione sullo schermo nell’interfaccia utente e di rendere disponibile l’immagine
acquisita al resto del programma. Non c’è molto altro da dire se non che il metodo di acquisizione è
del tipo un’immagine alla volta. Questa scelta è stata condizionata da due fattori, il più importante è
sicuramente quello legato al metodo di calcolo del flusso ottico, per cui la situazione ideale sarebbe
quella di elaborare l’immagine acquisita e confrontarla con quella mantenuta in memoria,
appartenente all’istante di acquisizione precedente, già elaborata, nell’intervallo di tempo richiesto
void Flusso4L::videoplay(){ int c; unsigned char *src; unsigned int colore;
QPainter p(video_group); QImage *qimmvideo = new QImage();
c = METEOR_CAP_SINGLE; ioctl(fd, METEORCAPTUR, &c); src = mmbuf + off.frame_offset[0]; temp = src;
qimmvideo->create(IMGX/2, IMGY/2, 32, 0, QImage::IgnoreEndian); for(int a = 0; a < IMGY/2; a++) { for(int b = 0; b < IMGX/2; b++) { colore = ((unsigned int) (((*(src++)) & 0xff)) |
(unsigned int) (((*(++src)) & 0xff) << 8) |(unsigned int) (((*(++src)) & 0xff) << 16));
src += 5; qimmvideo->setPixel(b, a, colore); } src += DIMX * 4; } if (sartoON) for (int a = mini[0]; a < maxi[0] + 1; a++) if ((a == mini[0]) || (a == maxi[0]))
for (int b = areaim[a - mini[0]][0]; b < (areaim[a - mini[0]][1] + 1); b++) { qimmvideo->setPixel(b, a, VALBLACK);}
else {qimmvideo->setPixel(areaim[a - mini[0]][0], a, VALBLACK);qimmvideo->setPixel(areaim[a - mini[0]][1], a, VALBLACK);
} p.drawImage(4, 4, *qimmvideo, 0, 0, -1, -1); qimmvideo->~QImage();
Figura 4.3
56
dalla scheda per acquisire la prossima immagine. In questo modo la scheda ed il software di
elaborazione lavorerebbero in modo indipendente. Purtroppo le librerie sviluppate per l’impiego
della scheda in ambiente Linux contengono degli errori per quanto concerne l’acquisizione in
modalità continua3. Questi errori possono portare alla elaborazione di immagini non completamente
acquisite, ad esempio un’immagine composta per metà da quella acquisita nell’istante t-1 e per l’altra
metà da quella dell’istante t. Il flusso ottico che ne consegue risulta errato e questo è il secondo
fattore. Ciò richiede che le due procedure di acquisizione ed elaborazione siano sincronizzate. Se a
questo aggiungiamo che la documentazione riguardo l’utilizzo delle funzionalità della scheda di
acquisizione non esaurisce in maniera sufficiente tutte le possibili applicazioni, si può affermare che
questo problema non presenta una facile soluzione. Considerando tutto questo ed il fatto che
l’operazione di acquisizione è di tipo non atomico, si comprende come la tecnica implementata sia la
più semplice ed affidabile a disposizione. Unico neo di questa scelta risulta essere l’enorme
dipendenza dai tempi di calcolo per quanto riguarda la visualizzazione sul monitor di quanto
osservato dalla telecamera. In figura 4.3 viene riportato il codice di questa procedura. Ultimo punto
degno di nota è la presenza del ciclo for condizionato dall’essere true la variabile sartoON. Questa
parte di codice serve a visualizzare a schermo la cosiddetta area di taglio che serve per simulare, a
livello software, l’impiego di una spira o comunque a ridurre la zona di ricerca del flusso ottico. Ne
parlerò in maniera più approfondita nel prossimo paragrafo.
4.2.3. La procedura Flusso ed i suoi moduli
Cominciamo ad analizzare il cuore pulsante del programma. Questa procedura può essere divisa in
due blocchi principali, il primo si occupa di gestire la prima immagine che viene elaborata, mentre il
secondo, che rappresenta il processo a regime, gestisce l’acquisizione delle immagini successive, la
loro elaborazione ed il confronto per l’estrazione del flusso ottico. In figura 4.4 (a) ne riporto il
diagramma di flusso ed il relativo codice in figura 4.4 (b), la variabile first_time serve per
discriminare quando si tratta della prima immagine o di una delle seguenti. Riassumendo l’algoritmo
funziona nel seguente modo: acquisisce la prima immagine e la elabora estraendone gli osservabili e
classificandola come immagine old; poi acquisisce l’immagine successiva, classificata new, la elabora
e la confronta con la old per estrarne il flusso ottico; a questo punto la new viene “trasformata” in
old ed il ciclo ricomincia con una nuova immagine new. Questa procedura è composta da una serie
3 Questo problema è stato evidenziato già dalle prime stesure del sistema APACHE.
Figura 4.4 (a)
void Flusso4L::Flusso(){ if (first_time) { img_old.grabba_scaled(temp, LARGHEZZA, SCALAX, SCALAY, caricafile); acquisisci_sfondo(); elaborazione_immagine_ppmgs(&img_old); first_time = false; } else { img_new.grabba_scaled(temp, LARGHEZZA, SCALAX, SCALAY, caricafile); elaborazione_immagine_ppmgs(&img_new); reset_of(); operatore_ppmgs(sartoON); combinaflussiottici(COMBFLOW); visualizzaflussoottico(); if (outfoON) salvafo(); img_old = img_new; }}
Figura 4.4 (b)
57
di moduli che vengo eseguiti ripetutamente per ogni immagine e per ogni coppia di immagini
consecutive. Andiamo ad analizzarli nel dettaglio.
4.2.3.1. grabba_scaledLe immagini acquisite dalla scheda Matrox vengono memorizzate, all’interno del programma, in una
struttura dati appropriata definita tramite la classe ‘image’. Questa struttura dati permette la loro
successiva elaborazione senza intaccare quella parte di memoria destinata alla scrittura dei dati video
prelevati dalla telecamera. Otteniamo così una separazione netta tra i dati che verranno elaborati dal
programma e quelli acquisiti dalla scheda video. Separazione che viene ottenuta tramite una
mappatura diretta della porzione di memoria nella quale la scheda andrà a scrivere mediante
l’istruzione C mmap. grabba_scaled è una delle funzioni membro di questa classe, situata all’interno
del file graphics.cpp si occupa del caricamento delle immagini acquisite nella struttura dati
appropriata operando anche un cambio di dimensioni. L’acquisizione delle immagini viene effettuata
alla risoluzione massima di 768 x 576 pixel. Questo valore, necessario ad APACHE per poter
estrarre correttamente le targhe, non risulta indispensabile per il calcolo del flusso ottico, anzi, è
preferibile ridurre la quantità di dati da elaborare per ridurre i tempi di calcolo il più possibile. Per
fare un semplice esempio consideriamo l’immagine a risoluzione piena per uno dei tre toni di colore,
essa sarà formata da 768 x 576 x 8 = 3'538'944 bit, se ora prendiamo una immagine i cui lati sono
la metà otteniamo 384 x 288 x 8 = 884'736 bit, esattamente un quarto del valore precedente. Per
questi motivi, è stata scelta una scalatura ad 1/3 delle dimensioni originali, valore che si è poi rivelato,
in sede di sperimentazione, come un buon compromesso tra i tempi di elaborazione e la risoluzione
del flusso ottico ricavato. Poiché la procedura in questione occupa, all’incirca, il 10 – 12%4 del
tempo complessivo di esecuzione, ho optato per una azione di scalatura molto brutale priva di
operazioni di mediazione.
Vengono trasferiti nell’immagine ridotta 1 pixel ogni n, dove n è il fattore di scalatura, quindi nel caso
del valore scelto in precedenza uno ogni tre. In figura 4.5 riporto il codice per questa procedura con
l’intento di far notare la presenza di una macro da me chiamata INDICE_X, con X uguale a 3 o 4,
che verrà ampiamente utilizzata all’interno della classe ‘image’.
4 I tempi di esecuzione saranno trattati più approfonditamente nel paragrafo 4.5.
58
Dato che la struttura dati ove vengono immagazzinate le immagini acquisite è definita come un vettore
di char unsigned (privo di segno) ho pensato di utilizzare una macro apposita per indirizzarne gli
elementi come se si trattasse di una matrice di pixel, con ad ogni pixel associate le tre componenti
colore5. In questo modo il codice risulta molto più semplice da comprendere ed è stata molto più
semplice la sua stesura.
4.2.3.2. acquisisci_sfondoUno dei problemi fondamentali nei processi di elaborazione di immagini riguarda la gestione del
rumore presente nelle immagini stesse introdotto dai dispositivi di acquisizione quali la telecamera.
Come precedentemente accennato nel capitolo 2, il rumore termico intrinseco alla matrice CCD, le
variazioni di posizione dell’obbiettivo prodotte da vibrazioni o folate di vento, nel caso in cui la
telecamera sia fissata in una posizione esposta agli agenti atmosferici, possono generare delle
variazioni di luminosità tra una acquisizione e quella successiva. Queste variazioni sono estremamente
dannose per le operazioni di ricerca delle corrispondenze soprattutto nella fase di estrazione dei
5 La scelta di utilizzare immagini a colori verrà spiegata poco più avanti.
void image::grabba_scaled(unsigned char *vi, int dim, int sX, int sY, bool sel)
{ int i, j;
switch (sel) { case 0: //grabba dalla scheda for (i = 0; i < H; i++) for (j = 0; j < L; j++) {
RgbPtr[INDICE_3(i, j, 2, L)] = vi[INDICE_4(i * sY, j * sX, 0, dim)]; //bRgbPtr[INDICE_3(i, j, 1, L)] = vi[INDICE_4(i * sY, j * sX, 1, dim)]; //gRgbPtr[INDICE_3(i, j, 0, L)] = vi[INDICE_4(i * sY, j * sX, 2, dim)]; //r
} break; case 1: //grabba dalla sequenza file for (i = 0; i < H; i++) for (j = 0; j < L; j++) {
RgbPtr[INDICE_3(i, j, 0, L)] = vi[INDICE_3(i * sY, j * sX, 0, dim)]; //rRgbPtr[INDICE_3(i, j, 1, L)] = vi[INDICE_3(i * sY, j * sX, 1, dim)]; //gRgbPtr[INDICE_3(i, j, 2, L)] = vi[INDICE_3(i * sY, j * sX, 2, dim)]; //b
} }}
Figura 4.5
59
contorni e della successiva codifica. Nell’algoritmo sviluppato da G. Maris trattato sempre nel
capitolo 2 si cerca di ridurre il peso di questi errori filtrando preventivamente le immagini da
elaborare. Questo è un comportamento di tipo general pourpose, si adatta abbastanza bene a molte
delle possibili situazioni ambientali che ci si può trovare ad osservare, però, non possedendo
conoscenze specifiche, non è in grado di sfruttare le proprie potenzialità al meglio. Al contrario il
dispositivo APACHE e il flusso4l possiedono alcune informazioni a priori che sarebbe bene sfruttare.
Una delle più importanti è sicuramente quella che ci permette di considerare come “immutabile”,
entro periodi di tempo medio-brevi, lo sfondo della scena osservata. Una volta che il dispositivo sarà
installato la strada che si appresterà a sorvegliare non potrà certamente mutare, almeno nella struttura
di base, da un’immagine all’altra. Considerando che il compito di flusso4l è quello di estrarre gli
oggetti in movimento ho pensato di sfruttare una delle idee di APACHE a mio favore. Sfruttare lo
sfondo come una specie di filtro con il quale pre-elaborare le immagini in modo da eliminare tutti gli
oggetti fissi il cui flusso ottico non deve essere elaborato, che potrebbero causare i problemi di cui
sopra. Questo modulo si occupa della memorizzazione dello sfondo in una variabile dedicata. Come
si può vedere nella figura 4.6, che ne riporta il codice, esso sfrutta la procedura descritta nel
paragrafo 4.2.3.1 più un’altra di cui darò spiegazione poco più avanti. In poche parole questo
modulo memorizza l’immagine di sfondo scalandola e poi ne calcola l’immagine gradiente,
quest’ultima e quella che viene restituita in uscita. È possibile richiamare questa procedura mediante
la pressione del pulsante, presente nell’interfaccia utente, chiamato “Acquisisci Sfondo”. Questa
funzionalità è stata inserita per permettere al sistema di adattarsi ad eventuali cambiamenti che
potrebbero insorgere, quali ad esempio un veicolo che parcheggia all’interno del campo visivo come
pure le variazioni di intensità luminosa nel passaggio tra il giorno e la notte e viceversa. Anche il
procedimento utilizzato per l’impiego dello sfondo memorizzato verrà affrontato più avanti, in una
sede più opportuna.
void Flusso4L::acquisisci_sfondo(){ img_sfondo.grabba_scaled(temp, LARGHEZZA, SCALAX, SCALAY, caricafile); img_sfondo.gradient(TYPE_GRAD);}
Figura 4.6
60
4.2.3.3. elaborazione_immagine_ppmgsQuesto è il modulo che si occupa di effettuare quella serie di elaborazioni sulle immagini, necessarie
per estrarre le features o osservabili indispensabili per il calcolo del flusso ottico tramite il metodo
delle corrispondenze discrete (vedi paragrafo 2.2). Le operazioni che vengono eseguite si basano su
alcune soglie, vedremo tutto ciò più in dettaglio nel paragrafo 4.3, è per questo che, nel codice,
rappresentato in figura 4.7, è presente una variabile booleana, per la precisione soglieON, che viene
impiegata come semaforo. Essa inibisce la modifica dei valori delle soglie quando il programma si
trova all’interno di questo modulo. In questo modo tutta l’immagine viene processata con i valori
delle soglie uguale per ogni pixel. La sequenza di operazioni che vengono effettuate è la seguente:
§ viene estratto il gradiente dell’immagine
§ il gradiente viene confrontato con lo sfondo per evidenziare gli oggetti in movimento
§ l’immagine ottenuta dal punto precedente viene binarizzata
§ l’immagine binarizzata viene codificata per estrarne gli osservabili
Le istruzioni di tipo if presenti all’interno di questo modulo servono unicamente per controllare la
visualizzazione delle uscite prodotte dalla varie procedure. Visualizzazione che avviene, se attivata,
void Flusso4L::elaborazione_immagine_ppmgs(image *img){ soglieON = false; img->gradient(TYPE_GRAD);
if ((multi1ON) && (multi11_sel == 0)) visualizza(img, 1, 0); if ((multi2ON) && (multi22_sel == 0)) visualizza(img, 2, 0); if (outgrad1ON) salva_grad(img, 1);
estrai_auto(img, TYPE_ESTR);
if ((multi1ON) && (multi11_sel == 1)) visualizza(img, 1, 1); if ((multi2ON) && (multi22_sel == 1)) visualizza(img, 2, 1); if (outgrad2ON) salva_grad(img, 2);
img->binary(TYPE_BIN, RANGEBIN, outistoON, DIROUT);
if ((multi1ON) && (multi11_sel == 2)) visualizza(img, 1, 2); if ((multi2ON) && (multi22_sel == 2)) visualizza(img, 2, 2);
img->CodSoft_ppmgs(); soglieON = true;}
Figura 4.7
61
nelle due porzioni dell’interfaccia utente classificate come display multifunzione (vedi Appendice A).
Si può scegliere se osservare il risultato prodotto dal gradiente, dall’estrazione dell’auto o dalla
binarizzazione.
4.2.3.4. reset_ofDel presente modulo c’è poco da dire. La sua funzione è quella di “resettare”, ossia portare a zero i
tre flussi ottici corrispondenti ai tre toni colore. Questa operazione è necessaria per evitare che, il
risultato del ciclo precedente influisca su quello attuale determinando un flusso ottico errato. Le
funzioni che utilizza appartengono alla classe ‘opt_flow’. Definita nel file opt_flow.cpp essa si occupa
della gestione della struttura dati nella quale vengono memorizzati i quattro flussi ottici, i tre per i toni
colore e quello cumulativo, elaborati dal programma durante ogni ciclo di confronto tra coppie di
immagini. Come la classe ‘image’ anch’essa utilizza, come struttura dati, un vettore di char dotati di
segno nel quale vengono registrate le componenti X e Y dei vettori ricavati per ogni pixel
dell’immagine. Lo stesso indirizzamento di questo vettore viene effettuato tramite una macro identica
nel concetto a quella descritta nel paragrafo 4.2.3.1.
4.2.3.5. operatore_ppmgsIl modulo descritto in questo paragrafo è il nucleo centrale del calcolo del flusso ottico. Si basa sul
discorso fatto nel capitolo 2 a proposito dell’estrazione del flusso ottico mediante corrispondenze
discrete. Il ciclo di base deriva in maniera totale dall’algoritmo di G. Maris. Come si può facilmente
vedere in figura 4.8 per ogni punto dell’immagine old la cui codifica risulta non nulla viene effettuata
la ricerca di un punto con codifica analoga nell’immagine new partendo dalle medesime coordinate
estratte dalla old6. La ricerca avviene mediante la sovrapposizione di opportune maschere di
matching all’immagine new. L’operazione viene ripetuta per tutti e tre i toni di colore producendo in
uscita i corrispondenti flussi ottici, chiamati OpFl_r, OpFl_g e OpFl_b. Ultimo punto da evidenziare
la ripetizione del codice all’interno dell’istruzione if … else. L’istruzione di controllo del flusso del
programma è stata introdotta per utilizzare l’opzione che si riconduce alla procedura di taglio di una
regione dell’immagine. Mediante la pressione del pulsante “Sarto ON” è possibile attivarla,
disattivarla o cambiarne i parametri. La ridondanza del codice è necessaria per verificare, nel caso in
void operatore_ppmgs(bool sartoON){ int i, j, h, l, starti, endi;
h = img_old.getH(); l = img_old.getL(); if (sartoON) { if (mini[1] >= limri + 1) starti = mini[1]; else starti = limri + 1; if ((maxi[1] + 1) <= h - limrf - 1) endi = maxi[1] + 1; else endi = h - limrf - 1; for (i = starti; i < endi; i++) for (j = areaop[i - mini[1]][0]; j < (areaop[i - mini[1]][1] + 1); j++) {
if (img_old.getR(i, j) != 0) matching_ppmgs(img_old.getR(i, j), &OpFl_r, i, j, 'r');if (img_old.getG(i, j) != 0) matching_ppmgs(img_old.getG(i, j), &OpFl_g, i, j, 'g');if (img_old.getB(i, j) != 0) matching_ppmgs(img_old.getB(i, j), &OpFl_b, i, j, 'b');
} } else { for (i = limri + 1 ; i < h - limrf - 1; ++i) // l'1 tiene conto della cornice nulla for (j = limci + 1; j < l - limcf - 1; ++j) {
if (img_old.getR(i, j) != 0) matching_ppmgs(img_old.getR(i, j), &OpFl_r, i, j, 'r');if (img_old.getG(i, j) != 0) matching_ppmgs(img_old.getG(i, j), &OpFl_g, i, j, 'g');if (img_old.getB(i, j) != 0) matching_ppmgs(img_old.getB(i, j), &OpFl_b, i, j, 'b');
} }
Figura 4.8 – versione senza parallelismo
62
cui il “sarto” sia attivo, che l’area di ricerca contenga la maschera di matching utilizzata, per una più
accurata descrizione di questa problematica si rimanda al paragrafo 4.4.
4.2.3.6. combinaflussiottici e visualizzaflussootticoGli ultimi due moduli della procedura Flusso servono, il primo, a combinare i tre flussi ottici ricavati
dalle componenti colore in un unico campo vettoriale, il secondo, alla visualizzazione di quest’ultimo
all’interno dell’interfaccia utente per poter avere un riscontro immediato dei calcoli effettuati. In figura
4.9 (a) e (b) riporto il codice per queste due funzioni.
La combinazione può avvenire secondo due modalità differenti di sommatoria vettoriale pesata. La
prima considera il peso delle tre componenti secondo l’equazione che determina, dati i valori dei tre
colori, la funzione di luminanza, quindi il rosso pesato con un fattore 0.299, il verde con 0.587 ed il
blu con 0.114. La seconda utilizza dei pesi uguali, quindi una combinazione a peso uniforme con
valore 0.333. La scelta tra quale delle due eseguire viene effettuata in fase di compilazione
impostando a 0 o a 1, rispettivamente, il parametro COMBFLOW. L’idea alla base della
combinazione vettoriale dei tre flussi ottici risiede nella possibilità di ridurre l’effetto degli errori
avvenuti nel calcolo di questi ultimi imputabili come sempre alla presenza di un rumore di tipo
aleatorio.
63
Per quanto riguarda la procedura di visualizzazione mi preme far notare come i vettori di flusso ottico
vengono rappresentati in una immagine negativa. Ho adottato questa soluzione per favorire la
velocità con la quale disegnare i pixel dei vettori e per non complicare troppo il codice. Situazione
che si era venuta a creare nella prima stesura dove, per evitare di inizializzare, rendendola
completamente bianca, l’immagine da presentare sullo schermo del monitor, ero stato costretto a
tenere conto dell’orientamento dei vettori stessi. In quest’ultima versione i vettori vengono
rappresentati con il colore bianco ed i loro punti di origine in verde, lo sfondo rimane nero. Un
esempio è riportato nella figura 4.10.
void combinaflussiottici(int sel){ int i, j, h, l;
h = OpFl.getH(); l = OpFl.getL(); switch (sel) { case 0: //combinazione peso luminanza for (i = 0; i < h; ++i) for (j = 0; j < l; ++j) OpFl.putXY(i, j,
(char) (0.299 * OpFl_r.getX(i, j) + 0.587 * OpFl_g.getX(i, j) + 0.114 *OpFl_b.getX(i, j)),
(char) (0.299 * OpFl_r.getY(i, j) + 0.587 * OpFl_g.getY(i, j) + 0.114 *OpFl_b.getY(i, j))); break; case 1: //combinazione peso uniforme for (i = 0; i < h; ++i) for (j = 0; j < l; ++j) OpFl.putXY(i, j,
(char) (0.333 * OpFl_r.getX(i, j) + 0.333 * OpFl_g.getX(i, j) + 0.333 *OpFl_b.getX(i, j)),
(char) (0.333 * OpFl_r.getY(i, j) + 0.333 * OpFl_g.getY(i, j) + 0.333 *OpFl_b.getY(i, j))); }}
Figura 4.9 (a)
64
void Flusso4L::visualizzaflussoottico(void){ int max_delta; float x, inc_x, y, inc_y;
QImage *qimm0 = new QImage(); QPainter p0(video_group);
qimm0->create(IMGX/SCALAXI, IMGY/SCALAYI, 32, 0, QImage::IgnoreEndian); for (int i = 0; i < IMGY/SCALAYI; i++) for (int j = 0; j < IMGX/SCALAXI; j++) if ((OpFl.getX(i, j) != 0) || (OpFl.getY(i, j) != 0)) {
//puntoqimm0->setPixel(j + 1, i, VALGREEN);qimm0->setPixel(j + 1, i + 1, VALGREEN);qimm0->setPixel(j, i + 1, VALGREEN);qimm0->setPixel(j - 1, i + 1, VALGREEN);qimm0->setPixel(j - 1, i, VALGREEN);qimm0->setPixel(j - 1, i - 1, VALGREEN);qimm0->setPixel(j, i - 1, VALGREEN);qimm0->setPixel(j + 1, i - 1, VALGREEN);//rettaif (abs(OpFl.getX(i, j)) > abs(OpFl.getY(i, j))) max_delta = abs(OpFl.getX(i, j));else max_delta = abs(OpFl.getY(i, j));inc_x = (float) OpFl.getX(i, j) / max_delta;inc_y = (float) OpFl.getY(i, j) / max_delta;for (int k = 0; k <= max_delta; ++k) { x = j + (k * inc_x); y = i - (k * inc_y); qimm0->setPixel((int) ceil(x), (int) ceil(y), VALWHITE);}
} p0.drawImage(8 + LARGHEZZA/2, 4, *qimm0, 0, 0, -1, -1); qimm0->~QImage();
Figura 4.9 (b)
65
4.3. L’elaborazione delle immaginiNel paragrafo 4.2.3.3 ho descritto a grandi linee il funzionamento della procedura che si occupa
dell’elaborazione delle immagini, con lo scopo di estrarne gli osservabili. In questo paragrafo
desidero riprendere il discorso per evidenziare le scelte effettuate in sede di “compilazione” degli
algoritmi e fornire una spiegazione più approfondita delle procedure che compongono la funzione
elaborazione_immagine_ppmgs.
Cominciamo con il dire che le operazione svolte in questa parte del programma, anche per quanto
riguarda la sequenza di esecuzione, derivano dall’algoritmo di G. Maris da me “rimaneggiato” per
questo scopo, come visto nel capitolo 2. La prima evoluzione che salta agli occhi è quella riguardante
il tipo dei dati da elaborare, non più immagini a toni di grigio ma immagini a colori. Come evidenziato
da P. Golland in [4] e [6], G. Buchsbaum in [5] e N. Otha in [7], la stima del flusso ottico è una fase
di basso livello del problema di ricostruzione del moto. L’uscita prodotta da questa fase è una
proiezione bidimensionale del campo delle velocità nel piano immagine, che viene analizzata
successivamente per ottenere descrizioni ad alto livello del moto. È assoldato che il problema di
determinazione del flusso ottico non può essere risolto completamente partendo da una sequenza di
immagini in Bianco e Nero senza introdurre delle informazioni aggiuntive sulla natura del moto
osservato.
Un’immagine a colori può essere considerata come una combinazione di un numero, normalmente
tre, di immagini indipendenti ottenute simultaneamente dallo stesso punto di vista da sensori visivi
differenti. Conseguentemente, da un punto di vista teorico, fornisce una quantità di informazione
equivalente a quella di un set di immagini in Bianco e Nero acquisite da differenti telecamere.
Figura 4.10
66
Comunque, un’immagine a colori, possiede un vantaggio significativo rispetto ad un’insieme di
immagini in Bianco e Nero: si ha piena corrispondenza tra le componenti colore dell’immagine.
Questo elimina la necessità di risolvere il problema della corrispondenza, che è conosciuto come
molto complesso da risolvere in generale. Quindi, le immagini a colori, sono candidate naturali per
l’ottimizzazione della soluzione al problema della stima del flusso ottico. Partendo da questo
osservazioni ho cercato di sviluppare un algoritmo basato sul lavoro svolto da P. Golland [4].
Purtroppo, già dalla prima stesura, ho compreso che i calcoli necessari per il conseguimento del
flusso ottico erano, da un punto di vista temporale, troppo onerosi, pregiudicando così un possibile
impiego in applicazioni real-time. Richiedevano l’utilizzo di strutture dati in virgola mobile. Sono
quindi tornato sui miei passi e, riprendendo l’algoritmo di Maris ho cercato il modo di integrare in
esso la capacità di gestire le immagini a colori.
4.3.1. gradient
La scelta più semplice e coerente mi è sembrata quella di considerare le tre componenti colore
dell’immagine come tre immagini a toni di grigio distinte. Quindi ho “moltiplicato” per tre la fase di
elaborazione dell’immagine per l’estrazione degli osservabili, rimandando ad una fase successiva a
quella di ricerca delle corrispondenze (vedi paragrafo 4.2.3.6), e la combinazione dei tre flussi ottici
ricavati.
Le prime due fasi dell’algoritmo visto nel capitolo 2 consistono nell’applicazione di un filtro
quantizzatore, con lo scopo di ridurre il rumore, e nella successiva estrazione e binarizzazione dei
contorni mediante l’operatore di Roberts. In una prima fase di sperimentazione sulle immagini a
colori ho osservato che queste due operazioni tendevano a fornire una quantità di dati eccessiva,
ossia si otteneva in uscita un’immagine a colori contenente dei contorni “rumorosi”. Con contorni
rumorosi intendo evidenziare la presenza di sagome, appartenenti ad osservabili di tipo fisso (lo
sfondo) e non, che risultavano instabili, modificandosi da un’immagine a quella consecutiva, anche di
un numero rilevante di pixel. Ho cercato di eliminare questo problema introducendo la conoscenza a
priori dello sfondo dell’area osservata sottraendolo all’immagine. Purtroppo, sempre a causa
dell’instabilità di prima, dovuta essenzialmente alla binarizzazione ricavata con l’estrattore di Roberts,
i risultati ottenuti non mi hanno soddisfatto. Le immagini risultanti contenevano, ancora, troppe
features non “interessanti” la cui presenza andava ad intaccare il calcolo del flusso ottico, dando
67
luogo a campi vettoriali “sporcati” dalla presenza di vettori relativi a spostamenti di osservabili
appartenenti allo sfondo. Ho quindi cercato una strada alternativa per l’estrazione dei contorni e la
loro binarizzazione.
Andando a rileggere la documentazione di APACHE mi sono accorto che in esso era presente
un’operazione di estrazione parziale dei contorni mediante il gradiente orizzontale. Questa procedura
sfrutta il cosiddetto operatore della differenza del massimo. Avendo la necessità di ottenere i
contorni sia orizzontali che verticali ho considerato la versione completa. Il funzionamento di
quest’operatore è il seguente: dato il pixel di coordinate (i, j) si considerano anche gli otto pixel
adiacenti; si analizzano i nove pixel per trovare quello con il valore massimo; l’entità del gradiente del
pixel preso in esame è uguale alla differenza tra il valore del pixel dato e quello del pixel ottenuto
dalla ricerca del massimo, il risultato ottenuto va poi memorizzato nell’immagine nella locazione del
pixel considerato, in figura 4.11 è riportata la maschera utilizzata. Dai primi test effettuati ho visto
che, oltre a fornire un’immagine dei contorni di buona qualità, il nuovo metodo di calcolo risultava più
veloce. Per ridurre ulteriormente i tempi di elaborazione ho pensato di effettuare immediatamente la
modifica del contenuto del pixel valutato, senza attendere che l’operazione di estrazione del gradiente
sia stata applicata a tutta l’immagine. In questo modo si ha sicuramente una perdita di informazione
che però, in fase di sperimentazione, è risultata accettabile, come si può vedere dalle due immagini
riportate in figura 4.12, la prima rappresenta il gradiente ottenuto dall’elaborazione “canonica”
(modifica alla fine delle operazioni), la seconda quello ottenuto dalla modifica immediata, viene
riportata anche l’immagine dalla quale sono state ricavate. La funzione che si occupa di questa
elaborazione, chiamata gradient, è una delle funzioni membro della classe ‘image’, a causa della
Figura 4.11
Figura 4.12
68
lunghezza il codice relativo non viene riportato. Ho comunque lasciato la possibilità di scegliere, in
fase di compilazione, modificando il parametro TYPE_GRAD definito in flusso_d.h, quale dei due
metodi utilizzare. Come ultima considerazione sottolineo il fatto che, data la forma della maschera da
applicare all’immagine, come riportata in figura 4.11, alla fine delle operazioni, una cornice di
spessore un pixel, costituita per la dalla prima e dall’ultima colonna e dalla prima e dall’ultima riga,
non essendo stata elaborata contiene informazioni non coerenti, per questo motivo il suo contenuto
viene posto a zero.
4.3.2. estrai_auto
In uscita dalla procedura di calcolo del gradiente abbiamo un’immagine nella quale sono stati
evidenziati i contorni degli oggetti presenti nel campo visivo della telecamera, che siano in movimento
oppure no. Bisogna quindi riuscire a discriminare tra gli osservabili appartenenti allo sfondo, e quindi
statici, e quelli in movimento. Per fare ciò ho pensato di introdurre nuovamente la conoscenza a priori
dello sfondo, confortato dal fatto che questo tipo di operazione è già presente nel sistema APACHE.
La funzione che se ne occupa è stata chiamata estrai_auto. Il funzionamento, in linea di massima è il
seguente. Nell’istante in cui viene avviato il sistema di calcolo del flusso ottico, il programma
memorizza la prima immagine acquisita, dopo l’estrazione del gradiente, considerandola come
sfondo. Alternativamente si può premere il pulsante di acquisizione dello sfondo nell’interfaccia
utente che funziona nello stesso modo, immagazzinando l’immagine “gradientizzata” appena acquisita
come sfondo. Come riportato in figura 4.7, la seconda operazione eseguita è l’estrazione dell’auto.
Sfruttando lo sfondo registrato lo si sottrae all’immagine che si sta elaborando, il modulo del valore
risultante dalla sottrazione viene confrontato con una soglia e se questo risulta minore il pixel
corrispondente viene azzerato. In poche parole si effettua un confronto fra pixel corrispondenti come
posizione delle due immagini, se la differenza è al di sotto di un certo valore si assume che questi
siano gli stessi e che quindi appartengano allo sfondo. Il valore della soglia, dopo ripetute prove, è
stato fissato a 25, comunque è stata introdotta la possibilità di modificarlo, interagendo con
l’interfaccia utente, per poterlo adattare alle varie situazioni ambientali in cui il sistema si potrà trovare
ad operare. Questo valore è risultato ottimale per condizioni classificabili come standard, ossia
funzionamento diurno in assenza di fenomeni atmosferici quali neve e nebbia, è stato verificato anche
in condizioni di pioggia e si è dimostrato funzionale. Un possibile sviluppo futuro potrebbe prevedere
69
l’eventualità di rendere automatico l’adattamento della soglia alle condizioni ambientali tramite il
feedback da un sensore di luminosità, sensore che potrebbe essere anche di tipo software. In figura
4.13 sono riportati alcuni risultati ottenuti per tre valori di soglia, con l’immagine di partenza
corrispondente. Anche per questa parte dell’algoritmo ho cercato soluzioni alternative per ridurre il
più possibile i tempi di elaborazione. Andando ad osservare il codice, non riportato sempre a causa
delle dimensioni, si può notare come esistano tre varianti della procedura. Nelle prime due il
confronto fra l’immagine di sfondo e quella appena acquisita viene effettuato in un intorno del pixel
dello sfondo, per la precisione gli otto pixel adiacenti. L’unica differenza fra questi due metodi risiede
nel tentativo di ottenere tempi di elaborazione differenti agendo sul metodo di indirizzamento di
questo intorno. Il terzo non considera più l’intorno ma il singolo pixel, in questo modo si ha
sicuramente una diminuzione dei tempi pagandola in termini di perdita di informazione. Anche in
questo caso la modifica dell’immagine può avvenire o alla fine delle operazioni sull’intera immagine
oppure subito dopo aver ottenuto il risultato per il singolo pixel. La scelta che ha sembrato
Soglia = 4 Soglia = 21
Soglia = 38
Figura 4.13
70
determinare, durante i test finali, il compromesso migliore fra tempi di esecuzione e perdita di
informazione è risultata quella di modificare l’immagine immediatamente utilizzando il secondo
metodo. Comunque si ha sempre la possibilità di agire in fase di compilazione scegliendo uno dei tre
metodi a disposizione con la modifica immediata o alla fine delle operazioni.
Le differenze grafiche tra un metodo e l’altro sono simili a quelle riportate nella figura 4.12 per
quanto riguarda l’operazione di estrazione del gradiente.
4.3.3. binary
A questo punto non rimane che binarizzare l’immagine in modo da poterla codificare
successivamente ed ottenere gli osservabili sui quali effettuare la ricerca delle corrispondenze. La
risoluzione di questa fase non è stata banale.
Considerando quanto visto finora avrei potuto utilizzare il metodo sviluppato in APACHE anche per
la procedura di binarizzazione. Purtroppo, le prime prove hanno dato un esito negativo. Andando ad
analizzare più in dettaglio questo procedimento si capisce immediatamente il perché. La funzione
presentata in APACHE utilizza un’unica soglia per decidere se un pixel deve essere acceso oppure
no, soglia che viene impostata sfruttando l’andamento di tipo bimodale dell’istogramma della
distribuzione dei toni di grigio dell’immagine. Viene scelto il minimo locale presente tra i due massimi.
Osservando i tre istogrammi prodotti dall’estrazione del gradiente si vede subito che questi non sono
di tipo bimodale, addirittura non ci si avvicinano nemmeno un poco. La funzione che meglio li
approssima è il ramo del primo quadrante dell’iperbole equilatera con coefficiente positivo. Come
conseguenza immediata la scelta della soglia risulta estremamente complessa.
71
Ho riconsiderato il sistema APACHE è ho notato un particolare che mi era sfuggito in precedenza,
l’estrazione dello sfondo veniva eseguita dopo la binarizzazione.
Ho quindi
pensato di
verificare
l’andamen
to degli
istogramm
i dopo la
sottrazion
e dello
sfondo
“gradientizzato”7. Come si vede nelle figure 4.14 (a) e (b) gli istogrammi assumono una forma
monomodale più o meno accentuata. Ciò mi ha portato ha pensare che la maggior parte dei pixel
Figura 4.14 (a)
Figura 4.14 (b)
72
rimanenti appartengano agli oggetti in movimento. Considerata la struttura di questi diagrammi ho
ideato un sistema di binarizzazione che potremmo definire a filtro passa banda. Non potendo
binarizzare tutti i pixel rimanenti, è sempre presente del rumore, ho pensato di concentrare la mia
attenzione su quei toni di colore che presentano il maggior numero di occorrenze. Ho quindi
predisposto due metodi di binarizzazione che definiscono una finestra da applicare agli istogrammi
suddetti per individuare quali pixel devono essere “accesi” e quali “spenti”.
Uno si comporta in tutto e per tutto come un filtro passa banda e per questo motivo l’ho definito di
tipo orizzontale. Ricerca il tono che presenta il maggior numero di occorrenze per poi porlo al centro
della finestra di passaggio. La larghezza di questa finestra viene impostata agendo sulla parte
dell’interfaccia utente che include le due barre a scorrimento per il gradiente e per la binarizzazione.
Dopo ripetute prove ho fissato il valore iniziale a 30, ed ho preferito dare una forma sbilanciata verso
destra alla finestra, con rapporti di ¼ e ¾, rispetto al tono che presenta il maggior numero di
occorrenze.
Il secondo metodo, definito di tipo verticale, agisce in una maniera leggermente diversa. Una volta
identificato il tono con il maggior numero di occorrenze vengono accesi quei pixel che appartengono
a quei toni il cui numero di occorrenze è entro una certa distanza dal valore del tono massimo.
Mentre prima si determinavano due soglie laterali che indicavano dei toni ben precisi, adesso viene
fissata un’unica soglia che indica non il tono ma un numero di occorrenze. Quindi un dato pixel viene
acceso se appartiene ad un tono il cui numero di occorrenze supera o al limite uguaglia la soglia.
A prima vista questi due metodi non sembrano essere molto differenti, in realtà la binarizzazione di
tipo verticale presenta una particolarità che, osservata in sede di studio teorico si è confermata a
pieno in fase di test. Essa permette a parità di soglia scelta, soprattutto nei casi di valori bassi, di
recuperare delle informazioni perse quando vengono utilizzate le immagini a colori. L’utilizzo di una
soglia minima sul numero di occorrenze fa si che anche i pixel appartenenti al tono zero vengano
accesi dato che questo tono contiene almeno il 50% dei pixel presenti nell’immagine. In questo modo
si ha un effetto di sagomatura dei contorni delle zone andate perse in negativo, si evidenziano i
contorni ma non i punti interni a queste regioni.
73
Per questi motivi ho preferito utilizzare questo metodo nel proseguo della tesi, fissando la soglia ad
un valore di 20, anch’esso sperimentalmente. In figura 4.15 riporto alcuni esempi di binarizzazione di
tipo verticale.
4.3.4. CodSoft_ppmgs
Giunti a questo punto non rimane che codificare le strutture presenti nell’immagine estraendo così gli
osservabili da ricercare nell’evolversi della scena. Per questa operazione mi sono ricondotto al
lavoro fatto sull’algoritmo di Maris. Come descritto nel capitolo 2 le strutture che si vanno a definire
rappresentano degli osservabili di dimensioni minime, infatti ogni singolo pixel viene etichettato in
funzione della disposizione, nel dominio acceso/spento, degli otto pixel adiacenti. Come conseguenza
l’informazione trasportata da queste features risulta minima, in compenso il tempo necessario alla
loro definizione è ridotto a minimi termini.
Soglia = 4 Soglia = 24
Soglia = 44
Figura 4.15
74
Avendo deciso di seguire il metodo Maris il grosso del lavoro è coinciso con il trovare delle soluzioni
per ridurre all’osso i tempi di elaborazione8. Come primo passo ho sfruttato il fatto che i dati trattati
sono di tipo on/off (i pixel possono essere accesi o spenti) ed ho quindi parallelizzato la ricerca
dell’etichetta corrispondente. Definendo l’intorno del pixel sotto indagine, corrispondente alla
maschera 3x3 di cui sopra, come una sequenza di bit posti a 1 o a 0 a seconda che il pixel sia
acceso o spento, è possibile sfruttare l’operazione “ex-or” bit a bit per velocizzare il confronto con le
etichette prestabilite. In questo modo, una volta definita la stringa di bit, il confronto avviene in un
unico ciclo di clock, si sfrutta così quel poco di capacità di calcolo parallelo presente anche nei
processori di tipo sequenziale. Considerando che i registri dei processori di ultima generazione sono
a 32 bit e che la codifica della maschera ne richiede 9, ho pensato di vedere se l’etichettatura
contemporanea di due pixel poteva presentare dei vantaggi, e questo è stato il secondo passo
intrapreso nell’ottimizzazione della procedura di codifica. In figura 4.16 sono riportati due stringhe di
esempio con la relativa maschera di partenza. In conclusione il passaggio dall’assenza di parallelismo
ad un parallelismo su di una sola etichetta ha migliorato i tempi di elaborazione riducendoli quasi a
metà (~ 46%). Il passaggio al parallelismo su due etichette, al contrario, non ha portato dei
miglioramenti significativi (~ 1 – 2%) rispetto alla situazione con l’etichetta singola. L’impossibilità di
scendere ulteriormente nei tempi considerando due o tre (massimo possibile con 32 bit) etichette
contemporaneamente risiede nei tempi aggiuntivi richiesti per la preparazione della stringa contenete
le tre maschere. Inoltre la ricerca dell’etichetta giusta da attribuire al pixel viene notevolmente
complicata, e questo sforzo computazionale aggiuntivo introduce, senz’altro, dell’ulteriore ritardo.
75
All’interno del codice elaborato, disponibile nella versione elettronica della presente tesi, si può
notare la presenza dei due metodi sviluppati con in aggiunta quello da cui sono partito. La scelta di
quale utilizzare viene effettuata in fase di compilazione impostando ad 1, 2 o 3 il parametro ALGOG
presente nel file flusso_d.h.
4.4. L’estrazione del flusso otticoUna volta effettuata la codifica di una coppia di immagini consecutive binarizzate si può passare alla
fase di estrazione del flusso ottico. Il metodo scelto è quello della ricerca delle corrispondenze
discrete fra i “micro-osservabili” estratti evidenziati nel passaggio precedente. Questa ricerca sfrutta
completamente le idee sviluppate nell’algoritmo di Maris, che, come descritto nel capitolo 2, significa
Figura 4.16
Figura 4.17
76
applicare a queste immagini una maschera di estrazione specifica. Nella figura 4.17 viene presentato
il diagramma di flusso della procedura operatore_ppmgs. In esso è presente la possibilità di
scegliere tra due funzioni di matching, una di tipo normale, l’altra di tipo parallelizzata, ne darò una
descrizione più approfondita qualche riga più avanti. La funzione può essere descritta nel seguente
modo: date le due immagini elaborate, si ricercano nella prima, discriminandole secondo gli istanti
temporali nei quali sono state acquisite, i pixel che presentano una codifica non nulla, una volta
trovato uno di questi pixel si applica alla seconda immagine, alle tre componenti colore
separatamente, la procedura matching_ppmgs (matching_ppmgs_par nella versione con il
massimo parallelismo possibile) nelle coordinate corrispondenti al pixel trovato per estrarre, se
possibile, il flusso ottico corrispondente.
La procedura di matching è quindi il cuore della fase di estrazione del flusso ottico. In figura 4.18 è
rappresentato il diagramma di flusso corrispondente sia per la versione normale (a) che per quella
parallelizzata (b).
La maschera di matching che viene sovrapposta alla seconda immagine serve per definire un
percorso di ricerca, in essa sono memorizzati gli offset rispetto al punto di applicazione (le
coordinate del pixel etichettato rilevato nella prima immagine) secondo i quali ci si deve muovere per
ritrovare l’etichetta di partenza. La forma di questa maschera risulta quindi fondamentale, sia per il
tipo di flusso ottico che si vuole rilevare, che per il tempo necessario alla ricerca. Considerando la
situazione ambientale nella quale la telecamera si trova ad operare ho sviluppato una serie di
maschere che potessero adattarvisi il più possibile. In figura 4.19 sono rappresentate le principali con
una immagine tipo prelevata dalla telecamera. Come si può facilmente intuire la sede stradale
osservata ci permette di definire una conoscenza a priori che influenza la forma di queste maschere,
una volta stabilita la direzione in cui le macchine possono muoversi non ha senso utilizzare una
maschera che rileva il flusso ottico prediligendo una direzione ortogonale a quella evidenziata. Per
questo motivo ho preferito concentrarmi su strutture rettangolari di tipo monolaterale, più adatte a
rilevare il flusso ottico di oggetti in movimento lungo una precisa direzione. Questi “oggetti” sono
utilizzati nel seguente modo: la maschera viene suddivisa in una serie di colonne (evidenziate in figura
4.20), si ricerca il match all’interno della prima colonna muovendosi dal centro verso l’esterno, se
viene trovato un match i valori degli offset, corrispondenti alla casella della maschera, sono
memorizzati come coordinate del flusso ottico e si esce dalla procedura di matching per quel pixel
particolare, altrimenti si prosegue considerando la colonna successiva e così via fino ad esaurimentoFigura 4.18 (a)
77
delle caselle dalla maschera. Cercando di fornire una valenza di generalità all’algoritmo ho costruito
le otto famiglie, nelle quali possono essere raggruppate le maschere (la stella di figura 4.19),
caratterizzandole mediante l'angolo con il quale sono orientate rispetto all’asse X del piano immagine.
All’interno di ognuna di queste famiglie le maschere possono assumere dimensioni differenti sia come
lato minore che come lato maggiore. Per semplificare la costruzione di questi oggetti ho sviluppato un
programma
molto
semplice
(chiamato
XxYYZZZ),
per la sua
descrizione
rimando
sempre alla
versione
elettronica.
In un primo momento era stata inclusa la possibilità di sfruttare completamente la dimensione della
maschera per ricercare le corrispondenze discrete. Purtroppo il codice necessario per il controllo
dell’uscita di parte dalla maschera dall’immagine si è rivelato complesso e dispendioso in termini di
Figura 4.18 (b)
Figura 4.19
78
tempo. Per questo motivo ho deciso di abbandonare la prima stesura delle maschere che
includevano un blocco iniziale percorso a spirale per tenere in considerazione l’aleatorietà del
rumore. Inoltre la struttura di questo blocco crea dei problemi di incompatibilità di codice nel
passaggio alle maschere di tipo bilaterali9. In figura 4.21 è riportato una maschera d’esempio. Ho
comunque lasciato la possibilità di utilizzare questo tipo di maschere nel programma, è necessario
impostare a 1 il parametro ALGOP presente nel file flusso_d.h10.
Le etichette utilizzate nell’operazione di codifica, come specificato nella tesi di Maris, sono 16,
questo vuol dire che per codificarle bastano 4 bit. Considerando nuovamente che i processori
impiegati nella maggior parte delle macchine disponibili sul mercato utilizzano parole di 32 bit,
sarebbe ipotizzabile effettuare la verifica di un possibile match su 8 pixel in parallelo. Ho quindi
analizzato i gradi di parallelismo implementabili nel codice. Prima di procedere rendiamo noto che i
test effettuati hanno permesso di fissare a 5 o a 7 le dimensioni ottimali del lato corto delle maschere.
Considerando il modo di procedere per colonne nella ricerca dei match si evidenzia immediatamente
l’impossibilità di sfruttare a pieno la quota di parallelismo disponibile. Infatti, procedendo secondo
quanto descritto poche righe sopra, la situazione ideale, quella di effettuare la ricerca all’interno di
una colonna alla volta, ci impone una soglia massima di pixel da verificare nel loro contenuto in
codifica, per l’appunto 5 o 7. In aggiunta a ciò le maschere diagonali presentano un ulteriore
problema, come si può vedere nella figura 4.22. Nella rotazione che da operatore orizzontale o
verticale determina la struttura diagonale, le colonne subiscono una “deformazione”, in linea di
massima ogni colonna contiene un numero maggiore di elementi.
Figura 4.20
79
Più precisamente questo valore risulta uguale a (2 · X) – 1 dove X rappresenta la dimensione del lato
corto della maschera. Dividendo la colonna in due parti, la prima contenete X elementi e la seconda
X – 1, è quindi possibile sfruttare lo stesso codice sviluppato per le maschere non-diagonali. A
questo punto non mi rimane che fornire una breve descrizione del funzionamento della procedura
matching_ppmgs_par per terminare questo paragrafo. Come si vede dal diagramma di flusso,
figura 4.18, la prima operazione consiste nel definire la stringa contenente l’etichetta da ricercare,
identificata nell’immagine old. La stringa viene costruita ripetendo 7 volte l’etichetta suddetta
traslandola, in ogni ripetizione, di 4 bit. Questa soluzione permette di risparmiare in complessità non
dovendo discriminare il tipo di maschera a seconda delle dimensioni del lato corto. Il passo
successivo è rappresentato da un ciclo che scorre l’intera maschera conteggiando il numero di
colonne che la compongono. Il suddetto ciclo viene suddiviso in due parti, la prima valida per tutte le
maschere, la seconda serve unicamente per quelle diagonali dato che considera il secondo blocco
della colonna. In tutte e due le parti la colonna in esame viene codificata in una stringa simile a quella
ottenuta per l’etichetta da ricercare, prelevando le etichette dei pixel corrispondenti nell’immagine
chiamata new. Questa operazione viene effettuata dalla funzione codifica, è importante sottolineare
come la stringa venga definita in modo tale da rispettare la sequenza di scansione, definita in
precedenza, delle celle della colonna. Se la codifica ottenuta è diversa da zero questa viene
confrontata bit a bit, sempre utilizzando l’operazione ex-or, con la stringa di partenza. Il risultato di
Figura 4.21
Figura 4.22
80
questo confronto viene elaborato dalla funzione estrai_match che verifica la presenza o meno di una
corrispondenza, restituendo, in caso di match positivo, le coordinate in cui si è verificato all’interno
della maschera permettendo così alla funzione chiamante di calcolare le componenti del vettore del
flusso ottico. Nel caso in cui la maschera sia di tipo diagonale e non sia stato identificato alcun match
da questa prima operazione, l’elaborazione viene ripetuta per la seconda parte della colonna. Questi
calcoli vengono ripetuti fino a quando non si riscontra una corrispondenza positiva o si esauriscono le
colonne della maschera. A questo punto vengono memorizzate le componenti del vettore del flusso
ottico, nulle nel caso non sia stato identificato alcun match.
4.5. Prove effettuate e risultati ottenuti
Avendo osservato il corretto funzionamento degli algoritmi sviluppati11. Il sistema completo è stato
sottoposto ad una serie di test con l’intento di misurarne i tempi di elaborazione e di verificarne i
limiti. A questo scopo sono state registrate su supporto magnetico (videocassetta S-VHS) un certo
numero di sequenze dell’area sorvegliata dalla telecamera installata presso la palazzina 1 del
dipartimento di Ingegneria. Si è cercato di riprendere il maggior numero di automezzi per poter
disporre di una vasta campionatura di forme e colori. Inoltre sono state registrate delle sequenze
“pilotate”, per poter conoscere a priori le velocità con le quali i veicoli si presentavano nel campo
visivo della telecamera, in modo da poter stimare i limiti di elaborazione conseguenti. Infine è stato
svolto un lavoro di ricerca sulla qualità del flusso ottico estratto a seconda del livello di zoom
utilizzato.
Nelle figure 4.23 e 4.24 sono riportati i flussi ottici ricavati con un’immagine appartenente alle
sequenze utilizzate. Le maschere con lato corto uguale a 7 si sono dimostrate più efficaci dal punto di
vista dei tempi di esecuzione. Situazione prevedibile a livello teorico a causa dello sfruttamento
migliore del parallelismo disponibile. Parlando sempre di tempi di elaborazione il sistema si è
assestato su valori dell’ordine di 3 – 4 elaborazioni di coppie di immagini al secondo. Ciò ha
permesso di definire, in condizioni di zoom “massimo”12, la velocità di soglia intorno a 20 – 25 km/h,
sorpassato questo valore il flusso rilevato è talmente scarno che non si può pensare di utilizzarlo per
attivare l’acquisizione del fermo immagine.
81
Per quanto riguarda la stabilità del sistema, in funzione del colore delle vetture, considerando sempre
le condizioni di zoom “massimo” (situazione critica in generale), si può affermare che qualche
problema viene sollevato dai colori chiari, quali il bianco ed il grigio metallizzato. Il flusso ottico
rilevato in questi casi si presenta poco denso rischiando di compromettere il suo impiego in una
possibile funzione di catalogazione degli oggetti in movimento13 (macchine, pedoni, motocicli, ecc.).
La forma degli oggetti in movimento non sembra influenzare in alcun modo la qualità del flusso ottico.
Passando ad esaminare i risultati ottenuti con lo zoom impostato ad un livello minimo non ho
osservato limitazioni degna di nota. Gli oggetti in movimento vengono individuati e risolti senza
evidenziare una velocità limite, bisogna tenere presente che nel tratto di strada osservato risulta
difficile se non impossibile superare i 40 km/h, o problemi legati a forme e colori. Inoltre, dato che il
campo visivo è molto più ampio, gli osservabili tendono ad occuparlo per un tempo maggiore
fornendo in questo modo un numero di immagini sulle quali lavorare superiore al caso precedente.
Situazione ideale per applicazioni di sorveglianza o simili.
82
0 50 100 150 200 2500
20
40
60
80
100
120
140
160
180
Flusso Ottico
0 50 100 150 200 2500
20
40
60
80
100
120
140
160
180
Flusso Ottico
0 50 100 150 200 2500
20
40
60
80
100
120
140
160
180
Flusso Ottico
Figura 4.23
83
0 50 100 150 200 2500
20
40
60
80
100
120
140
160
180
Flusso Ottico
0 50 100 150 200 2500
20
40
60
80
100
120
140
160
180
Flusso Ottico
Figura 4.24
84
Le stesse sequenze sono state utilizzate per determinare le dimensioni ottimali delle maschere per
quanto riguarda la loro lunghezza. Il compromesso migliore tra velocità di esecuzione e “qualità” del
flusso ottico ricavato è stato ottenuto considerando un numero di colonne tra le 20 e le 25, sia per le
maschere orizzontali e verticali, che per quelle diagonali.
Infine vorrei concludere questo paragrafo riportando alcuni risultati di test effettuati sfruttando delle
maschere di tipo bilaterale14. In figura 4.25 si può osservare come il flusso ottico ricavato non sia
perfettamente attendibile, presentando vettori sia in un verso che nell’altro. Effettuando un conteggio
dei vettori rilevati è però possibile identificare il verso “reale” lungo il quale si sta muovendo l’oggetto
analizzato. Ciò fa ben sperare per una migliore implementazione nel futuro.
4.6. Conclusioni
In conclusione il dispositivo sviluppato non ha raggiunto tutti gli obbiettivi che ci eravamo posti
all’inizio del lavoro. In questo paragrafo cercherò di comprendere quali possono essere i motivi di
questo “fallimento” e le possibili strade da intraprendere per raggiungere i traguardi mancati, non
dimenticando di evidenziare quelli che sono stati raggiunti.
I tempi di elaborazione troppo ampi sono sicuramente la causa principale dei “problemi” riscontrati.
Considerati gli sforzi fatti per raggiungere un buon grado di parallelismo, l’unica possibile soluzione è
quella di passare ad una macchina più potente che disponga, eventualmente, di hardware dedicato.
0 50 100 150 200 2500
20
40
60
80
100
120
140
160
180
Flusso Ottico
Figura 4.25
85
Il passaggio da un Pentium 600 MHz a uno a 1000 MHz permette di ridurre del 50% i tempi di
elaborazione delle operazioni sugli interi. La stessa scheda di acquisizione video può essere
migliorata, passando da una PCI ad una utilizzante il bus AGP il miglioramento è notevole. Si
potrebbe pensare di utilizzare un sistema bi-processore in modo da ottenere un grado superiore di
parallelismo e quindi effettuare le elaborazioni sulle immagini in tempi nettamente inferiori. In
definitiva, il passaggio ad un sistema con più potenza di calcolo, non porterebbe che miglioramenti
significativi al sistema permettendo di aumentare la velocità di esecuzione15.
L’impiego di una macchina dedicata dal punto di vista del sistema operativo porterebbe degli ulteriori
vantaggi in termini di tempi di esecuzione. Le prove sono state effettuate su di un calcolatore che
presentava dei processi attivi in grado di occupare il 5 – 10% delle risorse di calcolo del processore.
La qualità del flusso ottico rilevato è influenzata sensibilmente dalla maschera di matching utilizzata.
Quelle costruite approssimano solamente le situazioni ambientali che il sistema si può trovare ad
affrontare, non sono in grado di adattarsi per fornire il risultato migliore. Utilizzando una telecamera
dotata di feedback sulla sua posizione, azimut e tilt, è possibile, utilizzando delle formule di
deformazione prospettica, adattare la struttura della maschera all’ambiente osservato migliorando di
conseguenza il flusso ottico ottenuto.
Le micro-strutture utilizzate come osservabili nell’operazione di codifica dell’immagine sono un altro
parametro in grado di influenzare le proprietà del flusso ottico rilevato. È importante far notare che
queste sono le medesime utilizzate nell’algoritmo di Maris e che sono state rilevate, mediante un
lungo lavoro di ricerca statistica, in ambiente indoor. Per scene outdoor di tipo stradale potrebbero
essere differenti. Inoltre una dimensione maggiore del 3x3 utilizzato potrebbe essere migliore. Non
ho potuto compiere questo genere di valutazioni a causa del troppo tempo che richiedono, però
sarebbe utile che qualcuno affrontasse queste problematiche.
Concludendo posso dire che il sistema è ben lungi dall’essere finito, però penso di aver aperto una
strada percorribile della quale non si sapeva nulla, soprattutto se fosse transitabile e in quale
direzione si potesse sviluppare. Auguro quindi un sentito in bocca al lupo a chi proseguirà questo
lavoro.
86
Appendice A
Manuale utente
La trattazione di come si debba utilizzare Flusso for Linux è stata lasciata in un'appendice in quanto
non rappresenta ancora un argomento fondamentale. Il sistema è in fase di sviluppo e sarebbe
prematuro indicare ad un ipotetico
utente l'uso di funzionalità non ancora stabilizzate. Di contro è però necessario indicare, anche in
modo sommario, l'uso dei “tasti” e delle indicazioni
mostrati sullo schermo.
Una precisazione: questa guida all'uso non può prescindere dal resto della
presente trattazione. Non vengono qui spiegati di nuovo gli scopi di tutte le altre caratteristiche già
ampiamente descritte nel resto del testo.
A.1 PulsantieraDunque all'avvio del programma si trova sullo schermo una finestra suddivisa in più zone (figura A.1).
All’estrema destra si ha la pulsantiera dei comandi (dettaglio, figura A.2). Questa è stata suddivisa in
tre zone distinte facilmente identificabili. Muovendosi dall’alto verso il basso abbiamo le “Video
Options” mediante le quali è possibile regolare i parametri di visualizzazione delle immagini acquisite
quali: la luminosità, il contrasto, ecc. Il secondo gruppo, chiamato “Optical Flow Options”, consente
le regolazioni più importanti del sistema. Il primo bottone, “Flusso ON”, abilita o disabilita il calcolo
del flusso ottico quando le immagini vengono ottenute mediante la scheda di acquisizione video. Il
pulsante successivo permette di effettuare le elaborazioni prelevando una sequenza di immagini
statiche memorizzate su di un supporto fisico, quale ad esempio un hard disk. Il terzo pulsante
permette di cambiare l’operatore di matching (maschera) andandolo a prelevare nella directory
opportuna e visualizzandone il nome poche righe più sotto, vedi figura A.3. Il quarto pulsante serve
per acquisire una nuova immagine e memorizzarla come sfondo, viene abilitato una volta che è stato
attivato il processo di calcolo del flusso ottico.
87
La zona successiva contiene i pulsanti per scegliere quale tipo di binarizzazione utilizzare (figura A.4),
e le due barre a scorrimento per impostare le soglie dell’operazione di binarizzazione e di estrazione
del gradiente.
Immediatamente dopo si può individuare un’area chiamata “Output”, in essa sono presenti i pulsanti
per attivare il salvataggio dei prodotti intermedi dell’elaborazione (N.B. la funzione di salvataggio del
risultato della binarizzazione non è stata ancora implementata).
Figura A.1
88
Infine, sempre nell’area centrale abbiamo un pulsante chiamato “Sarto ON”, esso permette di
attivare e disattivare la funzione di spira virtuale caricando la struttura opportuna, in questo momento
ne sono disponibili due, e di cambiare la struttura utilizzata.
Nella terza zona sono presenti due pulsanti, il primo, etichettato “PLAY”, attiva o disattiva
l’acquisizione dalla scheda Matrox, l’altro permette di uscire dal programma.
A.2 Le finestreLa parte rimanente della finestra principale è adibita alla visualizzazione dei risultati ottenuti. In alto a
sinistra vengono riprodotte le immagini acquisite dalla scheda o prelevate dall’hard disk. Nel caso in
cui la spira virtuale sia attiva essa viene disegnata sovrapponendola all’immagine in elaborazione, un
esempio è riportato in figura A.4.
Figura A.2
Figura A.3
89
In alto a destra viene disegnato il flusso ottico ricavato secondo le modalità descritte
precedentemente nel capitolo 4. La rappresentazione del flusso ottico avviene automaticamente nel
caso in cui si stia operando in modalità “off line” (immagini statiche da hard disk).
La metà inferiore dello schermo è stata suddivisa in due display multifunzione, vedi dettaglio in figura
A.5, dove, tramite le corrispondenti pulsantiere, è possibile visualizzare i risultati delle fasi intermedie
dell’estrazione del flusso ottico. In particolare le immagini risultanti dalla binarizzazione,
dall’estrazione del gradiente e dall’estrazione dell’auto. Queste ultime funzionalità sono comode in
fase di analisi del flusso ottico “off line”, cioè quando si utilizzano sequenze di immagini statiche
memorizzate su hard disk, poiché appesantiscono notevolmente l’elaborazione.
Figura A.4
Figura A.5
90
Appendice B
Listati
Nella seguente appendice riporto i file header ai quali il sistema sviluppato fa riferimento. Non riporto
i sorgenti a causa dell’enorme quantità di righe che li compongono, né fornirò alcuna descrizione
degli header, visto che le parti più importanti sono già state affrontate nei capitoli precedenti. Mi
preme comunque sottolineare la modalità di compilazione. Ricordando che il sistema utilizza le
librerie grafiche QT, ci viene in aiuto un comodo generatore di Makefile. Il programma tmake
sembra fatto apposta per compilare programmi che richiedono questo tipo di librerie, si rimanda al
manuale relativo per i dovuti chiarimenti.
B.1 graphics.h/*************************************************************************** graphics.h - description ------------------- begin : Wed Dec 27 2000 copyright : (C) 2000 by email : ***************************************************************************/
/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/
#ifndef __GRAPHICS_H__#define __GRAPHICS_H__
#include <stdio.h>
void salva_istogrammi(int istr[], int istg[], int istb[], char* dir);
#define ALGOG 3#define INDICE_3(i, j, k, Larg) ((i) * 3 * (Larg) + (j) * 3 + (k))#define INDICE_4(i, j, k, Larg) ((i) * 4 * (Larg) + (j) * 4 + (k))
#if (ALGOG == 1)int match_uno(unsigned char Pixel[9], unsigned char *codifica);int match_zero(unsigned char Pixel[9], unsigned char *codifica);#endif
91
#if (ALGOG == 2)int match_uno(short pixelcod, unsigned char *codifica);int match_zero(short pixelcod, unsigned char *codifica);int contacod(short pixelcod, short val);#endif
#if (ALGOG == 3)int match_uno(unsigned int pixelcod, unsigned short *codifica, int flag);int match_zero(unsigned int pixelcod, unsigned short *codifica);int contacod(short pixelcod, short val);#endif
//dichiarazione della classe 'image'class image { private: int H, L; public: unsigned char *RgbPtr; image(int h, int l); image& operator=( const image&); void saveImageppm(FILE *file); void grabba_scaled(unsigned char *vi, int dim, int sX, int sY,
bool sel); int getH(void) {return H; }; int getL(void) {return L; }; unsigned char getR(int i, int j) {return RgbPtr[INDICE_3(i, j, 0, L)];}; unsigned char getG(int i, int j) {return RgbPtr[INDICE_3(i, j, 1, L)];}; unsigned char getB(int i, int j) {return RgbPtr[INDICE_3(i, j, 2, L)];}; void putRGB(int i, int j, unsigned char r, unsigned char g,
unsigned char b); void putR(int i, int j, unsigned char r); void putG(int i, int j, unsigned char g); void putB(int i, int j, unsigned char b); void gradient(int sel); void binary(int sel, int range, bool out, char *dir); void CodSoft_ppmgs(void); ~image();};
#endif
B.2 flusso_d.h/*************************************************************************** flusso_d.h - description ------------------- begin : Sat Mar 31 2001 copyright : (C) 2001 by email : ***************************************************************************/
/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/
#ifndef __FLUSSO_D_H__
92
#define __FLUSSO_D_H__
#include "graphics.h"#include "optflow.h"
#define DEV_VIDEO "/dev/mmetfgrab0"#define IDS_STATUS_DEFAULT "Ready."#define DIRIMM "/home/ampollini/Tesi/IMMAGINI/"#define DIROP "/home/ampollini/Tesi/OPERATORI/"#define DIROUT "/home/ampollini/Tesi/OUTPUT/"#define DIRSAR "/home/ampollini/Tesi/TAGLIO/"#define OPDEF "op_5x_36_45nb.op"#define COMBFLOW 0#define TYPE_ESTR 0#define TYPE_GRAD 1#define TYPE_BIN 0#define DIMX 768#define DIMY 576#define SCALAX 3.0#define SCALAY 3.0#define SCALAXI 3#define SCALAYI 3#define VALWHITE ((unsigned int) (255 & 0xff)) | ((unsigned int) (255 & 0xff) << 8) |((unsigned int) (255 & 0xff) << 16)#define VALGRAY ((unsigned int) (127 & 0xff)) | ((unsigned int) (127 & 0xff) << 8) |((unsigned int) (127 & 0xff) << 16)#define VALBLACK ((unsigned int) (0 & 0xff)) | ((unsigned int) (0 & 0xff) << 8) |((unsigned int) (0 & 0xff) << 16)#define VALGREEN ((unsigned int) (0 & 0xff)) | ((unsigned int) (255 & 0xff) << 8) |((unsigned int) (0 & 0xff) << 16)
#define ALGOF 2#define ALGOOP 2
//dichiarazioni delle variabili globali
extern int IMGX, IMGY, RANGE, op_type, no_blocco, colmax, ang_op, lato_op;
//dichiarazione delle funzioni visibili da tutti i moduli
void acquisisci_sfondo(void);void estrai_auto(image *img, int sel);void operatore_ppmgs(bool sartoON);#if (ALGOOP == 1)void matching_ppmgs(unsigned char b, opt_flow *OF1, int y, int x, char sel);#endif#if (ALGOOP == 2)void matching_ppmgs_par(unsigned char b, opt_flow *OF1, int y, int x, char sel);unsigned int codifica(int k, int ooc, char sel, int x, int y);bool estrai_match(unsigned int cod, int k, int ooc, int *indice);#endifvoid carica_operatore(FILE *file);void reset_of(void);void combinaflussiottici(int sel);void salvafo(void);void salva_grad(image *img, int sel);void freepattern(int **a, int n);
#endif
B.3 flusso4l.h/*************************************************************************** flusso4l.h - description
93
------------------- begin : gio feb 28 17:40:53 CET 2001 copyright : (C) 2001 by email : ***************************************************************************/
/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/
#ifndef FLUSSO4L_H#define FLUSSO4L_H
#define DEV_VIDEO "/dev/mmetfgrab0"
// include files for QT#include <qdialog.h>#include <qfiledialog.h>#include <qapplication.h>#include <qstring.h>#include <qmainwindow.h>#include <qpixmap.h>#include <qapp.h>#include <qstatusbar.h>#include <qmessagebox.h>#include <qpainter.h>#include <qpushbutton.h>#include <qlayout.h>#include <qlineedit.h>#include <qgroupbox.h>#include <qcheckbox.h>#include <qslider.h>#include <qlabel.h>#include <qbuttongroup.h>#include <qradiobutton.h>#include <qimage.h>#include <qcolor.h>#include <math.h>
// application specific includes#include "graphics.h"
/******************************************************//* This Class is the base class for your application. *//******************************************************/
class Flusso4L : public QMainWindow{ Q_OBJECT
public: int fd; //dev/video int LARGHEZZA ; int ALTEZZA; /* construtor */ Flusso4L(); /* destructor */ ~Flusso4L(); /* elaboration cicle */ void Flusso(); void Flussofromfile();
94
void initStatusBar(); /* overloaded for Message box on last window exit */ bool queryExit(); void carica_operatore(FILE *); void videoplay(); void videoplayClicked(); void dafileClicked(); void operatoreClicked(); bool videoOFF; bool caricafile; bool seq_loaded; bool op_loaded; bool seq_ended; bool flowOFF; bool first_time;
public slots: void slotB_Button1Clicked(); void slotB_videoplayClicked(); void slotB_caricadafile(); void slotB_inseriscioperatore(); void slotB_sarto(); void slotB_acquisiscisfondo(); void slotB_flussoON(); void slotCB_multi1ON(); void slotCB_multi2ON(); void slotCB_output(); void slotRB_multi11(int); void slotRB_multi22(int); void slotRB_binary(int); void slotS_ValueChanged(int);
private: QPushButton *button1, *b_videoplay, *b_sfondo, *loadFile, *OpEdit, *flussoon, *b_sarto; QGroupBox *video_group,*videooption_group,*flusso_group,*multi1_group, *multi2_group; QCheckBox *outfo, *outgrad1, *outgrad2, *outbin,*outisto, *multi1on, *multi2on; QSlider *ChromaSlider, *HueSlider, *ContrastSlider, *BrightSlider, *GradientSlider, *BinarySlider; QButtonGroup *out_group, *binary_group, *multi11_group, *multi22_group; QRadioButton *binary_v, *binary_h, *binary_multi1, *grad_multi1, *auto_multi1, *binary_multi2, *grad_multi2, *auto_multi2; QLabel *bright, *bright_value, *hue, *hue_value,*contrast,*contrast_value, *chroma, *chroma_value, *gradient, *gradient_value,*binary, *binary_value, *operatore, *OpInUse, *sartoInUse; bool outfoON, outgrad1ON, outgrad2ON, outbinON, outistoON; bool multi1ON, multi2ON, soglieON; bool sartoON; int multi11_sel, multi22_sel, binary_sel; void elaborazione_immagine_ppmgs(image *img); void init_frame_grabber(); void init_operatore(); int **carica_pattern_taglio(const char *s, int sel); void inserisci_nome_file(const char *s); void inserisci_operatore(const char *s); void acquisisci_sfondo(); int acquisisci_immagine(); void visualizza(image *img, int sel, int multi_sel); void visualizzaflussoottico(void);};
#endif // FLUSSO4L_H
95
B.4 opt_flow.h/*************************************************************************** optflow.h - description ------------------- begin : Tue Mar 28 2001 copyright : (C) 2001 by email : ***************************************************************************/
/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/
#ifndef __OPTFLOW_H__#define __OPTFLOW_H__
#define INDICE_2(i, j, k, Larg) ((i) * 2 * Larg + (j) * 2 + (k))
//dichiarazione della classe 'opt_flow'class opt_flow { private: int H, L; public: char *OFPtr; opt_flow(int h, int l); void reset(); void saveMatlabformat(FILE *file); int getH(void) {return H; }; int getL(void) {return L; }; char getX(int i, int j) {return OFPtr[INDICE_2(i, j, 0, L)];}; char getY(int i, int j) {return OFPtr[INDICE_2(i, j, 1, L)];}; void putXY(int i, int j, char valx, char valy); void putX(int i, int j, char valx); void putY(int i, int j, char valy); ~opt_flow();};
#endif
96
Bibliografia
[_] V. Cantoni e S. Levialdi. La visione delle macchine. TecnicheNuove. 1989.
[_] S. Russel e P. Norvig. Artificial Intelligence – a modern approach. Prentice Hall International
Editions. 1995.
[1] T. Camus. Calculating Time-to-Contact Using Real-Time Quantized Optical Flow. Technical
report n° 14 presso il Max-Planck-Institut für biologische kybernetik.
[2] M. Tistarelli. Multiple Constraints to Compute Optical Flow. Reprint from “IEEE
Transactions on Pattern Analysis and Machine Intelligence”. Vol. 18, No. 12, December
1996.
[3] M. Tistarelli e G. Sandini. On the Advantages of Polar and Log-Polar Mapping for Direct
Estimation of Time-to-impact from Optical Flow. Reprint from “IEEE Transactions on
Pattern Analysis and Machine Intelligence”. Vol. 15, No. 4, April 1993.
[4] P. Golland. Use of Color for Optical Flow Estimation. Master of Science in Computer
Science. Israel Institute of Technology, 1995.
[5] G. Buchsbaum. A spatial processor model for object color perception. Journal of Franklin
Inst., 1980.
[6] P. Golland e A.M. Bruckstein. Why R.G.B. ? or How to design color displays for Martians.
Technical Report 9401, IS Lab, CS Department, Technion, Haifa, Israel, 1994.
[7] N. Otha. Optical flow detection by color images. In Proceeding of IEEE International
Conference on Image Processing, Pan Pacific, Singapore, 1989.
97
[_] J.L. Barron, D.J. Fleet, S.S. Beauchemin, e T.A. Burkitt. Performance of Optical Flow
Techniques. In Computer Vision and Pattern Recognition, 1992.
[_] M.J. Black e P. Anandan. A Framework for the Robust Estimation of Optical Flow. In
International Conference on Computer Vision, 1993.
[_] M.C. Cooper. The Tractability of Segmentation and Scene Analysis. International Journal of
computer Vision, 30(1), 1998.
[_] D.J. Heeger. Optical Flow using Spatiotemporal Filters. International Journal of Computer
Vision, 1998.
[_] B.K.P. Horn e B.G. Schunk. Determining Optical Flow. Artificial Intelligence, August, 1981.
[_] B.G. Schunk e B.K.P. Horn. Constraints on Optical Flow Computation. In Pattern
Recognition and Image Processing, August, 1981.