SVILUPPO DI MODULI SOFTWARE PER IL CALCOLO DELLA DISTANZA DA IMMAGINI STEREOSCOPICHE



Documenti analoghi
Dimensione di uno Spazio vettoriale

Matematica in laboratorio

GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL

APPUNTI DI MATEMATICA LE FRAZIONI ALGEBRICHE ALESSANDRO BOCCONI

EXCEL PER WINDOWS95. sfruttare le potenzialità di calcolo dei personal computer. Essi si basano su un area di lavoro, detta foglio di lavoro,

13. Campi vettoriali

risulta (x) = 1 se x < 0.

~ Copyright Ripetizionando - All rights reserved ~ STUDIO DI FUNZIONE

Funzioni in C. Violetta Lonati

Lezione 8. La macchina universale

CATALOGO E-COMMERCE E NEGOZIO A GRIGLIA

Analisi sensitività. Strumenti per il supporto alle decisioni nel processo di Valutazione d azienda

4 3 4 = 4 x x x 10 0 aaa

Excel. A cura di Luigi Labonia. luigi.lab@libero.it

e-dva - eni-depth Velocity Analysis

Calcolo del Valore Attuale Netto (VAN)

MODULO 4: FOGLIO ELETTRONICO (EXCEL)

Il calendario di Windows Vista

2 Fortino Lugi. Figura Errore. Nel documento non esiste testo dello stile specificato Finestra attiva o nuovo documento

Plate Locator Riconoscimento Automatico di Targhe

Capitolo 3. L applicazione Java Diagrammi ER. 3.1 La finestra iniziale, il menu e la barra pulsanti

IL MIO PRIMO SITO: NEWS

Tale attività non è descritta in questa dispensa

Statistica. Lezione 6

Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12};

1 Serie di Taylor di una funzione

Operazioni fondamentali

Librerie digitali. Video. Gestione di video. Caratteristiche dei video. Video. Metadati associati ai video. Metadati associati ai video

La distribuzione Normale. La distribuzione Normale

lo PERSONALIZZARE LA FINESTRA DI WORD 2000

FUNZIONI DI IMPAGINAZIONE DI WORD

Corso di Informatica Generale (C. L. Economia e Commercio) Ing. Valerio Lacagnina Rappresentazione in virgola mobile

LE FUNZIONI A DUE VARIABILI

Forze come grandezze vettoriali

Fondamenti di Informatica 2. Le operazioni binarie

Mon Ami 3000 Varianti articolo Gestione di varianti articoli

Capitolo 2. Operazione di limite

INTEGRATORE E DERIVATORE REALI

( x) ( x) 0. Equazioni irrazionali

Sistema operativo: Gestione della memoria

Le funzioni continue. A. Pisani Liceo Classico Dante Alighieri A.S A. Pisani, appunti di Matematica 1

Guida all uso di Java Diagrammi ER

Capitolo 13: L offerta dell impresa e il surplus del produttore

Matematica 1 - Corso di Laurea in Ingegneria Meccanica

per immagini guida avanzata Uso delle tabelle e dei grafici Pivot Geometra Luigi Amato Guida Avanzata per immagini excel

f(x) = 1 x. Il dominio di questa funzione è il sottoinsieme proprio di R dato da

Mon Ami 3000 Provvigioni agenti Calcolo delle provvigioni per agente / sub-agente

Database 1 biblioteca universitaria. Testo del quesito

Mac Application Manager 1.3 (SOLO PER TIGER)

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo.

Domande a scelta multipla 1

Guida Compilazione Piani di Studio on-line

LA GRAFICA E LA GEOMETRIA OPERATIVA

Introduzione. Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD... 6

Istruzioni per l uso dei programmi MomCad, TraveCon, TraveFon

La pista del mio studio Riflettiamo sulla pista. Guida per l insegnante

Dispensa di Informatica I.1

PROCEDURA INVENTARIO DI MAGAZZINO di FINE ESERCIZIO (dalla versione 3.2.0)

Per studio di funzione intendiamo un insieme di procedure che hanno lo scopo di analizzare le proprietà di una funzione f ( x) R R

IL MIO PRIMO SITO NEWS USANDO GLI SCHEDARI

Quando troncare uno sviluppo in serie di Taylor

Sommario. Definizione di informatica. Definizione di un calcolatore come esecutore. Gli algoritmi.

LA PROGETTAZIONE DI UN NUOVO STRUMENTO PER IL WEB

ESEMPIO 1: eseguire il complemento a 10 di 765

Dispensa di database Access

Logica Numerica Approfondimento 1. Minimo Comune Multiplo e Massimo Comun Divisore. Il concetto di multiplo e di divisore. Il Minimo Comune Multiplo

Mon Ami 3000 Cespiti Gestione cespiti e calcolo degli ammortamenti

Interesse, sconto, ratei e risconti

ISTRUZIONI PER LA GESTIONE BUDGET

Vademecum studio funzione

Algoritmi e strutture dati. Codici di Huffman

Word processor funzione Stampa Unione

. A primi passi con microsoft a.ccepss SommarIo: i S 1. aprire e chiudere microsoft access Start (o avvio) l i b tutti i pro- grammi

INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI

Obiettivo Principale: Aiutare gli studenti a capire cos è la programmazione

Page 1. Evoluzione. Intelligenza Artificiale. Algoritmi Genetici. Evoluzione. Evoluzione: nomenclatura. Corrispondenze natura-calcolo

Appunti sulla Macchina di Turing. Macchina di Turing

Per chi ha la Virtual Machine: avviare Grass da terminale, andando su Applicazioni Accessori Terminale e scrivere grass

MANUALE UTENTE Fiscali Free

Documentazione esterna al software matematico sviluppato con MatLab

RAPPRESENTAZIONE GRAFICA E ANALISI DEI DATI SPERIMENTALI CON EXCEL

Corso di Laurea in Scienze della Formazione Primaria Università di Genova MATEMATICA Il

Esercizi su. Funzioni

TRASMISSIONE RAPPORTO ARBITRALE IN FORMATO PDF

Come costruire una distribuzione di frequenze per caratteri quantitativi continui

Sistemi Operativi. 5 Gestione della memoria

PROGETTO PER LA TRASMISSIONE DOCUMENTI RELATIVI ALL APPROVAZIONE DELLE MANIFESTAZIONI IN FORMA DIGITALE

Parte I. Prima Parte

Rapporto dal Questionari Insegnanti

Testi di Esercizi e Quesiti 1

Mon Ami 3000 Conto Lavoro Gestione del C/Lavoro attivo e passivo

Raggruppamenti Conti Movimenti

CREAZIONE DI UN DATABASE E DI TABELLE IN ACCESS

Mon Ami 3000 Ratei e Risconti Calcolo automatico di ratei e risconti

RECUPERO DATI LIFO DA ARCHIVI ESTERNI

Raccomandazione del Parlamento europeo 18/12/2006 CLASSE PRIMA COMPETENZE ABILITÀ CONOSCENZE. Operare con i numeri

Abilità Informatiche A.A. 2010/2011 Lezione 9: Query Maschere Report. Facoltà di Lingue e Letterature Straniere

Il database management system Access

Banca dati Professioniste in rete per le P.A. Guida all uso per le Professioniste

Prestazioni CPU Corso di Calcolatori Elettronici A 2007/2008 Sito Web: Prof. G. Quarella prof@quarella.

Transcript:

Universitá di Genova Facoltá di Ingegneria Dipartimento di Ingegneria Biofisica ed Elettronica Tesi di Laurea triennale in Ingegneria Elettronica SVILUPPO DI MODULI SOFTWARE PER IL CALCOLO DELLA DISTANZA DA IMMAGINI STEREOSCOPICHE Relatore: Prof. Fabio Solari Correlatori: Dott. Manuela Chessa Dott. Andrea Canessa Allievi: Valentina Bianchi Massimo Zampetti Genova, 24 Settembre 2010 Anno Accademico 2009-10

2 Dichiarazione del Relatore Alla Commissione di Laurea e di Diploma Alla Commissione Tirocini e Tesi Sottopongo la tesi redatta dagli studenti Valentina Bianchi e Massimo Zampetti dal titolo SVILUPPO DI MODULI SOFTWARE PER IL CALCO- LO DELLA DISTANZA DA IMMAGINI STEREOSCOPICHE. Ho esaminato, nella forma e nel contenuto, la versione finale di questo elaborato scritto, e propongo che la tesi sia valutata positivamente assegnando i corrispondenti crediti formativi. Il Relatore Accademico Prof. Fabio Solari

Ringraziamenti Prima di procedere con la trattazione del lavoro, desideriamo ringraziare il Professor Fabio Solari per la sua disponibilitá e professionalitá, e soprattutto per la pazienza con cui ha seguito lo sviluppo della nostra tesi in questi ultimi mesi. 3 Un ringraziamento particolare alla Dottoressa Manuela Chessa che é sempre stata disponibile per chiarimenti e spiegazioni, e ha seguito il nostro lavoro finché ha potuto, per poi dedicarsi alla piccola Alice. Ringraziamo, inoltre, tutti i membri del laboratorio, in particolar modo Agostino e Andrea, perché sicuramente ci mancherá l atmosfera che si era creata. Andrea, perché un giorno ha deciso di interessarsi al nostro lavoro (davvero poco conscio di ció a cui andava incontro), e da allora é diventato il nostro vero punto di riferimento nelle ultime intense settimane di lavoro. Un grazie per la sua infinita pazienza e il suo modo di fare, per aver condiviso con noi le ore serie, ma anche risate, battute, colazioni, pranzi...e Loacker! Agostino, per essere stato sempre disponibile quando abbiamo avuto bisogno del suo aiuto, soprattutto per quanto riguarda la scrittura della tesi in LaTex e, in particolare, per la bibliografia: nonostante tutto continuiamo a preferire MikTex a TexNic. Ovviamente grazie ai nostri genitori per averci supportati e sopportati in tutti questi anni di studio, e perché dovranno farlo ancora per molto!

4 Ringraziamo i compagni di corso, soprattutto Fede. Ringrazio tutti gli amici che in un modo o nell altro mi hanno accompagnato in questi tre anni, i miei coinquilini, gli amici savonesi e quelli genovesi. Infine un ringraziamento particolare alla Vale per aver affrontato con me quest avventura, i momenti piú tragici in cui il codice sembrava non voler proprio funzionare e quelli in cui un traguardo sembrava poi non essere cosí distante. Massimo Non posso non ricordare tutti gli amici sempre presenti, in particolare Giulia, per essere da sempre la migliore amica, portatrice sana di brutte figure, e Max e Lisa, perché, anche se lontani, hanno seguito tutti i risvolti del lavoro. Ed infine un grazie a Massimo, per la rapiditá con cui ha accettato di condividere con me questa esperienza, per la pazienza e la costanza con cui si é dedicato al lavoro, per la facilitá con cui ci siamo trovati a lavorare bene insieme, e per l incapacitá di rimanere seri a lungo. Valentina

Indice 1 Title and abstract 8 1.1 Title................................ 8 1.2 Abstract.............................. 8 2 Obiettivi e Premesse 11 3 La Disparitá binoculare 14 3.1 La Formazione dell Immagine.................. 14 3.2 La Stereopsi............................ 15 3.2.1 Problema della corrispondenza degli elementi...... 16 3.2.2 Triangolazione Stereoscopica............... 16 3.2.3 Correlazione e sfasamento................ 17 3.3 La Disparitá............................ 18 3.3.1 Disparitá orizzontale e verticale............. 19 3.4 Collegamento alle Reti Neurali.................. 21 4 Implementazione 23 4.1 Le Funzioni IPP.......................... 24 4.2 MEX-Files............................. 24 5

