· Nolwen Brosson · Blog  · 7 min read

Reconnaissance Faciale: Construire un système complet simple et léger

Introduction

La reconnaissance faciale fait partie du vaste domaine de la vision par ordinateur. Grâce aux progrès spectaculaires du Deep Learning ces dix dernières années, ce domaine, et toutes ses applications pratiques, a atteint un niveau de maturité impressionnant.

Mais la bonne nouvelle, c’est que comprendre comment fonctionne réellement un système de reconnaissance faciale, et même en construire un soi-même, n’a jamais été aussi accessible.

Dans cette article, je vous présente un petit projet que nous avons construit, étape par étape, afin que vous puississiez visualiser très concrètement la mécanique derrière ces algorithmes. L’objectif n’est pas d’aller dans la complexité, mais de fournir une explication claire, illustrée, et accompagnée de code simple à comprendre.

📌 Le code complet (assez vieux) du projet est disponible ici: https://github.com/nbrosson/tt-face-recognition

https://github.com/nbrosson/tt-face-recognition

(J’ai essayé de mettre du code un peu plus propre dans l’article, car celui-ci date de plusieurs années…)

C’est quoi, exactement, la reconnaissance faciale ?

La reconnaissance faciale est la capacité d’un système informatique à identifier ou vérifier une personne à partir d’une image ou d’une vidéo de son visage.

Il existe deux usages principaux :

  • Identification (one-to-many) : déterminer qui est la personne parmi un ensemble d’identités connues. C’est ce que nous allons utiliser dans ce projet.
  • Vérification (one-to-one) : vérifier qu’une personne est bien celle qu’elle prétend être.

TL;DR en version simple

Supposons que vous vouliez repérer si Julia, Thomas ou Rémy apparaissent dans une photo.

Notre modèle doit :

  1. Analyser plusieurs images de Julia, Thomas et Rémy.
  2. Extraire uniquement leur visage.
  3. Transformer chaque visage en un vecteur numérique.
  4. Faire la même chose sur la photo que vous envoyez à l’API.
  5. Mesurer la similarité entre les vecteurs.
  6. Identifier lequel du trio ressemble le plus à la personne sur l’image.

Et si la personne n’est dans aucune catégorie, le modèle retournera la personne la plus ressemblante.

Voici le résumé des étapes :

  • On applique une détection de visage sur toutes les images du groupe de référence (Julia, Thomas, Rémy).
  • On stocke ces résultats localement.
  • On applique la même détection sur l’image envoyée par l’utilisateur.
  • On transforme chaque visage en vecteur 1D via un modèle pré-entraîné (VGGFace).
  • On compare les vecteurs via la distance euclidienne.
  • Celui avec la plus petite distance est considéré comme le plus ressemblant.

Le projet

Voici la structure du dossier :

├── data/
│   ├── images/
│   │  ├── images_array/      # Résultats du face detection
│   │  ├── raw_images/        # Images brutes par personne
│   │  |  ├── player_1/
│   │  |  ├── player_2/
│   │  |  ├── .../
├── face_detector/
│   ├── *.py
├── templates/
│   ├── *.html

Nous voulons :

➡️ Construire un algorithme complet de reconnaissance faciale

➡️ Accessible via une simple application Flask

Le projet se découpe en 4 étapes :

1. Définir le groupe de référence

Images brutes dans data/images/raw_images/<person_name>.

2. Appliquer la détection de visage (benchmark)

C’est la partie coûteuse.

Nous la faisons hors ligne, une fois pour toutes.

On appelle ça le pipeline d’entraînement.

3. Appliquer l’embedding

Transformer les visages (images) en vecteurs 1-dimension via VGGFace / ResNet50.

Cette étape est légère, on peut la faire à chaque requête.

4. Faire la prédiction

Comparer les vecteurs → identifier la personne la plus ressemblante.

Le pipeline complet (détection + embedding + comparaison) constitue le pipeline de prédiction.

Ci-dessous un petit schéma récapitulatif

Plongée dans le code

Dépendances

Python ≥ 3.6.

Si OpenCV vous pose problème, utilisez opencv-python-headless.

opencv-python==4.2.0.34
Flask==1.1.1
mtcnn==0.1.0
Pillow==7.2.0
keras-vggface==0.6
tensorflow==2.2

Face Detection


import os
import cv2
import numpy as np
from mtcnn import MTCNN
from PIL import Image
from keras_vggface.vggface import VGGFace


def extract_face(img, required_size=(224, 224)):
    """
    Extrait le visage principal d'une image lors du pré-traitement du benchmark
    :param img: image sous forme numpy array (BGR, comme renvoyé par cv2)
    :return: tableau numpy (1, 224, 224, 3) ou None si aucun visage n'est trouvé
    """
    detector = MTCNN()
    results = detector.detect_faces(img)

    if len(results) == 0:
        return None  # Aucun visage détecté

    x1, y1, w, h = results[0]['box']

    # Sécuriser les bornes
    x1 = max(0, x1)
    y1 = max(0, y1)
    x2 = x1 + max(0, w)
    y2 = y1 + max(0, h)

    face = img[y1:y2, x1:x2]

    # Redimensionnement
    face_img = Image.fromarray(face)
    face_img = face_img.resize(required_size)
    face_array = np.asarray(face_img)

    return face_array.reshape(1, required_size[0], required_size[1], 3)

def load_image_from_bytes(file_bytes):
    """Convertit des bytes en image numpy utilisable par OpenCV 
    quand tu reçois une image via une API (upload), sous forme de bytes
    """
    nparr = np.frombuffer(file_bytes, np.uint8)
    return cv2.imdecode(nparr, cv2.IMREAD_COLOR)


