Tuto OpenCV : FaceTracking

Aujourd’hui je vais vous proposer un nouveau tutoriel pour OpenCV menant directement sur une application pratique: le Face Tracking. Au cours de ce tutoriel, nous allons réaliser un programme le plus simple possible permettant d’effectuer le suivi d’un visage à partir d’un flux vidéo.

La théorie

Et oui je sais que cela ne vous intéresse pas forcément, mais je vous assure que c’est important afin de mieux comprendre le fonctionnement du programme. Pour ce cas de Face Tracking, j’ai choisi d’utiliser une des techniques les plus utilisées: la méthode de Viola et Jones. 

Le but de cette méthode va être de créer un classificateur contenant un ensemble de propriétés d’un objet précis, dans notre cas un visage. Ce classificateur est donc construit en utilisant des caractéristiques pseudo-haar et un apprentissage grâce à un très grand nombre d’images. Viola et Jones choisissent d’utiliser une classification par boosting, le principe de ce boosting est d’utiliser plusieurs classificateurs dits faibles afin d’en créer un fort.

Les classificateurs faibles ne contiennent en général qu’une caractéristique à vérifier et sont donc rapides en terme de calcul. Par la suite on utilise cette série de classificateurs faible en « cascade » afin de vérifier si l’objet est détecté dans l’image. Pour plus d’information sur cette méthode, je vous invite à consulter l’article wikipédia sur le sujet.

La pratique

Pour cette implémentation, nous n’allons pas nous occuper de la construction du classificateur, je vous le fournis ici. Nous allons donc nous concentrer sur l’utilisation de l’algorithme qui est implémenté dans OpenCV.

La base

Pour commencer, nous allons mettre en place les premières briques du programme. On commence par inclure les fichiers nécessaires au programme.

#include <opencv/cv.h> 

#include <opencv/highgui.h>

 #include <stdio.h>

Ensuite on va déclarer deux de manière globale deux variables, le premier est le classificateur et le second une zone mémoire utilisée comme « buffer » pour la détection des visages. Le fait de les déclarer de manière globale n’est pas forcément idéal, mais le but est ici d’avoir le code le plus simple possible. Pour une application plus importante, il est recommandé de déclarer ces deux variables dans la partie nécessitant la détection de visage.

CvHaarClassifierCascade *cascade; 

CvMemStorage *storage;

int key;

On rentre maintenant dans la fonction main, et nous allons déclarer tous les objets nécessaires par la suite. Nous avons besoin de peu d’élément, le premier sera une image (IplImage), le second sera le périphérique d’entrée de la caméra (CvCapture) et le dernier sera un int nous permettant de sortir de la boucle de traitement. Ce qui nous donne:

CvCapture *capture; IplImage *frame; int key;

Il faut ensuite initialiser tout ce beau monde. On commence par ouvrir le fichier du classificateur Haar

cascade = ( CvHaarClassifierCascade* )cvLoad( « haarcascade_frontalface_alt.xml », 0, 0, 0 );

On ouvre le flux caméra avec cvCreateCameraCapture

capture = cvCreateCameraCapture(CV_CAP_ANY)

Enfin on initialise l’espace mémoire

storage = cvCreateMemStorage( 0 );

Afin d’afficher tout cela, nous aurons besoin d’une fenêtre

cvNamedWindow( « Window-FT », 1 );

Nous sommes en C++, il ne faut donc pas oublier de libérer la mémoire avant la fin du programme, on doit alors effacer tous les objets précédemment créé.

cvReleaseCapture( &capture ); 

cvDestroyWindow( « Window-FT » );

 cvReleaseHaarClassifierCascade( &cascade );

cvReleaseMemStorage( &storage );

Nous avons donc ici la base du programme. Si vous compilez vous ne devriez pas avoir grand-chose qui se passe et c’est normal, il manque l’essentiel, la boucle de traitement d’image ainsi que la fonction permettant l’affichage du tracking facial. C’est ce que nous allons voir dès maintenant.

La boucle de traitement

Cette étape est plutôt simple, mais elle est très importante, le principe est d’effectuer, à chaque image envoyée par la caméra au programme, la détection de visage. On en profite pour rajouter une petite commande permettant de quitter la boucle ( et donc le programme) en appuyant sur la touche « q ». Voici la boucle en question

while( key != ‘q’ )

{ 

img= cvQueryFrame( capture ); detectFaces( img ); key = cvWaitKey( 10 ); 

}