INDICE 6 4.3 Il Linguaggio C.......................... 26 4.4 L Organizzazione Strutturale della Memoria.......... 27 4.4.1 L Importanza della Struttura Piramidale........ 29 4.4.2 Buffer........................... 30 4.5 Struttura dell algoritmo..................... 32 5 Filtraggio Spaziale e Sfasamento 34 5.1 I Filtri di Gabor.......................... 34 5.2 Filtraggio: implementazione e Padding............. 36 5.3 Le funzioni usate per la convoluzione.............. 39 5.4 Verifica del Codice Attraverso la Risposta all Impulso..... 42 5.5 Sfasamento............................ 44 6 Modello a Energia e Decodifica della Popolazione 48 6.1 Scelta del Modello........................ 48 6.2 Modello a Energia......................... 50 6.2.1 Gestione della Memoria................. 50 6.2.2 Calcolo dell Energia.................... 50 6.2.3 Creazione della Maschera................. 53 6.3 Decodifica della Popolazione................... 55 7 Calcolo della disparitá 58 7.1 Disparitá Totale.......................... 58 8 Livelli Successivi 61 8.1 Ridimensionamento delle Mappe di Disparitá.......... 62 8.2 Warping.............................. 65

INDICE 7 8.3 Somma delle disparitá ed eliminazione dei valori non validi.. 69 8.3.1 Funzione Merge...................... 70 8.3.2 Funzione Cut Invalid................... 72 9 Risultati 74 9.1 Tempi di esecuzione........................ 78 10 Conclusioni 80 Bibliografia 81

Capitolo 1 Title and abstract 1.1 Title Development of Software Modules for Computing the Distance from Stereoscopic Images. 1.2 Abstract The work is to develop a C code for disparity computation, representing the basis for the perception of three-dimensionality of the world acquired through a binocular vision, from stereoscopic images. First, it was necessary to study the basic algorithm implemented in MAT- LAB where the aim is to achieve the disparity information by emulating the behavior of neurons. To do this, a fundamental role is played by Gabor filters that are used to model the properties of receptive fields of cells in the visual cortex. 8

CAPITOLO 1. TITLE AND ABSTRACT 9 The algorithm follows five basic steps to achieve the final result: ˆ convolution based on Gabor filters; ˆ phase shift; ˆ energy model; ˆ decoding of the population; ˆ the disparity computation. It was then to procede with writing a new code trying to improve the performance of the previous using the C programming language. Special attention to memory management and minimization of computational time was required which were two of the main problems presented by this approach. The algorithm has two important aspects that were fundamental in achieving the goal: ˆ The first is the use of Intel IPP libraries which have provided a good solution for the matrices: once given the matrices on which we had to work, it was not necessary to worry about the actual conduct and method of calculation. ˆ Secondly, it was necessary to use the MEX-files with which it was possible to write functions in C language and to simultaneously use MATLAB as an interface. Once we completed the algorithm, we started to compile and last of all testing using test images.

CAPITOLO 1. TITLE AND ABSTRACT 10 Following the description of the performed work, you can find an application of the algorithm on real world images.

Capitolo 2 Obiettivi e Premesse Gli obiettivi principali della tesi sono stati l implementazione e l ottimizzazione di un algoritmo neuromorfo il cui scopo è il calcolo della disparitá. Più precisamente, partendo da un codice MATLAB [Mathworks, 2010] giá esistente, siamo stati chiamati a riportarlo in C prestando particolare attenzione alla gestione della memoria e ai tempi di elaborazione, e cercando di sfruttare nel modo migliore le risorse computazionali. Le informazioni che otteniamo, infatti, possono essere direttamente usate da un robot per interagire con l esterno, pertanto le immagini devono poter essere acquisite in tempo reale e processate velocemente. Tra le operazioni svolte durante l elaborazione dell immagine vi é il calcolo della disparitá binoculare, che é l obiettivo del nostro lavoro. Sono molte le tecniche che permettono di giungere a tale risultato, caratterizzate da grandi prestazioni sia in termini di precisione della stima risultante, sia per quanto riguarda il tempo di esecuzione [Manuela Chessa, 2009]. É possibile distinguere, tra queste, le tecniche bioinspirate, caratterizzate 11

CAPITOLO 2. OBIETTIVI E PREMESSE 12 dal fornire un alta flessibilitá a discapito, tuttavia, del sensibile costo computazionale: il loro nome deriva dal fatto che il principio base riprende il modo in cui lavora la corteccia visiva. Cosí come in natura le informazioni visive (tra cui quelle legate alla disparitá) vengono codificate da una rete complessa di neuroni, ed elaborate ai fini della percezione visiva, analogamente il nostro codice dovrá rispettare queste due fasi: in primo luogo si servirá di una popolazione che simulerá il comportamento dei neuroni, i quali forniranno le informazioni che successivamente verranno decodificate con un opportuno meccanismo per giungere al risultato finale. Per raggiungere il nostro obiettivo, ovvero creare un codice che migliori le prestazioni rispetto a quello di partenza (soprattutto aumentarne la velocitá di esecuzione), focalizzeremo l attenzione su alcuni aspetti organizzativi e di calcolo. Il fatto di lavorare con immagini, implica di dover avere a che fare necessariamente con delle matrici, pertanto dovremo trovare il modo di gestire le operazioni su di esse al fine di ridurre il costo computazionale che caratterizza l elaborazione delle immagini. Il problema della gestione della memoria, comune alla maggior parte degli algoritmi di una certa complessitá, assumerá per noi ancora piú importanza, trovandoci a gestire un numero considerevole di immagini. Oltre a favorire la velocitá, anche la precisione é un requisito fondamentale, pertanto sará necessario trovare delle soluzioni che permettano al nostro codice di giungere a stime sufficientemente precise. Inoltre pensando a sviluppi futuri che interesseranno il nostro codice,

CAPITOLO 2. OBIETTIVI E PREMESSE 13 cercheremo di fornirgli un organizzazione che favorisca il piú possibile eventuali future estensioni.

Capitolo 3 La Disparitá binoculare 3.1 La Formazione dell Immagine Avendo basato il nostro lavoro sullo studio e l elaborazione di due immagini, andiamo, per prima cosa, a definire come si forma un immagine bidimensionale, a partire dalla luce riflessa dagli oggetti presenti su una scena. Servendoci di una fotocamera stenopeica, detta anche stenoscopio, possiamo vedere come un oggetto venga percepito dalla nostra retina invertito rispetto alla scena, scambiando la destra con la sinistra, e il sopra con il sotto (figura 3.1). Questo processo assume il nome di proiezione prospettica, resa matematicamente nel seguente modo: X f Y f = X Z = Y Z da cui: X = fx Z 14

B ; : CAPITOLO 3. LA DISPARITÁ BINOCULARE 15 F E = E = C E A E = C E A B H I J A F A E? C C A J J Figura 3.1: Formazione dell immagine Y = fy Z Z = -f dove x, y e z sono le coordinate dell oggetto sulla retina (piano immagine), mentre la focale f é la distanza del foro dal piano immagine. 3.2 La Stereopsi La stereopsi è la capacitá percettiva che consente di unire le immagini provenienti da due occhi, che a causa del loro diverso posizionamento strutturale, presentano uno spostamento laterale. Questa disparitá viene sfruttata dal cervello per trarre informazioni sulla profonditá e sulla posizione spaziale dell oggetto mirato. Di conseguenza la stereopsi si permette di generare la visione tridimensionale[rossetti, 2003]. Attraverso la stereopsi siamo quindi in grado di ottenere informazioni sulla struttura tridimensionale di una scena, a partire da due immagini proveni-

CAPITOLO 3. LA DISPARITÁ BINOCULARE 16 enti da due telecamere inquadranti la scena stessa [Emanuele Trucco, 1998]. 3.2.1 Problema della corrispondenza degli elementi Trovare la disparitá nelle immagini implica il problema della corrispondenza degli elementi, ovvero le caratteristiche di due immagini corrispondono una con l altra quando sono immagini dello stesso punto nel mondo esterno [Mallot, 2000]. Bisogna pertanto accoppiare, nelle due immagini, i punti che corrispondono allo stesso punto sulla scena, considerando che essi differiscono lievemente. Dopo aver estratto le caratteristiche ai livelli di grigio dell immagine, effettuando quindi quella che viene chiamata Analisi delle Features, si risolve il problema della corrispondenza e si va a trovare un immagine risultante che sia una delle possibili, ma non necessariamente quella corretta, a causa di possibili accoppiamenti sbagliati. Per ridurre eventuali errori si ricorre ad altri vincoli, tra cui quello epipolare: dato un punto, nell altra immagine il suo corrispondente si puó trovare solo su una retta (retta epipolare). 3.2.2 Triangolazione Stereoscopica Noi ci soffermiamo ad un caso particolare di triangolazione tridimensionale, quello semplificato che interessa due telecamere parallele; come vedremo in seguito, qui interviene solo la disparitá orizzontale. Considerata la figura 3.2, le equazioni di proiezione prospettica sono le seguenti: f = v z y f z = v y b,

B L + > : CAPITOLO 3. LA DISPARITÁ BINOCULARE 17 da cui otteniamo: z = bf v v A numeratore abbiamo la geometria del sistema, mentre a denominatore abbiamo la disparità; b, inoltre, è una sorta di fattore di scala, e definisce la lunghezza della linea di base, ovvero quella linea che si trova tra le due telecamere. L Figura 3.2: Triangolazione Stereoscopica 3.2.3 Correlazione e sfasamento A questo punto, la mappa di disparitá si può ottenere attraverso due approcci: correlazione e shift in fase.

CAPITOLO 3. LA DISPARITÁ BINOCULARE 18 Con il primo approccio si definisce la disparitá come quel disallineamento di due funzioni, a valori di grigio, che vada a massimizzare la loro similitudine. La tecnica dello shift in phase, invece, per ottenere la disparitá, usa i campi recettivi destro e sinistro(filtri 2D) gr(x) e gl(x) centrati nella stessa posizione nelle immagini destra e sinistra, ma con una certa differenza in fase ψ = ψl ψr. Per ogni orientazione spaziale, si sceglie un set di fasi tra - π e π, uniformemente distribuite, e si ottengono i valori di disparità δ θ = ψ /ω 0, orientate lungo la direzione ortogonale rispetto all orientazione del campo recettivo. Nel nostro caso, il filtraggio avviene con filtri di Gabor (in quanto usiamo funzioni sinusoidali con inviluppo gaussiano e tali per cui il rapporto tra la frequenza spaziale e σ sia costante). 3.3 La Disparitá Il concetto di disparitá risulta essere fondamentale ai fini del lavoro svolto; si vuole pertanto analizzarne il significato e approfondirne alcuni aspetti. Nel nostro caso si tratta piú nello specifico la disparitá binoculare, ovvero la differenza che si presenta fra le immagini acquisite, della stessa scena visiva, da due telecamere poste in modo tale da poter simulare il comportamento degli occhi di un essere umano[mallot, 2000]. La diretta conseguenza é la percezione della profonditá negli esseri umani, e quindi la rappresentazione dell informazione della distanza degli oggetti nelle scene nell ambito della Computer Vision. Tramite le due immagini viene effettuata una elaborazione che codifica la

