PAGE WIKI ETUDIANTS 2012-13 RECONNAISSANCE FACIALE

Description du projet
Le principe est de permettre d'identifier un utilisateur. Le système dispose d'une base de données contenant l'ensemble des utilisateurs connus et leur visage. Le système doit reconnaitre l'utilisateur test dans la base de données. Scénario type : Une personne se présente au système. Le système détecte son visage et analyse la situation. Il répond alors de 2 manières possibles :
 * La personne est vraisemblablement Monsieur X
 * La personne n'est pas dans la base de données

Composition de l'équipe
Chef de projet : Camille OSWALD Equipiers :
 * Marie CHEVALLIER
 * Fabien ELOY
 * Christopher GNATTO
 * Maxence RAOUX
 * Sylvain VIGIER
 * Clément WIRTH

Technologies et matériel utilisés

 * C++
 * librairies et frameworks : OpenCV, Qt Project, TinyXml
 * webcam

Chronologie de développement

 * 1) Détection avec quelques images en base
 * 2) * Nous commençons par élaborer un modèle avec une petite base pour nous permettre de faire des tests. Nous agrandirons la base pour davantage de tests par la suite.
 * 3) * L'acquisition des données de la webcam est très facilement gérée par OpenCV
 * 4) * La détection de visage se base sur la méthode de Viola et Jones, aussi géré par OpenCV
 * 5) * Se focaliser sur la phase critique : l'identification de visages
 * 6) Gérer la phase d'apprentissage du système
 * 7) optimiser le système (statistiques, réglage du seuil,…)

Utilisation de la librairie OpenCv
Comme ce projet nécéssitait l'utilisation d'une kinect, nous avions décidé de développer sous Windows.

Nous avons donc tout d'abord essayé d'utiliser Visual Studio pour développer notre projet. Par la suite, nous avons choisi de passer par un autre éditeur : Codeblocks.

1) Utilisation de Visual Studio (2012)

Après avoir réussi à inclure les librairies dans notre code, nous avons rencontrés les problèmes suivants :

Problèmes rencontrés :
 * Difficulté de comprendre le fonctionnement du compilateur de visual studio, notamment lorsqu'il faut ajouter de nouveaux fichiers
 * Problème lors du fonctionnement des objets utilisés par opencv (Exception au lancement, ou détection des visages non effectuées) pour certaines machines.
 * La librairie devait se trouver sur au même endroit pour toutes les machines

2) Utilisation de Code Blocks

Pour une meilleure compréhension et un projet fonctionnant sur des machines différentes avec des librairies qui ne sont pas installées aux mêmes endroits, nous avons choisi de porter le projet sur codeblocks.

Dans ce projet, nous avons utilisé Codeblocks 12.11 avec MinGW inclu (version 4.7.1).

Compilation de la librairie OpenCv
Afin d'éviter les problèmes d'utilisation de la librairie, il était nécessaire de recompiler la librairie avec le même compilateur que nous utilisons pour notre projet (ici MinGW fourni avec CodeBlocks).

Pour compiler la librairie, nous avons utilisé l'utilitaire Cmake.