Pas grand-chose à expliquer ici, une boucle while classique, on déclare une frame (image) qui correspondra à chaque instant à l’image envoyée par la caméra. La fonction detectFaces est la fonction que nous allons étudier juste après qui permet de faire la dectetion de visage à proprement parler. Si vous voulez tester votre caméra, il vous suffit de remplacer detectFaces(img); par cvShowImage( « Window-FT », img );si vous compilez, vous devriez voir en temps réel ce que votre caméra est en train de filmer.

La fonction detectFaces

Il ne reste plus qu’a créer la fonction detectFaces afin de permettre le face tracking. Voici la fonction

void detectFaces( IplImage *img ) {

int i;

CvSeq *faces = cvHaarDetectObjects(img, cascade, storage, 1.1, 3 , 0,  cvSize( 40, 40 ) );

for( i = 0 ; i < ( faces ? faces->total : 0 ) ; i++ )

{

CvRect *r = ( CvRect* )cvGetSeqElem( faces, i );

cvRectangle( img, cvPoint( r->x, r->y ), cvPoint( r->x + r->width, r->y + r->height ), CV_RGB( 255, 0, 0 ), 1, 8, 0 );

}

cvShowImage( « Window-FT », img );

}

La fonction récupère en paramètre l’image de la caméra récupérer pendant la boucle de traitement. C’est sur cette image que toutes les opérations vont être effectuées par la suite. C’est là où vous allez apprécier OpenCV, car pour la détection de visage, la méthode de Viola et Jones est déjà implémentée et deviens très facile à utilisé. Tout réside dans la fonction cvHaarDetectObjects.

Le résultat de cette fonction est une série d’objets qui ont passé les critères de sélection définis par votre classificateur. On définit donc une CvSeq qui correspond à une séquence d’objet d’un même type, dans notre cas ce sera nos différents visages détectés.

Au niveau des paramètres de cette fonction img représente l’image a traité, cascade est le classificateur choisi pour faire le test, storage est l’espace mémoire nécessaire pour effectuer l’opération, 1.1 représente le « scale factor », 3 représente le nombre de voisins minimum , 0 est un paramètre supplémentaire qui permet de rajouter des filtres particuliers par exemple un filtre de canny avec CV_HAAR_DO_CANNY_PRUNING cvSize( 40, 40 ) représente la taille minimale d’un objet dans la vidéo.

Nous avons maintenant l’ensemble des visages qui ont été détectés dans l’image dans notre CvSeq, et afin d’afficher le résultat à l’écran nous allons simplement dessiner un carré autour du visage détecté. Ensuite on rentre dans une boucle for qui va passer en revue tous les visages détectés, et pour chacun créer un rectangle autour du visage repéré.

Je ne détaille pas le dessin du rectangle, si vous avez des questions n’hésitez pas à les poser en commentaire. Enfin la dernière étape est d’afficher à chaque appel de fonction l’image de la caméra avec les carrés permettant de détecter les visages, grâce à la fonction cvShowImage( « Window-FT », img ); 

