MainCarto · note technique
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.
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.
À 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.
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.
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.
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é.
min_sharpness = 60
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.
| Modèle | Licence | Poids | ONNX | Python / Windows | Qualité sur notre cas | Friction |
|---|---|---|---|---|---|---|
| MediaPipe Hand Landmarker | Apache-2.0 | 7,8 Mo | n/a (.task) | 3.14 OK · vérifié | Excellente — mesurée | 1 |
| YOLO26 (Ultralytics) | AGPL-3.0 | 5–110 Mo | oui | 3.13 max | bonne si poids « hand » | 2 + licence |
| RF-DETR | Apache-2.0 Plus : PML 1.0 | variable | oui | via torch | bonne, aucun poids main | 3 |
| D-FINE / DEIM | Apache-2.0 | variable | oui | via torch | à fine-tuner soi-même | 3 |
| RTMPose / RTMW · via rtmlib | Apache-2.0 | variable | oui | ≥ 3.10 | bonne — 21 kpts/main | 2 |
| RTMPose · via mmcv/mmpose | Apache-2.0 | variable | — | mmcv gelé 04/2024 | — | 5 — à éviter |
| ViTPose++ (transformers) | Apache-2.0 | 90 Mo–1,3 Go | non | 3.14 OK | inadapté — exige une boîte personne | 3 |
| Sapiens (Meta) | CC-BY-NC 4.0 | 0,3–2 B | non | — | 308 kpts dont mains | 4 + licence |
| WiLoR / HaMeR | CC-BY-NC-ND MANO AGPL | 7 Mo (det.) | non | — | très bonne en 3D | 4 + triple licence |
100DOH hand_object_detector |
MIT (code) | Google Drive | non | Py 3.8 · torch 1.12 · build CUDA | le plus pertinent… et le moins utilisable | 5 — quasi mort |
| VLM (Claude) sur frames | API | — | — | — | excellent jugement, bbox non fiable | 1 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.
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.
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 ».
circle(), ellipse(),
inset(), polygon().0 —
mettez 0.5, sinon le plumé à alpha_feather_px = 1.5 rend le contour
bruyant.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.
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.
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.
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.
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.
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.
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.
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é.
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.
qualcomm/MediaPipe-Hand-Detection repéré sur Hugging Face, non audité.rtmlib
n'a été ni installé ni testé.torch Windows soit CPU-only : déduit de sa taille
(118 Mo), sans confirmation documentaire. Passez par l'index cu128 pour le GPU.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.