giovedì 13 dicembre 2012

Riconoscimento volti artigianale: il fantasma dell'overfitting

Con l'esempio di rete neurale per il riconoscimento dei volti che ho pubblicato il primo dicembre c'è un problema: l'overfitting. La questione è presto spiegata: il modello è sovradimensionato, ci sono troppi neuroni di ingresso. Con un coefficiente per ciascun neurone, ci sono circa 100mila paramentri. Il risultato è che, con appena 40 esempi in ingresso, la sua capacità di individuare singole differenze tra una immagine e l'altra è troppo fine.

Dal momento che può confrontare ogni singolo pixel tra una immagine e l'altra, succede che prendono il sopravvento dettagli minori: se in un dato punto compaiono capelli o sfondo, se in un altro c'è la maglietta beige o azzurra, e via di questo passo. E' talmente ampio il numero di dettagli su cui il modello può giocare che è in grado di identificare le singole immagini.

Di fatto le memorizza. Il risultato è che riconosce perfettamente le immagini usate in fase di apprendimento ma poi non è in grado di riconoscerne di nuove. Si basa su dettagli insignificanti e in effetti non fa la cosa più importante: non generalizza, non individua le caratteristiche distintive fondamentali tra un volto e l'altro. Come si risolve questo problema? Con alcune tecniche statistiche che riducono l'immagine a pochi parametri sui quali poi la rete può fare apprendimento. Il primo e il più noto è Eigenface.

Il mio interesse in questo studio però era un altro.

A me non interessa studiare il modo più efficiente di riconoscere i volti in sé, mi interessa studiare le reti neurali come lontano parente del meccanismo di funzionamento del cervello. Il tema è come fa il cervello a riconoscere gli oggetti, le persone, e così via, e ad associarvi suoni (che a sua volta impara a distinguere per fonemi) costruendoci poi sopra un linguaggio.

Detto in altre parole, il problema non è solo riconoscere Piero da Carlo, ma anche riconoscere un temperamatite o una sedia da Carlo e perciò ridurre il problema a pochi parametri facciali con un procedimento analitico a monte non risolve affatto il problema, lo aggira. Farei io il lavoro che vorrei capire invece come fa a fare il cervello.

C'è da dire che probabilmente il cervello non ha grossi problemi di overfitting perché ha letteralmente milioni di esempi su cui fare riconoscimento del pattern, avendo a disposizione un flusso di immagini dagli occhi mediamente 16 ore su 24 per anni, e non i miseri 40 di quel modello. Perciò forse, oltre a trovare un modo di ridurre i parametri senza compromettere l'esperimento, c'è anche da introdurre un meccanismo di input a flusso video, in modo da poter campionare migliaia di immagini.

Non appena avrò le idee più chiare su come affrontare il problema lo scriverò qui. Ringrazio molto Raistlin, della lista hackmeeting, per aver segnalato il problema.
 

sabato 1 dicembre 2012

Come farsi in casa una rete neurale per il risconoscimento dei volti

[Nota posteriore: questo procedimento contiene un problema che ho descritto qui]

Cosa sono le reti neurali e perché sono tanto interessanti? Perché sono una trovata matematica grazie alla quale si rovescia la normale logica di programmazione e si può costruire una macchina che impara da sè. Per esempio, le reti neurali sono perfette per riconoscere i volti delle persone. Ecco un programma che, dati due volti, riconosce se abbiamo di fronte l'uno o l'altro. Ecco i due volti sui quali lavoreremo.






