MediaPipe : détecte les 21 points de ta main en temps réel avec Python

MediaPipe : détecte les 21 points de ta main en temps réel avec Python

De quoi avez-vous besoin

Version de Python

3.x

Packages

  • {"nom":"mediapipe","version":"1.0.1"}
  • {"nom":"opencv-python","version":"5.0.0"}

Difficulté

Débutant

Reconnaître une main sur une photo, suivre ses 21 articulations en direct et compter tes doigts levés : on croit ce genre de magie réservé aux labos de Google. Raté. MediaPipe, le framework de vision open source de Google, fait tout ça sur ton PC, sans GPU, sans cloud et sans envoyer la moindre image ailleurs. Tout tourne en local, en Python, en une trentaine de lignes.

Ici, on ne parle pas de détection d'objets façon YOLO qui se contente de dessiner un rectangle autour d'un visage. MediaPipe pose un véritable squelette sur ta main : 21 points, du poignet au bout des doigts, reliés entre eux. On va brancher une webcam et voir le résultat en direct.

Au menu : installer deux paquets, charger le modèle, détecter une main sur une photo, passer en temps réel avec la webcam, puis compter les doigts levés et reconnaître un geste. Du code copiable à l'identique, testé sous Python 3.11.

Étape 1 : installer les deux seules dépendances

Deux paquets suffisent. opencv-python s'occupe de la webcam et de l'affichage, mediapipe de la détection. Crée un environnement propre pour ne pas polluer ton Python système.

bash
python -m venv venv
source venv/bin/activate        # sous Windows : venv\Scripts\activate
pip install mediapipe opencv-python

Étape 2 : récupérer le modèle

MediaPipe sépare le code du modèle. Le modèle hand_landmarker pèse environ 8 Mo et contient le réseau déjà entraîné. Télécharge-le une fois, à côté de ton script.

bash
curl -L -o hand_landmarker.task \
  "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"

Étape 3 : détecter une main sur une photo

On attaque le vif. Ce script charge le modèle, lui donne une image, et récupère pour chaque main une liste de 21 points normalisés : des coordonnées entre 0 et 1, qu'on multiplie par la largeur et la hauteur de l'image pour obtenir des pixels.

python
import cv2
import mediapipe as mp

# 1. Charger le modèle
BaseOptions = mp.tasks.BaseOptions
HandLandmarker = mp.tasks.vision.HandLandmarker
HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions
RunningMode = mp.tasks.vision.RunningMode

options = HandLandmarkerOptions(
    base_options=BaseOptions(model_asset_path="hand_landmarker.task"),
    running_mode=RunningMode.IMAGE,
    num_hands=2,
)
detector = HandLandmarker.create_from_options(options)

# 2. Lire l'image et la convertir en RGB
img = cv2.imread("main.jpg")
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)

# 3. Détecter
result = detector.detect(mp_image)

# 4. Dessiner les 21 points et leurs connexions
connections = mp.tasks.vision.HandLandmarksConnections.HAND_CONNECTIONS
h, w = img.shape[:2]
for landmarks in result.hand_landmarks:
    pts = [(int(p.x * w), int(p.y * h)) for p in landmarks]
    for c in connections:
        cv2.line(img, pts[c.start], pts[c.end], (0, 255, 0), 2)
    for x, y in pts:
        cv2.circle(img, (x, y), 4, (0, 0, 255), -1)

cv2.imwrite("main_detectee.jpg", img)
detector.close()

Chaque point porte un numéro précis : le 0, c'est le poignet ; le 4, le bout du pouce ; le 8, le bout de l'index. Chacun possède trois coordonnées x, y et z. x et y sont relatives à l'image, z donne la profondeur par rapport au poignet. Ce numérotage stable, c'est ce qui va te permettre de coder de la logique par-dessus.

Étape 4 : passer en temps réel avec la webcam

Pour la vidéo, deux changements seulement. Le running_mode passe en VIDEO, et on appelle detect_for_video avec un timestamp croissant en millisecondes. Le reste est identique, sauf qu'on boucle sur les images de la webcam.

python
import cv2
import time
import mediapipe as mp

BaseOptions = mp.tasks.BaseOptions
HandLandmarker = mp.tasks.vision.HandLandmarker
HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions
RunningMode = mp.tasks.vision.RunningMode

