Le projet

Pour créer un robot d’accueil le plus interactif possible nous avons décidé de faire en sorte qu’il soit capable de reconnaître les personnes avec qui il communique.

Afin d’atteindre ce but nous avons opté pour la bibliothèque OpenCV qui a le double avantage d’être facilement installée sur une Raspberry Pi et de contenir la plupart des composants nécessaires à la reconnaissance faciale.

Installation sur le RaspberryPi

Tout les détails relatifs à l’installation des composants sont décrits dans le post précédent « Comment créer un assistant d’accueil avec Raspberry Pi ? »

Nous ne nous étendrons donc pas plus sur ce point et pouvons ainsi rentrer dans le vif du sujet.

Le principe de la reconnaissance faciale

La reconnaissance faciale se fait en deux temps:

  1. La détection des visages dans les images enregistrées
  2. La reconnaissance des visages détectés dans l’étape précédente

La reconnaissance faciale nécessite beaucoup de ressources.
Nous avons donc déporté les calculs sur le serveur principal qui héberge S.A.R.A.H. Seule la détection des visages est effectuée en temps réel sur le Raspberry Pi.

Cette configuration nous a paru être le meilleur compromis pour limiter les échanges réseau tout en conservant une vitesse de reconnaissance acceptable.

Méthode de détection

Le but de la détection est d’extraire les visages de la vidéo en temps réel pour ensuite les envoyer au module de reconnaissance faciale situé sur le serveur disposant de plus de puissance de calcul.

Pour détecter les visages nous avons débuté avec la documentation OpenCV : http://docs.opencv.org/2.4/modules/contrib/doc/facerec/tutorial/facerec_video_recognition.html

Nous avons utilisé le fichier de configuration (haarcascade_frontalface_default.xml) par défaut qui après quelques tests est apparu très efficace pour notre besoin.

La détection en Python

Commençons donc par le début importer les librairies python nécessaires :

import os
import sys
import cv2
import numpy as np
import requests
import FaceRecogUtil

Nous importons les librairies classiques : opencv, numpy.

La librairie « requests » facilite la communication en http, nous l’utiliserons pour envoyer les visages extraits.

Le dernier import quant à lui contient toutes les fonctions utiles pour la détection de visage et la reconnaissance faciale.

La première étape consiste à initialiser le classifier d’OpenCV qui prend en paramètre le fichier de configuration « haar » et à démarrer la capture vidéo sur le device choisi (0 si un seul périphérique de capture est installé).

faceCascade = cv2.CascadeClassifier(config["detection_file_conf"])

video_capture = cv2.VideoCapture(config["device_id"])

Dans un boucle infinie, il suffit de récupérer toutes les frames du flux vidéo.
On en profite pour convertir les images en noir et blanc.

frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

Reste à appliquer la détection de visage sur chacune d’elles.
Nous verrons par la suite que traiter toutes les images en temps réel demande beaucoup de ressources pour la Raspberry Pi 2.

faces = faceCascade.detectMultiScale(
  gray,
  scaleFactor=1.2,
  minNeighbors=4,
  minSize=(config["face_minimum_width"], config["face_minimum_height"])
)

La fonction de détection de formes en fonction du paramétrage est déjà présente nativement dans OpenCV : Il suffit de passer en paramètre l’image capturée.

En sortie on récupère les coordonnées des visages dans l’image.
Il ne reste plus qu’a découper et récupérer les zones de l’image concernées

size = len(faces)

face_resized = []

for (x, y, w, h) in faces:
    #transform face - resize + gray color
    sideSquare = max([width,height])
    face = cv2.getRectSubPix(gray,(sideSquare,sideSquare),  (x +(w /2), (y +h /2)))
    face_resized.append(cv2.resize(face,(width,height),1.0,1.0,0))

Pour voir en direct ce qui est détecté on peut dessiner le rectangle à découper et l’afficher sur l’image en sortie :

cv2.rectangle(frame, (x, y), (x +w, y +h), (0, 255, 0), 2)
cv2.imshow('Webnet face detection', frame)

A ce niveau nous avons récupéré les visages des personnes présentes devant la caméra.
Il faut maintenant reconnaître les visages et y associer un nom.

Optimisation de la détection

L’utilisation de ce script sur la Raspberry Pi 2 nous a donné approximativement un framerate de 0.1 détection par seconde.

Notre besoin est d’utiliser la détection de visage pour reconnaître la personne présente devant la caméra. Cette fréquence de capture signifie que dans le pire des cas le sujet doit rester devant la caméra 10 secondes avant de pouvoir débuter le travail de reconnaissance faciale. Ce temps n’est pas acceptable pour un traitement de l’image en temps réel. Nous allons donc optimiser le code précédent pour obtenir un framerate plus raisonnable.

Le principe de détection « haar » fonctionne comme si une « fenêtre glissante » parcourait la surface de l’image. Schématiquement lorsque le modèle défini (dans notre cas un visage) est présent dans la fenêtre glissante alors il est détecté.