PROGRAMMAZIONE CLASSICA
Anche con un programma classico, a impostazione imperativa, si può fare riconocimento dei volti. Basta individuare le caratteristiche distintive delle due immagini. Il primo dei due ragazzi ha capelli più lunghi e più neri, sopracciglia più marcate, naso più lungo. Se prendiamo la matrice di pixel che compone l'immagine possiamo confrontare porzioni equivalenti e stabilire se appartengono all'uno o all'altro ragazzo in modo relativamente semplice. Qual è il problema? Che il programma funzionerà solo con queste due facce, non sarà per niente flessibile. Lo si può fare allora in modo decisamente più complesso, in modo che, dati due volti generici, il programma ne analizzi le porzioni equivalenti e memorizzi le differenze. Così sarà più flessibile, sarà in grado di esaminare qualsiasi faccia, ma il lavoro per scriverlo sarà molto complesso. Questa è programmazione con una impostazione imperativa classica. La programmazione con le reti neurali invece è radicalmente diversa. Di fatto noi non abbiamo bisogno di individuare le caratteristiche distintive tra le due immagini. E' la rete che lo fa.

E come lo fa? Correggendo la propria configurazione interna, in funzione degli esempi che le mettiamo davanti. Le diamo in pasto tante immagini del primo ragazzo, dicendole che è il primo ragazzo, e poi le diamo in pasto tante immagini del secondo, dicendole che è il secondo. Con un processo di correzione progressiva, che è quello viene definito apprendimento, la rete arriva a confingurarsi in modo tale da essere in grado di distingere i due volti.


COS'È UNA RETE NEURALE
Andiamo per ordine. Cos'è una rete? In effetti è ciò che il nome suggerisce: un insieme di nodi collegati tra loro. La caratteristica fondamentale di una rete neurale è quella di avere alcuni nodi di ingresso, alcuni nodi di uscita ed eventualmente alcuni nodi intermedi. Può esserci più di uno strato di nodi intermedi a seconda della complessità della rete.

Questa è una rete semplice, senza nodi intermedi:


Partendo dal generale per andare via via nel dettaglio e nel particolare, l'idea di base è questa: c'è uno scatolotto con alcuni ingressi e una uscita. Nel nostro caso gli ingressi rappresentano i valori dei pixel che compongono l'immagine (i volti sono dentro una bitmap di risoluzione 352x288, quindi abbiamo 101.376 ingressi, uno per pixel) mentre l'uscita sarà una sola e risulterà pari a un numero che identifica il volto: -1 per il primo, 1 per il secondo.

Nella prima fase, quella di apprendimento, forniamo alla rete sia l'ingresso che l'uscita. Le diamo tanti esempi del primo volto, a cui associamo l'uscita -1 e tanti esempi del secondo, dicendo che con quest'altro deve restituire invece +1. Esaurita la fase dell'apprendimento, lo scatolotto dovrebbe essere in grado, data una nuova foto che non aveva mai visto, di restituire l'uscita corretta.

Come lo fa? Generalizzando. Quello che succede è che la rete impara a distinguere le differenze significative da quelle non significative.

In pratica la cosa funziona così: ogni neurone di ingresso è collegato all'unico neurone di uscita. Il valore in arrivo al neurone di uscita sarà perciò la somma dei valori dei neuroni ingresso, vale a dire dei codici colore dei pixel dell'immagine, ognuno moltiplicato per un coefficiente.

Nella figura sono solo due, nel nostro caso sono 101.376, che vanno da x1 a x101376. Ciascun xi è moltiplicato per il rispettivo wi, cioè il relativo coefficiente. Il valore in ingresso al neurone di output è dato perciò da:


L'idea è che i pixel importanti, quelli che permettono di distinguere una immagine dall'altra (per esempio dove ci sono i capelli, che hanno colore diverso tra i due ragazzi), avranno coefficienti alti. Quelli meno importanti (per esempio quelli dello sfondo, la parete azzurrina), che non hanno alcuna importanza nell'individuare di quale dei due volti si tratti, avranno coefficienti molto bassi, prossimi allo zero. In questo modo si ottiene un valore che dà rilievo alle differenze tra le due immagini. Questi sono i pesi sinaptici. Il risultato, cioè a sommatoria complessiva dei valori dei pixel per i rispettivi pesi, poi, a sua volta passa attraverso una funzione di attivazione. Una funzione che si presta molto bene è la tangente iperbolica, che ha questo tipo di risposta:


Perché utilizziamo una funzione di attivazione, in particolare questa? Perché garantisce una uscita sempre compresa tra -1 e +1, qualsiasi sia il dato di partenza. Nel nostro caso, poiché la sommatoria di oltre 100mila ingressi, per quanto i coefficienti possano essere bassi, porta a un valore piuttosto alto, ho diviso - trovando empiricamente il valore vedendo un po' cosa usciva dal programma - il valore per 10.000. Il valore così trovato va in pasto alla tangente iperbolica che restituisce un valore prossimo a -1 o +1 e viene approssimato a quello più vicino.


L'APPRENDIMENTO
Una rete così realizzata è in grado di riconoscere un volto dall'altro se configurata con i coefficienti giusti. E il calcolo dei coefficienti giusti è quello che si definisce apprendimento. Come si calcolano i coefficienti giusti? Per correzioni progressive. All'inizio dell'apprendimento la rete calcola una uscita, in funzione degli ingressi che ha (al primo giro tutti i valori sinaptici valgono zero). Poi corregge i coefficienti in funzione della differenza tra l'uscita calcolata e quella teorica (cioè che noi vorremmo che restituisse, quella che noi abbiamo associato a quella determinata immagine: nel caso del primo volto -1, nel caso del secondo +1).

Dato:
t = risposta teorica
y = risposta calcolata dalla rete con gli attuali pesi sinaptici
xi = valore di ingresso (da 0 a 83.839)

La differenza è calcolata così:

differenza = (t - y) xi (1 - y*y)

(1 - y*y) rappresenta la derivata (y*y vuol dire "y al quadrato") della funzione di attivazione del neurone di uscita, cioè della tangente iperbolica.
Una volta calcolato la differenza, si correggono i pesi sinaptici così:

wi = wi + differenza

Per evitare di andare fuori strada correggendo il differenziale troppo a ogni nuova immagine sottoposta, si applica anche un ulteriore coefficiente, detto tasso di apprendimento, che serve a limitare la velocità del cambiamento. Il tasso di apprendimento è compreso tra 0 e 1 e in genere viene fissato intorno a 0,8. Se a è il tasso di apprendimento, il calcolo della differenza diventa allora:

differenza = a (t - y) xi (1 - y*y)

La cosa migliore è dare in pasto alla rete le immagini in modo incrociato. Cioè prima un esempio del primo volto, poi un esempio del secondo, poi di nuovo uno del primo e così via. In questo modo l'apprendimento è più efficiente, perché a ogni passaggio emergono con più evidenza le differenze tra una e l'altra.

Una volta esaurito il compito, quello che abbiamo è di fatto una matrice di coefficienti, che possiamo salvare, per comodità, ancora in una bitmap, che si presta perfettamente allo scopo, essendo come formato una matrice di valori. Facendo così, poi, è possibile visualizzare l'immagine che ne viene fuori e il risultato è impressionante:

Pesi calcolati con numeri float

Non solo. Ma c'è anche un'altra cosa interessante. Questa è l'immagine che viene fuori se i pesi sono numeri float, cioè numeri con la virgola. Ma se utilizziamo come coefficienti dei numeri interi, allora l'immagine, pur molto simile, perde alcuni dettagli.

Pesi calcolati con numeri interi



DEBOLEZZE DI QUESTA RETE NEURALE
La principale debolezza di questa rete neurale è il fatto che, avendo un solo strato, assegna di fatto un coefficiente a ogni pixel. Quindi le due facce devono trovarsi grosso modo sempre nella stessa posizione. Se in una immagine il volto invece di trovarsi al centro, si trova a destra o a sinistra, la rete perde la bussola. Per renderla in grado di individuare e riconoscere le somiglianze tra le facce anche se l'immagine si sposta, occorrerebbe uno strato di neuroni intermedio. In questo modo il primo strato di connessioni sinaptiche individuerebbero la posizione del volto e il secondo individuerebbe le caratteristiche del volto. L'apprendimento di una rete con uno strato intermedio è però molto diverso da una rete semplice come questa e lo vedremo in futuro.


