简介:面向计算机相关专业毕业设计、课程设计及人体姿态识别入门学习者打造的完整项目资源,基于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 实时性 | 上手成本 |
|---|---|---|---|---|
| OpenPose | 18/25 | 好 | 依赖 GPU | 配置复杂,依赖多 |
| YOLOv8-Pose | 17 | 好 | 较好 | 需单独训练或下载权重 |
| MediaPipe BlazePose | 33 | 默认单人 | 好 | 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) >= 7smooth_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_lite、pose_landmark_full、pose_landmark_heavy三档,分别对应解决方案 API 里的model_complexity=0、1、2。如果你拿到的是一个手写加载逻辑的源码包,第一件事就是把模型文件路径和参数对应关系确认清楚:
| model_complexity 取值 | 对应模型 | 适用环境 |
|---|---|---|
| 0 | lite 轻量版 | 低配 CPU、嵌入式设备 |
| 1 | full 标准版 | 普通 PC 摄像头场景 |
| 2 | heavy 高精度版 | 离线分析、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 裁剪和阈值设置,而不是急着换模型。这个数据文件也是后续做混淆矩阵、误报分析的原料,值得在工程一开始就留在代码里。
本文还有配套的精品资源,点击获取