Pour cette fonction le « Window-FT » est le nom de la fenêtre que vous avez créée et le img représente l’image à afficher dans la fenêtre. Voilà vous disposer maintenant d’un programme fonctionnel, je vous invite à poster vos questions en commentaires. Vous trouverez le classificateur ici et pour les sources de ce petit programme c’est par ici.

 

  • vlp

    Hello,

    Comme pourrais-je faire pour faire le meme programme sans l’affichage de la video ?
    La seule chose q j’aurai besoin sont les coordonnées r->x et r->y.

    Merci et super tutorial !!

    A+

  • Bonjour,
    Oui cela est possible sans problème.
    Les coordonnées r->x et r->y sont lié au rectangle « CvRect ».
    Il est donc tout à fait possible en utilisant la bibliothèque openCV de dessiner un rectangle et de recupérer ses coordonnées en utilisant r->x et r->y, la vidéo n’a pas d’influence là dessus.

    J’espère avoir répondu à la question 🙂

  • ben10

    Salut,
    Tout d’abord je doit dire que j’admire ton tuto,
    cependant j’ai quelque difficulté a comprendre le rapport entre le classificateur et le programme.
    D’ou mes questions:
    -comment créé t’on haarcascade_frontalface_alt.xml ?
    -comment utilise-t-on le classificateur fournis ?
    (le liens étant une ligne de caractères)

    Merci d’avance pour les réponses …
    Et bonne continuation 🙂

  • Merci pour ton commentaire 🙂

    Pour la création du haarcascade_frontalface_alt.xml, c’est en fait un fichier XML formaté d’une certaine façon. Pour mieux comprendre je te conseil de l’ouvrir avec un éditeur de texte afin de regarder comment c’est fait. Si tu veux plus d’informations concernant la création de ce type de classificateur, il faut se tourner vers d’autres tutoriaux (globalement la création d’un tel classificateur se fait en utilisant un programme d’apprentissage qui va remplir le classificateur de données en lui donnant des images tests). En cherchant sur google tu devrait trouver des informations précises (http://fr.wikipedia.org/wiki/M%C3%A9thode_de_Viola_et_Jones#Apprentissage qui est la méthode purement théorique).

    Pour la partie utilisation du classificateur encore une fois l’avantage d’OpenCV est que les fonctions de base pour utiliser le classificateur. Si tu veux aller plus loin, je te recommande soit d’e regarder le code sources des fonctions OpenCV associé (je sais pas si il est disponible 😡 ) ou sinon de regarder la méthode de Viola et Jones sur wikipédia ou directement les papiers de recherches associés.

  • Fred

    Bonjour, et merci pour ton tuto! J’aurai aimé savoir comment tu as généré ton classificateur; ceci afin de pouvoir appliquer la détection à autre chose (chien, chats…). Comment sont déterminés les critères (manuellement ou par algo?) combien d’images utilisées… si tu peux m’éclairer la dessus je t’en serai très reconnaissant!

  • Bonjour,
    Ce classificateur est un parmi d’autre qui est cité en exemple lorsque l’on effectue des recherche sur le sujet.
    La construction d’un tel classificateur est assez complexe, et je n’ai jamais essayé d’en réaliser un par moi même.

    A priori le classificateur a été généré avec un algo, et un échantillon de plusieurs milliers d’images est utilisé (dans le papier de recherche de référence, ils parlent de 5000 images il me semble).

    Je te recommande de faire des recherches sur le Haartraining si le sujet t’intéresse et je te renvoie sur deux liens qui t’éclaireront surement plus que moi:
    http://coding-robin.de/2013/07/22/train-your-own-opencv-haar-classifier.html
    http://note.sonots.com/SciSoftware/haartraining.html

  • Mazouz

    bonjour,
    d’abord merci pour le tuto!j’ai un problème avec la boucle lorsque j’utilise la fonction showImage(img); la fenêtre de resultat contient que des pixels gris.
    c vous pouvez m’aider
    merci d’avance

  • Missa

    Bonjour ,
    J’ai lus le tuto .. pour moi je veux faire un la detection sur une image fix pas capturé par la camera , comment faire ?
    Mercii

  • Bonjour,
    Normalement cela est assez simple, il suffit d’enlever la boucle While testant toutes les images venant de la caméra, et remplacer le img dans:
    detectFaces( img )
    par la source de l’image (actuellement la boucle while met dans la variable img chaque image issue de la caméra)

    En espérant avoir pu aider.

  • soumah

    je trouve ton tuto super intéressant, je te remercie beaucoup, J’ai un MIni projet sur ce thème détection de visage avec mouvement de la bouche du nez en appliquant le Masque, je voulais savoir comment faire cela, genre comment utiliser opencv et opengl pour faire ce travail j’ai besoin de votre aide merci.

  • Bonjour,
    Je ne comprend pas vraiment le but du projet.
    Est ce qu’il s’agit de détecter des mouvement de bouche sur un visage ?
    Si cèst le cas en utilisant un masque simple cela doit être possible regarde par exemple cela:
    https://ratiler.wordpress.com/2014/09/08/detection-de-mouvement-avec-javacv/
    Ou d’autre méthodes de détection de mouvement avec OpenCV.

    Mais détecter un visage par rapport aux mouvement de bouche me semble un peu compliqué. En se basant sur un masque simple, on pourra détecter un mouvement mais que ce sera n’importe quel mouvement on ne pourra pas définir qu’il s’agit d’un visage uniquement sur cela.

  • Boubacar Sidy Diallo

    Bonjour, ton tuto est très interessant !! je cherche un programme C++ qui me permettra de detecter la tête. Pas seulement le visage, car je veux compter des personnes qui sont présentes dans une salle. j’ai des soucis pour l’algorithme car je n’arrive pas à construire le bon fichier xml.

  • Bonjour,
    Je ne suis pas sur que cela soit possible de manière simple (ou alors j’essayerai de passer par des filtres de base), car une tête est difficile à définir pour permettre un apprentissage ou la construction d’un fichier de sources précis.

    Je suppose que la caméra est en hauteur donc « une tête » est en réalité une forme ronde avec une démarcation aléatoire entre la peau et les cheveux. Mais entre toutes les couleurs de peaux, types de cheveux/coiffures. Cela me parait faisable dans une salle avec peu de personne en isolant les formes qui bougent, mais dans une salle remplie je ne vois pas vraiment de solution qui soit simple ou précise.

  • Boubacar Sidy Diallo

    Merci beaucoup !!!

  • Pauline Roche

    Bonjour,

    Je code en python est j’arrive à afficher un rectangle pour « traquer » mon visage mais maintenant j’aimerais filtrer mon visage que dans ce rectangle mais je n’arrive pas à voir comment appliquer mon filtre qu’a ce rectangle. Sachant que mon rectangle est défini par x, y, longueur, largeur grace à la fonction cv2.rectangle

  • Bonjour,

    Je ne suis pas certains de comprendre la question mais si je comprend bien, vous obtenez bien le rectangle qui suis le visage et vous voulez rajouter un filtre uniquement sur ce rectangle et non toute l’image.
    Il suffit alors que pour l’utilisation du filtre en paramètre vous passiez non pas l’image entière mais le rectangle renvoyé par OpenCV.

    Cela ressemblerais à quelque chose comme :

    CvRect *myRectangle = ( CvRect* )cvGetSeqElem( faces, i ); // de cette facon myRectangle est « l’image » à l’intérieur du rectangle

    myFiltre(myRectangle) // on applique le filtre uniquement sur le rectangle et non toute l’image

    Quel fonction de filtre utilisez vous ?
    Car selon son paramètre d’entrée cela peut varier.

  • Pauline Roche

    En fait j’utilise plusieurs filtre car mon objectif est de detecter mes pupilles et leurs positions ^^’ :

    import cv2
    import numpy as np
    import imutils

    #https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml
    face_cascade = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)

    MIN_THRESH = 12

    cap = cv2.VideoCapture(0)
    print « Coucou1 »
    while 1:
    ret, img = cap.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    retval, threshold = cv2.threshold(gray, 35, 255, cv2.THRESH_BINARY)
    threshol = 255 – threshold

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,7,8),2)
    #roi_gray = gray[y:y+h, x:x+w]
    #roi_color = img[y:y+h, x:x+w]

    #gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    #blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    #thresh = cv2.threshold(blurred, 124, 255, cv2.THRESH_BINARY)[1]

    #find contours in the thresholded image

    cnts = cv2.findContours(threshol.copy(), cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if imutils.is_cv2() else cnts[1]

    # loop over the contours
    for c in cnts:

    #compute the center of the contour
    M = cv2.moments(c)
    if (M[« m00 »]==0):
    M[« m00 »]=1

    cX = int(M[« m10 »] / M[« m00 »])
    cY = int(M[« m01 »] / M[« m00 »])

    # draw the contour and center of the shape on the image

    cv2.drawContours(img, [c], -1, (156, 2, 255), 5)
    cv2.circle(img, (cX, cY), 7, (255, 145, 89), -1)
    cv2.putText(img, « center », (cX – 20, cY – 20),
    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (78, 189, 34), 2)

    cv2.imshow(‘img’,img)
    #im2, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    #cv2.imshow(‘img’,im2)
    print « Hello »
    #print « contour »
    k = cv2.waitKey(30) & 0xff
    if k == 27:
    print « Au revoir3 »
    break

    cap.release()
    cv2.destroyAllWindows()

  • Le premier problème avec la détection des pupilles va être que la taille des pupilles vaêtre minuscule en comparaison de la tête de la personne. Cela ne va représenter que quelques pixels noir. En prenant par exemple mes images d’illustration, il est impossible de repéré uniquement la pupille dedans.

    Si votre image est de plus grand format et/ou résolution, cela sera peut être possible, mais pour faire cela je recommanderai de faire des gros plans.
    Bon courage !

    Au niveau du code. Je ne vois pas dans votre code à quel moment vous faites la détection de visage.
    Mais a priori pour le cas du filtre « gray »:

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    Je remplacerai juste le img par le rectangle de pixel retourné par :

    CvRect *myRectangle = ( CvRect* )cvGetSeqElem( faces, i );

    Ce qui donnerait:
    gray = cv2.cvtColor(myRectangle , cv2.COLOR_BGR2GRAY)

    De cette façon ce filtre se fera uniquement à l’intérieur du cadre suivant la tête.
    Je ne sais pas si cela est clair et va fonctionner, mais c’est comme cela que je ferai.