MainCarto · note technique

Quel détecteur de mains pour MainCarto

Onze options passées au banc pour un seul étage du pipeline : localiser une main qui manipule des cartes. MediaPipe gagne, et la contrainte Python 3.12 tombe.

21 septembre 2026 Corpus visé ~40 vidéos · 2 700 frames Machine Win 11 · Ryzen 5 3600 · RTX 3070 8 Go

Le verdict

1

MediaPipe Hand Landmarker

Le seul candidat dont la qualité a été mesurée sur nos conditions au lieu d'être supposée. Apache-2.0 sans ambiguïté, une seule dépendance pip, dépôt vivant — commits jusqu'au 18/09/2026.

Il rend 21 landmarks et aucune bbox — c'est la doc qui le dit, et c'est exactement ce que bbox_padding = 0.18 anticipe déjà dans config.toml.

2

rtmlib (RTMPose / RTMW) — filet de secours

À n'activer que si MediaPipe rate un cadrage sur le corpus réel. Apache-2.0, ONNX, installable par pip, maintenu (0.0.16, 04/08/2026). Surtout : c'est la voie qui contourne mmcv, dont le gel est le vrai piège d'OpenMMLab. Plus de pièces pour un gain non démontré — n'y allez pas d'emblée.

3

Un VLM en re-classeur, jamais en détecteur

Pas pour trouver les mains : pour trancher les ~200 candidats vers la douzaine finale, là où « la main manipule vraiment des cartes, et c'est beau » est un jugement qu'aucun détecteur ne porte. Sur les crops déjà produits, pas sur les 2 700 frames.

Deux raisons structurelles l'excluent de l'étage de détection : la doc Claude qualifie elle-même ses sorties de localisation d'« approximatives », or le matting exige un cadre serré ; et un appel réseau contredit le « hors ligne » de la spec.

Ce qui change le projet

La contrainte Python 3.12 n'existe plus.

La ligne de CLAUDE.md — « Python 3.12 obligatoire (pas de wheel MediaPipe en 3.14) » — est périmée. Vérifié en exécutant, pas en lisant :

mediapipe 1.0.1  ·  Python 3.14.7  ·  Windows 11 x64   → installé
HandLandmarker sur 2 mains  → 2 détections, 21 landmarks, scores 0.936 / 0.961

La cause est dans le tag de la wheel. Depuis la 1.0.0 (27/07/2026), MediaPipe ne publie plus cp310/cp311/cp312 mais py3-none-win_amd64 : tag pur Python, requires_python absent. Le C++ est passé derrière un DLL C unique (tasks/c/libmediapipe.dll, 52,7 Mo) appelé sans dépendance à l'ABI CPython. Il n'y a plus de porte de version Python du tout. Les classifiers PyPI listent encore 3.9–3.12 : ils sont obsolètes, pas normatifs.

Le reste de la chaîne suit : torch 2.14.0 a bien une wheel cp314-cp314-win_amd64 (GIL, pas seulement cp314t), les index CUDA cu126 à cu130 ont des wheels Windows cp314, et onnxruntime 1.30.0 comme transformers 5.17.0 supportent 3.14.

Le piège qui vous attend à coup sûr

mp.solutions n'existe plus — zéro fichier sous solutions/ dans la wheel 1.0.1.

>>> mediapipe.solutions
AttributeError: module 'mediapipe' has no attribute 'solutions'

Tout tutoriel mp.solutions.hands.Hands() est mort ; la seule API est mediapipe.tasks.python.vision.HandLandmarker. Bonne nouvelle : pipeline/ n'est pas encore écrit, donc aucune dette de migration.

Mesuré, pas supposé

Le gros plan n'est pas le point faible qu'on croyait.

L'objection sérieuse à MediaPipe, c'était « ça décroche sur les gros plans et quand un objet occulte la main » — précisément nos deux conditions. Mesuré sur des mains réelles :

Robustesse, sur mains réelles

Chaque ligne est un balayage, pas un point isolé.

Main de 27 % à 100 % de la largeur du cadredétectée partout · 0,92–0,97
Rotation de 0° à 180°invariance complète · 0,92–0,96
Occlusion opaque jusqu'à 60 % de la main2 mains détectées
Flou de mouvement, noyau 5 → 41 pxdétecte encore · 0,58–0,93
46,6 ms
par frame 405×720, 1 thread CPU
126 s
les 2 700 frames, mono-thread
~21 s
sur 6 process
7,8 Mo
poids du modèle
netteté (variance du Laplacien, % du max) confiance de détection seuil min_sharpness = 60
0 50 100 net 5 9 15 21 31 41 noyau de flou (px)
Netteté normalisée à son maximum mesuré (177,3) ; confiance de détection sur 0–1. Les deux séries partagent l'axe pour montrer leur divergence, pas pour se comparer en valeur.

C'est le point de conception à retenir : la confiance de détection n'est pas un proxy de netteté. MediaPipe voit très bien des mains que min_sharpness = 60 rejettera. Le découplage sharpness 0.5 / det_conf 0.3 de config.toml est donc le bon choix — gardez-le.

Et le débit rend l'étage non problématique : le traitement nocturne est inutile ici. La RTX 3070 ne sert à rien pour la détection — gardez-la pour le matting.

Comparatif

ModèleLicencePoidsONNX Python / WindowsQualité sur notre casFriction
MediaPipe Hand Landmarker Apache-2.07,8 Mon/a (.task) 3.14 OK · vérifiéExcellente — mesurée1
YOLO26 (Ultralytics) AGPL-3.05–110 Mooui 3.13 maxbonne si poids « hand »2 + licence
RF-DETR Apache-2.0 Plus : PML 1.0 variableouivia torch bonne, aucun poids main3
D-FINE / DEIM Apache-2.0variableoui via torchà fine-tuner soi-même3
RTMPose / RTMW · via rtmlib Apache-2.0variableoui ≥ 3.10bonne — 21 kpts/main2
RTMPose · via mmcv/mmpose Apache-2.0variable— mmcv gelé 04/2024—5 — à éviter
ViTPose++ (transformers) Apache-2.090 Mo–1,3 Gonon 3.14 OKinadapté — exige une boîte personne3
Sapiens (Meta) CC-BY-NC 4.00,3–2 Bnon —308 kpts dont mains4 + licence
WiLoR / HaMeR CC-BY-NC-ND MANO AGPL 7 Mo (det.)non— très bonne en 3D4 + triple licence
100DOH hand_object_detector MIT (code)Google Drivenon Py 3.8 · torch 1.12 · build CUDA le plus pertinent… et le moins utilisable5 — quasi mort
VLM (Claude) sur frames API——— excellent jugement, bbox non fiable1 mais en ligne

Le coût du VLM, calculé et non estimé. Une frame 405×720 coûte ⌈405/28⌉ × ⌈720/28⌉ = 390 tokens visuels. Sur 2 700 frames : ~1,60 $ en Haiku 4.5, ~3,20 $ en Sonnet 5, ~8 $ en Opus 5 — à diviser par deux via la Batch API. Le coût n'est donc pas l'objection ; la bbox approximative et le hors-ligne le sont.

Option front · le texte contourne le détourage

La propriété est shape-outside, pas mask-border.

Précision de vocabulaire d'abord, parce qu'elle fait perdre du temps : mask-border découpe l'image de bordure d'un élément et n'a aucun effet sur le flux du texte. Pareil pour clip-path, qui rogne le rendu mais laisse le texte contourner le rectangle. La seule propriété qui fait couler le texte le long d'un contour, c'est shape-outside — et dans sa variante url(), elle lit le canal alpha de l'image. C'est exactement ce que l'étage de matting produit.

Démonstration réelle

La silhouette ci-dessous est un PNG à canal alpha généré dans la page ; le texte suit son contour, pas sa boîte.

Silhouette d'une main tenant une carte, générée en canvas.

Du texte en colonne, une dizaine de mains détourées posées dessus qui défilent plus lentement que le texte et libèrent en dérivant ce qu'elles cachaient. Le contour que vous voyez ici n'est pas dessiné à la main : il est calculé par le moteur à partir des pixels dont l'alpha dépasse le seuil. Basculez le bouton pour voir la différence avec le comportement par défaut, où le texte ne connaît que le rectangle de l'image et laisse de grandes poches vides entre les doigts et la marge.

Le réglage qui compte est shape-image-threshold : à 0 le contour épouse le moindre pixel non totalement transparent, ce qui rend les bords plumés très bruyants ; à 0.5 il suit la matière franche. C'est le même seuil que celui qui décide, en amont, de ce qu'on appelle « le bord de la main ».

shape-outside: url()
Lit le canal alpha de l'image et en fait le contour de dérivation du texte. Accepte aussi circle(), ellipse(), inset(), polygon().
shape-image-threshold
Seuil d'alpha qui définit le bord. Défaut 0 — mettez 0.5, sinon le plumé à alpha_feather_px = 1.5 rend le contour bruyant.
shape-margin
Marge de respiration entre le contour et le texte. Sans elle, les glyphes viennent toucher les doigts.
float obligatoire
Sans effet sur un élément non flottant. C'est la contrainte qui décide de tout, ci-dessous.
même origine
Image cross-origin sans CORS → le contour est ignoré silencieusement et vous retombez sur la boîte. Vos PNG sont servis depuis web/public/data/, donc même origine : sans objet ici.

Le conflit à trancher, et il est net

shape-outside exige float, donc un élément dans le flux normal. Or l'invariant 2 du projet fait dériver les mains en parallaxe à k ∈ [0,65 ; 0,80], ce qui suppose des couches positionnées hors flux. Les deux ne peuvent pas coexister sur la même main : une main flottante ne parallaxe pas, et une main en parallaxe ne réserve aucune gouttière.

