MediaPipe人体姿态识别实战:BlazePose关键点提取与Python动作判定
2026/9/11 10:35:44 网站建设 项目流程

简介:面向计算机相关专业毕业设计、课程设计及人体姿态识别入门学习者打造的完整项目资源,基于MediaPipe框架实现人体关键点检测与姿态识别,涵盖Python源码与预训练模型。该项目源自作者经导师指导通过的毕业设计,评审得分96.5分,代码经完整测试可稳定运行,适合作为毕设参考、课设作业或深度学习实践的教学示例。压缩包共138个文件,约11MB,包含7个Python源码文件、1个h5训练模型、120个npy关键点数据文件及8个mp4演示视频,另附README说明文档辅助理解项目结构与运行逻辑。已有204人学习浏览,项目完整度高,既可直接复现完整姿态识别流程,也可基于源码二次开发,实现动作分类、交互应用等扩展功能,适合不同基础的开发者按需取用。

1. MediaPipe 人体姿态识别解决什么问题,为什么用 Python 复现

目标检测回答“人在哪”,姿态识别回答“人摆了什么姿势”:前者输出一个矩形框,后者输出一组关节坐标,两者的接口和误差形态完全不同。以 mediapipe 为底座实现的人体姿态识别 python 源码加模型,核心链路可以压缩成一句话:用 BlazePose 模型把视频帧变成 33 个带置信度的归一化关键点,再把关键点连成骨架,交给上层动作逻辑。标题里这组 zip 包含了两类东西,模型文件决定关键点回归质量,源码决定视频流与模型之间如何对接、如何绘制、如何提取特征。这种组织的意义在于避开了最常见的落地落差:照 API 文档跑通一张图很容易,把摄像头数据稳定地喂进模型并拿到可用的动作判断,才是真正的分水岭。适合做动作识别、健身计数、安防行为分析的应用工程师,也适合想搞懂检测与关键点回归如何协作的初学者。

2. MediaPipe Pose 检测跟踪管线原理与模型选型依据

2.1 姿态识别为什么先要分清“检测”和“跟踪”

姿态模型的推理量通常高于普通目标检测网络,因为输出端要去回归 33 个关节点的高维表达,直接对每一帧做全图推理,会把 CPU 场景下的帧率拖到不可用。mediapipe 的思路是把问题拆成两段:第一帧先做全图人体检测,拿到人体框;后续帧不再全图扫描,而是根据上一帧的关键点位置推算当前帧人体所在的 ROI(Region of Interest),只在这个区域里做关键点回归。这个策略直接反映在 Pose API 的两个参数上:min_detection_confidence管检测阶段,min_tracking_confidence管跟踪阶段。

理解这个拆分对调参很关键。很多人把两个阈值一样设成 0.5,结果发现人只要侧过身、手遮住躯干,关键点就开始乱跳,原因是跟踪阶段置信度跌破阈值后,管线会退回全图检测,而全图检测在低算力设备上会掉帧。正确做法是让检测阈值宽松一些,跟踪阈值严格一些,让管线尽量待在跟踪模式里,避免频繁“重新检测”造成的抖动。

2.2 BlazePose 双模型协作与 ROI 机制

BlazePose 实际由两个模型接力完成:Pose Detector 负责在整帧中定位人体框,Pose Landmark 负责在裁剪后的 ROI 中回归 33 个关键点。Pose Landmark 的输入不是全图,而是上一帧关键点外接框适当外扩后的裁剪区域,并且会结合关键点历史位置做运动预测,减少快速移动时的滞后感。

这种设计的收益在于,Landmark 模型的输入分辨率被限制在一个固定且轻量的尺度上,帧与帧之间的计算量可预测,不会出现画面里出现多个人或大尺度变化时推理时间陡增的情况。代价也很明显:ROI 一旦预测偏了,后续帧可能整体跟丢,所以static_image_mode=True时管线会强制每帧都做完整检测,适合单张图片,不适合视频流。

2.3 33 个姿态关键点的编号结构与 python 端取值

MediaPipe Pose 的 33 个关键点覆盖三个区域:0 到 10 是面部关键点,包含鼻子、眼睛、耳朵和嘴部;11 到 22 是上肢与躯干,肩膀、肘部、手腕、髋部都在这一段;23 到 32 是下肢,覆盖膝盖、脚踝和脚掌。相比 COCO 的 17 点模型,多出来的点集中在面部轮廓和脚部,这对需要判断面部朝向或脚部受力的场景很有价值。代码里取坐标的方式如下:

lm = results.pose_landmarks.landmark for i in range(33): pt = lm[i] print(i, f"x={pt.x:.3f} y={pt.y:.3f} z={pt.z:.3f} visibility={pt.visibility:.2f}")

这里的 x、y 是相对于图像宽高的归一化坐标,z 表示关键点与相机距离的深度估计,visibility 是模型对该点可见度的置信度。要注意的是,视频流中摄像头画面通常是镜像的,x 坐标与实际空间左右相反,做左右手判断前需要记得翻转。

2.4 模型选型对比与关键参数速查

如果把姿态识别方案放一起比较,选型逻辑会比较直观:

常用方案关键点数量多人支持CPU 实时性上手成本
OpenPose18/25依赖 GPU配置复杂,依赖多
YOLOv8-Pose17较好需单独训练或下载权重
MediaPipe BlazePose33默认单人pip 安装即可用

MediaPipe 的优势同样也是它的边界:单模型单人场景优化最好,多人场景要靠外部检测器配合。参数层面,model_complexity决定使用哪个精度的模型,0 对应轻量版,1 是标准版,2 是最高精度版;smooth_landmarks开启时会对关键点做时域平滑,视频流建议保持开启,但做离线逐帧分析时会掩盖真实抖动,反而不好排查问题。

3. Python 环境搭建与实时姿态识别最小可运行代码

3.1 创建虚拟环境并安装依赖

环境隔离这一步值得做,因为 mediapipe 的 Python 轮子对解释器版本有要求,直接装进系统 Python 容易和已有依赖冲突。我一般锁定 Python 3.10 或 3.11,这两个版本对 mediapipe 的轮子发布覆盖最全,避免安装阶段就卡住:

python -m venv .venv # Windows 下用 .venv\Scripts\activate source .venv/bin/activate python -m pip install --upgrade pip pip install opencv-python mediapipe python -c "import mediapipe as mp; print('mediapipe', mp.__version__)"

最后一行是验证安装是否完整的快捷方式,能正常打印版本号说明依赖链没有问题。opencv-python 负责摄像头读取和图像绘制,mediapipe 提供姿态模型和 API。如果安装过程中出现二进制轮子找不到的情况,优先检查 Python 版本,而不是换源重试。

3.2 基于摄像头实时跑通姿态识别最小代码

拿到模型和 API 后,最快出效果的方式是直接打开摄像头跑一版。下面的代码覆盖了推流、推理、绘制、退出四个环节:

import cv2 import mediapipe as mp mp_pose = mp.solutions.pose mp_draw = mp.solutions.drawing_utils pose = mp_pose.Pose( static_image_mode=False, model_complexity=1, smooth_landmarks=True, min_detection_confidence=0.5, min_tracking_confidence=0.5, ) cap = cv2.VideoCapture(0) while cap.isOpened(): ok, frame = cap.read() if not ok: break # MediaPipe 要求 RGB 输入,但绘制要回到 BGR 才会显示正常颜色 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = pose.process(rgb) if results.pose_landmarks: mp_draw.draw_landmarks( frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, mp_draw.DrawingSpec(color=(0, 255, 0), thickness=2), mp_draw.DrawingSpec(color=(0, 0, 255), thickness=1), ) cv2.imshow("mediapipe pose", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

代码逻辑分成三段:process之前是色彩空间转换,process是模型推理,draw_landmarks是骨架绘制。DrawingSpec里第一个参数是线的样式,第二个参数是关节点样式,需要调显示效果时改这两个对象就可以。waitKey(1)括号里的 1 是等待键盘输入的毫秒数,视频流场景保持 1 即可,改成 0 会导致画面冻结。

3.3 results 里的三个输出对象怎么区分

pose.process返回的results包含三类数据,用错地方是常见问题:

if results.pose_landmarks: # 1. 图像坐标系下的归一化关键点,绘制时用 lm = results.pose_landmarks.landmark[15] # 左腕 print("左腕像素x =", int(lm.x * frame.shape[1])) # 2. 以髋部中心为原点的米制坐标,动作分析时用 world = results.pose_world_landmarks.landmark[15] print("左腕世界坐标 =", world.x, world.y, world.z) # 3. 人体分割掩码,需要抠人像时用 if results.segmentation_mask is not None: print("segmentation mask shape =", results.segmentation_mask.shape)

