Controllo faccia con rete neurale

Page 1

Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

Uso di ImageEN con altre librerie Molte volte è necessario utilizzare ImageEn insieme ad altri librerie come da esempio con FANN, la libreria relativa alle reti neurali. Anche in questo caso cercheremo di ideare una funzione che possa essere utilizzata all’interno di nostri progetti. Quando si deve eseguire un riconoscimento facciale è fondamentale eseguire precedentemente un identificazione del volto in modo tale da potere passare all’algoritmo di riconoscimento solo l’immagine del volto senza nessun altra parte al suo interno. Come abbiamo già detto precedentemente questa funzione di estrazione dei volti può essere eseguita sfruttando le trasformate HAAR. Questa metodologia classifica insiemi di bits presenti nell’immagine al fine d’identificare quelli relativi alla porzione del volto. Purtroppo esistono condizioni di insiemi cromatici presenti in alcune immagini acquisite che confondono il modello matematico creando in questo modo false immagini. Chiaramente il fatto di acquisire false immagini, credendo che questi siano volti, potrebbe condurre a problemi computazionali anche gravi. Per fare un esempio in uno dei progetti AMC avevamo collegato all’algoritmo di estrazione dei volti un codice che memorizzava le immagini dentro ad un database e successivamente a questo salvava su file AVI le azioni svolte dalla persona classificata. Questo sistema era indirizzato ad un utilizzo mediante telecamera nascosta in un ambiente che doveva essere controllato. L’installazione venne fatta di giorno e quindi i test fatti sulla tipologia delle immagini catturate non dava problemi. Durante la notte la luce cambio radicalmente passando da buio completo a luce fornita da lampadina ad incandescenza. Alcuni oggetti contro il muro illuminati in questo modo confondevano il sistema della trasformata haar creando immagini false le quali venivano memorizzate in continuazione dentro al database saturandolo nel giro di una sola notte. Come potrebbe essere fatta una funzione atta a controllare se un immagine acquisita è un volto ? Se avessimo un certo numero di facce e un altro di non facce potremmo utilizzare una rete neurale istruendola ad eseguire il controllo. Il programma dovrebbe essere composto da due parti e precisamente da una indirizzata ad eseguire il training e da un'altra che utilizzando i dati derivati dalla prima sia in grado di verificare se l’immagine è veramente un volto. Il programma dovrebbe essere in grado di mantenere un database di volti e di non volti e a richiesta dovrebbe creare un file di dati utilizzabile dalla rete neurale per risolvere il problema dato dalla domanda : ‘L’immagine rappresenta un volto ?’ Per fare questo utilizzeremo un altro tipo di oggetto implementato in ImageEn e precisamente nei campi ImageENDbView. Si tratta di campi immagine che sono agganciati ad un dataset di database. Il programma si suddivide in due parti funzionali. La prima si interessa di leggere e memorizzare le immagini dentro ad un database dopo averle ridimensionate e convertite in toni di grigio. La seconda invece prende le immagini memorizzate e dopo avere scritto un file di training idoneo al formato della rete neurale esegue la fase di apprendimento grazie alla quale viene scritto il file .NET adatto all’utilizzo con al rete neurale vera e propria. I dati vengono memorizzati in un database la cui struttura è la seguente : 1. 2. 3.

Numero SEQUENZIALE (+) Immagine GRAFICO BINARIO (B) Status LOGICO (L) © Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