CAPITOLO 3. LA DISPARITÁ BINOCULARE 19 differenza in pixel dei punti corrispondenti dello stesso oggetto nello spazio 3D: piú precisamente il codice da noi implementato fornisce come output due mappe, una per la disparitá verticale e una per la disparitá orizzontale. 3.3.1 Disparitá orizzontale e verticale Per comprendere meglio il significato della disparitá si consideri un punto isolato P (si veda la figura 3.3) il quale risulta essere in posizione leggermente differente nelle due immagini destra e sinistra. La disparitá orizzontale é definita come la componente orizzontale della differenza tra le distanze delle proiezioni del punto P dal punto centrale delle due immagini[mallot, 2000]. La disparitá verticale, invece, risulta presente solo se gli assi di visione non sono paralleli, ovvero se i due assi perpendicolari ai piani su cui sono poste le telecamere convergono su un punto. La scelta di una condizione piuttosto che l altra, unita alla diversa posizione degli occhi, rappresenta una delle cause principali della disparitá. Nel caso in cui si abbia a che fare con due telecamere é necessario, ai fini di un buon risultato, che i parametri interni impostati siano uguali. Possiamo quindi distinguere, come accennato, due differenti geometrie stereoscopiche: la prima prevede che gli assi siano paralleli con conseguente generazione della sola disparitá orizzontale e la presenza di questa per tutti i punti; la seconda invece prevede che gli assi convergano permettendo cosí di acquisire anche la disparitá verticale. In questo caso saranno invece presenti

. > 5 2 CAPITOLO 3. LA DISPARITÁ BINOCULARE 20 alcuni punti in cui la disparitá risulterá nulla come per esempio quelli lungo l intersezione dei due assi. =. = 2 C C H 4 Figura 3.3: geometrie stereoscopiche La figura 3.3 rappresenta schematicamente il secondo caso preso in esame. Nei punti L e R sono posizionate le telecamere mentre F é il punto fissato in cui convergono i due assi. Questi determinano un angolo αf il quale rimarrá costante per ogni F posta in un punto qualsiasi lungo la circonferenza (Vieth - Müller circle)[mallot, 2000]. P rappresenta infine un punto generico. In figura sono riportati determinati angoli poiché questi svolgono un ruolo fondamentale. Quando si utilizza la geometria ad assi convergenti, é conveniente esprimere i valori di disparitá proprio mediante gli angoli. Tale discorso é valido per tutti i punti P che si trovano sul piano determinato da L, R e F, ma bisogna tenere conto anche della possibilità di inclinare il piano su cui sono poste le telecamere, ovvero la possibilitá di inclinare la

CAPITOLO 3. LA DISPARITÁ BINOCULARE 21 testa da una parte all altra. Proprio a causa di questa condizione é necessario considerare la disparitá verticale la quale puó essere generata solo tramite la geometria ad assi convergenti. 3.4 Collegamento alle Reti Neurali In molti hanno analizzato i diversi approcci con cui rappresentare le popolazioni di neuroni, per combinare le loro risposte al fine di ottenere informazioni utili dai segnali visivi. L informazione é, infatti, contenuta in una rete di neuroni semplici o complessi, detta popolazione, che racchiude numerose informazioni, tra cui alcune relative alla disparitá. Già nel 1971, venne formulata l ipotesi di esistenza di canali di selezione della disparitá nella percezione umana, che fu poi successivamente confermata[mallot, 2000]. In particolare per decodificare l informazione relativa ad una specifica zona dell immagine, si considera l intera attivitá di quella parte della popolazione nella zona considerata. Osserviamo adesso come viene elaborata la stereopsi dal sistema percettivo degli umani: le disparitá, orizzontale e verticale, sono calcolate definendo il vettore di disparitá δ, come il vettore di differenza nelle posizioni di punti corrispondenti negli occhi destro e sinistro, pertanto le informazioni provenienti dai due occhi devono convergere nelle stesse cellule. Inoltre molti neuroni binoculari rispondono in maniera ottimale quando le

CAPITOLO 3. LA DISPARITÁ BINOCULARE 22 immagini retiniche si trovano su punti corrispondenti delle due retine: questa é la base fisiologica dell horopter, ovvero lo spazio dei punti a disparitá nulla, sia verticale sia orizzontale. Comunque, molti altri neuroni binoculari rispondono in maniera ottimale quando la stessa immagine occupa due punti leggermente distinti sulle retine dei due occhi (sono cioé regolati su una determinata disparitá binoculare).

Capitolo 4 Implementazione Mediante l utilizzo del MEX-file [Mathworks, 2010] é stato possibile scrivere le funzioni in linguaggio C e contemporaneamente servirsi di MATLAB come interfaccia. Quest ultimo infatti permette di passare i dati e le immagini su cui effettuare le operazioni al termine delle quali i risultati verranno riportati in MATLAB per essere visualizzati ed effettuarne poi un analisi più veloce e accurata. Gli algoritmi di elaborazione di immagini richiedono sempre un costo computazionale molto elevato, come conseguenza delle loro alte prestazioni, ma se considerati come elaborazioni matriciali é possibile effettuare alcuni accorgimenti. Inoltre, in genere, tali algoritmi sono pensati per immagini semplici, e rappresentano un modello di come lavora la corteccia visiva. 23

CAPITOLO 4. IMPLEMENTAZIONE 24 4.1 Le Funzioni IPP Lavorare con matrici significa spesso poter parallelizzare più operazioni con conseguente riduzione dei tempi qualora le risorse disponibili siano adeguate. Questa é la ragione per cui sono state utilizzate nello sviluppo del codice alcune funzioni appartenenti alle librerie IPP di Intel[INTEL, 2010]. Tali funzioni ricevono in ingresso una o piú matrici per poi ritornare il risultato dell operazione a partire da una somma o un prodotto fino alle operazioni piú complesse[intel, 2007]. Mediante le IPP inoltre é possibile ottenere prestazioni ancora piú soddisfacenti se i dati presenti in memoria sono allineati; ció non significa che in caso contrario le funzioni appartenenti a queste librerie non possano essere utilizzante, bensí che un confronto tra i risultati nei due casi potrebbe evidenziare notevoli differenze. Infine, un ulteriore aspetto di tali librerie é la loro massima compatibilitá: seppur ideate e ottimizzate per ottenere prestazioni elevate su processori Intel, funzionano correttamente con buoni risultati anche su altri processori. Analizziamo ora piú nello specifico le due principali tecnologie software che, insieme alle IPP appena descritte, si sono utilizzate evidenziandone le caratteristiche maggiormente sfruttate ai fini di raggiungere il nostro obiettivo. 4.2 MEX-Files I MEX-files sono funzioni C che consentono un interfaccia MATLAB. Come accennato in precedenza, il vantaggio é ottimizzare il codice MATLAB quan-

CAPITOLO 4. IMPLEMENTAZIONE 25 do, in casi come questo, risulta pesante da un punto di vista computazionale. La struttura di un MEX-file si basa su una funzione di gateway la quale permette il passaggio di puntatori alle variabili di ingresso e uscita ed ha la seguente forma: void mexfunction ( int nlhs, mxarray * plhs [ ], int nrhs, const mxarray * prhs [ ] ) con: ˆ nlhs: numero di variabili in uscita; ˆ *plhs[]: puntatore ad un array di mxarray in uscita; ˆ nrhs: numero di variabili in ingresso; ˆ *prhs[]: puntatore ad un array di mxarray in ingresso. Per effettuare il passaggio delle matrici da MATLAB al MEX-file viene utilizzato il tipo fondamentale mxarray il quale di fatto non fa altro che riportare la struttura dati interna che viene utilizzata dallo stesso MATLAB per rappresentare gli array. Tale struttura contiene al suo interno: ˆ il nome della variabile MATLAB; ˆ la dimensione; ˆ il tipo; ˆ reale o complessa. Qualora siano presenti numeri complessi, saranno presenti due vettori distinti contenenti rispettivamente la parte reale e la parte immaginaria.

CAPITOLO 4. IMPLEMENTAZIONE 26 4.3 Il Linguaggio C Il vantaggio principale é rappresentato dalla sua vicinanza al linguaggio macchina nonostante sia ad alto livello. La semantica alla base del C [Kernighan, 2004] si basa su concetti sostanzialmente semplici che si avvicinano molto al funzionamento di un hardware dei calcolatori. L aspetto piú importante e maggiormente sfruttato nel codice che abbiamo implementato é sicuramente rappresentato dal concetto di puntatore il quale permette un accesso e una gestione della memoria semplice e veloce come vedremo in seguito nella creazione delle strutture che andranno a contenere i dati. Infine si ricorda che il C é un linguaggio standardizzato e pertanto assicura la portabilitá del codice su qualsiasi piattaforma. In seguito a queste premesse analizziamo alcuni aspetti del codice implementato per fornire le basi dalle quali partire per poi effettuarne una descrizione piú dettagliata nei capitoli seguenti. Per prima cosa é utile soffermarsi sulla struct MatrixIpp : essa rappresenta la struttura base che conterrá tutte le informazioni necessarie riguardanti le immagini su cui si andrá a lavorare. struct MatrixIpp { Ipp32f *ptr ; \\puntatore alla matrice int m; \\righe della matrice int n ; \\colonne della matrice int stp ; \\stepbyte: dimensione in byte delle righe della matrice

CAPITOLO 4. IMPLEMENTAZIONE 27 } Il primo campo é un puntatore a un vettore Ipp32f: la scelta di utilizzare un vettore singolo nonostante le matrici siano bidimensionali fornisce come diretta conseguenza una gestione piú flessibile e immediata dei dati. I due campi successivi riportano, mediante interi, il numero di righe e colonne della matrice e infine il quarto riporta lo stepbyte, ovvero l effettiva lunghezza della riga. Quest ultimo é necessario in quanto le righe di ogni matrice potrebbero essere piú lunghe di quelle effettive per una semplice questione di allineamento dei dati e conseguente aumento delle prestazioni delle funzioni IPP. Questo peró comporta che nella matrice possano essere presenti celle vuote al termine di ogni riga ed é quindi necessaria una variabile che tenga conto della dimensione dell immagine vera e propria. 4.4 L Organizzazione Strutturale della Memoria Uno degli aspetti su cui ci siamo concentrati fin da subito é stato quello riguardante l occupazione della memoria e la gestione della stessa. Questo problema é sorto dal momento che abbiamo dovuto lavorare con due immagini che possedevano requisiti differenti, e che avevano dimensioni di volta in volta diverse, ma soprattutto per avere un idea sempre chiara e corretta di come muoverci dovendo lavorare con strutture che a loro volta puntavano ad altre e cosí via.

CAPITOLO 4. IMPLEMENTAZIONE 28 Per tale motivo abbiamo pensato ad alcune strutture che ci potessero agevolare ad organizzare nel modo migliore il nostro lavoro, e che abbiamo usato lungo tutto il codice. Esse pertanto sono state allocate all inizio del codice, in modo da averle giá disponibili da subito, e da poterle opportunamente riempire durante l esecuzione. Analogamente, al termine del programma, ottenute le due mappe di disparitá desiderate, é stata effettuata un serie di chiamate a funzioni con lo scopo di liberare la memoria occupata nel corso dell algoritmo. Uno studio preventivo della memoria che sarebbe stata utilizzata, e, quindi, una conseguente allocazione di strutture adeguate ha permesso una gestione semplice e nello stesso tempo una minimizzazione dello spazio occupato. La porzione di memoria coinvolta risulta comunque tutt atro che irrilevante nonostante tutti i calcoli a priori effettuati ed é stato ritenuto utile deallocarla al termine del processo. Troviamo quindi le funzioni che operano sulle principali strutture: i 2 buffer, energy, dec_energy, population e dec_popuation. Si opera i- noltre sulla memoria riservata alle due piramidi rispettivamente dell immagine sinistra e destra, ai corrispondenti filtri, al vettore di disparitá e ai filtri di Gabor. La struttura interna é simile in tutte le funzioni: una serie di cicli for permette di iterare su tutti gli indici e liberare la memoria tramite le chiamate a ippifree() e mxfree(). Piú precisamente la prima consente di liberare la memoria allocata mediante ippimalloc, la seconda invece se si é utilizzata una mxmalloc.