pose_landmarks的坐标是归一化的,直接乘图像宽高就能得到像素位置,但受画面分辨率影响;pose_world_landmarks是米制坐标,独立于图像尺寸,用来算关节角度和动作幅度更稳定。segmentation_mask默认是空,需要把enable_segmentation=True才会计算,不是每次推理都会有值。

3.4 环境与运行时常见报错对照

现象常见原因处理方式
摄像头黑屏且 cap.isOpened() 为 False摄像头被其他进程占用关闭占用软件,或把 VideoCapture 参数从 0 改成 1
导入 mediapipe 报错Python 版本和轮子不匹配换用 3.10/3.11 重建虚拟环境
推理速度很慢输入帧分辨率过高推理前把帧 resize 到宽度 640
process 返回的 landmarks 恒为空人体占比太小或背对镜头拉近拍摄距离,或降低 min_detection_confidence

还有一个容易忽略的点:frame如果一直保持 4K 分辨率,mediapipe 内部会先把图像缩放再喂模型,每帧的缩放开销比推理本身还高。常见做法是先对帧做一次cv2.resize到统一的输入宽度,再送进process,这类文件在源码包里通常被封装成preprocess函数。

4. 从姿态识别到动作识别:关键点特征计算与规则判定

4.1 角度特征为什么比坐标差更稳

拿到 33 个关键点后,“人体姿态识别”才算完成了一半,剩下的是把坐标转换成业务语义。直接用坐标差做判断在固定机位下可行,但人一旦前后移动或靠近摄像头,肢体在画面里会被放大缩小,同一个动作的坐标差会剧烈变化。角度特征不存在这个问题:肘关节角度由肩膀、肘部、手腕三个点的相对位置决定,与人体在画面中的尺寸无关,天然具备尺度不变性。用atan2计算角度的方式如下:

import math def angle_at(a, b, c): # a、b、c 是关键点对象,b 是夹角顶点 v1 = (a.x - b.x, a.y - b.y) v2 = (c.x - b.x, c.y - b.y) ang1 = math.atan2(v1[1], v1[0]) ang2 = math.atan2(v2[1], v2[0]) deg = abs(math.degrees(ang1 - ang2)) if deg > 180: deg = 360 - deg return deg

atan2而不是向量点积求余弦,有两个好处:一是角度范围覆盖 0 到 180 度,不会出现余弦函数在接近 0 度和 180 度时对噪声不敏感的情况;二是atan2不需要额外做分母判零,关键点重合时不会报错。返回值是顶点 b 处两条边的夹角,对应到人体上就是关节弯曲程度。

4.2 用肘角和肩角判断手臂是否水平前伸

以“左臂水平前伸”为例,这个动作需要两个角度同时满足条件:肘关节接近伸直,肩关节接近 90 度。代码实现如下:

def left_arm_angles(lm): shoulder = lm[11] elbow = lm[13] wrist = lm[15] hip = lm[23] elbow_angle = angle_at(shoulder, elbow, wrist) shoulder_angle = angle_at(hip, shoulder, elbow) return elbow_angle, shoulder_angle def is_left_arm_forward(elbow_angle, shoulder_angle): return (160 <= elbow_angle <= 180) and (70 <= shoulder_angle <= 110)

shoulder_angle取的是髋、肩、肘三个点,当手臂自然下垂时约为 180 度,手臂抬平后接近 90 度;elbow_angle取肩、肘、腕,伸直时接近 180 度。这两个角度组合起来,就能把“抬手”和“伸手”区分开:只抬手不屈肘,肘角会变小;只伸手不抬臂,肩角会偏大。

4.3 用滑窗判决让动作识别摆脱单帧抖动

单帧角度计算即使再准,也会因为跟踪器的微小抖动产生误判,常见的做法是引入滑窗投票:把最近 N 帧的判定结果放进一个队列,当队列中“动作成立”的帧数超过阈值时才切换状态。下面的实现可以放在识别循环里:

from collections import deque state_q = deque(maxlen=10) def update_state(angle_pair): is_pose = 1 if is_left_arm_forward(*angle_pair) else 0 state_q.append(is_pose) return sum(state_q) >= 7

