简介:面向人体姿态估计与手势识别学习者的 Mediapipe 整体跟踪工具,基于谷歌开源库 Mediapipe 的 Python API 实现,可对视频中的人脸、手部与人体姿态关键点进行同步检测与跟踪,广泛应用于动作分析、健身辅助、虚拟数字人等场景。项目内置命令行入口,只需通过 -i、-o、-f 分别指定输入视频路径、输出路径和模型参数即可运行,结构精简,适合初学者快速理解整体跟踪流程,也便于开发者在此基础上扩展为实时摄像头或批量视频处理。资源包共 2 个文件,分别为 app.py 核心脚本和 README.md 使用说明,整体体积仅 2KB,轻量无冗余,方便直接阅读和修改。目前已有 812 人学习下载。读者可获得完整可运行的跟踪脚本、清晰的调用说明,以及针对视频整体跟踪场景的实践参考,对入门 Mediapipe 应用开发有直接帮助。
1. 项目概述:这到底在追什么
1.1 一句话讲清 Holistic Tracking 是什么
Mediapipe-Holistic-Tracking 这个项目,本质上是用 Google 开源的 Mediapipe 框架,把**人体姿态(Pose)、手部关键点(Hands)和面部关键点(Face)**三套检测模型统一到同一条视频流里,实现对人体上半身(甚至全身)的 360 度无死角追踪。为什么叫 Holistic?因为它是整体的、全息的,而不是只盯着某一个部位。
过去你要做手部追踪,得单独跑一个 Hands 模型;要做姿态估计,又得单独跑一个 Pose 模型;要做面部关键点,还得再折腾一个 FaceMesh。三个模型各跑各的,输出结果坐标系还不统一,光是数据对齐就够写几百行代码。Mediapipe Holistic 把这三种能力打包成一个统一的 Pipeline,一次推理同时输出 543 个关键点(面部 468 个、左手 21 个、右手 21 个、姿态 33 个),而且这些关键点的坐标在同一个归一化坐标系下,直接就能用,不需要你去做坐标变换。
对于做动作捕捉、健身计数、手势识别、虚拟形象驱动、康复评估这类应用的人来说,这个项目能省掉大量底层工作,把精力集中在业务逻辑上。这篇博文适合刚接触 Mediapipe 的初学者,也适合已经跑通过 Pose 或 Hands、想整合成完整人体追踪方案的开发者。
1.2 和单独用 Pose、Hands 有什么区别
我先说一个很多人容易忽略的点:Holistic 并不是简单地把 Mediapipe 的三个解决方案(Pose、Hands、FaceMesh)顺序调了一遍,而是用 Pose 模型先粗略定位人体区域,再用这个结果去引导 Hands 和 Face 模型做精细检测。这样有两个好处:
- 手部和面部的 ROI(感兴趣区域)不需要在整张图上搜索,计算量大幅下降,所以整体 FPS 反而比你顺序跑三个模型要高。
- 因为都在同一个 Pipeline 里,三个子模型之间天然共享了帧数据和时间戳,不会出现三个模型各处理各的帧、导致结果错位的问题。
我实测下来,同样的 CPU 机器上,顺序跑三个模型大概只有 8-12 FPS,而用 Holistic 可以稳定在 15-20 FPS。在 GPU 上差距更明显。所以如果你要做实时应用,Holistic 是比 "Pose + Hands + FaceMesh 三合一" 更合理的选择。
2. 环境准备:最小可运行的依赖组合
2.1 版本选择与 Python 环境
先强调一个版本搭配问题。Mediapipe 的 API 在不同版本之间有过几次比较大的调整,尤其是mp.solutions.holistic这个模块的参数名称和返回结构,在 0.8.x 到 0.10.x 之间就有区别。我这边用的组合是:
- Python 3.8 - 3.10 都可以,建议用 3.9 或 3.10,太老的 Python 版本可能装不上新版 Mediapipe。
- Mediapipe 0.10.7 或更高版本(0.10.x 系列都行)。
- OpenCV-Python 4.8.0 以上,用于读取摄像头和绘制结果。
- NumPy 1.24 以上,用于坐标数据处理。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Python | 3.9 - 3.10 | 兼容性最好,踩坑最少 |
| mediapipe | 0.10.7+ | API 稳定,参数命名统一 |
| opencv-python | 4.8.0+ | 视频流读取和图像绘制 |
| numpy | 1.24+ | 坐标数组处理 |
2.2 安装步骤与初始化
安装没什么复杂的,一条命令搞定:
pip install mediapipe opencv-python numpy如果你是在国内网络环境下安装,建议加上国内 PyPI 镜像源,速度会快很多:
pip install mediapipe opencv-python numpy -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,先跑一个最小的初始化测试,确认 Holistic 能正常加载模型权重。我第一次跑的时候卡在模型下载上很久,后来才发现 Mediapipe 会在首次运行时自动从网络下载模型文件。如果下载失败,需要手动把模型文件放到~/.mediapipe/目录下,或者检查网络环境。这里有个经验:如果代码运行后长时间无响应,八成是模型文件在下拉,耐心等一下就行。
3. 核心代码实现:从摄像头到完整骨架
3.1 初始化 Holistic 模型与视频流
先看一段最核心的代码实现,把整个流程串起来:
import cv2 import mediapipe as mp mp_holistic = mp.solutions.holistic mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles # 初始化 Holistic 模型 holistic = mp_holistic.Holistic( static_image_mode=False, # 视频流模式 model_complexity=1, # 模型复杂度:0轻量、1完整、2高精度 smooth_landmarks=True, # 姿态关键点平滑 enable_segmentation=False, # 不需要背景分割就关掉 min_detection_confidence=0.5, # 检测置信度阈值 min_tracking_confidence=0.5 # 追踪置信度阈值 ) cap = cv2.VideoCapture(0) # 读取默认摄像头 while cap.isOpened(): success, frame = cap.read() if not success: break # BGR 转 RGB,Mediapipe 要求 RGB 输入 image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image.flags.writeable = False # 执行推理 results = holistic.process(image) # 转回 BGR 用于 OpenCV 绘制 image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # 在图像上绘制三种关键点 mp_drawing.draw_landmarks( image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style() ) mp_drawing.draw_landmarks( image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style() ) mp_drawing.draw_landmarks( image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style() ) cv2.imshow('Mediapipe Holistic', image) if cv2.waitKey(5) & 0xFF == 27: # ESC 退出 break cap.release() cv2.destroyAllWindows()这段代码的核心就三件事:初始化模型、循环读帧推理、绘制关键点。
static_image_mode这个参数很多人容易搞混。它是用来区分图片模式和视频模式的。视频模式下,Mediapipe 会利用帧与帧之间的时序信息做关键点追踪,速度更快也更平滑;图片模式下则只针对单帧检测。对实时摄像头应用,保持False就行。如果你是要离线处理一堆图片,那才设成True。
model_complexity也是调优重点。0 是最轻量级的模型,速度快但精度一般;1 是平衡模式;2 是最精细的模型,关键点抖动更少,但计算量也更大。在普通 CPU 上推荐用 1,GPU 上可以放心上 2。
3.2 关键点数据结构与坐标换算
推理结果results里包含四个主要字段:face_landmarks、pose_landmarks、left_hand_landmarks、right_hand_landmarks。每个字段都是一个NormalizedLandmarkList,里面每个关键点有x、y、z三个坐标值,并且是归一化坐标——x和y的取值范围是 0 到 1,相对于图像宽度和高度的比例;z表示深度,单位与 x 相近,越大表示离镜头越远。
归一化坐标最大的好处是不受输入图像分辨率影响。你无论用 640x480 还是 1280x720 的输入,拿到的坐标都是 0-1 之间的浮点数。但如果你需要换算成像素坐标,就得手动乘回去:
h, w, _ = image.shape # 以鼻尖关键点为例,Pose 中鼻尖索引是 0 if results.pose_landmarks: nose = results.pose_landmarks.landmark[0] nose_x_pixel = int(nose.x * w) nose_y_pixel = int(nose.y * h) print(f"鼻尖像素坐标: ({nose_x_pixel}, {nose_y_pixel})")这里有个很实用的技巧:z坐标虽然在 Mediapipe 里是近似值,精度不算高,但在做手势识别时非常有用。比如判断手是朝镜头伸还是远离镜头,只看z的变化方向就够了,不需要精确的深度值。
4. 三个子模型的协作机制与细节解析
4.1 为什么三个模型能"同时工作"
Mediapipe Holistic 内部实际上是一个基于图(Graph)的 Pipeline。它在 Pose 模型输出的基础上,把每个关键点的位置映射为手部和面部的中心点候选区域,然后在这个小区域里再做精细检测。这个设计非常聪明,因为手和脸在画面中通常只占很小一部分,全图跑一遍检测成本太高,而有了 Pose 的先验位置信息,就只需要在小图块上推理。
这个"由粗到精"的策略在日常生活中很像你先在大街上找到一个人(Pose),再凑近了看他是谁(Face),最后看他在做什么手势(Hands)。不是同时做三件事,而是用上一步的结果帮下一步省力。
4.2 不同子模型的坐标体系一致性
我见过不少人在整合三个模型时被坐标系搞崩溃,比如左手坐标和右手坐标搞反、或者 Face 坐标跟 Pose 坐标用了不同原点的系统。Holistic 的设计就省心得多了:所有 543 个关键点都在同一个归一化坐标空间里,直接可以算欧氏距离、角度,不需要任何变换。
不过有一个细节要特别注意:左右手的判定是"从人物视角"出发的。也就是说,画面中你看到的"左边那只手",如果它属于被测人物的右手,那么它会被放进right_hand_landmarks,而不是left_hand_landmarks。这个在视觉上有点反直觉,做手势识别时一定要记得这一点,否则左右手逻辑会完全反掉。我在这上面吃过亏,后来给代码加了条注释:# 注意:这是目标对象的左右,不是画面左右。
关键点索引方面,Pose 有 33 个关键点,常用的有:
| 索引 | 部位 | 索引 | 部位 |
|---|---|---|---|
| 0 | 鼻尖 | 11 | 左肩 |
| 12 | 右肩 | 13 | 左肘 |
| 14 | 右肘 | 15 | 左手腕 |
| 16 | 右手腕 | 23 | 左髋 |
| 24 | 右髋 | 27 | 左踝 |
| 28 | 右踝 | 32 | 右脚跟 |
Hand 有 21 个关键点,索引 0 是腕部,1-4 是大拇指,5-8 是食指,9-12 是中指,13-16 是无名指,17-20 是小拇指。Face 有 468 个关键点,通常用轮廓、眉毛、眼睛、嘴唇等特定索引的组合来提取特征。
5. 踩坑记录与性能调优
5.1 常见问题速查表
我把跑这个项目时遇到的高频问题整理成了一张表,方便你排查:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动很慢,卡在初始化 | 首次运行需下载模型文件 | 等待;或手动下载模型放到~/.mediapipe/ |
| FPS 极低(个位数) | model_complexity太高或输入分辨率太大 | 降到 0 或 1;把输入帧缩放至 640px 宽 |
| 手部关键点频繁消失 | 手离身体太远或手部区域太小 | 拉近镜头;增大输入分辨率;提高追踪置信度阈值 |
| 关键点剧烈抖动 | 没有开启平滑或光照条件差 | 设smooth_landmarks=True;改善光照均匀度 |
| 左右手对调 | 混淆了画面视角与人物视角 | 检查是left_hand_landmarks还是right_hand_landmarks |
| 多人同时入镜 | Holistic 只支持单人 | 裁剪 ROI 只保留目标人物,或换用检测框先定位 |
这里要详细说一下关键点抖动的问题。抖动在高要求的动捕场景下是个大麻烦,比如你要用关键点去驱动一个虚拟角色,手部坐标一跳一跳的话,角色就会疯狂抽搐。除了打开smooth_landmarks,我自己用得最多的办法是加一个一阶低通滤波,对关键点的坐标做平滑:
import numpy as np class LandmarkSmoother: def __init__(self, alpha=0.6): self.alpha = alpha self.prev = None def apply(self, landmarks): if landmarks is None: return None current = np.array([[lm.x, lm.y, lm.z] for lm in landmarks]) if self.prev is None or self.prev.shape != current.shape: self.prev = current else: self.prev = self.alpha * self.prev + (1 - self.alpha) * current return self.prev smoother = LandmarkSmoother(alpha=0.5) # 在循环中使用 smoothed = smoother.apply(results.pose_landmarks.landmark)alpha越大表示历史权重越高,轨迹越平滑,但响应也越慢;alpha越小则越跟手。我一般取 0.4 到 0.6 之间,既能滤掉高频抖动,又不会让动作看起来"飘"。
5.2 性能提升的实操思路
如果你在追求更高帧率,按照下面的顺序优化收益最大:
第一优先级是减小输入分辨率。很多手机摄像头默认输出 1080p,但 Holistic 检测时并不需要那么高的分辨率,内部甚至会把图缩到更小去跑。所以你在读帧后先缩放到 640x480 或者 512x512,推理速度可以翻倍,关键点精度几乎不受影响。
第二优先级是调低model_complexity。复杂度从 2 降到 1,在 CPU 上大概能提升 30%-50% 的推理速度。如果你的场景只是简单的挥手检测,用 0 也可以。
第三优先级是只画可见的关键点。有时候手被身体挡住,results.right_hand_landmarks会是None,绘制前先判断一下能避免不少无用计算,代码也更健壮:
if results.left_hand_landmarks: mp_drawing.draw_landmarks( image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS )还有一个小技巧:把摄像头帧率验证一遍。有的 USB 摄像头名义上支持 30FPS,但实际输出只有 15FPS。你可以在循环里算一下实际帧率:
import time prev_time = time.time() while cap.isOpened(): # ... 推理代码 ... current_time = time.time() fps = 1 / (current_time - prev_time) prev_time = current_time cv2.putText(image, f"FPS: {fps:.1f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)实测下来,很多所谓"卡顿"问题其实是摄像头本身的输出瓶颈,而不是 Mediapipe 的问题。
6. 这玩意儿还能怎么玩:应用扩展方向
6.1 动作识别与健身计数
拿到 543 个关键点之后,最经典的应用就是动作识别。不需要训练复杂的深度学习模型,只需要根据关键点之间的角度变化就能判断动作。比如做俯卧撑计数,看肘关节(Pose 13/14 号点)的角度变化:身体下降时角度变小,撑起时角度变大,一个完整的波形就是一次俯卧撑。
手肘角度计算代码:
def calculate_angle(a, b, c): """计算三点之间的角度(度)""" import math a = np.array([a.x, a.y]) b = np.array([b.x, b.y]) c = np.array([c.x, c.y]) radians = math.atan2(c[1]-b[1], c[0]-b[0]) - \ math.atan2(a[1]-b[1], a[0]-b[0]) angle = abs(radians * 180.0 / math.pi) return angle # 左肘关键点:Pose 13 if results.pose_landmarks: landmark = results.pose_landmarks.landmark left_elbow_angle = calculate_angle(landmark[11], landmark[13], landmark[15]) print(f"左手肘角度: {left_elbow_angle:.1f}")这种方案在跳舞评分、动作对比、健身纠正等场景下非常实用,全流程只需摄像头加一段 Python 代码,成本极低。
6.2 虚拟形象驱动与康复评估
另一个很有想象力的方向是虚拟数字人驱动。用 Holistic 的关键点数据去驱动 Unity、Blender 里的骨骼模型,关键在于把归一化坐标映射到虚拟骨骼的旋转角度。经典做法是:对每个关节计算三点的空间向量,利用向量的夹角确定对应骨骼的旋转欧拉角。因为 Holistic 的坐标都统一了,这个映射逻辑对全身、双手、面部都通用,代码模板可以复用。
在康复医疗场景里,Holistic 可以辅助做帕金森患者的运动评估、中风患者的上肢动作范围测量等。医生不需要给患者佩戴任何传感器,只用普通摄像头就能完成初步筛查。这类应用对数据平滑的要求更高,通常我会把smooth_landmarks打开,并且在后处理阶段用更复杂的平滑算法(如 Savitzky-Golay 滤波或者卡尔曼滤波)进一步降低噪声。
还有一个方向是手势控制。配合 21 个手部关键点,做 PPT 翻页、音量调节、鼠标控制都没问题。手势分类也很简单,算一下手指之间的夹角和距离就能判断是握拳、张开还是食指指向前方。比起数据手套或者深度摄像头,Holistic 只需要一个 RGB 摄像头,成本优势非常明显。
7. 写在最后的个人体会
跑通 Mediapipe Holistic 之后我最大的感受是,它能让你在几小时之内就做出以前需要一两周才能完成的原型。人体关键点追踪的技术本身已经很成熟,但对于大多数非算法团队来说,真正困难的是把模型工程化、产品化。Holistic 把最大的一块硬骨头啃了下来,剩下的就看你自己的创意了。
如果你是在做实时交互类的产品,我建议你在项目早期就把性能预算想清楚。帧率不是等做完功能再优化的,而是在设计时就该定好的约束。先想清楚你的目标设备是什么,CPU 还是 GPU,支持什么分辨率,然后针对这些约束去调整参数,而不是一味追求检测精度。
最后给个扩展建议:这个项目的代码结构非常适合封装成独立的 Python 模块。把初始化、推理、绘制分离,分别做成函数或类,之后无论是做 Web 应用(Flask + Socket 推流)、还是桌面客户端,都能直接复用。我目前就在用这套封装做一个小型的体感游戏原型,后续如果有心得,再来分享。
本文还有配套的精品资源,点击获取