CAPITOLO 4. IMPLEMENTAZIONE 29 4.4.1 L Importanza della Struttura Piramidale Per ogni singola immagine abbiamo dovuto utilizzare un approccio che seguisse una struttura piramidale, ovvero abbiamo lavorato sulle dimensioni della stessa immagine, con lo scopo di poter ottenere una mappa di disparitá che fosse quanto piú precisa possibile. La struttura piramidale non é altro che una struttura allocata in memoria al fine di poter contenere delle immagini, e piú precisamente l immagine di partenza, caricata da MATLAB, e le sue riproduzioni in scala ridotta [Adelson, 1985]: il numero di riproduzioni é dato dalla variabile n_liv, che indica piú precisamente il numero di livelli di cui la piramide é composta, e quindi di quante volte le dimensioni originarie dell immagine debbano essere dimezzate. Per meglio spiegare questo concetto consideriamo ad esempio di lavorare con un immagine di dimensioni 64x64 pixels e di scegliere opportunamente una variabile n_liv pari a 3 livelli (la scelta opportuna é dettata da ragioni prettamente pratiche al fine di evitare di lavorare con immagini che siano di dimensioni troppo ridotte, e tali quindi da impedire la corretta implementazione dell algoritmo, soprattutto per quanto riguarda il filtraggio con filtri di Gabor, aspetto che analizzeremo in seguito, ma che limita l immagine più piccola a non essere inferiore a 11x11 pixels). Questo significa che lavoreremo con 3 immagini di dimensioni 64x64, 32x32 e 16x16 pixels. A tal proposito, quindi, all inizio del nostro codice allochiamo lo spazio in memoria necessario alla realizzazione della piramide, che andiamo opportunamente a riempire non appena carichiamo le due immagini (destra e sinistra): l allocazione avverrá ovviamente per due piramidi, una per imma-

CAPITOLO 4. IMPLEMENTAZIONE 30 gine, che saranno peró completamente identiche strutturalmente (si veda la funzione allocate_for_pyramid per la struttura, e pyramid_no_alloc per riempirla). 4.4.2 Buffer Una volta creata la piramide, andiamo a svolgere una serie di calcoli per ogni immagine in essa contenuta, e il risultato di tali operazioni viene inserito in un altra struttura appositamente progettata e allocata: il buffer. Il buffer é, per noi, forse la struttura principale, nonché quella piú usata e piú consistente dal punto di vista dell occupazione della memoria, e gestita in modo tale da essere comune per le due immagini, destra e sinistra, peró differenziate tramite l uso di indici. Anche in questo caso si nota la presenza di numerosi puntatori, la cui gestione non si é sempre rivelata semplice, ma é stato proprio per far fronte alla difficoltá di gestione di una parte di memoria cosí consistente che si é rivelato necessario pensare, prima, e realizzare, poi, una struttura ordinata e precisa. Per l allocazione in memoria del buffer si richiedono: il numero di livelli della piramide (così che per ogni riproduzione dell immagine di partenza si possa svolgere l intero lavoro), il numero di frame, una variabile sx_dx, le dimensioni dell immagine originaria. Come possiamo notare dalla figura 4.1, dopo aver dedicato uno spazio per ogni livello della piramide, siamo andati ad allocare la memoria anche per un determinato numero di frame, in quanto abbiamo pensato ad uno sviluppo

CAPITOLO 4. IMPLEMENTAZIONE 31 * 7.. - 4 5 : 4 -. 4 ) -. ) 5 1, : 1 1 8-1 2 1 4 ) 1, - ) 6 4 1 + - + 6 - - 6-1 4 1 5 7 6 ) 6, -. 1 6 4 ) / / 1 5 2 ) 1 ) - 4 1-6 ) - 6 1 4-1 ) 6 4 1 + - + 6 - - 6-1 4 1 5 7 ) 6, - 5 0 1. 6 1 2 0 ) 5 -. ) 5 1 4 1-6 ) - 6 1 Figura 4.1: Struttura del Buffer futuro del nostro lavoro: il calcolo della disparitá unito allo studio del flusso ottico, e quindi studiare non solo due immagini statiche, bensí due immagini in movimento (quindi con un numero di frame superiore a uno, che é il valore che abbiamo usato nel nostro codice). Abbiamo poi pensato a ogni singola immagine, andando quindi ad allocare due spazi in memoria che abbiamo sfruttato per distinguere l immagine destra (per noi con indice a 0) dalla sinistra (indice a 1). Analogamente interviene la variabile sx_dx, che serve, all interno di un if/else, per capire con quante immagini stiamo effetivamente lavorando

CAPITOLO 4. IMPLEMENTAZIONE 32 (una sola o due) e come gestirle: per noi le immagini saranno sempre due, e in questo modo, considerate le parti reale e immaginaria e gli orientamenti (l immagine verrá filtrata con 8 filtri di Gabor, ognuno orientato in maniera differente), siamo andati ad allocare la memoria per quanto riguarda gli shift in phase. Proprio in base al valore di sx_dx possiamo gestire gli shift in phase, e andare ad allocare, pertanto, la quantitá di memoria per contenere i risultati dell elaborazione basata sullo shift dell immagine destra (7 fasi) e dell immagine sinistra (questa non é elaborata con filtri con diverse fasi, quindi abbiamo un solo posto in memoria). 4.5 Struttura dell algoritmo Lo schema in figura 4.2 introduce l idea base su cui é strutturato il nostro algoritmo, le cui singole fasi verranno descritte nel dettaglio nei capitoli successivi.

CAPITOLO 4. IMPLEMENTAZIONE 33 I E A N F = @ B E J H = C C E I F = E = A & H E A J = A J E I D E B J E F D = I A % B = I E I E M = H F E C A A H C E = A F F = E A @ E I F = H E J J J = A A H C A? > E = E A @ E I F = H E J @ A A @ E L A H I A I? = A I E? K J E L = E @ A E E = E A @ = J E E L = E @ E I E = F F A @ E @ E I F = H E J @ A B E E J E L A Figura 4.2: Schema dell algoritmo per il calcolo della disparitá