options = HandLandmarkerOptions(
    base_options=BaseOptions(model_asset_path="hand_landmarker.task"),
    running_mode=RunningMode.VIDEO,
    num_hands=2,
)
detector = HandLandmarker.create_from_options(options)

connections = mp.tasks.vision.HandLandmarksConnections.HAND_CONNECTIONS
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    frame = cv2.flip(frame, 1)  # effet miroir
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
    ts = int(time.time() * 1000)
    result = detector.detect_for_video(mp_image, ts)

    h, w = frame.shape[:2]
    for landmarks in result.hand_landmarks:
        pts = [(int(p.x * w), int(p.y * h)) for p in landmarks]
        for c in connections:
            cv2.line(frame, pts[c.start], pts[c.end], (0, 255, 0), 2)
        for x, y in pts:
            cv2.circle(frame, (x, y), 4, (0, 0, 255), -1)

    cv2.imshow("MediaPipe - mains", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()
detector.close()

Appuie sur q pour quitter. Le flip horizontal crée un effet miroir naturel, comme si tu te regardais dans une glace.

Étape 5 : compter les doigts levés

Avec ces 21 points, tu peux écrire de la vraie logique. Le plus simple : compter les doigts levés. Pour chaque doigt, on compare la position du bout avec celle de l'articulation juste en dessous. Si le bout est plus haut que l'articulation, le doigt est levé. Le pouce, lui, se compare sur l'axe horizontal.

python
FINGER_TIPS = [4, 8, 12, 16, 20]  # pouce, index, majeur, annulaire, auriculaire

def compter_doigts(pts):
    doigts = 0
    if pts[4][0] > pts[3][0]:          # pouce : bout à droite de l'articulation
        doigts += 1
    for tip, pip in [(8, 6), (12, 10), (16, 14), (20, 18)]:
        if pts[tip][1] < pts[pip][1]:  # bout plus haut que l'articulation
            doigts += 1
    return doigts

# à insérer dans la boucle webcam, juste après le calcul de pts :
cv2.putText(frame, f"Doigts : {compter_doigts(pts)}", (10, 40),
            cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)

Attention : le sens du pouce s'inverse selon que la main est gauche ou droite. Si ton compteur se trompe sur le pouce, inverse simplement le signe de la comparaison.

Bonus : reconnaître un geste précis

Compter les doigts, c'est bien. Reconnaître un geste, c'est mieux. Le pouce levé se détecte en vérifiant que le bout du pouce est plus haut que tous les autres bouts de doigts :

python
def pouce_leve(pts):
    # le bout du pouce (4) est plus haut que tous les autres bouts
    return pts[4][1] < min(pts[i][1] for i in (8, 12, 16, 20))

# dans la boucle webcam, après le calcul de pts :
if pouce_leve(pts):
    cv2.putText(frame, "Pouce leve !", (10, 80),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)

Tu peux enchaîner les conditions pour détecter le signe de la paix (index et majeur levés, les autres repliés) ou coder un pierre-feuille-ciseaux. La seule vraie limite, ce sont les cas limites : éclairage changeant, main gauche ou droite, doigts partiellement cachés.

Trois pièges à éviter

Premier piège, le timestamp : en mode VIDEO, detect_for_video exige un entier strictement croissant, sinon MediaPipe lève une erreur. time.time() en millisecondes fait largement l'affaire. Deuxième piège, l'ordre des canaux : OpenCV travaille en BGR, MediaPipe attend du RGB. Oublier le cvtColor, c'est obtenir zéro main détectée, sans aucun message d'erreur. Troisième piège, la taille de l'image : une main trop loin ou une image trop grande peut passer inaperçue. Si rien ne sort, réduis la résolution ou rapproche ta main.

Et maintenant ?

Tu tiens un détecteur de mains local, gratuit et sans clé API. À partir de là, tout est possible : reconnaître des gestes comme le pouce levé ou le signe de la paix, jouer à pierre-feuille-ciseaux, contrôler un volume, ou s'attaquer à la langue des signes. MediaPipe ne se limite pas aux mains : pose du corps, maillage du visage, segmentation, détection d'objets, reconnaissance de gestes, il fournit des modèles pour tout ça, toujours en local.

La vraie leçon, c'est que l'IA de perception n'est plus une affaire de datacenter. Elle tourne sur ton processeur, en direct, dans une simple boucle while. Le plus dur aura été d'installer deux paquets.