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.
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.
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.
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.
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.
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 :
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.