Capitolo 5 Filtraggio Spaziale e Sfasamento 5.1 I Filtri di Gabor Il principio di partenza per l elaborazione basata sullo shift di fase prevede che a ogni traslazione nello spazio corrisponda una differenza di fase nel dominio delle frequenze e, a sua volta, la differenza di fase verrá utilizzata per il calcolo della disparitá. Ci serviremo di una serie di filtri, ognuno dei quali fornirá la propria risposta per un determinato range di disparitá: decodificando opportunamente i risultati di tutti questi é possibile ricavare, per ogni pixel, il corrispondente valore di disparitá [Fleet et al., 1996]. I filtri che utilizzeremo prendono il nome dall ingegnere e fisico ungherese Dennis Gabor, che nel 1946 presentó i filtri ideati da lui stesso, e caratterizzati dal minimo prodotto ampiezza spaziale-larghezza di banda ottenibile per filtri lineari a valori 34

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 35 complessi. Tale soluzione ha permesso di risolvere i problemi legati alla coesistenza di un alta discriminazione spaziale e una buona selettivitá in frequenza. Si puó osservare come i casi limite producano effettivamente risultati poco soddisfacenti. Se si considera come funzione finestra un impulso, si otterrá massima determinazione nello spazio ma nello stesso tempo minima in frequenza; il risultato é opposto se la funzione finestra é una costante. I filtri ideati da Gabor risultano essere il miglior compromesso. Riportiamo di seguito l espressione di un filtro monodimensionale: g(x x 0 )=e (x x 0 ) 2 2σ 2 e jω 0(x x 0 ) Come si può notare, il risultato é dato dal prodotto tra due fattori: il primo é un inviluppo gaussiano con deviazione standard σ, mentre il secondo una funzione armonica la cui pulsazione é rappresentata da ω 0 che identifica anche il centro dello spettro; x 0 determina invece la posizione del filtro. La funzione g può essere scomposta in parte reale e parte immaginaria ottenendo quindi: g even (x x 0 )=e (x x 0 ) 2 2σ 2 cos [ω 0 (x x 0 )] g odd (x x 0 )=e (x x 0 ) 2σ 2 sin [ω 0 (x x 0 )] Si deduce quindi che la parte reale ha simmetria pari mentre la parte immaginaria ha simmetria dispari. 2

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 36 5.2 Filtraggio: implementazione e Padding La prima operazione che viene effettuata, una volta ricevuta in ingresso l immagine, é il filtraggio nel dominio spaziale tramite un set di filtri di Gabor orientati secondo 8 orientamenti. Il numero dei filtri é stato scelto in modo tale da coprire in maniera sufficientemente completa tutto lo spettro di frequenze. Vedremo che sará successivamente integrato da 7 valori di fase che ci consentono invece di cogliere al meglio i valori della disparitá lungo la direzione ortogonale ad ogni filtro (ogni filtro, infatti, rileva solo la disparitá lungo la sua direzione ortogonale). Ci troveremmo quindi a dover compiere 56 convoluzioni, ma questo comporterebbe un elevato costo computazionale, andando contro il nostro obiettivo di fornire un codice quanto piú veloce possibile: vediamo quindi nel seguito come abbiamo risolto tale problema. Il set é caricato da MATLAB sotto forma di matrice 9x11 e in seguito opportunamente elaborato tramite la funzione load_spatial_gabor_fiter _alloc. Ricevuta in ingresso la matrice dei filtri, ció che si vuole fare é salvare in una struttura opportuna ciascuna riga separatamente. La convoluzione mediante matrici bidimensionali si puó eseguire partendo da due array monodimensionali ed effettuando quindi due convoluzioni successive. L alternativa appena suggerita permette di diminuire il costo computazionale e contemporaneamente aumentare le prestazioni in termini di tempo dell esecuzione dell algoritmo. La scomposizione della matrice 9x11 permetterá

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 37 di seguire questo approccio ponendo in una struttura ordinata tutti i vettori riga; sará la funzione successiva ad occuparsi della scelta, dell eventuale trasposizione e dell ordine con il quale utilizzare gli array in base al risultato che si vuole ottenere. Il codice di tale funzione risulta essere abbastanza semplice: una malloc permette di creare la struttura temp_filter che verrá riempita tramite due cicli for annidati. Il primo permette di selezionare ciascuna riga e definire le dimensioni del corrispondente elemento in temp_filter, il secondo itera su ciascun elemento della riga per effettuarne la copia. A questo punto viene quindi effettuato il filtraggio mediante filt_spatial _gabor_no_alloc la quale riceve in ingresso l immagine su cui lavorare e i filtri ottenuti tramite la funzione appena descritta. La prima operazione che deve essere compiuta peró, dopo aver effettuato tutte le dichiarazioni e le allocazioni necessarie delle variabili che saranno utilizzate in seguito, è il padding, svolto dall omonima funzione. Di fatto il problema si presenta ogni qualvolta si effettui la convoluzione tra una matrice, che nel nostro caso é l immagine, e un altra matrice, che chiameremo filtro. Questo agisce su ogni pixel dell immagine iniziale mediante un calcolo matematico che coinvolge i pixels intorno a quello in esame; é facile notare come il problema si presenti lungo i bordi. É necessario pertanto scegliere convenzionalmente come procedere: nel nostro caso si é preferito aumentare le dimensioni dell immagine di partenza in modo tale da ottenere al termine del filtraggio una matrice avente sempre le stesse dimensioni: tale approccio richiede peró che siano stabiliti altri due parametri.

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 38 Per prima cosa bisogna individuare il numero corretto di pixels da aggiungere e conseguentemente quali devono essere le dimensioni della nuova immagine. Tale scelta é legata alle dimensioni del filtro che si applica, e piú precisamente a ciascun bordo deve essere aggiunto un numero di pixels pari alla metá meno uno della dimensione del filtro. Nel nostro caso infatti il filtro risulta avere dimensione 11 e conseguentemente l immagine all uscita di padding_alloc presenta le due dimensioni incrementate di 10. In secondo luogo bisogna assegnare ai nuovi pixels dei valori opportuni: il nostro codice fornisce due alternative che é possibile selezionare tramite un opportuna stringa passata come parametro alla funzione: ˆ L opzione samezero é quella impostata nel nostro codice per effettuare le prove e i tests; essa prevede semplicemente che tutti i pixels aggiunti siano posti a zero. ˆ samerepeat, invece, consiste nel riproporre i valori sul bordo anche nei pixels della cornice. In entrambi i casi i risultati validi si troveranno proprio in corrispondenza dell immagine di partenza mentre quelli esterni non saranno corretti e pertanto dovranno essere eliminati. Tale operazione viene effettuata automaticamente durante il filtraggio e questo porta quindi ad avere l immagine filtrata al termine delle due convoluzioni con dimensioni pari a quelle di partenza. A questo punto é possibile lavorare sulla nuova immagine ed effettuare il filtraggio vero e proprio.

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 39 Come descritto in precedenza, per ottenere il risultato voluto, effettuiamo due convoluzioni consecutive partendo dall immagine in uscita da padding_ alloc con due array monodimensionali posti verticalmente o orizzontalmente secondo un ordine opportuno. 5.3 Le funzioni usate per la convoluzione Le funzioni che permettono di compiere le convoluzioni secondo tutte le loro combinazioni sono quattro: é necessario infatti distinguere quali coinvolgano righe, quali colonne e inoltre se sia necessario o meno effettuare una nuova allocazione per il risultato o se sia sufficiente sovrascriverlo. Entrando maggiormente nel dettaglio osserviamo quali differenze presentano tali funzioni. In ciascun caso la lettera finale indica sempre se la convoluzione venga effettuata con un vettore riga ( r ) o un vettore colonna ( c ). Nel primo caso il filtro viene mantenuto cosí come viene passato alla funzione, nel secondo caso invece viene trasposto invertendo le dimensioni ed ottenendo così un vettore colonna. Come accennato, le funzioni si distinguono inoltre in alloc e no_alloc: la differenza é rappresentata dalla presenza o meno di una malloc che permette l allocazione di una nuova matrice che conterrá il risultato. Ciascuna matrice risultante verrá posta all interno della struttura buffer nella corretta posizione, in modo da mantenere costantemente un ordine, e poter effettuare poi le successive operazioni partendo da queste, fornendo alle funzioni successive sempre la stessa struttura.

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 40 Si può osservare che filt_spatial_gabor_no_alloc viene chiamata due volte consecutive per effettuare il filtraggio inizialmente sull immagine sinistra e successivamente sull immagine destra. Tale funzione inserirá i risultati in buffer del quale sono giá stati specificati livello della piramide e frame; nel primo caso, si andrá a riempire la parte della struttura riguardante l immagine sinistra, nel secondo, l altra metà. Quanto descritto finora é un analisi generale dei principali aspetti su cui si basa la funzione. Si vuole ora entrare piú nello specifico per vedere in quale modo quanto appena descritto venga utilizzato nei differenti casi. Si consideri la convoluzione relativa all orientamento 0, il codice corrispondente è il seguente: convolution2d_valid_allocc(imm_padd, fil[0], &temp_c); convolution2d_valid_no_allocr(temp_c, fil[1], &temp_ris[0][0][0]); convolution2d_valid_no_allocr(temp_c, fil[2], &temp_ris[1][0][0]); La prima funzione riceve in ingresso l immagine le cui dimensioni sono state aumentate da padding_alloc e il vettore filtro contenuto nella posizione 0 della struttura fil. Al suo interno il vettore verrá trasposto e il risultato della convoluzione salvato in temp_c la cui allocazione é avvenuta all interno della funzione stessa. La chiamata successiva permette di effettuare la seconda e ultima convoluzione. Questa volta si utilizza il filtro in posizione 1 come vettore riga e il risultato viene posto nell opportuna posizione all interno della struttura buffer: parte reale, orientamento 0, fase 0.

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 41 Infine, l ultima funzione permette di ottenere il filtraggio della corrispondente parte immaginaria. Analogamente, ma con una combinazione diversa di filtri, si otterranno le immagini filtrate relative all orientamento π. Per quanto riguarda invece 2 π e 3π i risultati sono ottenuti tramite somme e differenze di particolari 4 4 combinazioni di convoluzioni. Di seguito il codice a cui si fa riferimento: convolution2d_valid_allocc(imm_padd, fil[3],&temp_c); convolution2d_valid_no_allocr(temp_c, fil[3], &temp_1); convolution2d_valid_no_allocr(temp_c, fil[4], &temp_2); convolution2d_valid_allocc(imm_padd, fil[4], &temp_c); convolution2d_valid_no_allocr(temp_c, fil[3], &temp_3); convolution2d_valid_no_allocr(temp_c, fil[4], &temp_4); π 4 subtraction_matrixipp_no_alloc(temp_1, temp_4, &temp_ris[0][2][0]); addition_matrixipp_no_alloc(temp_2, temp_3, &temp_ris[1][2][0]); 3π 4 addition_matrixipp_no_alloc(temp_1, temp_4, &temp_ris[0][6][0]); subtraction_matrixipp_no_alloc(temp_3, temp_2, &temp_ris[1][6][0]); Mediante le prime sei funzioni si ottengono le quattro convoluzioni che permettono di giungere ai risultati desiderati se sottratte o sommate opportunamente. In modo analogo si procede per determinare le immagini filtrate, distinte in parte reale e parte immaginaria, relative agli orientamenti π, 7π, 3π e 5π. 8 8 8 8

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 42 All interno del codice sono state utilizzate, ove possibile, le funzioni ippifree le quali hanno permesso di deallocare le matrici temporanee di cui ci siamo serviti per effettuare i calcoli sempre in modo tale da gestire nel modo migliore la memoria, uno dei problemi principali che ci si é trovati ad affrontare. 5.4 Verifica del Codice Attraverso la Risposta all Impulso Per effettuare un controllo del codice appena descritto é stata utilizzata un immagine ben precisa in ingresso, scelta in modo tale da sapere cosa ci saremmo dovuti aspettare in uscita e quindi poter verificare la correttezza o meno della funzione: l immagine a cui abbiamo fatto riferimento é un impulso ovvero un punto posto al centro della matrice. Tutti i pixels hanno valore zero escluso il punto centrale che assume valore uno. La figura 5.1 mostra il risultato che si é ottenuto in uscita da padding_alloc: per costruzione della matrice non é possibile distinguere i bordi della matrice dalla cornice in quanto tutti i pixels sono a zero (nero) escluso il punto centrale a uno (bianco). Partendo da dimensione 64x64, ora la matrice ha un numero di righe e colonne incrementato di dieci proprio come descritto in precedenza. Questo sará il punto di partenza per ottenere tutti i filtraggi desiderati. Ciò che ora ci aspettiamo é visualizzare per ciascun orientamento i filtri di Gabor, o, meglio, la corrispondente parte reale e parte immaginaria, orientati secondo il corretto angolo.

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 43! " # $ %! " # $ % Figura 5.1: Risultato del Padding di un impulso (64x64pixels)

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 44 Riportiamo quindi, in figura 5.2 e 5.3, i risultati: Figura 5.2: Filtraggio di un impulso, parte reale Figura 5.3: Filtraggio di un impulso, parte immaginaria 5.5 Sfasamento Come abbiamo potuto osservare, il filtraggio ha richiesto l uso di 8 filtri di Gabor, quelli che noi sostanzialmente andiamo a definire come gli 8 orientamenti con cui analizziamo le immagini. Il passo successivo ha previsto di occuparci di elaborare, con filtri a diverse fasi (rispetto a quelli applicati all altra immagine), una delle due immagini (per noi la destra) in modo tale da poter cogliere al meglio la mappa delle disparitá: infatti, ogni filtro é in grado di percepire la disparitá lungo la direzione ortogonale allo stesso, pertanto, non solo ci sono serviti gli 8 o- rientamenti, ma é stato anche necessario filtrare una delle due immagini con filtri sfasati, siccome le immagini presentavano una differenza in fase tra loro (abbiamo cioé seguito quello che viene chiamato modello dello shift in phase). Scegliendo 7 filtri con fasi diverse, si lavora con un vettore di disparità di 7 valori, e si devono analogamente usare 7 valori di fase, che abbiamo scelto opportunamente, e tali da essere uniformemente distribuiti nel range

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 45 [-1.5, 1.5] e spaziati di 0.5. Tali valori (ovvero l estremo dell intervallo e lo step) vengono passati alla funzione che calcola lo shiftin phase, insieme all immagine filtrata, che deve essere elaborata (cioé si calcola l immagine corrispondente ad un filtraggio con un filtro con fase diversa da zero), e al numero di orientamenti. void (float ph_extr, float ph_step, struct MatrixIpp ***imm_filt, struct MatrixIpp ****ris, int n_orient) Di fatto il codice é semplice, e si occupa di andare a calcolare sia la parte reale che la parte immaginaria dello shift dell immagine destra come segue: Re{immagine shiftata} = Re{imm filt}öcos(ph shift) =Imm{imm filt} Ösin(ph shift) Imm{immagine shiftata} = Imm{imm filt} Öcos(ph shift) + Re{ imm filt} Ösin(ph shift)} ph_shift è un angolo, e vale: 2π 1 i, dove i non é altro che l indice 4 di un ciclo for che scorre il vettore delle fasi: quindi assume i valori da -1.5 (ph_extr) a 1.5 a passi di 0.5. for (i =-ph_extr;i <= ph_extr ; i=i+ph_step) Gli accorgimenti che abbiamo dovuto usare nel nostro codice, e che hanno richiesto un minimo di attenzione, sono stati principalmente due: ˆ Per prima cosa abbiamo pensato di gestire i 7 indici delle fasi in modo tale che a sfasamento nullo corrispondesse l indice centrale, ovvero

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 46 l indice 3, nel rispetto della simmetria delle fasi stesse. Quindi abbiamo semplicemente copiato in tale posizione il risultato ottenuto dalla precedente operazione di filtraggio. Questo ha necessariamente comportato una appropriata gestione della variabile cont (da noi usata per indicare l indice) nel resto del codice, servendoci di un if/else che escludesse il caso cont==3. ˆ Il secondo aspetto su cui porre l attenzione riguardava la gestione della memoria. Avendo pensato alla struttura del buffer come a quella struttura che dovesse accogliere l immagine prima filtrata e poi eventualmente shiftata (nel caso della destra), il rischio era che si potesse fare confusione andando a lavorare su immagini già shiftate. Per risolvere tale problema abbiamo gestito fin dall inizio il caso corrispondente a uno shift nullo (quindi indice 3), usando tale valore (temp[0][h][3], per la parte reale, e temp[1][h][3] per la parte immaginaria), a quel punto inalterabile, per creare tutti i successivi sfasamenti. Vediamo il codice relativo allo shift nullo: for(h=0 ; h<n_orient ; h++) { copy_matrixipp_no_alloc(imm_filt[0][h][0],&temp[0][h][3]); copy_matrixipp_no_alloc(imm_filt[1][h][0],&temp[1][h][3]); } Ovviamente tale operazione è stata eseguita per tutti gli orientamenti. Si rivela necessario precisare che la semplicitá del codice interviene anche praticamente durante l implementazione, grazie alla possibilitá di sfruttare

