MediaPipe人体姿态识别实战:BlazePose关键点提取与Python动作判定
简介面向计算机相关专业毕业设计、课程设计及人体姿态识别入门学习者打造的完整项目资源基于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 的思路是把问题拆成两段第一帧先做全图人体检测拿到人体框后续帧不再全图扫描而是根据上一帧的关键点位置推算当前帧人体所在的 ROIRegion 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_modeTrue时管线会强制每帧都做完整检测适合单张图片不适合视频流。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, fx{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_modeFalse, model_complexity1, smooth_landmarksTrue, min_detection_confidence0.5, min_tracking_confidence0.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), thickness2), mp_draw.DrawingSpec(color(0, 0, 255), thickness1), ) 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_segmentationTrue才会计算不是每次推理都会有值。3.4 环境与运行时常见报错对照现象常见原因处理方式摄像头黑屏且 cap.isOpened() 为 False摄像头被其他进程占用关闭占用软件或把 VideoCapture 参数从 0 改成 1导入 mediapipe 报错Python 版本和轮子不匹配换用 3.10/3.11 重建虚拟环境推理速度很慢输入帧分辨率过高推理前把帧 resize 到宽度 640process 返回的 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(maxlen10) 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平滑的是关键点本身滑窗平滑的是动作判定结果两者解决的问题不同。maxlen10意味着最多保留 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_complexity0、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, fnose 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 裁剪和阈值设置而不是急着换模型。这个数据文件也是后续做混淆矩阵、误报分析的原料值得在工程一开始就留在代码里。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →