Où est Charlie contre AWS

par | 7 Nov 2019 | Infrastructure & opérations

Faire de la détection de visage peut être un sujet compliqué. Mais l’API de AWS Rekognition vous permet de faire des choses assez poussées très rapidement. Dans ce billet je vais vous présenter comment gagner à “où est Charlie” à tous les coups.

Prérequis

Je vais vous présenter une utilisation de l’API en python en utilisant la librairie boto3 qui permet d’automatiser à peu près tout ce que vous souhaitez sur AWS en python. J’utiliserai python3.6 (ou +) dans ce tuto.

On va donc créer un virtualenv pour s’isoler de votre machine et pas trop la polluer avec de nouveaux paquets python :

python -m venv mon-venv

On l’active :

source mon-venv/bin/activate

Et on installe boto3 :

pip install boto3

Et c’est parti !

Configuration

Pour communiquer avec AWS via boto3, il y a quelques étapes préalables. Il faut commencer par créer une IAM dans AWS et récupérer les clés d’accès et la clé secrète. Une fois fait, il y a plusieurs façon de les utiliser. Soit on les déclare dans ~/.aws/credentials et boto3 va automatiquement les lire pour les utiliser. Soit on les définit dans l’environnement en utilisant export comme ceci :

export AWS_ACCESS_KEY="ma-super-clé"
export AWS_SECRET_KEY="ma-clé-que-personne-connait"

C’est parti pour la détection !

On va commencer par instancier le client boto3 pour rekognition puis on va ouvrir notre image en flux pour l’envoyer dans l’API :

import boto3
import os
client = boto3.client('rekognition',
    aws_access_key_id=os.environ['AWS_ACCESS_KEY'],
    aws_secret_access_key=os.environ['AWS_SECRET_KEY'])
image = open('/path/to/image.jpg').read()
client.detect_faces(Image={'Bytes': imageBytes},
    Attributes=['ALL'])

Si on fait le test avec cette image :

On obtient le résultat suivant : (j’ai tronqué un peu pour plus de lisibilité)