IL PROGRAMMA
Questi di seguito c'è il programma in C per fare tutto questo.
Il primo file, neuralnet.c, è il programma che, prendendo una immagine, dice se si tratta del primo o del secondo volto.
Il secondo file, training.c, rappresenta il programma di apprendimento della rete e calcola i pesi sinaptici (attenzione: ci sono letteralmente milioni di moltiplicazioni da fare, il processore ci mette del tempo, potrebbe volerci anche qualche ora a completare il processo di apprendimento).
Il terzo file, neuralnet.h, è un file libreria e contiene tutte le funzioni utilizzate nei primi due file.

Se avete una distribuzione Linux i file si compilano così:

gcc neuralnet.c -o neuralnet -lm
gcc training.c -o training -lm

Avete ora così a disposizione i due programmi. Il training si avvia così:

./training

Il programma impara da 40 immagini bitmap di risoluzione 352x288. Le immagini devono essere nella stessa cartella del programma, numerate, e chiamarsi da Image000.bmp a Image039.bmp (con la i maiuscola). Quelle pari, (0, 2, 4, ecc.) devono essere della prima faccia, quelle dispari della seconda. Il programma genera un file che si chiama pesi.bmp nel quale sono registrati tutti i pesi sinaptici.

Esaurito l'apprendimento, si può utilizzare la rete, con il seguente comando:

./neuralnet

L'immagine da verificare deve essere una bitmap di risoluzione 352x288 che si chiama image2test.bmp ed è sempre nella stessa cartella.


CODICE SORGENTE IN C


NEURALNET.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "./neuralnet.h"
#define num_pixel 76032
#define image_widht 352

int main(void)
{
unsigned int x[num_pixel]; // array dei neuroni dell'immagine di input
float w[num_pixel],x_float[num_pixel]; // array dei pesi sinaptici
unsigned char header_image[54];
char filename_immagine[20]="image2test.bmp"; // nome del file dell'immagine da testare
char filename_pesi[20]="pesi.bmp"; // nome del file dell'immagine dei pesi
float out_neurone[num_pixel];
float out_neurone_uscita = 0;
int j;

if (open_image_int_mono(x,header_image,filename_immagine)==1) 
    {printf("errore di apertura dell'immagine %s",filename_immagine);
    exit (1);}

for (j=0;j<num_pixel;j++)
    {
    x_float[j] = x[j];
    }

if (normalize(x_float)==1) 
    {printf("errore di normalizzazione dell'immagine %s",filename_immagine);
    exit (1);}

if (open_image_float(w,header_image,filename_pesi)==1) // legge l'array dei pesi
    {printf("errore di apertura dell'immagine %s",filename_pesi);
    exit (1);}

/*
// fine della fase di apprendimento
// uso della rete
printf("\nTEST DELLA RETE\n");
printf("la rete risponde con -1 per un'immagine simile a una delle prime 50 immagini di training (0-49)\n");
printf("la rete risponde con +1 per un'immagine simile a una delle seconde 50 immagini di training (50-99)\n");
printf("(ATTENZIONE: i neuroni sono bipolari quindi i valori sono 1 e -1, non i soliti 1 e 0.)\n");
*/

for (j=0;j<num_pixel;j++)
    {
    out_neurone[j] = x_float[j] * w[j];
    out_neurone_uscita = out_neurone_uscita + out_neurone[j];
    }

    out_neurone_uscita = out_neurone_uscita /100000;
    printf("out_neurone_uscita non elaborata: %f\n",out_neurone_uscita);

    out_neurone_uscita=tanhf(out_neurone_uscita);
    printf("out_neurone_uscita con la tang iperbolica: %f\n",out_neurone_uscita);

/* 
attenzione: se il neurone è bipolare come in questo caso e non continuo allora Ú necessario interpretare il valore raggiunto e verificare se supera la soglia, in modo da determinare se vale 1 oppure -1. un neurone bipolare non può avere un'uscita come 3 oppure -3 ma solo 1 e -1.
il comportamento di questi neuroni è:
y = 1 se { sommatoria di tutti i [valore di input della sinapsi per il relativo peso] > 0 }
y = 1 se { sommatoria di tutti i [valore di input della sinapsi per il relativo peso] <= 0 }
(è riportato a pagina 68 del manuale)
*/

if (out_neurone_uscita < 0) printf ("\nvolto 1\n");
if (out_neurone_uscita > 0) printf ("\nvolto 2\n");

//printf("\n y: %f\n",out_neurone_uscita);

}