Il primo viene incrementato automaticamente dal gestore del database. Il secondo campo è un campo BLOB BINARIO dentro al quale viene memorizzata l’immagine. L’ultimo campo dice se l’immagine abbinata è una faccia o no. Dopo aver creato una directory di lavoro attiviamo il DATABASE DESKTOP. Utilizzeremo i file database in formato PARADOX gestito tramite BDE. Per prima cosa creiamo l’ALIAS che ci permetterà di riferirci al DB utilizzando l’opzione ALIAS MANAGER sotto la voce di menu TOOLS. Dopo aver premuto NUOVO diamo come nome dell’alias ISFACE e settiamo il percorso in modo che punti alla directory appena creata. Confermiamo e chiudiamo l’alias manager. Ora creiamo la tabella con le informazioni appena viste precedentemente. Chiudiamo il database desktop a creiamo un nuovo progetto sotto C++BUILDER. Chiamiamolo anche questo ISFACE. Come abbiamo appena detto useremo la libreria FANN sfruttando le librerie modificate come abbiamo visto precedentemente. Vi ricordo brevemente che le librerie distribuite con FANN sono per Visual Studio per cui non sono compatibili con l’ambiente BORLAND. La conversione delle librerie è molto semplice in quanto si parte dalle DLL sfruttando il seguente comando inserito dentro ad un file DOS .bat oppure dando in sequenza le istruzioni : del fannfloat.lib impdef -a fannfloat.def fannfloat.dll implib -a fannfloat.lib fannfloat.def Chiaramente il nome fannfloat è dovuto al fatto che utilizziamo le librerie in virgola mobile. Prima di inziare il programma vero e proprio è necessario definire alcune cose. Le immagini che utilizzeremo dovranno essere 40x50 pixels. Usiamo una dimensione ridotta al fine di non caricare eccessivamente il lavoro della rete neurale. Le immagini adatte a fare questo lavoro possono essere prese da uno dei tanti database di volti utilizzati negli ambiti dei riconoscimenti facciali. Ne esistono diversi tra i quali : http://cvc.yale.edu/projects/yalefaces/yalefaces.html http://cvc.yale.edu/projects/yalefacesB/yalefacesB.html http://www.ri.cmu.edu/projects/project_418.html http://amp.ece.cmu.edu/projects/FIADataCollection/ http://cbcl.mit.edu/software-datasets/heisele/facerecognition-database.html http://mambo.ucsc.edu/psl/joehager/images.html Per un elenco di database più dettagliato andate a vedere la sezione legata al riconoscimento facciale.

© Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

Le immagini sopra rappresentano degli esempio di immagine valide per il nostro scopo. Per immagini di non facce potrete prendere immagini che non siano volti di persone. Come funziona la fase di training ? In pratica creeremo una rete neurale strutturata nel seguente modo. 3 STRATI (1 INGRESSO 1 NASCOSTO 1 USCITA) 2000 NEURONI IN INPUT (40x50 pixels = 2000 neuroni) 1 NEURONE OUTPUT (faccia/confaccia) 38 NEURONI NASCOSTI ( SQRT(2000+1)) Il calcolo usato per calcolare il numero dei neuroni nello strato nascosto è stato anche questo visto nella sezione delle reti neurali. Come passiamo i valori dei pixels ai neuroni d’input ? Ogni pixel è un valore compreso tra 0 e 255 a seconda del colore dello stesso. Dato questo metodo di rappresentazione potremmo usarlo per effettuare il passaggio di valori alla rete neurale. Il problema è che generalmente la rete neurale nelle sue funzioni interne utilizza valori compresi tra 0 e 1 (0.4327 ad esempio) per cui un metodo migliore potrebbe essere quello di passare i valori in questa modalità. Questo lo possiamo fare dividendo il valore di ogni pixel per 255. Vedremo comunque più avanti la routine per assegnare i valori alla rete neurale interagendo con ImageEn. Iniziamo ora a vedere la prima parte del programma ovvero quello indirizzato a mantenere visualizzato il database di immagini. Il programma deve possedere una form dentro ala quale devono essere presenti : 1 OGGETTO ImageEnView per leggere da file l’immagine 1 OGGETTO ImageEnProc associato al primo per convertire l’immagine letta. 1 OGGETTO ImageEnIO associato al primo per eseguire la lettura da file. 1 OGGETTO ImageEnDialog per creare la dialog di scelta 1 OGGETTO ImageEnDbView per memorizzare l’immagine dentro al database.

© Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

Oltre a questi oggetti fondamentali ci sono poi un'altra serie di altri utilizzati per copiare, selezionare, cancellare e svolgere atre azioni. Iniziamo quindi a posizionare sul video il primo campo di visualizzazione. Inseriamo subito anche gli oggetti abbinati al trattamento e all’ I/O dell’immagine. L’oggetto ImageEnView1 viene utilizzato come contenitore per l’immagine dopo che questa è stata selezionata da file. In ImageEn esistono altri oggetti che vengono utilizzati per compiere le funzioni nel programma come ad esempio selezionare, inserire, scorrere ecc. Per prima cosa inseriamo un DataSet e una Table utilizzata per gestire il collegamento con il Database creato precedentemente. Inseriamo come riferimento del nome database IsFace e selezioniamo come tabella IsFace o come avete chiamato la tabella di prima. Ora inseriamo in campo ImageEnDBView settando come dataset quello appena creato. Il campo che deve essere è quello BLOB relativo all’immagine. Dato che di fatto, dopo l’inserimento, il database conterrà diverse immagini è anche opportuno inserire uno strumento che permetta la gestione dei records come ad esempio le funzioni di scorrimento, quella di cancellazione, ecc. Inseriamo anche in DBCheckBox che permetta di settare il valore di true e false dentro al campo che dice se l’immagine è relativo ad un volto o meno. Inseriti questi campi possiamo iniziare a pensare alle funzioni volute. In altre parole dovremo avere un pulsante che premendolo ci permetta di scegliere l’immagine da caricare nel campo ImageEnView1. La visualizzazione della dialog di scelta avviene utilizzando l’oggetto OpenImageDialog1. Inseriamo un pulsante sul form e scriviamo dentro alla caption SCEGLI. Inseriamo anche una CheckBox che ci permetta di dire se il campione è vero o falso prima di copiarlo dentro al database. Inseriamo anche un pulsante AGGIUNGI che ci permetta di copiare dal campo provvisorio di visualizzazione all’interno di quello del database l’immagine selezionata. Adesso iniziamo scrivere il codice del pulsante SCEGLI. Il richiamo alla funzione Execute dell’oggetto OpenImageEnDialog1 restituisce true se è stato selelzionato un file oppure falso se non è avvenuta nessuna selezione. Se il valore restituito è true allora all’interno di OpenImageEnDialog1-

>FileName

Ci sarà il nome del file scelto. Facciamo click e scriviamo

dentro alla funzione generata :

void __fastcall TForm1::Button1Click(TObject *Sender) { if(OpenImageEnDialog1->Execute()) { ImageEnIO1->LoadFromFile(OpenImageEnDialog1->FileName); ImageEnProc1->ConvertToGray(); ImageEnProc1->Resample(40,50,rfBilinear); © Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it } }

La barra di scorrimento e di gestione records è sufficiente associarla al dataset aperto. Se l’immagine selezionata è conforma alle nostre aspettative allora premendo il pulsante AGGIUNGI la dovremmo inserire dentro al database. Facciamo click sul pulsante in modo che C++Builder crei la funzione vuota. Il codice dentro alla funzione dovrà inserire un nuovo records dentro alla database, copiare l’immagine dentro a ImageEnView dentro al CLIPBOARD e successivamente copiarlo dentro al bitmap ndi ImageEnDBView. Il codice che svolge questo compito è il seguente: void __fastcall TForm1::Button2Click(TObject *Sender) { Tablie1->Insert(); ImageEnProc1->CopyToClipboard(); ImageEnProc2->PasteFromClipboard(); Table1->FieldByName("Status")->AsBoolean = CheckBox1->Checked; Table1->Post(); }

Come vedete le funzionalità di copia incolla sono dentro all’oggetto di processing associato all’immagine DB e a quella di visualizzazione. Chiaramente oltre all’immagine dovremo leggere la CheckBox che dice se si tratta di un volto o meno e lo dovremo inserire dentro al campo del database. Fino a questo punto abbiamo terminato la parte che legge le immagini e le inserisce nel database. Ora dobbiamo concentrarci sulla parte di training. Come abbiamo detto precedentemente le immagini costituite da 2000 pixels devono essere passate ai neuroni d’input. Per fare questo dovremo utilizzare delle funzioni che ci permettano di leggere pixel a pixel l’immagine. Dopo aver letto il valore di ogni singolo pixel lo divideremo per 255 in modo da normalizzare il valore ad altri compresi tra 0 e 1 più adatti al formato numerico usato internamente dalla rete neurale. Il file di training dovrà avere questa forma : NOME_FILE.DATA 164 2000 1 0.352941 0.352941 0.352941 0.113725 0.113725 0.113725 0.141176 0.141176 0.141176 0.270588 0.270588 0.270588 0.521569 0.521569 0.521569 0.819608 0.819608 0.819608 0.894118 0.894118 0.894118 0.874510 0.874510 0.874510 0.803922 0.803922 0.803922 0.709804 0.709804 0.709804 0.752941 0.752941 0.752941 0.843137 0.843137 0.843137 …….. 1 …….. Il primo numero è quello dei campioni che andiamo a mettere dentro al file di training. Il secondo è il numero dei neuroni d’input e il terzo quelli di output. Il numero di campioni è il numero dei records dentro al database immagini. Per ogni immagine ci saranno 2000 valori del tipo 0.113725 seguiti alla fine da 1 o da 0 a seconda che si tratti di una faccia o meno. Se il primo numero è, come nell’esempio, 164 allora ci dovranno essere 164 blocchi : n1, n2, …, nx v © Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

Come facciamo a creare questo file ? Clickiamo sul pulsante ESEGUI TRAINING ed editiamo la funzione creata. void __fastcall TForm1::Button3Click(TObject *Sender) { Byte *ptr; bool is; int numero; register int y, x1; int xmax, ymax; AnsiString buffer; int iFileHandleData; int numRecs = Table1->RecordCount; Table1->FlushBuffers(); Table1->First(); _fpreset(); buffer.printf("c:\\Lavoro\\Borland\\Isface\\isface.data"); iFileHandleData = FileCreate(buffer.c_str()); ImageEnDBView1->Refresh(); xmax = ImageEnDBView1->Width; ymax = ImageEnDBView1->Height; numInput = xmax*ymax; buffer.printf("%d %d 1\n", numRecs, numInput); FileWrite(iFileHandleData, (char*) buffer.c_str() , buffer.Length()); for(int x=0;x!=numRecs;x++) { Application->ProcessMessages(); _clearfp(); ImageEnProc2->ConvertToGray(); ImageEnProc2->Resample(40,50,rfNone); for(y=0;y!=ymax;y++) { ptr = (byte *) ImageEnDBView1->Bitmap->ScanLine[y]; for(x1=0;x1!=xmax;x1++) { buffer.printf("%1.6f ", (float)((float) ptr[x1] / 255.0f)); FileWrite(iFileHandleData, (char*) buffer.c_str() , buffer.Length()); } } is = Table1->FieldByName("Status")->AsBoolean; buffer.printf("\n%d.0\n", is==TRUE ? 0:1); FileWrite(iFileHandleData, (char*) buffer.c_str() , buffer.Length()); Table1->Next(); ImageEnDBView1->Refresh(); } FileClose(iFileHandleData); ExecuteTrain(); }

Questa funzione prende il numero dei records e crea un file ASCII con in testa i valori necessari. I valori dei pixels sono ricavati mediante la parte di codice seguente : for(y=0;y!=ymax;y++) { ptr = (byte *) ImageEnDBView1->Bitmap->ScanLine[y]; for(x1=0;x1!=xmax;x1++) { Š Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it buffer.printf("%1.6f ", (float)((float) ptr[x1] / 255.0f)); FileWrite(iFileHandleData, (char*) buffer.c_str() , buffer.Length()); } }