{'Confidence': 99.99996185302734,
 'FaceDetails': [{'AgeRange': {'High': 34, 'Low': 22},
 'BoundingBox': {'Height':…,'Left':…,'Top':…,'Width':…},
 'Emotions': [
  {'Confidence': 0.184126317501068, 'Type': 'CONFUSED'},
  {'Confidence': 0.522737801074981, 'Type': 'SAD'},
  {'Confidence': 0.094188556075096, 'Type': 'SURPRISED'},
  {'Confidence': 0.011522555723786, 'Type': 'FEAR'},
  {'Confidence': 98.85511779785156, 'Type': 'CALM'},
  {'Confidence': 0.038878932595252, 'Type': 'DISGUSTED'},
  {'Confidence': 0.062760457396507, 'Type': 'HAPPY'},
  {'Confidence': 0.230674102902412, 'Type': 'ANGRY'}],
 'Beard': {'Confidence': 89.78614044189453, 'Value': True},              
 'Eyeglasses': {'Confidence': 97.04179382324219, 'Value': False},
 'EyesOpen': {'Confidence': 99.22305297851562, 'Value': True},
 'Gender': {'Confidence': 98.88516998291016, 'Value': 'Male'},
 'Smile': {'Confidence': 99.73528289794922, 'Value': False},
 'Sunglasses': {'Confidence': 98.868484497070, 'Value': False},
 'MouthOpen': {'Confidence': 99.42462158203125, 'Value': False},
 'Mustache': {'Confidence': 66.82974243164062, 'Value': False},
 'Landmarks': [
   {'Type': 'eyeLeft',
     'X': 0.38918817043304443,
     'Y': 0.2930305302143097},
   {'Type': 'eyeRight', …},
   {'Type': 'mouthLeft', …},
   {'Type': 'mouthRight', …},
   {'Type': 'nose', …},
   {'Type': 'leftEyeBrowLeft', …},
   {'Type': 'leftEyeBrowRight', …},
   {'Type': 'leftEyeBrowUp', …},
   {'Type': 'rightEyeBrowLeft', …},
   {'Type': 'rightEyeBrowRight', …},
   {'Type': 'rightEyeBrowUp', …},
   {'Type': 'leftEyeLeft', …},
   {'Type': 'leftEyeRight', …},
   {'Type': 'leftEyeUp', …},
   {'Type': 'leftEyeDown', …},
   {'Type': 'rightEyeLeft', …},
   {'Type': 'rightEyeRight', …},
   {'Type': 'rightEyeUp', …},
   {'Type': 'rightEyeDown', …},
   {'Type': 'noseLeft', …},
   {'Type': 'noseRight', …},
   {'Type': 'mouthUp', …},
   {'Type': 'mouthDown', …},
   {'Type': 'leftPupil', …},
   {'Type': 'rightPupil', …},
   {'Type': 'upperJawlineLeft', …},
   {'Type': 'midJawlineLeft', …},
   {'Type': 'chinBottom', …},
   {'Type': 'midJawlineRight', …},
   {'Type': 'upperJawlineRight', …}],
  'Pose': {'Pitch': …, 'Roll': …, 'Yaw': …},
  'Quality': {'Brightness': …, 'Sharpness': …}}]

On peut voir qu’il détecte vraiment beaucoup de choses ! Les émotions, la position des éléments du visage, quelques traits distinctifs (barbe, lunettes, …), les rotations du visage, et chaque détail est toujours accompagné d’un indice de confiance. Il est aussi capable de traiter une photo avec plusieurs visages et de donner les caractéristiques de tous les visages reconnus sur la photo.

Et maintenant à nous deux Charlie !

On va chercher une bouille dans un joyeux panorama de gens bizarres :

Une bouille (oui la même qu’au dessus. Et alors ? J’allais pas en plus chercher une nouvelle image !)
Plein de gens bizarres

On va donc juste envoyer tout ça dans la moulinette pour voir ce que ça donne.

face = open('/path/to/image-face.jpg').read()
team = open('/path/to/image-team.jpg').read()
client.compare(face, team, 90) # Le 90 magique c'est un seuil de détection basé sur l'indice de confiance de rekognition

Pfiou ! Après tous ces efforts on pourrait faire une pause mais non on est courageux et on va analyser le résultat pour voir ce que ça donne :

{'FaceMatches': [
  {'Face': 
    {'BoundingBox': 
      {'Height': 0.06476461887359619,
       'Left': 0.5835976004600525,
       'Top': 0.2256196290254593,
       'Width': 0.023400133475661278},
     'Confidence': 99.99836730957031,
     'Landmarks': [
       {'Type': 'eyeLeft',
        'X': 0.5919212698936462,
        'Y': 0.2561943531036377},
       {'Type': 'eyeRight', …},
       {'Type': 'mouthLeft', …},
       {'Type': 'mouthRight', …},
       {'Type': 'nose', …}]},
  ],
 'UnmatchedFaces': [{}, {}, …]}

Il m’a trouvé ! Il place ma trombine dans la photo, il donne quelques caractéristiques (mais moins que le detect_faces), toujours avec l’indice de confiance et en bonus il nous donne tous les visages trouvés qui ne correspondent pas. Selon le seuil, la qualité de la photo, l’effort de photoshop pour mettre mon visage partout, il pourrait aussi trouver plusieurs visages qui “matchent”.

Le test ultime

On va voir ce que AWS peut faire contre Charlie :

On commence déjà par la détection simple histoire de voir si Charlie n’a pas quelque chose en plus qui lui permet de se camoufler et le résultat :

{'FaceDetails': [], …}

Zut !
Trop fort ce Charlie…

Benoît

Benoît

Lead dev @theTribe

Et si on discutait ?