Posts taggés Python

Comment créer un assistant d’accueil avec Raspberry Pi ? – Reconnaissance faciale avec OpenCV

0

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

 

N’hésitez pas à aller voir notre vidéo YouTube sur ce sujet.

Comment créer un assistant d’accueil avec Raspberry Pi ?

2

Le projet

Dans le cadre de PLANET (Plan d’excellence technologique), nous souhaitions nous lancer dans un projet en rapport avec le monde des objets connectés.
C’est comme cela que lors d’un brainstorming, nous avons émis l’idée de créer un « assistant » d’accueil basé sur une des nouvelles plateformes d’IoT (Internet of Things) qui sont apparus récemment.
Nous avions le choix entre plusieurs solutions : RaspberryPi, Galileo (Intel), Adruino, etc. …

Nous avons finalement choisi la RaspberryPi 2 qui est une des plus performantes par rapport au prix et qui, de plus, est suivie et supportée par une très grande communauté active sur internet.

Les différentes solutions

Nous étant mis d’accord sur la base de travail de notre assistant, nous nous sommes mis à la recherche de solutions open-source existantes permettant de gérer les commandes vocales.

Nous avons trouvé plusieurs solutions et avons finalement sélectionné deux solutions pour tenter une première implémentation : JASPER et S.A.R.A.H.

JASPER est une solution qui nous a semblé prometteuse et qui a l’avantage d’avoir été conçue pour fonctionner sur RaspberryPi.

Malheureusement après nos premiers tests, nous nous somme rapidement aperçu d’une limitation assez gênante : La majorité des moteurs de STT (Speech to Text) qui sont compatibles avec la solution ne supportent pas très bien la langue française.

Le seul moteur de reconnaissance qui gérait très bien la langue de Molière était celui fourni avec l’API Google. Là encore, il y’a un gros bémol : L’API possède une limitation du nombre d’appels d’environ 50 appels/jour. Cela a fini par sceller le sort de Jasper en ce qui nous concerne.
Notre solution devant être un assistant d’accueil dans nos locaux (en France), cela n’était tout simplement pas viable sur le plan pratique.

S.A.R.A.H. nous a finalement convenu parfaitement.
La solution se base sur la reconnaissance vocale de Windows qui gère très bien la langue française de manière native.
S.A.R.A.H. n’étant compatible qu’avec Windows, nous avons donc décidé d’installer S.A.R.A.H. sur un ordinateur et de déporter la capture audio et vidéo sur la RaspberryPi grâce au module RTP de S.A.R.A.H.

Principe de fonctionnement de S.A.R.A.H.

La solution S.A.R.A.H. est donc conçue pour fonctionner sur Windows.

Elle se décompose en deux parties :

  • Le client: C# (avec gestion de la Kinect) pour la gestion de la reconnaissance de la voix, des gestes, des visages et des des QRCodes.
  • Le serveur: NodeJS (ExpressJs) pour la communication et l’interaction avec le reste des fonctionnalités et les objets connectés.

La partie « client » recueille les commandes (vocales en ce qui nous concerne) et après reconnaissance les transmet au serveur NodeJs.
Grâce au fichier de grammaire défini dans chacun de nos plugins (NodeJs) S.A.R.A.H. détermine le code à exécuter.

Il suffit donc de coder la fonctionnalité et de renvoyer une réponse au format texte que S.A.R.A.H. va faire prononcer par Windows.

Architecture de la solution

1

La solution complète que nous avons retenu est constituée de :

  • Serveur Windows (un simple ordinateur peut faire l’affaire)
  • RaspberryPi 2 model B avec un dongle Wifi
  • Une webcam USB Logitech HD Pro C920 avec micro intégré
  • Haut-parleurs externes connectés sur le port Jack de la Raspberry

 

Une transaction classique se déroulera de la façon suivante :

1. La Raspberry capture l’audio et la transmet à S.A.R.A.H. via RTP grâce à FFMpeg
2. S.A.R.A.H. effectue le traitement et donne sa réponse audio
3. La réponse audio est transférée à la RaspberryPi via RTP grâce à FFMpeg et Virtual Audio Capture Grabber
4. La Raspberry « joue » la réponse audio reçu sur les haut-parleurs

Installation de S.A.R.A.H.

Nous avons donc décidé d’installer S.A.R.A.H. à l’aide de l’installateur blog.encausse.net/sarah/

Nous nous somme basé sur la version 4.0 Bêta, le module RTP ne fonctionnant pas sur la version 3.
Nous reviendrons plus tard sur cette fonctionnalité.

Nous avons également installé les modules suivants pour faire fonctionner la solution :

 

Installation RaspberryPi

Nous avons choisi de nous baser sur la distribution Raspbian.

Il nous a ensuite fallut installer :

 

Interconnexion S.A.R.A.H. et RaspberryPi

Une fois que le client et le serveur S.A.R.A.H. sont démarrés, nous allons pouvoir leur envoyer des commandes vocales.

Pour pouvoir capturer l’entrée audio depuis la Raspberry on lance la commande suivante :

