简介:一份面向Python开发者与计算机视觉初学者的Mediapipe整体跟踪示例工程,基于Google mediapipe Python API实现视频中人体姿态、面部与手部关键点的整体捕捉。工程通过命令行参数指定输入视频、输出地址与模型路径,便于快速接入自有数据或替换成摄像头实时流,适用于动作识别、手势分析等场景的入门验证。资源压缩包仅2KB,包含2个文件:1个Python脚本负责调用模型并完成跟踪主流程,1个Markdown文档说明环境配置与参数用法,结构简单,适合直接读源码学习接口调用。目前已有815人浏览学习该资源,通过示例可快速掌握Holistic模型的初始化、推理与关键点输出格式,结合文档理解输入输出参数含义,减少官方文档检索与排错成本。尤其适合需要快速验证整体姿态识别效果的团队或个人,把精力集中在业务应用而非底层接口细节。
1. Mediapipe Holistic Tracking:用 Python 拿到全身 543 个关键点的跟踪方案
Mediapipe Holistic Tracking 把人体姿态、两只手和一张人脸网格合并成一次 Python 推理调用,单帧就能输出 33 个 Pose 点、21×2 个手部点和 468 个面部点。它不是把三个模型简单拼起来:内部先用 Pose 定位躯干区域,再在区域内裁出手和脸的 ROI,所以比分开调用三个模型更省事,四组关键点的坐标也天然对齐在同一个帧坐标系里。适合动作对比、手语识别、虚拟人骨骼驱动、健身计数这类需要连续跟踪全身关键点的 Python 项目。很多人卡在依赖安装和参数调配上,这篇笔记把 Mediapipe 安装、接口结构、参数设置、踩坑点串成一条能照抄的落地链路。
2. 环境搭建与 Mediapipe 安装:先把版本粘合弄明白
2.1 为什么不拆着跑,要选 Holistic 这个组合方案
刚接触 Mediapipe Holistic Tracking 的人,第一个念头往往是“我直接装 Pose、FaceMesh、Hands 三个模型分开调不就行了”。确实,单独调用任何一个都不难,mp.solutions.pose.Pose()一行就能建实例。但你真正要做全身关键点跟踪时,拆开跑的代价比想象中大得多。
第一层代价是预处理重复。三套模型各做一遍 RGB 转换、图像缩放、归一化,CPU 上的开销是肉眼可见的。我一开始用 i5 笔记本拆着跑三路推理,画面分辨率 640x480,帧率直接掉到 12 以下。第二层代价是内存里挂三份推理上下文,线程一多还容易互相抢资源。第三层代价最隐蔽:独立的手部模型直接对手部做检测,手在画面里只有几十个像素时经常直接漏检,拿不到数据你后面所有手势分析都得停摆。
Holistic 的解决思路是把三个模型按“先身体定位、再截手和脸、最后局部推理”的顺序串成一条流水线。Pose 在这一帧里先找到人的大致位置,用这个位置去裁剪手和脸的 ROI,然后各自做推理。这样一来,只要 Pose 不丢,手和脸多数时候能被稳定带出来。这是它在设计上比“三个模型自行组合”更适合做跟踪的原因。输出上它一次process()返回四个字段:pose_landmarks、face_landmarks、left_hand_landmarks、right_hand_landmarks,四者的时间戳全部对应当前输入帧,做时序分析时不需要自己同步。
选 Holistic 的最后一个理由是接口风格统一。构造实例、调用 process、读取 results,这套用熟了之后换 Pose、换 FaceMesh 都是同一套心智模型。对一个要快速验证原型的项目来说,统一接口意味着掉坑概率低。
2.2 Mediapipe 安装:版本与 Python 解释器怎么匹配
安装步骤的翻车率其实比跑模型高。我一般会先建一个干净的虚拟环境,不直接用系统 Python,免得 mediapipe 装依赖时把全局环境的 numpy 降级,搞挂其他项目。下面这段是我在 Windows 和 Linux 上都跑过的流程:
# 创建虚拟环境,Windows 可以显式指定 py -3.10 -m venv python -m venv holist_env # Windows 激活 holist_env\Scripts\activate # Linux / macOS 激活 source holist_env/bin/activate # 先把 pip 升到最新,旧版 pip 解析 wheel 文件容易选错 python -m pip install --upgrade pip # 安装两个核心依赖 pip install mediapipe opencv-python激活虚拟环境这一步在 Windows 上最常见的报错是“执行脚本被禁止”,那是 PowerShell 执行策略的问题,用Set-ExecutionPolicy -Scope CurrentUser RemoteSigned一次性放开即可,不影响其他项目安全。装完 mediapipe 之后你会发现它并没有把 TensorFlow 拉进来,因为官方 wheel 已经把推理所需的动态库打包在内部了,不需要单独装 TF,这一点会让很多第一次装的人松一口气。
如果你所在网络环境拉取 PyPI 包不稳定,把下载源指到国内镜像就行。我用清华 TUNA 的情况最多:
pip install mediapipe -i https://pypi.tuna.tsinghua.edu.cn/simple版本选择这件事,我在 0.10.x 这条主流代际上踩过几次。大致规律是 Python 3.8 到 3.10 下官方 wheel 最全,直接装基本没障碍;Python 3.11 往上,protobuf 的版本约束会变,装完 import 时偶尔有警告;Python 3.12 用户务必先确认 PyPI 上有没有对应 wheel,有选择的话直接用 3.10 环境最省事。工程上的习惯是装完立刻跑一次import mediapipe验证动态库能正常加载。
版本适配的参考判断:
| Python 解释器 | 常见表现 | 建议 |
|---|---|---|
| 3.8 - 3.10 | wheel 齐全,安装顺畅 | 推荐使用 |
| 3.11 | 依赖解析正常,偶见 protobuf 告警 | 先升级 pip 再装 |
| 3.12 | 老版本可能缺 wheel | 优先选 0.10 系列较新的发行版 |
提示:Windows 上 import mediapipe 如果报 DLL Load Failed,先装 Visual C++ 运行库,装完重启终端再跑验证脚本。
2.3 快速验证:第一个 Holistic 实例
装完别急着接摄像头,先用一帧真实图像做静态验证,确认模型在你这台机器上真的能跑通。我用摄像头直接读一帧来做最小验证,比下载测试图更贴近实战:
# quick_check.py import cv2 import mediapipe as mp mp_holistic = mp.solutions.holistic # Windows 下用 CAP_DSHOW 可以明显加快摄像头初始化 cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) with mp_holistic.Holistic( static_image_mode=True, model_complexity=1, min_detection_confidence=0.5, min_tracking_confidence=0.5, ) as holistic: ok, frame = cap.read() if not ok: raise SystemExit("读不到摄像头帧,先检查摄像头是否被占用") # Mediapipe 要求 RGB 输入,OpenCV 读出来是 BGR rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = holistic.process(rgb) print("pose 点:", len(results.pose_landmarks.landmark) if results.pose_landmarks else 0) print("face 点:", len(results.face_landmarks.landmark) if results.face_landmarks else 0) print("左手/右手:", bool(results.left_hand_landmarks), bool(results.right_hand_landmarks)) cap.release()这段代码里的几个判断要解释清楚。static_image_mode=True表示对每一帧做完整检测,适合验证单张图;真正跑视频时要把这个参数设回 False,否则每一帧都重新做整图检测,速度会明显下降,而且平滑参数smooth_landmarks只在视频模式下生效。如果 print 出来三行都有数据,说明安装链路是通的,可以进入参数和坐标的细节阶段了。
3. 接口原理与关键参数:读懂 543 个点的坐标系
3.1 Holistic 的输出结构:一份 results,四组坐标
Holistic 的process()返回的 results 对象,本质上是四组 NormalizedLandmarkList。它们的关键点数量、坐标参考原点都不一致,先列一张对照表:
| 字段 | 关键点数量 | x/y 范围 | z 轴参考 |
|---|---|---|---|
| pose_landmarks | 33 | 0~1 | 以臀部为深度参考,离镜头越远 z 越大 |
| face_landmarks | 468 | 0~1 | 以人脸区域中心为原点,面部凹凸近似 |
| left_hand_landmarks | 21 | 0~1 | 以手腕为参考点 |
| right_hand_landmarks | 21 | 0~1 | 以手腕为参考点 |
这里最容易想错的是“归一化坐标”。x、y 都是相对值,x 除以图像宽度、y 除以图像高度得到,所以同一组坐标在不同分辨率下直接用新的宽高乘回去就能换算成像素,不需要重新跑模型。z 坐标的语义在每组里是独立的:pose 的 z 是相对臀部的深度,hands 的 z 是相对当前手腕的深度,face 的 z 更接近凹凸近似值,它们本质上是三个坐标系的值,不能混在一起当成同一个深度通道用。
手部关键点 ID 有明确约定:0 是手腕,4 是大拇指尖,8 是食指尖,12 是中指尖,16 是无名指尖,20 是小指尖。这个编号在后续抓取手势特征时非常关键,比如判断“食指抬起”就看 8 号点的 y 值是否明显小于 6 号近指节。脸上 468 个点里,0 到 33 左右是脸部轮廓,后续依次覆盖眉毛、眼睛、鼻、口区域,Mediapipe 官方的FACEMESH_CONTOURS常量已经把这些点编成连接组,直接拿来画轮廓线就行。
3.2 关键参数:static_image_mode、model_complexity 与两个 confidence
四个参数决定 Holistic 在大多数场景下的表现,直接列参数表:
| 参数 | 默认值 | 作用与坑点 |
|---|---|---|
| static_image_mode | False | False 走跟踪模式,会复用上一帧位置;True 每帧完整检测,视频流不要开 |
| model_complexity | 1 | 0/1/2 三档;2 精度最高但 CPU 上帧率断崖 |
| smooth_landmarks | True | 只对 pose 点做平滑,静态模式下不生效 |
| min_detection_confidence | 0.5 | 检测阶段置信度;太高漏检、太低抖动 |
| min_tracking_confidence | 0.5 | 跟踪阶段置信度;视频流建议 0.3~0.6 |
static_image_mode控制推理管道的第一环是全图检测还是跟踪复用。True 在每一帧都从整张图重新找人,缺点一是在视频里每帧都完整跑一遍检测,速度慢;二是帧与帧之间没有时间关联,输出点会跳。False 则利用上一帧位置做跟踪,速度快得多,前提是画面里的人不能突然大幅移动或完全出画。处理录好的视频文件时,我始终坚持 False,跟丢了它会自己重新检测,不需要人工干预。
min_detection_confidence和min_tracking_confidence的配合是调参翻车重灾区。很多人为了“更准”把min_tracking_confidence拉到 0.9,结果视频流里关键点疯狂跳。原因是跟踪阶段一帧不满足阈值,就判定为跟踪丢失,重新回到整图检测;而检测结果是独立的,帧间没有时序关联,自然抖。我的经验是跟踪阈值控制在 0.5 以下,检测阈值从 0.5 起步,先确认不会漏检,再去压低抖动。
3.3 坐标转换:从归一化坐标到像素坐标
拿到 landmarks 之后,最常见的操作是把它画回原图,或者转成像素坐标供下游骨骼分析使用。下面是每个项目我都会放进公共工具集的转换函数:
def normalized_to_pixel(landmarks, img_shape): """NormalizedLandmark 列表转像素坐标列表,返回 [(x, y), ...]""" h, w = img_shape[:2] points = [] for lm in landmarks.landmark: # x、y 是比例值,乘上宽高即得像素坐标;z 是深度,这里用不到 px = min(int(lm.x * w), w - 1) py = min(int(lm.y * h), h - 1) points.append((px, py)) return points里面有一处细节值得说明:min(..., w - 1)和h - 1是为了防止模型输出的浮点数在边界处溢出。归一化坐标理论上不会超过 [0,1],但浮点结果在坐标等于 1 附近偶尔会越界一小点,直接拿去画图就是一行越界报错。这个保护加上去之后,后续怎么处理都不用再担心边界问题。
绘制关键点,我习惯不用官方 drawing_utils 的整套默认画法,线条太多,打印出来全是花花绿绿的点,反而干扰视线。简单画法是只画点:
def draw_points(frame, points, color=(0, 255, 0), radius=2): for (px, py) in points: cv2.circle(frame, (px, py), radius, color, -1)到这里坐标基础已经打通,下一章进入完整的视频流实现,并把关键点数据导出成 CSV 供后续分析。
4. 视频流完整实现:关键点绘制与 CSV 导出
4.1 从摄像头读帧到 Holistic 推理的完整主循环
视频流场景和静态图的本质区别是:每一帧都要完成“读帧、转 RGB、推理、绘制、显示”五步,而且这个过程要扛住帧率,不能每帧重建模型。完整可运行的代码如下:
import cv2 import mediapipe as mp mp_holistic = mp.solutions.holistic mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles # 640x480 是 CPU 推理的甜点分辨率,比 1280x720 快一倍以上 cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) with mp_holistic.Holistic( static_image_mode=False, # 视频流必须关掉每帧完整检测 model_complexity=1, min_detection_confidence=0.5, min_tracking_confidence=0.5, ) as holistic: while cap.isOpened(): ok, frame = cap.read() if not ok: break # process 只认 RGB,OpenCV 默认是 BGR rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = holistic.process(rgb) # 官方绘图:身体骨骼、人脸网格、左右手 mp_drawing.draw_landmarks( frame, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS, landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style(), ) mp_drawing.draw_landmarks( frame, results.face_landmarks, mp_holistic.FACEMESH_CONTOURS, landmark_drawing_spec=None, connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_contours_style(), ) for hand in (results.left_hand_landmarks, results.right_hand_landmarks): mp_drawing.draw_landmarks( frame, hand, mp_holistic.HAND_CONNECTIONS, mp_drawing_styles.get_default_hand_landmarks_style(), mp_drawing_styles.get_default_hand_connections_style(), ) cv2.imshow("Holistic Tracking", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这段代码有三个地方容易踩。其一,draw_landmarks内部会判断 landmark 列表是否为 None,所以不需要在外层套 if,直接传 None 也不会报错。其二,draw_landmarks是直接修改传入的 frame 对象,如果你后面还需要用原始帧做别的处理,记得先frame.copy()一份。其三,cv2.waitKey(1)的返回值在 64 位系统上要特意与0xFF取与,否则某些平台会判断不准确,按 q 半天没反应。
4.2 自定义关键点的连接与绘制
官方连接线适合看整体效果,但做具体项目时往往只需要部分骨骼。做下肢动作识别时,比如深蹲、踢腿,我通常只画骨盆到膝、膝到踝的连线:
# 在 4.1 主循环的 process 之后接着写 pose = results.pose_landmarks if pose: h, w = frame.shape[:2] # 23 左髋、24 右髋、25 左膝、26 右膝、27 左踝、28 右踝 leg_lines = [(23, 25), (25, 27), (24, 26), (26, 28)] for a, b in leg_lines: p1 = pose.landmark[a] p2 = pose.landmark[b] x1, y1 = int(p1.x * w), int(p1.y * h) x2, y2 = int(p2.x * w), int(p2.y * h) cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)自定义连接线的要点是知道自己要哪些关键点 ID。官方把所有连接关系维护在POSE_CONNECTIONS常量里,查那个列表就能得到完整骨架的 ID 关系,然后按照功能需求筛选成自己的连接对。画线前判空是必须的,任何一帧 Pose 跟丢都会导致访问landmark[a]报错。
4.3 把关键点数据导出成 CSV:给后续分析留接口
很多项目最终不做可视化,而是拿关键点的时序数据当特征喂给下游动作分类模型。我常用的做法是每帧把 33+468+21+21 个点的 xyz 拼成一个扁平特征向量,缺失的位置补零,然后逐帧追加写入文件。拆成两个函数:
def landmarks_to_vec(landmarks, expected_count): """把某个 landmark 列表转成 3 * expected_count 的定长数组,缺失补 0""" if landmarks is None: return [0.0] * (3 * expected_count) vec = [] for lm in landmarks.landmark: vec.extend([lm.x, lm.y, lm.z]) return vec def frame_to_feature(results): """把一帧 results 拼成一维向量,顺序固定为 pose、face、左手、右手""" feature = [] feature += landmarks_to_vec(results.pose_landmarks, 33) feature += landmarks_to_vec(results.face_landmarks, 468) feature += landmarks_to_vec(results.left_hand_landmarks, 21) feature += landmarks_to_vec(results.right_hand_landmarks, 21) return feature写文件时要避免每帧 open 和 close,开一次文件句柄用 csv.writer 持续追加,循环结束后再关闭:
import csv csv_file = open("landmarks.csv", "w", newline="") writer = csv.writer(csv_file) # 表头:总点数 543,每个点 3 个轴,列名形如 p0_x, p0_y, p0_z... writer.writerow([f"p{i}_{axis}" for i in range(543) for axis in "xyz"]) # 主循环里每帧调用一次 writer.writerow(frame_to_feature(results))表头生成式展开之后是 1629 列,Excel 打开能看但肉眼很难排。实战中我一般只导出需要的子区域,比如去掉 468 个 face 点,只保留 pose 加两只手,列数就降到 75 个点乘以 3,也就是 225 列,排起版来舒服很多。导出之后用 pandas 拉一下每列方差,方差为零的列说明关键点长时间缺失,需要回头查检测环节。
5. 避坑与常见问题排查:Holistic 实战中踩过的四个坑
5.1 手部关键点一直为空,pose 却正常
现象:results.pose_landmarks正常,left_hand_landmarks和right_hand_landmarks永远打印为 None。
原因:Holistic 内部是先用 Pose 定位身体区域,再用身体区域去裁手部 ROI。当手在画面里占的比例太小,或者手臂肤色与背景太接近,裁剪出来的 ROI 置信度不足以让手部模型输出关键点。单独跑 Hands 模型时,因为它的检测范围是整张图,反而偶尔能出结果,这也是很多人误以为模型坏了的原因。
解决:三个有效动作。第一,把min_detection_confidence从 0.5 降到 0.3 到 0.4,牺牲一点误检换手部召回;第二,让人手在画面中占比大一些,我通常保证手部宽度超过整幅画面的四分之一;第三,身体刚进入画面时先把单手张开、停在前方,让检测阶段先拿到一个稳定的手部位置,后续跟踪阶段就会稳很多。
5.2 安装 mediapipe 之后,项目里别的代码 import numpy 失败
现象:原本正常的机器学习项目,在pip install mediapipe后 import numpy 直接报错,或者出现np.bool这类老接口不存在的问题。
原因:mediapipe 0.10.x 在安装时会拉取自己依赖的 numpy 版本。如果你的项目之前用的是 numpy 2.x,pip 为了满足 mediapipe 的约束会自动降级,原项目代码被新旧 API 差异搞崩。
解决:最省心的是隔离环境,单独建 venv 给 Holistic 用,不要和主项目混在一个环境里。如果必须共用环境,在 requirements.txt 里固定numpy<2并且优先安装 mediapipe,让 pip 自带的依赖解析器一次把版本关系算清楚。已经坏了就直接重装 numpy:
pip install "numpy<2"重装完再跑一次import mediapipe,确认两边不冲突。
5.3 视频流里关键点在相邻帧之间跳动
现象:单张图检测效果很好,一进视频循环,身体某些点在帧与帧之间跳几像素到几十像素,尤其在抬手和转头的瞬间。
原因:视频模式默认启用跟踪,当min_tracking_confidence设得过高,超过 0.7 时跟踪器输出被判不可信,触发重新检测。帧与帧之间的检测结果独立,缺少时序关联,于是产生跳变。另一种情况是model_complexity=2时噪声变大,内置平滑滤波来不及收敛。
解决:把min_tracking_confidence调回 0.4 到 0.5,min_detection_confidence保持 0.5,构造 Holistic 时保留默认的smooth_landmarks=True。如果仍然跳,就进入第 6 章,用 EMA 滤波在后端做一层缓存。
5.4 多人场景只检测到一个人,或姿态串扰
现象:两个人都站在画面里,输出只有一个人的 pose,且这个人的关键点偶尔被另一人干扰,骨骼线条串到旁边身体上。
原因:Holistic 被设计成单主体方案,内部的人体检测取的是画面中显著性最高的人区域。多人同时出现时,Pose 只选一个人定位;如果两个人重叠或交叉,ROI 会被另一个人影响。
解决:先加一个人体检测器,比如 YOLOv8n 的 person 类,把每个人框出来,分别裁剪成独立图块后再调用holistic.process(),最后把坐标按裁剪框偏移量映射回原图。裁剪框要外扩 20% 左右,给手和脸留出空间,不然切到边界会直接漏检。
6. 提升连续跟踪稳定性:EMA 平滑与一个简单验证方法
第五章说的抖动问题,到这里可以用一个很直接的后端技巧收尾:对关键点时间序列做指数移动平均(EMA)平滑。思路是给每个关键点维护一个“历史均值”,每来一帧都把新坐标按权重和历史均值混合,权重系数控制平滑强度。落地代码很薄:
import numpy as np class LandmarkSmoother: """对一组 landmarks 做 EMA 平滑;alpha 越大越平滑,延迟也越大""" def __init__(self, alpha=0.6): self.alpha = alpha self.cache = None def __call__(self, landmarks): if landmarks is None: return None pts = np.array([[lm.x, lm.y, lm.z] for lm in landmarks.landmark]) if self.cache is None or len(self.cache) != len(pts): self.cache = pts.copy() else: self.cache = self.alpha * self.cache + (1 - self.alpha) * pts return self.cache用的时候,对 pose 和 hands 各建一个平滑器,face 关键点太多,一般只对轮廓点做。注意平滑后的数组不能直接回填 results,因为 results 是只读对象。我通常只把平滑结果用于绘制和 CSV 导出,原始值留一份单独保存,方便出错时对比。
验证平滑是否有效的办法值得固化下来。录一段固定机位的测试视频,相机和光照都不要变,连续跑 200 帧,统计相邻帧同一关键点的坐标差均值。如果均值压到 1 像素以内,说明参数合适;如果发现动作跟随明显迟钝,把 alpha 往 0.5 方向调,在平滑和实时性之间找平衡。从那以后,我每次接 Holistic 都强制走一遍三步验证:装完环境先用静态图确认输出非空,再用测试视频验证跟踪稳定性,最后导出 CSV 跑一次坐标方差。三步走过,后面接动作识别、手势分类这些下游任务才有底,希望这份拆解能帮你在 Mediapipe Holistic Tracking 上少踩几个坑。
本文还有配套的精品资源,点击获取