TRAINING.C

#include <stdio.h>
#include <stdlib.h>
//#include <string.h>
#include <math.h>
#include "./neuralnet.h"
#define num_pixel 101376
#define image_widht 352

void main(void)
{
unsigned int array_image[288][image_widht]; // array dei pixel dell'immagine
unsigned int x[num_pixel]; // vettore monodimensionale che raccoglie i valori dell'array dell'immagine
float x_float[num_pixel]; // vettore che trasforma i valori in float
//unsigned int *punt;
unsigned char header_image[54];
float w[num_pixel]; // vettore dei pesi
unsigned int w_int[num_pixel]; // vettore dei pesi convertiti in intero

//fase di apprendimento

float apprendimento;
char filename[100][20];
char filenamew[20]="pesi.bmp"; // nome del file per salvare i pesi sinaptici calcolati
float t[100];
//float x[num_pixel];
float y,y_tot,y_diviso;
float delta_w;
int i,j,k,p,n;

// nb: num_pixel è il risultato di 288 x image_widht.

apprendimento=0.9;// tasso di apprendimento

strcpy(filename[0],"Image000.bmp"); t[0]=-1;
strcpy(filename[1],"Image001.bmp"); t[1]=1;
strcpy(filename[2],"Image002.bmp"); t[2]=-1;
strcpy(filename[3],"Image003.bmp"); t[3]=1;
strcpy(filename[4],"Image004.bmp"); t[4]=-1;
strcpy(filename[5],"Image005.bmp"); t[5]=1;
strcpy(filename[6],"Image006.bmp"); t[6]=-1;
strcpy(filename[7],"Image007.bmp"); t[7]=1;
strcpy(filename[8],"Image008.bmp"); t[8]=-1;
strcpy(filename[9],"Image009.bmp"); t[9]=1;

strcpy(filename[10],"Image010.bmp"); t[10]=-1;
strcpy(filename[11],"Image011.bmp"); t[11]=1;
strcpy(filename[12],"Image012.bmp"); t[12]=-1;
strcpy(filename[13],"Image013.bmp"); t[13]=1;
strcpy(filename[14],"Image014.bmp"); t[14]=-1;
strcpy(filename[15],"Image015.bmp"); t[15]=1;
strcpy(filename[16],"Image016.bmp"); t[16]=-1;
strcpy(filename[17],"Image017.bmp"); t[17]=1;
strcpy(filename[18],"Image018.bmp"); t[18]=-1;
strcpy(filename[19],"Image019.bmp"); t[19]=1;

strcpy(filename[20],"Image020.bmp"); t[20]=-1;
strcpy(filename[21],"Image021.bmp"); t[21]=1;
strcpy(filename[22],"Image022.bmp"); t[22]=-1;
strcpy(filename[23],"Image023.bmp"); t[23]=1;
strcpy(filename[24],"Image024.bmp"); t[24]=-1;
strcpy(filename[25],"Image025.bmp"); t[25]=1;
strcpy(filename[26],"Image026.bmp"); t[26]=-1;
strcpy(filename[27],"Image027.bmp"); t[27]=1;
strcpy(filename[28],"Image028.bmp"); t[28]=-1;
strcpy(filename[29],"Image029.bmp"); t[29]=1;

strcpy(filename[30],"Image030.bmp"); t[30]=-1;
strcpy(filename[31],"Image031.bmp"); t[31]=1;
strcpy(filename[32],"Image032.bmp"); t[32]=-1;
strcpy(filename[33],"Image033.bmp"); t[33]=1;
strcpy(filename[34],"Image034.bmp"); t[34]=-1;
strcpy(filename[35],"Image035.bmp"); t[35]=1;
strcpy(filename[36],"Image036.bmp"); t[36]=-1;
strcpy(filename[37],"Image037.bmp"); t[37]=1;
strcpy(filename[38],"Image038.bmp"); t[38]=-1;
strcpy(filename[39],"Image039.bmp"); t[39]=1;

for (i=0;i<num_pixel;i++)    // inizializzazione dei pesi sinaptici, uno per ogni neurone di ingresso
    {
    w[i] = 0;
    }

/* QUESTO È STATO COMMENTATO PERCHÉ CONVENIVA INCROCIARE LE IMMAGINI
for (i=0;i<20;i++)    // impostazione dell'uscita desiderata: -1 per le immagini da 0 a 19.
    {
    t[i] = -1;
    }

for (i=20;i<40;i++)    // impostazione dell'uscita desiderata: 1 per le immagini da 20 a 39.
    {
    t[i] = 1;
    }
*/


for (i=0;i<40;i++)
  {
    if (open_image_int_mono(x,header_image,filename[i])==1)
        {printf("errore di apertura dell'immagine %s",filename[i]);
        exit (1);}

    for (j=0;j<num_pixel;j++)
        {
        x_float[j] = x[j];
        }

    if (normalize(x_float)==1)
        {printf("errore di normalizzazione dell'immagine %s",filename[i]);
        exit (1);}

/*     VERBOSE MODE

        for (j=0;j<num_pixel;j++)
        {
        printf("x_float[%i]: %f - x: %u\n",j,x_float[j],x[j]);
        }
*/


    y=0;
    for (j=0;j<num_pixel;j++) // calcolo dell'uscita con i pesi correnti
        {
        y = y + (x_float[j] * w[j]);
        }
    y_tot=y;
    y_diviso=(y/10000);
    y=tanhf(y_diviso); // tangente iperbolica calcolata su un numero float
    printf("i: %i - y totale: %f - y diviso: %f - y iperbolica: %f\n",i,y_tot,y_diviso,y);

    for (j=0;j<num_pixel;j++)
        {
        delta_w = apprendimento * (t[i] -y) * x_float[j] * (1-y*y); // correzione con la differenza con
            // il delta_w contiene tra i fattori la derivata della funzione di attivazione:
            // per la tangente iperbolica corrisponde a (1-y*y). dato che la funzione però non
            // è tang_iperb(x), ma tang_iperb(x/100000), si può scorporare un tang_iperb(1/100000),
            // che è una costante, e quindi può essere mantenuta fattore costante. siccome è
            // è un fattore costante, può essere considerato incorporato nel tasso di apprendimento.
        w[j] = delta_w + w[j];
        //if (i>=20) {printf("i: %i - w%i: %f - delta_w: %f - x_float: %f - y: %f - t: %f\n",i,j,w[j],delta_w,x_float[j],y,t[i]);}
        }
  }

/* VERIFICA DEI VALORI DEI PESI SINAPTICI CALCOLATI
printf("PESI SINAPTICI\n");
for (j=0;j<num_pixel;j++)
    {
    printf("w%i: %f\n",j,w[j]);
    }
*/


/* SALVATAGGIO SU PESI2.BMP DEI PESI CON NUMERI INTERI
for (j=0;j<num_pixel;j++)
    {
    w_int[j]=(int)w[j];
    }

if (write_image_int(w_int,header_image,"pesi2.bmp")==1)
    {
    printf("errore di scrittura dell'immagine pesi2.bmp");
    exit(1);
    }
*/


// salvataggio dei pesi sinaptici calcolati:
if (write_image_float(w,header_image,filenamew)==1)
    {
    printf("errore di scrittura dell'immagine %s",filenamew);
    exit(1);
    }


printf("\nI pesi sinaptici sono stati salvati nel file %s\n\n",filenamew);
}




 NEURALNET.H