smooth_landmarks平滑的是关键点本身,滑窗平滑的是动作判定结果,两者解决的问题不同。maxlen=10意味着最多保留 10 帧历史,sum >= 7表示动作状态需要在 10 帧里出现 7 次才算数,这样误判帧会被平均掉。滑窗长度和阈值需要按视频帧率调整,30FPS 视频用 10 帧窗口大约是 330ms 的决策延迟,人对这个延迟基本无感知。

4.4 把角度组合成特征向量接入分类模型

规则判定适合动作状态清晰、边界分明的场景,但同一个动作有多种姿态变体时,规则会越写越复杂。更可持续的做法是把角度特征拼成向量,交给分类器。特征向量构建方式如下:

JOINT_TRIPLETS = [ (11, 13, 15), (12, 14, 16), # 左右肘 (23, 25, 27), (24, 26, 28), # 左右膝 (23, 11, 13), (24, 12, 14), # 左右肩 ] def feature_vector(lm): return [angle_at(lm[a], lm[b], lm[c]) for a, b, c in JOINT_TRIPLETS]

这个向量只有 6 个维度,但已经能区分大部分上肢和下肢动作。把每帧的特征向量按时间顺序拼接成滑动窗口,就变成(window_size, 6)的张量,可以直接输入 LSTM 或 1D-CNN。模型产出的动作概率再和 4.3 的滑窗逻辑结合,比纯规则判定的泛化能力强很多。这里需要注意,特征向量里的点必须来自连续帧的同一跟踪目标,多人场景下要先按检测框 ID 做关键点分组,否则特征序列会混入不同人的数据。

5. 工程收尾:模型文件对应关系、置信度验证与多人场景

5.1 模型文件和 model_complexity 的对应关系

源码包里的模型通常不只有一个文件,MediaPipe 官方常见的命名把姿态模型分成pose_landmark_litepose_landmark_fullpose_landmark_heavy三档,分别对应解决方案 API 里的model_complexity=0、1、2。如果你拿到的是一个手写加载逻辑的源码包,第一件事就是把模型文件路径和参数对应关系确认清楚:

model_complexity 取值对应模型适用环境
0lite 轻量版低配 CPU、嵌入式设备
1full 标准版普通 PC 摄像头场景
2heavy 高精度版离线分析、GPU 或高配 CPU

确认方法很简单:把model_complexity从 0 切到 2,看推理耗时和关键点稳定度是否有实质差异。如果表现没变化,大概率是代码里加载的是同一个模型文件,而不是真正的三档切换。

5.2 把置信度画到画面里验证识别稳定性

很多姿态识别项目“跑起来”但“用不了”,问题出在没人知道哪一帧识别是可靠的。一个实用的调试技巧是把关键点置信度实时绘制到画面角落:

if results.pose_landmarks: nose = results.pose_landmarks.landmark[0] confidence = nose.visibility cv2.putText( frame, f"nose conf: {confidence:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2, )

观察这个数值可以发现两类问题。一是置信度周期性跳变,说明跟踪器和检测器在反复切换,需要调整min_tracking_confidence;二是置信度稳定但不为零,可关键点位置却很飘,说明 ROI 裁剪范围不合适,要检查输入分辨率是否被过度缩放。

5.3 多人场景的处理策略

MediaPipe 的 Pose Landmark 模型默认面向单人跟踪,多人场景直接调用会把多个人定位到同一个 ROI,输出结果完全不可用。常见的处理方式是把姿态识别嵌到目标检测器的下游:先用检测模型标出所有人形框,再把每个人形框裁剪出来分别送入姿态模型。这样做的开销是检测器每帧必须全图跑一次,不再享受 ROI 跟踪的加速,因此多人场景建议把static_image_mode设为True,并在裁剪时丢弃过小的人形框,避免趴在地面远处的人浪费推理时间。

最后留一个可操作的验证技巧:把整个流程的输出结果记录成 CSV,每行包含帧号、动作状态、平均置信度,跑完一段 5 分钟视频后按状态切片统计平均置信度。低于 0.6 的动作段基本可以断定跟踪不稳定,优先检查 ROI 裁剪和阈值设置,而不是急着换模型。这个数据文件也是后续做混淆矩阵、误报分析的原料,值得在工程一开始就留在代码里。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询