Les fichiers de configurations de Cmake étant déjà fourni avec opencv (fichiers CMakeLists.txt), son utilisation devient très simple. Nous avons suivi le tutoriel ci-dessous :


 * Lancement de Cmake-gui
 * Dans le champs Source Code, spécifier le chemin de la librairie openCv (exemple : C:\opencv-2.4.3)
 * Dans le champs build, mettre le chemin ou la librairie compilée sera enregistrée (exemple : C:\openCV-build)
 * Cliquer ensuite sur configure (créer le nouveau dossier)
 * Spécification du générateur : MinGW Makefiles
 * Sélectionner "Specify native compilers"
 * Définir le compilateur C (ex : C:/Program Files/CodeBlocks/MinGW/bin/gcc.exe)
 * Définir le compilateur C++ (ex: C:/Program Files/CodeBlocks/MinGW/bin/g++.exe)
 * Cliquer sur Finish
 * Cliquer sur generate
 * Fermer Cmake
 * Ouvrir une fenêtre de commande et aller dans le dossier que vous avez spécifié dans le champs build (ici : C:\openCV-build)
 * Executer "ming32-make" (prend du temps et nécessite peut être d'avoir le répertoire bin de MinGW dans le PATH)
 * Enfin executer "mingw32-make install"

Configuration de codeblocks
Pour configurer codeblocks, nous avons suivi ce tutoriel : http://opencv.willowgarage.com/wiki/CodeBlocks

Afin que chacun puisse installer sa librairie openCv où bon lui semble, nous avons décidé de configurer chacun notre CodeBlocks. La démarche suivie est la suivante :


 * Menu "settings" / "Compiler"
 * Onglet "search directories"
 * Dans le sous onglet "Compiler", spécifier le dossier include de notre openCv recompilé
 * Dans le sous onglet "Linker", spécifier le dossier lib de notre openCv recompilé
 * Aller dans l'onglet "linker settings" et ajouter toutes les libraies .dll.a contenues dans le dossier lib.
 * Inclure le dossier bin de notre openCv recompilé dans la variable système PATH (nécessite peut être un redemmarrage)

Après ces étapes nous pouvons inclure les librairies openCV dans n'importe quel projet Codeblocks.

Installation de QT
Installer QT :
 * Télécharger Qt 5.0.1(MinGW 4.7) ou une version compatible avec MinGW 4.7 depuis le site qt-project
 * Installer QT depuis l’exécutable téléchargé.

Intégré QT à Codeblocks de la même manière qu'OpenCV. C'est à dire :
 * Menu "settings" / "Compiler"
 * Onglet "search directories"
 * Dans le sous onglet "Compiler", spécifier le dossier include de QT (/QT5.0.1/5.0.1/mingw47_32/include)
 * Dans le sous onglet "Linker", spécifier le dossier lib de QT (/QT5.0.1/5.0.1/mingw47_32/lib)
 * Aller dans l'onglet "linker settings" et ajouter toutes les libraies a contenues dans le dossier lib.

Utilisation de QT
Les inclusions de bibliothèque se fond en précisant le bon chemin, par exemple pour : QLabel : #include  QWidget : #include  QPixmap : #include  etc...

En ce qui concerne les signaux et les slots, propre a QT. Une manipulation est nécessaire à leurs bon fonctionnement. Comprendre leurs fonctionnement sur le site du zéro par exemple puis il est nécessaire de lancer une commande pour que le signal marche.

Depuis la racine du projet exécutez la commande : moc  -o \moc_.cpp

Par exempe un signal est contenu dans include avec le nom interface.h, le cpp correspondant se trouve dans src/interface.cpp La ligne de commande nécessaire sera : moc include/interface.h -o src/moc_interface.cpp

Cette commande génére un fichier cpp qui rend le signal utilisable.

Détection de visages
Référence : 
 * Tutoriel pour la détection de visage par OpenCV
 * Tutoriel pour la détection de visage

Après nous être inspirés de ces tutoriels, voici comment la détection de visages est implémentée dans notre projet : // Déclaration des variables char Filexml[]="haarcascade_frontalface_alt.xml"; CvHaarClassifierCascade* cascade = 0; IplImage* frame = 0; struct stat buf; int statResult = stat(Filexml,&buf); char s[] = "picture.jpg"; int nbFrame = 0; CvCapture* capture; IplImage *subImg; CvSeq *faceRectSeq; CvMemStorage *storage = cvCreateMemStorage(0); // Teste si le fichier d'entrée est bien chargé if (statResult ||buf.st_ino<0) { printf("xml non trouvé"); }   else { // Crée un nouveau Haar classifier : // Crée des classes permettant d'identifier des objets dans des rectangles d'images // Le système s'entraine sur les images du fichier xml cascade = (CvHaarClassifierCascade*) cvLoad(Filexml); }   // Détection des visages en continue, à partir de la webcam while (true) {       // Capture de la caméra capture = cvCaptureFromCAM(1); // Crée un IplImage à partir de la caméra frame = cvQueryFrame(capture); // Crée une fenêtre pour afficher les visages détectés cvNamedWindow("Sample Program", CV_WINDOW_AUTOSIZE); cvNamedWindow("Visage", CV_WINDOW_AUTOSIZE); // Détection des objets dans l'image de la caméra faceRectSeq = cvHaarDetectObjects(frame,cascade,storage,1.2, 3,CV_HAAR_DO_CANNY_PRUNING,cvSize(50,50)); CvRect *r; for ( int i = 0; i < (faceRectSeq? faceRectSeq->total:0); i++ ) {           r = (CvRect*)cvGetSeqElem(faceRectSeq,i); CvPoint p1 = { r->x, r->y }; CvPoint p2 = { r->x + r->width, r->y + r->height }; cvRectangle(frame,p1,p2,CV_RGB(0,255,0),1,4,0); // Récupération de l'image à l'interieur du rectangle r            cvSetImageROI(frame, *r); subImg = cvCreateImage(cvGetSize(frame), frame->depth, frame->nChannels); cvCopy(frame, subImg, NULL); // On enregistre l'image du visage saveTrainImg(frame, subImg, nbFrame); // Affiche l'image dans le rectangle, et l'image de la caméra dans son intégralité cvShowImage("Visage", subImg); cvResetImageROI(frame); cvShowImage("Sample Program", frame); }       // Fermeture de l'application sur commande Echap int c = cvWaitKey(10); if( (char)c == 27 ) {           cvSaveImage(s, frame); exit(0); }       nbFrame++; } }

Traitement des images
// Copie l'image source frame dans subImg pour traitement subImg = cvCreateImage(cvGetSize(frame), frame->depth, frame->nChannels); cvCopy(frame, subImg, NULL); // Crée le nom de fichier pour l'enregistrement de l'image string Result; ostringstream convert; ostringstream convertIdPersonne; convert << nbFrame; convertIdPersonne <depth,3) IplImage *dstGray = cvCreateImage(cvSize(400, 400),dst->depth,1); // Redimensionne l'image cvResize(subImg,dst, CV_INTER_LINEAR ); // Convertie en matrice et en niveaux de gris cvCvtColor(dst, dstGray, CV_RGB2GRAY); // Enregistre l'image retraitée cvSaveImage(Result.c_str, dstGray); return Result;

Identification de visages
Référence :  Tutoriel pour la reconnaissance de visage L'identification de visage consiste à reconnaitre un utilisateur dans un ensemble de visages connu. Plusieurs algorithmes existent pour comparer un visage à un autre : Eigenfaces, Fisherfaces, Local Binary Patterns Histograms,...

Dans notre solution, nous utilisons la technique Fisherfaces. Il faut générer un modèle, permettant de lier chaque identité à un set d'images lui correspondant. A la phase d'identification, l'image test (visage) sera comparée avec ce modèle. On va sélectionner le visage le plus proche du visage test, selon un certain calcul de distance. Il est possible d'améliorer les résultats en jouant sur le seuil d'acceptation.

Création du modèle  // Vecteurs permettant de stocker l'ensemble des images et des noms des utilisateurs pour le modèle vector images; vector labels; // Va chercher les données dans notre fichier de données vector personnes = collection->getPersonnes; // Vecteur contenant les images de notre système de données vector imageRefs; // Parcours de l'ensemble des personnes du système de données Personne personne; for(int i=0;i<(int)personnes.size;i++){ personne = personnes[i]; imageRefs = personne.getImageReferences; for(int j=0;j<(int)imageRefs.size;j++){ // Récupère l'image IplImage* src = cvLoadImage(imageRefs[j].getChemin.c_str, CV_LOAD_IMAGE_GRAYSCALE); // Transforme l'image en matrice Mat matDst=src; // Stocke l'image dans le vecteur images et la lie à l'identité dans le vecteur labels images.push_back(matDst); labels.push_back(personne.getId); } }  // Crée le modèle avec la technique Fisherfaces Ptr model = createFisherFaceRecognizer; // Entraine/génère le modèle en fonction des vecteurs créés model->train(images, labels); return model;

Identification du visage

// Formatage de l'image du visage test IplImage *dstTest = cvCreateImage(cvSize(400, 400),subImg->depth,3); IplImage *dstTestGray = cvCreateImage(cvSize(400, 400),subImg->depth,1); cvResize(subImg,dstTest, CV_INTER_LINEAR ); cvCvtColor(dstTest, dstTestGray, CV_RGB2GRAY); // Création de la matrice de l'image de visage test pour comparaison avec le modèle Mat matTest=dstTestGray; // Variable stockant la confiance accordée au résultat double confidence = -1.0; // Identité résultat prédite int predicted = -1; // Demande la prédiction à partir du modèle cv::FaceRecognizer: model->predict(matTest, predicted, confidence);

Persistance des données
Pour réaliser un système de reconnaissance faciale, il faut mettre en place une base des utilisateurs. Lorsqu'une personne souhaitera être authentifiée (ou identifiée), la comparaison sera faite avec les individus présents dans cette base. Pour chaque individu, nous lui associons une liste d'images, captures de son visage. Le système de reconnaissance d'OpenCV fonctionnant directement avec des images, nous les stockons directement en format JPG dans un dossier "imgResizedGray".

Format de données
Les données sont stockées en XML (fichier Personnes.xml dans le dossier data). Pour chaque personne, nous enregistrons son nom, prénom, un identifiant unique (généré automatiquement), et une liste d'images associées (date + chemin). Voici un exemple de fichier:    <Nom> Mercier </Nom> <Prenom> Jean-Francois </Prenom> <Id> 1       </Id> <Image> <Chemin> MercierJean-Francois.jpg </Chemin> <Date> DD/MM/YYYY HH:MM:SS </Date> </Image> <Image> <Chemin> MercierJean-Francois2.jpg </Chemin> <Date> DD/MM/YYYY HH:MM:SS </Date> </Image> </Personne> </ListePersonnes> Une fois parsées, les données sont en mémoire dans la classe "Connexion". Les modifications des données effectuées dans cette structure sont enregistrée en même temps dans le fichier xml.

Librairie utilisée
La librairie utilisée pour parser le fichier xml est tinyXML. L'import de la librairie est très simple puisque ce sont des classes C++ à ajouter dans le projet:

<ul> <li>tinystr.cpp <li>tinyxml.cpp <li>tinyxmlerror.cpp <li>tinyxmlparser.cpp </ul>

La documentation est disponible ici. Quelques éléments sur le fonctionnement de la librairie: <ul> <li> Chargement d'un fichier XML :</li> TiXmlDocument doc = TiXmlDocument(xmlFile); <li> Accès à une balise fille (dans ce cas c'est <ListePersonnes>) :</li> TiXmlHandle hdl(&doc); TiXmlElement *elem1 = hdl.FirstChildElement("ListePersonnes").Element; //Si l'élément cherché est non trouvé if(!elem1) return false; <li> Accès à une balise soeur (on passe de <Nom> à <Prenom>) :</li> TiXmlElement *elem1=elem1->NextSiblingElement("Prenom"); <li> Modification du fichier XML (ajout d'une balise fille <Personne> ainsi que ses balises filles <Nom>, <Prenom>, <Id>) :</li> TiXmlText* newText; TiXmlElement new_personne("Personne"); TiXmlElement new_nom("Nom"); TiXmlElement new_prenom("Prenom"); TiXmlElement new_id("Id");

//Récupération des informations de la personne newText = new TiXmlText(firstName.c_str); new_nom.InsertEndChild(*newText); newText = new TiXmlText(lastName.c_str); new_prenom.InsertEndChild(*newText); oss <<id; newText = new TiXmlText(oss.str.c_str); new_id.InsertEndChild(*newText); //Création des balises filles de <Personne> new_personne.InsertEndChild(new_nom); new_personne.InsertEndChild(new_prenom); new_personne.InsertEndChild(new_id); //Création de la balise fille <Personne> elem1->InsertEndChild(new_personne); <li> Sauvegarde du fichier XML :</li> doc.SaveFile(xmlFile); </ul>