#define num_pixel 76032
#define image_widht 352

int open_image_int(unsigned int array[288][image_widht], unsigned char header[54], char filename[20]);
int open_image_int_mono(unsigned int array[num_pixel], unsigned char header[54], char filename[20]);
int open_image_float(float array[num_pixel], unsigned char header[54], char filename[20]);

int write_image_int(unsigned int array[num_pixel], unsigned char header[54], char filename[20]);
int write_image_float(float array[num_pixel], unsigned char header[54], char filename[20]);

int normalize(float array[num_pixel]);

int open_image_int(unsigned int array[288][image_widht], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
unsigned int array_f[288][image_widht];

if ((fp=fopen(filename,"rb"))==NULL) 
    {
        return 1;
    }
   else
    {
        fread(&header_f,sizeof(header_f),1,fp);
        fread(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
        for (i=0;i<54;i++)
            {
            header[i]=header_f[i];
            }
        for (i=0;i<288;i++)
            {
                for (j=0;j<image_widht;j++)
                    {
                    array[i][j]=array_f[i][j];
                    }
            }
    }
return 0;
}

int open_image_int_mono(unsigned int array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
unsigned int array_f[num_pixel];

if ((fp=fopen(filename,"rb"))==NULL) 
    {
        return 1;
    }
   else
    {
        fread(&header_f,sizeof(header_f),1,fp);
        fread(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
        for (i=0;i<54;i++)
            {
            header[i]=header_f[i];
            }
        for (i=0;i<num_pixel;i++)
            {
            array[i]=array_f[i];
            }
    }
return 0;
}

int open_image_float(float array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
float array_f[num_pixel];

if ((fp=fopen(filename,"rb"))==NULL) 
    {
        return 1;
    }
   else
    {
        fread(&header_f,sizeof(header_f),1,fp);
        fread(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
        for (i=0;i<54;i++)
            {
            header[i]=header_f[i];
            }
        for (i=0;i<num_pixel;i++)
            {
                array[i]=array_f[i];
            }
    }
return 0;
}

int write_image_float(float array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
float array_f[num_pixel], *array_punt;

if ((fp=fopen(filename,"wb"))==NULL) 
    {
        return 1;
    }
else
    {
        for (i=0;i<54;i++)
            {
            header_f[i]=header[i];
            }

        for (i=0;i<num_pixel;i++)
            {
            array_f[i]=array[i];
            }

/*
        for (i=0;i<288;i++)
            {
                for (j=0;j<image_widht;j++)
                    {
                    array_f[i][j]=array[i][j];
                    }
            }
*/

        fwrite(&header_f,54,1,fp);
        fwrite(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
    }
return 0;
}

int write_image_int(unsigned int array[num_pixel], unsigned char header[54], char filename[20])
{
FILE *fp;
int i,j;
unsigned char header_f[54];
unsigned int array_f[num_pixel], *array_punt;

if ((fp=fopen(filename,"wb"))==NULL) 
    {
        return 1;
    }
else
    {
        for (i=0;i<54;i++)
            {
            header_f[i]=header[i];
            }

        for (i=0;i<num_pixel;i++)
            {
            array_f[i]=array[i];
            }


/*
        for (i=0;i<288;i++)
            {
                for (j=0;j<image_widht;j++)
                    {
                    array_f[i][j]=array[i][j];
                    }
            }
*/

        fwrite(&header_f,54,1,fp);
        fwrite(&array_f,sizeof(array_f),1,fp);
        fclose(fp);
    }
return 0;
}

int normalize(float array[num_pixel])
{
int i;
float max=0;

float array_f[num_pixel];

    for (i=0;i<num_pixel;i++)
        {
        array_f[i]=array[i];
        }

    for (i=0;i<num_pixel;i++)
        {
        if (array_f[i]>max) { max=array_f[i]; }
        }

    for (i=0;i<num_pixel;i++)
        {
        array_f[i]=(array_f[i]/max)*(array_f[i]/max);
        }

    for (i=0;i<num_pixel;i++)
        {
        array[i]=array_f[i];
        }

return 0;
}




DOWNLOAD
Scarica i sorgenti
Scarica i compilati