arecord -D hw:0,0 -f S16_LE -r 16000 -c 2 -t wav | ffmpeg -i pipe:0 -bufsize 64k -ac 2 -f "volume=20dB"  -ar 16000 -acodec pcm_s16le -f rtp rtp://[IP_SERVER_SARAH]:7887

-D hw:0,0  » correspond au device de capture.
Pour trouver l’id de votre périphérique de capture, il vous suffira de lancer la commande « arecord -l », ils seront alors tous listés avec un ID de « device » et de « subdevice »

 

Maintenant les commandes vocales devraient être reconnues par S.A.R.A.H. mais la réponse audio se fera entendre sur l’ordinateur sur lequel S.A.R.A.H. est installée.

Pour pouvoir faire suivre les réponses vers la RaspberryPi. Il faut lancer le l’utilitaire de Virtual Audio Capture Grabber

Démarrer > Tous les programmes > Screen Capture Recorder > Record > broadcast > Setup local audio broadcast streaming server

Ensuite lancer l’invité de commande de Windows puis exécuter la commande suivante :

ffmpeg -f dshow -i audio="virtual-audio-capturer" -acodec libmp3lame -f rtp rtp://[IP_RASPBERRY_PI]:7888

Maintenant, Il suffit de parler dans le micro de la webcam reliée à la Raspberry et ainsi recevoir la réponse instantanément sur la sortie audio.

 

Mise en place de la reconnaissance faciale

La mise en place de la reconnaissance faciale est décrite en détail dans l’article suivant : Reconnaissance faciale avec OpenCV

Je vais quand même décrire le principe de fonctionnement que nous avons mis en place.

Nous avons décomposé la reconnaissance faciale en deux parties.
En effet, principalement pour des raisons de performances, nous avons découpé la reconnaissance faciale comme suit :

1. Le script de détection de visages est lancé sur la Raspberry en tâche de fond
2. Une fois qu’un ou plusieurs visages sont détectés, la ou les photos découpées des visages sont transmises sur le serveur S.A.R.A.H. (qui est également un serveur Web NodeJs)
3. Le contrôleur coté S.A.R.A.H. dépose les fichiers dans un dossier en attente de traitement et met à jour une table avec le nombre de personnes détectées devant la Raspberry
4. Le script de reconnaissance faciale est lancé sur le même serveur ou se situe S.A.R.A.H.
5. Ce script scanne en permanence le dossier contenant les photos déposées par le détecteur de visages
6. Il va ensuite effectuer la reconnaissance faciale et loguer les noms des personnes reconnues en BDD
7. Il suffit désormais de faire une requête en BDD depuis n’importe quel handler de commande S.A.R.A.H. pour savoir si une ou plusieurs personnes se situent devant la Webcam et si elles ont été reconnues.
Ainsi, nous pouvons personnaliser encore plus les réponses de l’assistant et imaginer des questions du type « S.A.R.A.H. est-ce que J’ai des RDV aujourd’hui ? », la réponse devenant contextuelle à l’interlocuteur de S.A.R.A.H qui interrogera par exemple le calendrier Outlook de la personne reconnue.

Difficultés rencontrées

Voilà maintenant notre solution entièrement fonctionnelle. Mais cela n’a pas été une promenade de santé.

Nous avons en effet rencontrés de nombreuses difficultés que nous avons eu à surmonter pour arriver à une solution fonctionnelle. Je ne citerais que les 2 principales :

La transmission audio

La transmission audio n’a pas été simple à mettre en place au début car nous avions utilisé la première webcam que nous avions eue sous la main, pensant que cela pourrait faire l’affaire.
Ainsi, nous avons été incapables de faire reconnaître quoi que ce soit à S.A.R.A.H. via la transmission RTP jusqu’à ce que nous changions de Webcam.
Le fait de de passer sur une webcam avec une capture audio digne de ce nom, nous a permis de trouver notre salut.
Pensez donc à vous équiper d’une webcam disposant d’un bon micro. De notre côté, nous avons trouvé notre bonheur via la Logitech HD Pro C920.

Performances

Au début, nous avions décidé de mettre la capture audio, la détection de visages et la reconnaissance faciale sur la Raspberry. Nous avions surestimé grandement la puissance de cette dernière et nous nous sommes retrouvé avec des décalages de détection/reconnaissance faciale de l’ordre de 4 secondes, avec des pointes de consommation mémoire et CPU de l’ordre de 99%.

Après de nombreux tests nous avons finalement conservé uniquement la détection de visage sur la Raspberry et avons déporté la reconnaissance faciale sur un serveur bien plus puissant.

Conclusion

Cet article décrit le cheminement de notre réflexion pour mettre en place un assistant d’accueil, en intégrant des solutions open source et peu coûteuses.

On peut voir qu’aujourd’hui, il est relativement simple (en étant un peu bidouilleur) de mettre en place ce genre d’installation.

La seule limite est finalement l’imagination et les possibilités d’interfaçage avec d’autres services ou fonctionnalités.

Nous allons continuer à améliorer notre solution et espérons bientôt pouvoir publier une vidéo de démonstration de notre assistant dans des conditions d’utilisation normales.