PAGE WIKI ETUDIANTS 2012-13 RECONNAISSANCE FACIALE

From air
Jump to navigation Jump to search

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

Gestion du projet

Grands temps de développement du projet
Apprentissage Détection Technologie Sous-équipe Etat
Base de données X - TinyXml Fabien & Christopher & Sylvain FAIT
Acquisition de données X X OpenCV Sylvain & Christopher FAIT
Détection de visages X X OpenCV Marie & Maxence & Camille & Clément FAIT
Authentification - X OpenCV Marie & Camille A tester
Documentation - - Wiki air Marie En cours
Interface - - Qt project Maxence & Clément En cours
Synchro. Interface-Auth. - - Qt project Camille & Christopher & Sylvain & Fabien En cours

Technologies et matériel utilisés

Chronologie de développement

  1. Détection avec quelques images en base
    • 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.
    • L'acquisition des données de la webcam est très facilement gérée par OpenCV
    • La détection de visage se base sur la méthode de Viola et Jones, aussi géré par OpenCV
    • Se focaliser sur la phase critique : l'identification de visages
  2. Gérer la phase d'apprentissage du système
  3. optimiser le système (statistiques, réglage du seuil,…)

Fonctionnement du projet

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.

La bibliothèque Qt

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 (<chemin d'installation de QT>/QT5.0.1/5.0.1/mingw47_32/include)
  • Dans le sous onglet "Linker", spécifier le dossier lib de QT (<chemin d'installation 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 <QtWidgets/QLabel> 
QWidget : #include <QtWidgets/QWidget>
QPixmap : #include <QtGui/QPixmap>

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 <chemin vers le .h comprenant le signal> -o <chemin vers les sources du cpp>\moc_<nom du .h>.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 :


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 <<idPersonne;
 Result = "imgResizedGray\\picture"+convertIdPersonne.str()+""+convert.str()+".jpg";
 // Variables nécessaires au traitement de l'image
 IplImage *dst = cvCreateImage(cvSize(400, 400),subImg->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<Mat> images;
 vector<int> labels;
 // Va chercher les données dans notre fichier de données
 vector<Personne> personnes = collection->getPersonnes();
 // Vecteur contenant les images de notre système de données
 vector<Image> 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<FaceRecognizer> 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:

<?xml version="1.0" encoding="utf-8"?>
<ListePersonnes>
   <Personne>
       <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:

  • tinystr.cpp
  • tinyxml.cpp
  • tinyxmlerror.cpp
  • tinyxmlparser.cpp


La documentation est disponible ici. Quelques éléments sur le fonctionnement de la librairie:

  • Chargement d'un fichier XML :
  • TiXmlDocument doc = TiXmlDocument(xmlFile);
  • Accès à une balise fille (dans ce cas c'est <ListePersonnes>) :
  • TiXmlHandle hdl(&doc); TiXmlElement *elem1 = hdl.FirstChildElement("ListePersonnes").Element(); //Si l'élément cherché est non trouvé if(!elem1) return false;
  • Accès à une balise soeur (on passe de <Nom> à <Prenom>) :
  • TiXmlElement *elem1=elem1->NextSiblingElement("Prenom");
  • Modification du fichier XML (ajout d'une balise fille <Personne> ainsi que ses balises filles <Nom>, <Prenom>, <Id>) :
  •  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);
    
  • Sauvegarde du fichier XML :
  • doc.SaveFile(xmlFile);