Le faux espoir à écarter tout de suite : réserver la gouttière avec un flottant invisible et peindre la main en couche parallaxe séparée. Les deux se désalignent dès le premier pixel de scroll — et c'est précisément le mode de défaillance que l'invariant 2 existe pour interdire. Donc shape-outside est une alternative à la parallaxe, pas un ajout : soit des mains ancrées dans le texte qui le repoussent selon leur silhouette, soit des mains qui dérivent au-dessus. Le test de lisibilité par simulation reste obligatoire dans les deux cas, mais il ne mesure pas la même chose : occultation résiduelle d'un côté, gouttière réservée de l'autre.

Pièges vérifiés

licence

Ultralytics : pire que la lecture AGPL habituelle

AGPL-3.0 confirmée (champ PyPI et classifier OSI). Ultralytics soutient publiquement que tout usage, y compris interne en R&D et non commercial, exige une licence Enterprise sauf si vous ouvrez tout le projet en AGPL — plus large que le déclenchement AGPL classique par distribution ou service réseau.

Pour un projet privé non distribué, la lecture FSF ne déclencherait rien, mais vous seriez en désaccord avec l'éditeur. Évitez : vous n'en avez aucun besoin. Ce piège contamine aussi WiLoR, qui en dépend.

abandon

OpenMMLab est gelé

mmcv n'a plus de release depuis le 24/04/2024, mmpose depuis le 12/07/2024, avec des classifiers Python plafonnant à 3.10 et 3.9. Toute recette « installez mmcv » est un cul-de-sac en 3.14. Passez par rtmlib.

abandon

100DOH : la bonne sémantique, hors d'atteinte

Code MIT, et il prédit l'état de contact main-objet (N/S/O/P/F) — exactement notre sémantique. Mais Python 3.8, PyTorch 1.12.1, CUDA 11.3, compilation d'extensions CUDA, poids sur Google Drive. Et les auteurs précisent n'avoir pas entraîné le contact_state des objets. Le ressusciter sous Windows/3.14 coûterait plus que tout le reste du pipeline.

licence

Non commercial, à écarter par réflexe

Sapiens en CC-BY-NC 4.0 ; WiLoR en CC-BY-NC-ND — le ND interdit même les dérivés — plus MANO (inscription, non commercial). HaGRID est en CC-BY-SA-4.0 : commercial autorisé, mais ShareAlike, donc viral si vous entraîniez un modèle dessus.

structure

Les modèles whole-body ne peuvent pas marcher ici

ViTPose et ViTPose++ sont top-down : ils exigent une boîte personne fournie par un détecteur externe. Sur un gros plan où seule une main est visible, il n'y a pas de personne à détecter — la chaîne casse avant la pose. Que ViTPose++ ait un expert COCO-WholeBody (dataset_index=5) à 21 points par main n'y change rien.

périmètre

Le geste n'est pas la manipulation

MediaPipe Gesture Recognizer ne connaît que 8 gestes conserve (Closed_Fist, Open_Palm, Thumb_Up…) et sa doc ne mentionne aucune interaction main-objet. Inutile pour « manipule des cartes ». Idem HaGRID : gestes statiques.

pérennité

Le modèle de main n'a pas bougé depuis 2023

float16/1 est la seule version publiée, et float16/latest est bit-à-bit identique (SHA-256 vérifié). C'est le runtime qui est maintenu, pas les poids. Pérennité bonne côté runtime, figée côté qualité.

mineur

Deux détails d'environnement

Pas de YOLOv13 chez Ultralytics : la génération courante est YOLO26 (janvier 2026), YOLO27 en R&D non publié. Et uv n'est pas sur le PATH de cette machine, alors que CLAUDE.md le prescrit.

Ce que je n'ai pas pu vérifier

  • L'existence et la licence précise de poids ONNX « hand » prêts à l'emploi — qualcomm/MediaPipe-Hand-Detection repéré sur Hugging Face, non audité.
  • Les checkpoints main spécifiques de RTMPose/RTMW et de DWPose : rtmlib n'a été ni installé ni testé.
  • HaMeR au-delà de ce qu'en dit la fiche WiLoR.
  • L'existence d'un YOLOv13 académique tiers, hors Ultralytics.
  • La disponibilité ONNX modèle par modèle pour D-FINE, DEIM et RF-DETR.
  • L'empreinte VRAM réelle de Qwen3-VL sur 8 Go — sources secondaires contradictoires : 8 Go paraît juste pour un 8B, le 4B est plus sûr.
  • Que la wheel PyPI torch Windows soit CPU-only : déduit de sa taille (118 Mo), sans confirmation documentaire. Passez par l'index cu128 pour le GPU.
  • Le comportement de shape-outside sur un vrai matte FeyNoBg : la démonstration ci-dessus utilise une silhouette synthétique, pas une main détourée du corpus.

Sources