CAPITOLO 5. FILTRAGGIO SPAZIALE E SFASAMENTO 47 le librerie IPP: come già accennato in precedenza, infatti, le funzioni messe a disposizione da Intel hanno la caratteristica ed il vantaggio di poter lavorare su matrici, con la particolaritá di gestirle direttamente pixel a pixel. In questo modo, quindi, il codice non solo é stato piú conciso, ma é stato soprattutto piú intuitivo e meno laborioso. Riportiamo in figura 5.4 e 5.5 una rappresentazione grafica dell elaborazione basata su shift di un immagine di 64x64 pixels rappresentante un impulso (quindi un solo punto, come nel caso del filtraggio al capitolo precedente): Figura 5.4: Esempio di shift di un impulso, parte reale Figura 5.5: Esempio di shift di un impulso, parte immaginaria

Capitolo 6 Modello a Energia e Decodifica della Popolazione 6.1 Scelta del Modello Nel nostro codice utilizziamo il modello a energia e la decodifica della popolazione come sostitutivi di quelli che vengono definiti metodi di misura. Questa scelta é dettata dal fatto che noi vogliamo giungere al calcolo della disparitá a partire da due immagini stereoscopiche di due differenti telecamere, vedendone peró sempre il collegamento con le immagini percepite dagli occhi umani. In figura 6.1 sono riportati schematicamente i passaggi che, a partire dal calcolo dell energia, ci consentono di raggiungere la mappa di disparitá finale. Come si nota, abbiamo associato a ogni fase le relative funzioni, che saranno analizzate piú in dettaglio in seguito. 48

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE49 4 4 ' B E J H E & H E A J = A J E % B = I E A A H C E = > E? K = H A F F K = J E @ A? F F K = J E @ A? @ E B E? = @ A = F F = E A @ E L A F E H = E @ A H E A J = A J B K @ E I F = H E J O A H C A E L A I K?? A I I E L? K J E L = E @ I J E = @ A = @ E I F = H E J F A H E E L A? H H A J A @ @ E I F = H E J J J = A Figura 6.1: Modello per il calcolo della disparitá Il modello a energia e, soprattutto, la decodifica della popolazione si basano, infatti, sullo studio della decodifica della popolazione neurale di cui si serve il cervello umano per rilevare la disparitá e processare le informazioni, sia come risposta di un singolo neurone, sia come risposta dell intera rete neurale. L uso di questa tecnica richiede un costo computazionale sufficientemente elevato, in quanto necessita di un gran numero di neuroni (consideriamo infatti il caso di risposta globale della rete neurale e non della singola cellula) per processare le informazioni in maniera completa, tuttavia tale costo si puó ridurre andando a servirsi di apposite tecniche di progetto.

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE50 6.2 Modello a Energia Questo modello ci permette di ottenere l energia come risposta di ogni cellula neurale: pertanto abbiamo un valore di energia per ogni orientamento e valore di fase. 6.2.1 Gestione della Memoria Anche in questo caso abbiamo opportunamente allocato la memoria per poter ospitare due strutture: una in cui salvare i vari valori di energia monoculare per gli 8 orientamenti e le 7 fasi, e una piú estesa in cui, invece, introduciamo anche le due immagini (energia binoculare). Per poter chiarire questo concetto, riportiamo in figura 6.2 e 6.3 le due strutture. La necessitá di dover usare due strutture invece di una soltanto, e, quindi, di dover occupare maggiore memoria, nasce dal fatto che calcoliamo l energia come unione delle energie dell immagine destra e sinistra: queste ultime, tra l altro, vedremo che saranno utili per la normalizzazione dell energia globale. 6.2.2 Calcolo dell Energia Entriamo adesso piú nello specifico, descrivendo il modello a energia e le nostre scelte di implementazione, ricordando, per queste ultime, che il passaggio da MATLAB a C, seppure forse meno intuitivo, é stato agevolato dalla possibilitá di poter disporre delle funzioni IPP. In questo modo, come giá ribadito, é stato possibile lavorare direttamente ed esplicitamente sulle matrici, lasciando alle funzioni stesse il compito di lavorare punto a punto (quindi iterare su ogni pixel).

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE51 - - 4 / ; 1 8-1 2 1 4 ) 1, - ) 6 4 1 + - + 6 - - 6 - - - 4 / 1 ) * 1 + 7 ) 4 -. ) 5 1 4 1-6 ) - 6 1 Figura 6.2: Struttura dell energia binoculare, - + - - 4 / ; 5 :. ) 5 1, : 1 8-1 2 1 4 ) 1, - ) 6 4 1 + - + 6 - - 6 - - - 4 / 1 ) + 7 ) 4-5 1 1 5 6 4 ) 4 1-6 ) - 6 1 ) 6 4 1 + - + 6 - - 6 - - - 4 / 1 ) + 7 ) 4 -, - 5 6 4 ). ) 5 1 Figura 6.3: Struttura dell energia monoculare

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE52 Iterando opportunamente sugli orientamenti e sulle fasi (ad esclusione del calcolo dell energia per l immagine sinistra, che non shifta, e che quindi viene calcolata nel for degli orientamenti ma all esterno di quello delle fasi), calcoliamo E (l energia globale), El ed Er (le energie sinistra e destra), come segue: E = (pari left + dispari right ) 2 + (dispari left + pari right ) 2 E r = dispari 2 right + pari2 right E l = dispari 2 left + pari2 left Entrando nello specifico nel codice, abbiamo: for(h=0 ; h<n_orient ; h++) { //creiamo El sqr_matrixipp_no_alloc(imm[1][0][h][0],&dec_temp[h][1][0]); sqr_matrixipp_no_alloc(imm[1][1][h][0],&dec_temp1); addition_matrixipp_inplace(&dec_temp[h][1][0], dec_temp1); for(k=0 ; k< n_phase ; k++) { addition_matrixipp_no_alloc(imm[1][0][h][0],imm[0][0][h][k], &temp_energy1); sqr_matrixipp_no_alloc(temp_energy1,&temp_energy[h][k]); addition_matrixipp_no_alloc(imm[1][1][h][0],imm[0][1][h][k], &temp1); sqr_matrixipp_no_alloc(temp1,&temp);

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE53 addition_matrixipp_inplace(&temp_energy[h][k], temp); sqrt_matrixipp_inplace(&temp_energy[h][k]); } } //creiamo Er sqr_matrixipp_no_alloc(imm[0][0][h][k],&dec_temp[h][0][k]); sqr_matrixipp_no_alloc(imm[0][1][h][k],&dec_temp1); addition_matrixipp_inplace(&dec_temp[h][0][k], dec_temp1); Rispettando il modo in cui abbiamo gestito la memoria, si puó notare appunto come, per Er ed El, si sia ricorsi alla struttura comprendente le due immagini, mentre per E il salvataggio sia avvenuto nella struttura generale. A questo proposito, chiamate dec_energy e energy le corrispondenti strutture (come segnalato sopra), chiariamo quanto appena detto attraverso le seguenti righe di codice: temp_energy = *energy; dec_temp = *dec_energy; 6.2.3 Creazione della Maschera A questo punto ci troviamo ad avere le energie monoculari e l energia binoculare. Il passo successivo riguarda le varie normalizzazioni ed il confronto con il valore della soglia: le due energie monoculari sono state normalizzate, ovvero divise per il loro massimo, ottenendo quindi El_norm ed Er_norm. Queste ultime sono state poi usate per creare la maschera, che ci é servita

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE54 per eliminare tutti i valori di energia per cui piú della metá degli orientamenti erano sotto la soglia. La maschera viene creata nel seguente modo: mask=el norm >th & Er norm >th Dove th é il valore della soglia, scelto dall utente. Nel momento in cui abbiamo implementato tale equazione, abbiamo usato due funzioni IPP, che settassero a 0 tutti i valori di El_norm ed Er_norm minori della soglia, e a 1 tutti i loro valori piú alti, cosí da trovarci ad avere, nella struttura dec_energy, una serie di zeri e uno che ci agevolassero nell uso della funzione IPP che svolge l and tra due matrici. In questo modo la maschera é stata creata in maniera abbastanza semplice ed intuitiva: for(h=0 ; h<n_orient ; h++) { threshold_ltval_inplace(&dec_temp[h][1][0], th, 0); threshold_gtval_inplace(&dec_temp[h][1][0], th, 1); for(k=0 ; k< n_phase ; k++) { threshold_ltval_inplace(&dec_temp[h][0][k], th, 0); threshold_gtval_inplace(&dec_temp[h][0][k], th, 1); } } for(h=0 ; h<n_orient ; h++)

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE55 for(k=0 ; k< n_phase ; k++) { and_matrixipp_inplace(&dec_temp[h][0][k], dec_temp[h][1][0]); mul_matrixipp_inplace(&temp_energy[h][k], dec_temp[h][0][k]); } Nel for più esterno lavoriamo su El, mentre nel for più interno agiamo su Er che é legata all immagine che shifta; con l and andiamo a creare la maschera, mentre la moltiplicazione implementa l equazione E=E*mask. É a questo punto che normalizziamo anche l energia binoculare. 6.3 Decodifica della Popolazione Il vero controllo sull energia viene fatto nella funzione population_no_alloc, dove valutiamo quindi se il filtro abbia risposto in maniera corretta. In questa funzione, inoltre, andiamo ad ottenere, per ogni orientamento, un solo valore di disparitá a partire dai valori di energia (eliminiamo quindi le fasi), o meglio, una stima della disparitá. Le strutture che conterranno tali valori di disparitá sono, in questo caso, allocate_for_population e allocate_for_decomposition_of_population, quest ultima pensata per allocarne le componenti orizzontale e verticale, cosí da ricondurci al sistema di coordinate cartesiane. Consideriamo adesso nel dettaglio come si arriva al calcolo della disparitá attraverso la decodifica della popolazione, considerando che adesso si lavora pixel a pixel, pertanto iteriamo su ogni singolo punto della matrice contenente i valori dell energia.

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE56 Inoltre, dalla funzione population_no_alloc viene tornata una maschera per ogni orientamento, che é diversa da quella calcolata in precedenza nella funzione energy_no_alloc, perché ci permette di trovare i valori di energia per cui i filtri non funzionano correttamente, o per cui si trovano dei valori di disparità non validi. Per ogni orientamento, abbiamo trovato il massimo ed il minimo della matrice dell energia, e abbiamo ragionato come segue: ˆ Se il massimo é pari a 0 (e di conseguenza lo é anche il minimo), la maschera viene posta a 0 nel punto corrispondente, mentre il relativo punto nella popolazione assume un valore fittizio, una sorta di valore sentinella che testimoni che quel valore di disparitá non é valido (infatti, se cosí non facessimo,arriveremmo ad incontrare una divisione per zero). ˆ Se, invece, il valore massimo é diverso da zero, abbiamo che: E norm = E min(e) max(e) min(e) Se, poi, E norm assume un valore minore di 0.7 (<3dB), allora viene posto a 0. In questa situazione, il valore della maschera viene settato a 1. energy[h][k].ptr[(r*temp)+c]=((energy[h][k].ptr[(r*temp)+c])- - min_temp1)/(max_temp1-min_temp1) if(energy[h][k].ptr[(r * temp) + c]<0.7) energy[h][k].ptr[(r * temp) + c]=0;