Embedding

On initialise le modèle une seule fois :

from keras_vggface.vggface import VGGFace

model = VGGFace(
    model='resnet50',
    include_top=False,
    input_shape=(224, 224, 3),
    pooling='avg'
)

Embedding & Aggregation

def compute_embedding(face_array):
    """
    Appelée après extract_face, sur le visage extrait.
    """
    return model.predict(face_array)[0]  # shape (2048,)

def compute_benchmark_embeddings(face_arrays_dict):
    """
    Appelée une fois que tu as tous les visages extraits pour chaque personne du benchmark.
    
    :param face_arrays_dict: {"personA": [face_array1, face_array2], ...}
    :return: {"personA": embedding_vector, ...}
    """
    embeddings = {}
    for person, arrays in face_arrays_dict.items():
        if len(arrays) == 0:
            continue
        vectors = [compute_embedding(arr) for arr in arrays]
        embeddings[person] = np.mean(vectors, axis=0)
    return embeddings

Comparaison des visages

La distance euclidienne:

def euclidean_distance(a, b):
    """
    Appelée pendant la prédiction, pour comparer l’embedding de l’image uploadée à chaque embedding du benchmark.
    """
    return np.linalg.norm(a - b)

Script final

def load_benchmark_faces(raw_images_root):
    """
    Parcourt le dossier raw_images_root et extrait les visages.
    Structure attendue :
        raw_images_root/
            person_1/
                img1.jpg
                img2.jpg
            person_2/
                img3.jpg
    :return: dict {"person_name": [face_array1, face_array2, ...], ...}
    """
    benchmark_faces = {}

    for person_name in os.listdir(raw_images_root):
        person_dir = os.path.join(raw_images_root, person_name)
        if not os.path.isdir(person_dir):
            continue

        face_arrays = []
        print(f"\\n👤 Traitement de la personne : {person_name}")

        for filename in os.listdir(person_dir):
            filepath = os.path.join(person_dir, filename)
            if not os.path.isfile(filepath):
                continue

            # On charge l'image avec OpenCV
            img = cv2.imread(filepath)
            if img is None:
                print(f"  ⚠️ Impossible de lire le fichier {filepath}")
                continue

            # Étape : face detection + extraction du visage
            face_array = extract_face(img)
            if face_array is not None:
                face_arrays.append(face_array)
                print(f"  ✅ Visage extrait depuis {filename}")
            else:
                print(f"  ❌ Pas de visage détecté dans {filename}")

        benchmark_faces[person_name] = face_arrays

    return benchmark_faces

# =========================
# 4. Pipeline de prédiction
# =========================

def predict_person_from_image(image_path, benchmark_embeddings):
    """
    Prend une image d'entrée et renvoie la personne la plus ressemblante.

    :param image_path: chemin vers l'image à tester
    :param benchmark_embeddings: dict {"person_name": embedding_vector}
    :return: (best_match_name, best_distance) ou (None, None) si échec
    """
    img = cv2.imread(image_path)
    if img is None:
        print("⚠️ Impossible de lire l'image d'entrée.")
        return None, None

    # 1) Extraction du visage
    face_array = extract_face(img)
    if face_array is None:
        print("⚠️ Aucun visage détecté dans l'image d'entrée.")
        return None, None

    # 2) Embedding de l'image uploadée
    uploaded_embedding = compute_embedding(face_array)

    # 3) Comparaison avec tous les embeddings du benchmark
    best_name = None
    best_distance = None

    for person, emb in benchmark_embeddings.items():
        dist = euclidean_distance(uploaded_embedding, emb)
        print(f"Distance avec {person} : {dist:.4f}")

        if best_distance is None or dist < best_distance:
            best_distance = dist
            best_name = person

    return best_name, best_distance

if __name__ == "__main__":
    RAW_IMAGES_ROOT = "data/images/raw_images"

    print("🔹 Chargement et extraction des visages du benchmark...")
    face_arrays_dict = load_benchmark_faces(RAW_IMAGES_ROOT)

    print("\\n🔹 Calcul des embeddings du benchmark...")
    benchmark_embeddings = compute_benchmark_embeddings(face_arrays_dict)

    TEST_IMAGE_PATH = "data/images/test_image.jpg"

    print(f"\\n🔹 Prédiction sur l'image : {TEST_IMAGE_PATH}")
    best_match, distance = predict_person_from_image(TEST_IMAGE_PATH, benchmark_embeddings)

    if best_match is not None:
        print(f"\\n✅ Personne la plus ressemblante : {best_match} (distance = {distance:.4f})")
    else:
        print("\\n❌ Impossible de trouver une correspondance (aucun visage ou erreur de lecture).")

Exécution du projet

Assurez-vous avant tout :

  • que chaque personne du benchmark a son dossier dans raw_images
  • que images_array/ existe et est vide au départ

Lancer le projet

pip install -r requirements.txt

# Appliquer la face detection hors ligne
python main.py apply-face-detection-on-benchmark-people

# Lancer l’app Flask
export FLASK_APP=app.py
flask run --port=5000

Vous pouvez maintenant aller sur :

📍 http://127.0.0.1:5000/

Uploader une image, et vérifier que le modèle retrouve la bonne personne.

Conclusion

Nous avons construit un pipeline complet de reconnaissance faciale :

  • détection du visage
  • extraction
  • embedding via VGGFace
  • comparaison vectorielle
  • API Flask pour tester l’ensemble
Back to Blog