En conséquence plus le nombre d’itération sur l’image est grand, plus le temps nécessaire à la détection est lent. Le but est de réduire le temps nécessaire à la détection, tout en conservant un taux de détection de visage satisfaisant.

Le premier axe d’amélioration est de modifier la taille de la fenêtre de détection (minsize de la fonction detectMultiScale). Plus la fenêtre est grande, plus le nombre d’itérations sur l’image se réduit, plus la détection peut échouer.

Après plusieurs tests nous avons finalement opté pour une division de la taille de l’image par trois, soit une surface 9 fois moins grande à parcourir par la fenêtre glissante. Le résultat semble plus efficace qu’en jouant directement sur la propriété minsize.

La reconnaissance devient donc :

gray_resized=cv2.resize(gray,(0,0),fx=0.3333,fy=0.3333)

faces = faceCascade.detectMultiScale(
     gray_resized,
     scaleFactor=1.2,
     minNeighbors=4,
     minSize=(config["face_minimum_width"], config["face_minimum_height"])
)

Nous avons maintenant une détection de visage 10 fois plus rapide, mais aussi des visages 3 fois plus petits. Globalement les détails de l’image sont peu importants pour la détection de visage mais sont indispensables à la reconnaissance faciale.

L’astuce consiste alors à découper les visages sur l’image d’origine en utilisant les coordonnées de la détection multipliées par 3 :

for (x, y, w, h) in faces:
    sideSquare = max([w*3,h*3])
    face = cv2.getRectSubPix(gray,(sideSquare,sideSquare),(x*3+(w*3/2),(y*3+h*3/2)))
    face_resized.append(cv2.resize(face,(width,height),1.0,1.0,0))

On obtient des visages de la même qualité mais 9 fois plus rapidement et on arrive à atteindre un framerate de 1 détection de visage par seconde.

En jouant sur les paramètres scalefactor, minNeighbors, minSize et maxSize de la fonction de détection nous avons finalement atteint un framerate proche de 3 détections par seconde tout en gardant une qualité suffisante pour notre besoin.

La détection est désormais opérationnelle, il reste maintenant à reconnaître les visages détectés …

Reconnaissance faciale

On cherche maintenant à associer un nom à un visage qui à été reconnu devant la caméra.

Le choix de la méthode

Par défaut openCV contient 3 algorithmes de reconnaissance faciale : Eigenfaces, fisherfaces et LBPH (Local Binary Patterns Histograms).

Nous avons choisi d’utiliser le recognizer LBPH puisqu’il a le double avantage :

  • D’avoir un modèle qui peut être mis à jour sans avoir besoin d’être reconstruit à chaque modification.
  • De pouvoir fonctionner avec un échantillon d’image réduit .

La reconnaissance

Le Raspberry Pi est déjà bien chargé avec la détection de visage, la reconnaissance faciale est donc déportée sur un serveur annexe qui gère les communications du robot d’accueil.

On commence donc par créer le recognizer et charger le modèle :

model = cv2.createLBPHFaceRecognizer( threshold=200)
model.load(config["path_model_file"])

Le paramètre threshold définit le seuil à partir duquel l’indice de confiance est insuffisant et que l’on considère que le visage ne correspond à personne de connu.

Ici le modèle est déjà existant, nous verrons dans la suite comment l’initialiser.

Il faut maintenant récupérer les images issues de la détection de visage en temps réel et les préparer pour la détection.

face = cv2.imread(filePath)
unlink(filePath)
face_resized = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
face_resized = cv2.resize(face,(width,height),1.0,1.0,0)

Après avoir chargé l’image en mémoire on la supprime du système de fichier puis on la convertit en variations de gris et on la redimensionne afin qu’elle est la taille des photos utilisées dans le modèle.

Il ne reste plus alors qu’à soumettre l’image à l’algorithme :

[p_label, p_confidence] = model.predict(face_resized)

if p_label == -1:
    p_confidence = 0
    name = -1
    print name
else:
    name = p_label
    print "Predicted label = %s (confidence=%.2f)" % ( name,p_confidence)

logFaceRecog(name,p_confidence)

La méthode predict du modèle prend en paramètre l’image et applique l’algorithme de reconnaissance. Si l’indice de confiance n’est pas supérieur au seuil définit lors de la création du modèle le visage est inconnu.

Le nom retourné par le modèle est un entier qui représente la personne reconnue, il ne reste plus qu’a y associer les informations de l’utilisateur reconnu via une base de données.
Dans notre cas la fonction logFaceRecog enregistre en base toutes les personnes identifiées par la caméra.

Conclusion

Pour une première expérience, la réalisation d’une procédure de détection et de reconnaissance de visage à été largement facilitée par l’utilisation d’OpenCV.

Le résultat est correct mais doit encore être optimisé, la reconnaissance des visages échoue encore régulièrement.

Nous prévoyons d’améliorer la reconnaissance en jouant sur la qualité des images et en améliorant le modèle de reconnaissance.
Nous pensons également tester très prochainement une librairie plus avancée qu’OpenCV pour la reconnaissance faciale qui pourrait s’avérer bien plus performante : OpenBR