CAPITOLO 6. MODELLO A ENERGIA E DECODIFICA DELLA POPOLAZIONE57 A questo punto troviamo il valore della popolazione come segue: d(i, j)= Σdisp E norm ΣE norm dove disp è il vettore contenente i sette valori di disparitá di cui abbiamo parlato in precedenza. num += disparity.ptr[k]*(energy[h][k].ptr[(r*temp)+c]); den += energy[h][k].ptr[(r * temp) + c]; temp_population[h].ptr[(r * temp) + c] = num/den; temp_mask[h].ptr[(r * temp) + c]=1; Di conseguenza, per ogni orientamento, otteniamo una mappa di disparitá provvisoria, che viene passata, insieme alla maschera, alla successiva funzione fulldisparity_no_alloc.

Capitolo 7 Calcolo della disparitá 7.1 Disparitá Totale Giunti a questo punto si ha per ogni orientamento del primo livello il valore della disparitá. É necessario quindi ottenere un unico valore unendo i risultanti ottenuti da ciascun orientamento. La struttura che conterrá tali risultati risulta essere quindi relativamente semplice: é sufficiente infatti che per ogni livello della piramide permetta di salvare distintamente la mappa di disparitá orizzontale e la corrispondente verticale. Questa struttura viene allocata da allocate_for_fulldisparity la quale riceve in ingresso solo il numero di livelli e le dimensioni dell immagine. Tale funzione viene chiamata per due volte consecutive durante le allocazioni di tutte le altre strutture necessarie: la prima volta con il numero di livelli effettivo della piramide mentre la seconda con un numero di livelli diminuito di uno e una nuova destinazione copy_full. 58

CAPITOLO 7. CALCOLO DELLA DISPARITÁ 59 La doppia allocazione risulterá indispensabile successivamente come vedremo per il warping e il merge. É possibile rappresentare graficamente le disparitá per ciascun orientamento mediante vettori sul piano cartesiano: Figura 7.1: vettori di disparità Dalla figura 7.1 si nota come a ciascuno degli otto orientamenti corrisponda un vettore il cui modulo rappresenta il relativo valore di disparitá. Lungo l asse delle ascisse troviamo quindi la disparitá corrispondente all orientamento 0 e a seguire in senso antiorario 1π 8, 1π 4, 3π 8, 1π 2, 5π 8, 3π 4 e 7π 8. La funzione full_disparity permette di trovare il vettore che meglio approssima questi determinando cosí la miglior stima della disparitá. Piú precisamente si otterranno le sue componenti orizzontale e verticale, le quali non sono altro che la proiezione del vettore risultato rispettivamente sull asse delle ascisse e delle ordinate [Theimer and Mallot, 1994].

CAPITOLO 7. CALCOLO DELLA DISPARITÁ 60 All interno della funzione viene effettuato un controllo per verificare il buon funzionamento dei filtri basandoci sulla maschera prodotta da population _no_alloc, e producendo in uscita una seconda maschera, mask2. In particolare, la sequenza di cicli for e istruzioni if permette di individuare, per ogni pixel, quali di questi hanno un valore di disparitá accettabile per meno della metá dei filtri in modo tale da escluderli dalla mappa di disparitá totale. mask2 é una matrice posta inizialmente a zero, i cui valori vengono settati a 1 nel caso in cui si verifichino due condizioni: la disparitá sia valida per piú della metá dei filtri, e i calcoli non portino ad una divisione per zero. Quindi tale maschera tiene conto di dove siano i valori validi. Il codice di tale funzione risulta meno intuitivo rispetto a quanto giá analizzato finora; le istruzioni seguono comunque fedelmente il procedimento su cui si basa il codice MATLAB di riferimento. In generale, il concetto di fondo é quello appena descritto; le scelte delle operazioni e dei nomi delle variabili sono semplicemente dovute a un maggiore avvicinamento alle espressioni matematiche di base piuttosto che a una comprensione piú intuitiva e veloce del codice.

Capitolo 8 Livelli Successivi Ciò che si é quindi ottenuto fino a questo punto é una stima della disparitá riferita alle immagini a piú bassa risoluzione: é necessario ora iterare il processo sulle altre scale, ovvero sui diversi livelli della piramide, in modo tale da poter raffinare il risultato ottenuto per una stima della disparitá che sia maggiormente veritiera. Come, ovviamente, si puó comprendere, all aumentare del numero dei livelli, aumenta conseguentemente la precisione con cui vengono create le matrici di disparitá orizzontale e verticale. Abbiamo quindi ripetuto tutto il procedimento svolto fino ad ora, iterando, attraverso un ciclo for, sul numero dei livelli: le funzioni utilizzate sono sostanzialmente quelle giá descritte in precedenza, con l aggiunta peró di qualche accorgimento rispetto al primo livello. Tali funzioni hanno lo scopo di consentirci di usare i risultati trovati per un livello, per migliorare quelli del livello successivo (è il caso, ad esempio, della funzione expand che agisce sulle mappe di disparità). 61

CAPITOLO 8. LIVELLI SUCCESSIVI 62 Analizziamo ora in dettaglio le funzioni contenute all interno del ciclo. 8.1 Ridimensionamento delle Mappe di Disparitá La prima operazione effettuata é l expand: per poter confrontare i risultati di due differenti scale é necessario portarli alle stesse dimensioni. Nel nostro caso, infatti, alla funzione passiamo la mappa di disparitá del livello precedente affinché le sue dimensioni vengano raddoppiate e quindi adattate al livello corrente. Questo aspetto agevolerá pertanto anche l integrazione tra le due mappe di disparitá trovate a livelli differenti (come si vedrá nella funzione merge). L omonima funzione, interna a expand, si serve dell interpolazione bilineare per attribuire un valore ai pixels che non lo possiedono, basandosi su quelli circostanti che sono noti. In generale l interpolazione é un procedimento che permette di determinare approssimativamente valori incogniti partendo da quelli conosciuti. Ne esistono diverse tipologie a seconda dei principi su cui si basano: la lineare, per esempio, fornisce i valori incogniti presenti tra due punti noti per mezzo della retta che unisce questi due punti. Nel nostro caso ci siamo serviti di un interpolazione bilineare che non coinvolge piú solo due punti come nel caso della lineare bensí quattro. Anche in questa circostanza ci si é trovati di fronte a un problema giá affrontato precedentemente durante le convoluzioni spaziali, ovvero la gestione dei pixels lungo i bordi dell immagine.

" * * CAPITOLO 8. LIVELLI SUCCESSIVI 63 In questo caso si é deciso di replicare i valori noti piú vicini in modo da tale fornire sempre all interpolazione i dati necessari al calcolo. Figura 8.1: matrice Calcolo dell interpolazione bilineare di un punto interno alla Lo schema in figura 8.1 mostra quali valori assumano i nuovi pixels partendo da quelli giá noti circostanti ovvero A, B, C e D. In prossimitá dei bordi, invece, si segue l approccio in figura 8.2: ) ) ) * ) * + + * + * + * * Figura 8.2: Calcolo dell interpolazione bilineare per un punto sul bordo della matrice Nel riquadro ritroviamo il caso generale di interpolazione bilineare, al di fuori di esso invece é possibile osservare come viene gestito il problema

CAPITOLO 8. LIVELLI SUCCESSIVI 64 dei bordi. Non avendo elementi sufficienti per applicare l espressione che determina il valore dei pixels al contorno si ripropone il piú vicino. Il risultato finale é una matrice di dimensioni raddoppiate e quindi uguali all immagine presa in esame al livello successivo; ció permetterá in seguito di confrontare i due risultati. L ultima considerazione riguarda la moltiplicazione per due del valore di ciascun pixel che viene effettuata sull uscita della funzione expand: expand_no_alloc(full_in[i], &temp_full[i]); mul_with_costant_matrixipp_inplace(&temp_full[i], 2); Questa operazione risulta necessaria in quanto il movimento di un pixel a una determinata scala risulta uguale a 2 pixels alla scala superiore. Dopo aver esteso le dimensioni delle mappe di disparità, procediamo a svolgere un operazione analoga per la maschera che accompagna tali mappe, ovvero mask2, poiché dobbiamo di volta in volta confrontare la maschera trovata al livello attuale con quella del livello precedente. Ecco quindi che interviene la funzione expand_mask, identica a expand_mulx2_fulldisparity _no_alloc ad eccezione della moltiplicazione per due, e che riceve in ingresso le dimensioni a cui adattare la maschera. Le operazioni che seguiranno le funzioni di expand saranno quindi effettuare i filtraggi sulle immagini sinistra e destra del successivo livello della piramide dopodiché procedere con lo shift in phase: filt_spatial_gabor_no_alloc(pyramsx[i], fil_spaz, &buff[i][0][1], c, soglia_spaz); filt_spatial_gabor_no_alloc(pyramdx[i], fil_spaz, &buff[i][0][0],

CAPITOLO 8. LIVELLI SUCCESSIVI 65 c, soglia_spaz); shift_in_phase(ph_extr, ph_step, buff[i][0][0], &buff[i][0][0], n_orient); Dal codice si puó osservare come le chiamate alle funzioni siano del tutto analoghe a quanto visto in precedenza per l immagine a risoluzione piú bassa. L unica differenza é rappresentata dalla presenza della variabile i che permette di iterare sui livelli della piramide tramite il ciclo for. Questa permette infatti di selezionare una differente scala dell immagine dalle strutture pyramsx e pyramdx e nello stesso tempo quale parte della struttura buffer andare a riempire. 8.2 Warping Il warping é una delle funzioni piú importanti perché permette di raffinare, a ogni iterazione la stima della disparitá. Il warping agisce sull immagine destra, traslandola di una quantitá pari alla disparitá stimata all iterazione precedente. L immagine destra assomiglierá sempre di piú alla sinistra, riducendo cosí la disparitá residua che si dovrá stimare all iterazione successiva. Cosí facendo i filtri si troveranno a lavorare sempre piú vicini al loro punto di lavoro ottimale. Siccome serve per migliorare le mappe di disparitá trovate, quindi é utile quando ne abbiamo almeno 2 coppie (una coppia é composta da disparitá orizzontale e verticale), questa funzione la incontriamo soltanto all interno del ciclo for che itera sui livelli della piramide, perció per un valore n_liv >1.

CAPITOLO 8. LIVELLI SUCCESSIVI 66 Alla funzione warping_filt_gabor noi passiamo il buffer, la disparitá (orizzontale e verticale), il numero di frame (in realtá a noi non servono direttamente, ma si é pensato ad uno sviluppo futuro che calcoli anche il flusso ottico), il numero di orientamenti e fasi; vediamo adesso il codice piú nello specifico: temp_dispx=full_disp[vx]; temp_dispy=full_disp[vy]; for(j = 0 ; j < 2 ; j++) for(w = 0 ; w < n_orient ; w++) { for(k=0; k<n_phase; k++) warping(buf[dx][j][w][k],temp_dispx,temp_dispy, &copy[dx][j][w][k]); copy_matrixipp_no_alloc(buf[sx][j][w][0],&copy[sx][j][w][0]); } Possiamo osservare che, di fatto, la funzione che noi chiamiamo dal main non é la funzione che esegue il warping vero e proprio, ma é quella che valuta quale delle due immagini subisce il warping, e quale viene invece semplicemente copiata cosí come é: sia il risultato del warping che l immagine sinistra vengono salvate nella struttura copy_buff, che é assolutamente analoga al buffer. Analizziamo adesso la funzione warping vera e propria, ricordando che si basa sull interpolazione bilineare, la cui logica é stata giá introdotta quando abbiamo parlato della funzione expand.

