MediaPipe手势识别:21点3D手部追踪怎么跑通
【免费下载链接】mediapipeCross-platform, customizable ML solutions for live and streaming media.项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe
MediaPipe 是 Google 开源的端侧机器学习框架,支持 Android、iOS、桌面、Web 和边缘设备。它的手部追踪能力可以从单帧图像推断出 21 个 3D 手部关键点,在手机上就能实时运行。这篇文章讲清楚两件事:怎么用几行 Python 代码跑通摄像头实时追踪,以及 5 个配置参数各自的含义。
🖐️ 先搞懂管线:两个模型各管一段
手部追踪由两级模型串成。先说明为什么拆两段:单手检测很困难,手的尺寸跨度能到约 20 倍,又没有脸那样的高对比特征。所以第一段用手掌检测模型(palm detector)在全图上找一个带朝向的方框,只框住手掌这个刚性区域;第二段用关键点模型在裁剪出的小图块上直接回归 21 个 3D 坐标。
拆开的好处有两个。裁剪后输入是标准化的小图,训练时基本不需要旋转、平移、缩放这类数据增强,网络容量可以全部用来提升坐标精度。跟踪时优先用上一帧的关键点位置直接裁剪,只有关键点模型失效了才重新调用检测模型定位,大部分帧省掉了最贵的检测环节,这是延迟低的关键。
关键点布局固定为 21 个:0 是腕部,1-4 拇指,5-8 食指,9-12 中指,13-16 无名指,17-20 小指,每个指节的关节和指尖依次排列。
环境准备:装包与克隆仓库
Python 3.8 以上即可,装一个包就能用:
pip install mediapipe想编译桌面 C++ 示例或看 Model Maker 的训练代码,再克隆仓库:
git clone https://gitcode.com/GitHub_Trending/med/mediapipe仓库版本见 version.bzl,当前是 1.0.1。注意两点:mp.solutions.hands属于 Legacy Solutions(旧版 API),代码仓库按原样维护、不再新增功能;新方案是 MediaPipe Tasks 的 Hand Landmarker,本文示例以 Legacy API 为准,它仍是 Python 端最省事的入口。
三步跑通摄像头实时追踪
下面这段代码打开摄像头、追踪双手、画出手部骨架,Esc 键退出:
import cv2 import mediapipe as mp mp_drawing = mp.solutions.drawing_utils # 官方绘制工具 mp_drawing_styles = mp.solutions.drawing_styles cap = cv2.VideoCapture(0) with mp.solutions.hands.Hands( model_complexity=0, min_detection_confidence=0.5, min_tracking_confidence=0.5) as hands: while cap.isOpened(): ok, frame = cap.read() if not ok: continue frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 模型吃RGB result = hands.process(frame) if result.multi_hand_landmarks: for lm in result.multi_hand_landmarks: mp_drawing.draw_landmarks(frame, lm, mp.solutions.hands.HAND_CONNECTIONS) cv2.imshow('hands', cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)) if cv2.waitKey(5) & 0xFF == 27: break cap.release()跑起来后能拿到的三组输出:
multi_hand_landmarks:x、y 已按图像宽高归一化到 [0,1],z 是深度,以手腕为原点,值越小离镜头越近,量级和 x 接近。multi_hand_world_landmarks:真实世界 3D 坐标,单位是米,原点在手掌的几何中心附近。要量手指长短就用这组。multi_handedness:左右手标签加置信分,分数不小于 0.5。
想要食指指尖的像素坐标,取 8 号点乘宽高即可:
tip = lm.landmark[mp.solutions.hands.HandLandmark.INDEX_FINGER_TIP] px, py = tip.x * frame_width, tip.y * frame_height处理一批静态照片时加static_image_mode=True,并对每张图先cv2.flip(image, 1)水平翻转再送入,左右手标签才正确。
参数怎么调:5 个配置项逐个看
这 5 个参数就是全部配置,默认值都偏保守,按场景微调即可。
static_image_mode:默认false,按视频流处理,只在首帧和跟踪失败时跑检测。处理静态图片时设为true,每帧都跑检测。
max_num_hands:默认 2。要追踪第三只手,改这里;跑 C++ 桌面示例时,改的是图文件里ConstantSidePacketCalculator的选项。
model_complexity:0是轻量模型,1是默认的高精度模型。准确率和延迟都随复杂度上升。摄像头实时场景从0开始,处理照片或视频文件时用1。
min_detection_confidence:检测模型的阈值,默认 0.5。调低后更容易检出手,误检也会变多;手在画面里很小的时候尤其要调低。
min_tracking_confidence:跟踪模型的阈值,默认 0.5。低于它,下一帧自动触发重新检测。调高更稳、更费时间;静态图模式下无效。
Android 端对应HandsOptions,多了setRunOnGpu开关,示例代码在 mediapipe/examples/android/solutions/hands。iOS 端类似,看 mediapipe/examples/ios。桌面端用 Bazel 编译,离线视频示例:
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 \ //mediapipe/examples/desktop/hand_tracking:hand_tracking_tflite bazel-bin/mediapipe/examples/desktop/hand_tracking/hand_tracking_tflite \ --calculator_graph_config_file=mediapipe/graphs/hand_tracking/hand_tracking_desktop.pbtxt \ --input_side_packets=input_video_path=in.mp4,output_video_path=out.mp4常见现象速查:手丢了、闪烁、左右反了
现象、原因、处理放在一张表里:
| 现象 | 原因 | 处理 |
|---|---|---|
| 手检测不到 | 检测阈值过高,或手在画面里太小 | min_detection_confidence降到 0.3 左右,或提高输入分辨率 |
| 关键点闪烁、频繁重定位 | 跟踪置信度要求高,频繁回退到检测 | 调低min_tracking_confidence |
| 左右手标签反了 | handedness 按镜像(前摄自拍)图判定 | 送入前水平翻转图像,或在后摄场景手动交换标签 |
| 帧率不够 | 模型复杂度或输入分辨率偏高 | model_complexity=0,摄像头分辨率降到 640x480 量级 |
| z 值看着没物理意义 | z 是以手腕为原点的相对深度 | 需要物理尺寸改用multi_hand_world_landmarks |
官方文档里的一个数据供参考:手掌检测模型的平均精度为 95.7%(论文评估集口径),换用普通交叉熵损失训练只有 86.22%,可见 focal loss 对大尺度差异场景的作用。你自己的设备上能跑多少帧,以实测为准。
进阶:用 Model Maker 训练自己的手势分类器
关键点模型只输出 21 个坐标,"握拳"还是"剪刀手"这种业务分类要自己训一个。仓库里自带训练代码 gesture_recognizer 和示例数据 raw_data。数据按类别分文件夹存放,训练脚本会自动拆分数据集:
from mediapipe.model_maker.python.vision import gesture_recognizer data = gesture_recognizer.Dataset.from_folder(dirname='my_data') train_data, rest = data.split(0.8) val_data, test_data = rest.split(0.5) model = gesture_recognizer.GestureRecognizer.create( train_data=train_data, validation_data=val_data, options=gesture_recognizer.GestureRecognizerOptions( model_options=gesture_recognizer.GestureRecognizerModelOptions( dropout_rate=0.05, layer_widths=[300, 300]), hparams=gesture_recognizer.HParams(export_dir='./out'))) print(model.evaluate(test_data, batch_size=2)) model.export_model() # 导出 tflite,供推理端加载layer_widths定义隐藏层宽度,每层之间接 BatchNorm、Dropout 和 ReLU,默认是不加隐藏层。导出的 tflite 拿到推理端,配合 Hand Landmarker 输出的关键点坐标做输入即可闭环。
下一步做什么
- 把示例里的
model_complexity从 0 改成 1,对比同一摄像头下的帧率差异,感受两档模型的取舍。 - 用 raw_data 里现成的三类样本跑一遍上面的训练脚本,导出你的第一个手势分类模型。
【免费下载链接】mediapipeCross-platform, customizable ML solutions for live and streaming media.项目地址: https://gitcode.com/GitHub_Trending/med/mediapipe
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考