Si tratta di cicli annidati, uno per le righe e uno per le colonne. Dentro al primo si ricava un puntatore ad ogni singola riga di pixels: for(y=0;y!=ymax;y++) { ptr = (byte *) ImageEnDBView1->Bitmap->ScanLine[y];

Poi per ogni pixels presente nella riga : for(x1=0;x1!=xmax;x1++) { buffer.printf("%1.6f ", (float)((float) ptr[x1] / 255.0f));

Quando il file è stato creato allora si deve attivare la funzione vera e propria di training. All’ interno del class explorer posizioniamoci sulla classe del form e dopo aver premuto il tasto destro del mouse selezioniamo aggiungi metodo. Diamo il nome di ExecuteTraining. void TForm1::ExecuteTrain() { AnsiString file; unsigned int n; const float connection_rate = 1.0f; const float learning_rate = 0.7f; const unsigned int num_layers = 3; const unsigned int num_input = 2000; const unsigned int num_hidden = 37; const unsigned int num_output = 1; const float desired_error = 0.001f; const unsigned int max_iterations = 500000; const unsigned int iterations_between_reports = 100; FANN::neural_net net; net.create(connection_rate, learning_rate, num_layers, num_input, num_hidden, num_output); net.set_activation_steepness_hidden(1.0); net.set_activation_steepness_output(1.0); net.set_activation_function_hidden(3); net.set_activation_function_output(3); file.printf("c:\\Lavoro\\Borland\\Isface\\isface.data"); FANN::training_data data; if (data.read_train_from_file(file.c_str())) { net.init_weights(data); net.train_on_data_callback(data, max_iterations,iterations_between_reports, desired_error, print_callback); file.printf("c:\\Lavoro\\Borland\\Isface\\isface.net"); net.save(file.c_str()); data.destroy_train(); } net.destroy(); } © Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

La funzione utilizza i metodi di FANN per creare il training. Le prime variabili in alto servono a specificare i valori che devono essere utilizzati dalla rete neurale. Come potete vedere l’esecuzione del training vero e proprio viene eseguita dalla funzione : net.train_on_data_callback(data, max_iterations,iterations_between_reports, desired_error, print_callback);

L’esecuzione potrebbe essere anche molto lunga per cui il programma potrebbe sembrare fermo. Per permettere alla funzione di segnalare lo stato viene utilizzata una funzione di callback settata tra gli argomenti delle funzione stessa. Avrete notato nella maschera del programma che erano stati messi dei campi di cui fino ad ora non avevamo detto nulla. In pratica i cicli fatti dalla funzione di training sono chiamate EPOCHS ed è infatti questo valore he viene visualizzato dentro ad uno di quei campi. La rete neurale ogni epoch ipotizza una soluzione che potrebbe essere molto distante dalla soluzione ottimale, quella che gli abbiamo fornito come output. Questa valorizzazione viene fornita come valore d’errore. In fase di settaggio della funzione, all’interno della costante : const float desired_error = 0.001f;

abbiamo specificato il coefficente d’errore per noi accettabile. All’interno della funzione di callback gli faremo scrivere anche questo valore tanto per capire se il training è ad un punto morto o se con l’andare dei tentativi il valore dell’errore migliora. Per gestire la funzione di callback dovremo scrivere una funzione autonoma al di fuori delle classi del programma. int FANN_API print_callback(unsigned int epochs, float error) { AnsiString buffer; if(epochs > 1) { buffer.printf("%3.4f", error); Form1->Edit2->Text = buffer; Form1->Edit1->Text = epochs; Form1->Refresh(); } return 0; }

Il numero di epoch e il codice d’errore FANN li passa alla funzione di callback la quale a sua volta li visualizza in questa parte del form. Potrebbe capitare che la rete neurale non riesca a generalizzare il problema per cui se dovesse capitare che per un periodo troppo lungo il valore dell’errore non migliori, allora si potrebbe interrompere il programma cercando di cambiare i valori come ad esempio il numero di neuroni nello strato nascosto, il learning rate, il connection rate ecc. Ricordiamoci che il numero di esempi deve essere sufficientemente grande e non sicuramente composto da 5 esempi. Ora il programma è completo se non per il fatto che sarebbe un buona cosa mettere nel programma anche la parte che permette di capire se il training ha dato buon esisto. Come fare questo ? © Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Progettazione hardware e software di sistemi di sicurezza intelligenti - 2007 Flavio Bernardotti http://www.bernardotti.it

Basta che aggiungiamo un pulsante di Test in modo che l’immagine letta nel campo ImageEnView venga usata con la rete neurale settata mediante il file salvato dal training. Inseriamo il pulsante e un campo utilizzato per visualizzare il risultato del testClickiamo sul pulsante per editare la funzione e scriviamo il codice per l’uso della rete neurale. void __fastcall TForm1::Button4Click(TObject *Sender) { Byte *ptr; int n = 0; AnsiString buffer; FANN::neural_net net; float maxval, f; fann_type *calc_out; net.create_from_file("c:\\Lavoro\\Borland\\Isface\\isface.net"); for(int y=0;y!=50;y++) { ptr = (Byte *) ImageEnView1->Bitmap->ScanLine[y]; for(int x1=0;x1!=40;x1++) { input[n++] = float(float(*ptr++)/255.0f); } } calc_out = net.run(input); buffer.printf("%1.5f",*calc_out); Edit3->Text = buffer; net.destroy(); }

Come avrete notato l’array di pixels passato alle varie funzioni delle rete neurale è chiamato input. Questo lo dovrete dichiarare globalmente in testa al modulo di gestione del form. fann_type input[2000];

La funzione net.create_from_file("c:\\Lavoro\\Borland\\Isface\\isface.net");

Legge il file .NET creato dal modulo di training e lo utilizza con i dati forniti in input relativi all’immagine che si vuole testare. L’immagine viene settata allo stesso modo con il quale avevamo creato gli array nella fase di training ovvero per ogni riga richiediamo il puntatore a questa e poi per ogni pixel nella riga chiamiamo : input[n++] = float(float(*ptr++)/255.0f);

Una volta settata la rete e richiamata la funzione di run questa restituirà dentro a calc_out il codice d’errore. Un valore tendente a 1 vale true mentre tendente a 0 vale false. Il modulo di test sarà la stesso che dentro ai nostri programmi verrà chiamato IsFace. Questa funzione restituirà vero o falso a seconda che l’immagine catturata sia un volto oppure no. In questo modo potremo controllare il lavoro delle trasformate haar indirizzate a estrarre volti dalle immagini.

© Copyright 2007 Flavio Bernardotti - http:/www.bernardotti.it


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.