O N H CAPITOLO 8. LIVELLI SUCCESSIVI 67 H M O N Figura 8.3: Warping Considerando il piano cartesiano (figura 8.3), attribuiamo a x le righe, mentre a y le colonne; in particolare: x=x-δ x y=y-δ y Dove: X=j- δ x e Y=i-δ y. I pixels che subiscono il warping valgono, seguendo quanto imposto da MATLAB: x w =floor[x(x,y)]= floor[j- δ x ]= j-floor[δ x ] y w =floor[y(x,y)]= floor[i- δ y ]= i-floor[δ y ] A questo punto calcoliamo il valore di x s e y s, che ci servono per l interpolazione lineare: x s =X- x w =(j- δ x )- (j-floor[δ x ])= -δ x + j x w y s =Y- y w =(i- δ y )- (i-floor[δ y ])= -δ y + i y w Dal punto di vista del codice abbiamo: for (i = 0 ; i < dest->m ; i++) { dummy = i * app;

CAPITOLO 8. LIVELLI SUCCESSIVI 68 for (j = 0 ; j < dest->n ; j++) { off = j + dummy; dest->ptr[off] = 0; y_w = i - floor((int)(dy.ptr[off])); x_w = j - floor((int)(dx.ptr[off])); if((y_w < dest->m - 1) && (y_w >= 0) && (x_w < dest->n - 1) && (x_w >= 0)) { off_w = x_w + (y_w * app); off_x = - (Ipp32f)(x_w) - dx.ptr[off] + j; //xs off_y = - (Ipp32f)(y_w) - dy.ptr[off] + i; //ys } } } //interpolazione bilineare dest->ptr[off] = sorg.ptr[off_w]*((1 - off_x)*(1 - off_y)); dest->ptr[off] += sorg.ptr[off_w + 1]*((off_x)*(1 - off_y)); dest->ptr[off] += sorg.ptr[off_w + app]*((1 - off_x)*(off_y)); dest->ptr[off] += sorg.ptr[off_w + app + 1]*((off_x)*(off_y)); Seguono il warping quattro funzioni giá analizzate in precedenza: energy_no_alloc(copy_buff[i][0], &energy[i],&dec_energy[i], n_orient, n_phase,th);

CAPITOLO 8. LIVELLI SUCCESSIVI 69 population_no_alloc(energy[i], disparity, &population[i], &mask[i], n_orient, n_phase); decomposition_of_population_no_alloc(population[i], &dec_population[i], n_orient); fulldisparity_no_alloc(dec_population[i], &copy_full[i-1],mask[i], &mask2, n_orient, nc_min); 8.3 Somma delle disparitá ed eliminazione dei valori non validi Ricordiamo che stiamo adesso analizzando tutte quelle funzioni che hanno lo scopo di migliorare la disparitá trovata, e che, quindi, si pongono all interno del ciclo for che itera sui livelli: questo perché abbiamo almeno due valori da confrontare. Attraverso lo stesso principio visto per il primo livello della piramide si giunge alle mappe di disparitá finali mediante il calcolo dell energia e la decodifica della popolazione. Le strutture utilizzate per contenere i risultati sono copy_buff e copy_full: in questo modo si potrá sia avere ad ogni ciclo i valori relativi al corrispondente livello sia tenere conto di quanto ottenuto fino a quel punto dalle iterazioni precedenti per poi sommare di volta in volta i risultati.

CAPITOLO 8. LIVELLI SUCCESSIVI 70 La funzione successiva, merge, permetterá proprio di compiere questa operazione come verrá descritto nel paragrafo successivo. 8.3.1 Funzione Merge Calcolata la disparitá al primo livello, abbiamo che l expand adatta le sue dimensioni a quelle del livello successivo, salvando le mappe all interno della struttura full_disparity[i]; successivamente, poi, con la funzione full_ disparity_no_alloc andiamo a calcolare la disparitá al secondo livello all interno della struttura copy_full[i-1]. Lo stesso avviene tra il secondo livello ed il terzo, il terzo ed il quarto e cosí via fino all esaurimento dei livelli e quindi all uscita dal ciclo for. Possiamo pertanto notare che, per ogni iterazione, abbiamo a disposizione due valori di disparitá assolutamente confrontabili perché contenuti in matrici di dimensioni uguali: questo per poter combinare adeguatamente le stime di disparitá ottenute ad ogni livello. Interviene allora la funzione merge che ha il compito di andare a sommare la stima di disparitá attuale con quella del livello precedente (e quindi copy_full[i-1] con full_disparity[i]). Tuttavia la somma non é sufficiente, in quanto é necessario effettuare dei controlli sul valore della stessa, per gestire eventuali valori invalidi: per valore invalido intendiamo che entrambi i dati sommati siano invalidi, altrimenti basta che uno dei due sia valido per avere un risultato valido. Consideriamo adesso il codice per maggiore chiarezza: merge(copy_full[i-1], &full_disparity[i])};

CAPITOLO 8. LIVELLI SUCCESSIVI 71 questa é la chiamata al codice dal main, e, come possiamo vedere, passiamo alla funzione le due disparitá, assumendo di salvare il risultato della somma in full_disparity[i], che sará poi la struttura che, al ciclo successivo, subirá l expand. Questo evidenzia che di fatto noi lavoriamo tra la disparitá attuale e quella della scala precedente, ma quest ultima racchiude in sé tutte quelle delle scale fino a lí analizzate. Osserviamo invece adesso la funzione: for(k = 0 ; k < 2 ; k++) addition_matrixipp_inplace(&merged[k], full_disp[k]); Il ciclo for é necessario per effettuare la somma tra le due corrispondenti mappe di disparitá (orizzontale e verticale), mentre, come già visto, la funzione addition_matrixipp_inplace si serve al suo interno di una funzione IPP per svolgere la somma punto a punto. Dal punto di vista dei controlli e della gestione dei dati invalidi andiamo a occuparci delle maschere in uscita dalla funzione fulldisparity_no_alloc nel seguente modo: temp=mask2.stp/sizeof(ipp32f); for(j=0; j<mask2.m;j++) for(k=0;k<mask2.n;k++) { if(mask2.ptr[(j*temp) + k]==0 && copymask2.ptr[(j*temp) + k]==0) mask2.ptr[(j*temp) + k]=0; else

CAPITOLO 8. LIVELLI SUCCESSIVI 72 mask2.ptr[(j*temp) + k]=1; } Seguendo fedelmente il codice MATLAB, e quindi quanto detto in precedenza, consideriamo invalidi quei valori del merge tali per cui entrambi i valori di disparitá sono invalidi. Ecco quindi che svolgiamo un and tra le maschere che ci aggiorni opportunamente il valore della variabile mask2, ovvero quella che al livello successivo subirá l expand. In questo modo, esauriti i livelli, mask2 terrá traccia di dove siano i valori invalidi di disparitá che dovranno essere eliminati. 8.3.2 Funzione Cut Invalid Prima di analizzare l ultima funzione, é bene fare una premessa: ci troviamo adesso nelle condizioni di poter calcolare la disparitá, in seguito a filtraggi e shift in phase, passando per energia e popolazione, di poterla sommare con quella del livello precedente e procedere in questi termini fino all esaurimento del ciclo for che itera sulle scale. Tutto questo tenendo conto dei valori invalidi, sia nel merge che nel corso del codice, servendoci, in quest ultimo caso, di una maschera; é bene ricordare di non confonderla con quella calcolata per l energia. Non rimane a questo punto che eliminare quelli che sono stati definiti come dati invalidi, e quindi opportunamente distinti dagli altri attraverso la maschera mask2. Applichiamo la funzione cut_invalid: void cut_invalid_at_end(struct MatrixIpp mask,struct MatrixIpp

CAPITOLO 8. LIVELLI SUCCESSIVI 73 **fullmerged, int nc_min) Vediamo come avviene l eliminazione: for(i = 0 ; i < merged[0].m ; i++) for(j = 0 ; j < merged[0].n ; j++) if(mask.ptr[(i*temp)+j]==0) { merged[vx].ptr[(i * temp) + j] = 0; merged[vy].ptr[(i * temp) + j] = 0; } Detto questo, quindi, il controllo serve per eliminare, e quindi cosiderare nulli, quei valori di disparitá corrispondenti a un valore della maschera finale che sia pari a 0, pertanto il dato viene cosiderato invalido.

Capitolo 9 Risultati Riportiamo a questo punto un esempio di calcolo della disparitá, utilizzando due immagini particolari (random dot). A partire dalla prima immagine, quella sinistra, ne abbiamo estratto un quadrato centrale, e lo abbiamo riportato nell immagine destra shiftato di un pixel in orizzontale (verso destra). Per maggiore chiarezza si rimanda alle figure 9.1 e 9.2. Il riquadro presente in figura é stato utilizzato per evidenziare il quadrato di cui abbiamo parlato sopra, poiché per costruzione delle immagini non sarebbe distinguibile. La scelta di queste particolari immagini, é stata dettata dalla necessitá di verificare con chiarezza la correttezza del nostro codice: ci aspettavamo, infatti, che le mappe di disparità registrassero valori significativi per la disparità orizzontale, e poco percettibili per quella verticale (si vedano le figure 9.3 e 9.4). Le mappe di disparitá in uscita rispecchiano quanto effettivamente ci aspettavamo di ottenere, ricordando che stiamo lavorando su immagini di dimensioni 64x64pixels e due livelli di piramide. 74

CAPITOLO 9. RISULTATI 75! " # $! " # $ Figura 9.1: Immagine sinistra! " # $! " # $ Figura 9.2: Immagine destra

CAPITOLO 9. RISULTATI 76 Figura 9.3: Mappa di disparità orizzontale Figura 9.4: Mappa di disparità verticale

CAPITOLO 9. RISULTATI 77 Il codice puó lavorare su immagini di qualunque dimensione, tuttavia noi possiamo usare immagini non oltre una certa dimensione a causa delle caratteristiche del calcolatore su cui il lavoro é stato svolto, avendo riscontrato problemi di memoria. In particolare noi abbiamo testato il codice con immagini di 64x64 pixels e due livelli della piramide. Per completezza, riportiamo adesso le mappe di disparitá (figure 9.5 e 9.7) a partire da due esempi di immagini stereoscopiche (figura 9.6 e 9.8) di dimensioni 256x256 pixel e ottenute iterando su 5 livelli della piramide: 50 50 100 100 150 150 200 200 250 50 100 150 200 250 250 50 100 150 200 250 Figura 9.5: Coppia di immagini stereoscopiche (Venus) 50 50 100 100 150 150 200 200 250 50 100 150 200 250 250 50 100 150 200 250 Figura 9.6: Mappe di disparitá di Venus

CAPITOLO 9. RISULTATI 78 50 50 100 100 150 150 200 200 250 50 100 150 200 250 250 50 100 150 200 250 Figura 9.7: Coppia di immagini stereoscopiche (Tsukuba) 50 50 100 100 150 150 200 200 250 50 100 150 200 250 250 50 100 150 200 250 Figura 9.8: Mappe di disparitá di Tsukuba 9.1 Tempi di esecuzione Ricollegandoci al nostro obiettivo principale, di riduzione dei tempi di calcolo delle mappe di disparitá rispetto al codice MATLAB, abbiamo provveduto a registrare i tempi di esecuzione dei due codici per poterli confrontare. Basandoci su una serie di prove, con immagini di dimensioni differenti [pixels] e diverse scale, abbiamo ottenuto i risultati in figura 9.9 (media delle prove in secondi): L applicazione dei filtri ad un immagine richiede che la dimensione di questa non risulti inferiore a 11x11 pixel, in modo tale da poter effettuare correttamente le convoluzioni. Questo é il motivo per cui la tabella in figura 9.5 presenta alcune celle vuote: applicare 4 livelli ad