资讯中心

基于MediaPipe与状态机的实时俯卧撑计数系统:从原理到工程实践

📅 2026/8/27 17:19:54
基于MediaPipe与状态机的实时俯卧撑计数系统:从原理到工程实践
1. 项目缘起从“数到崩溃”到“让机器看懂”去年帮一个体育学院的朋友做体能测试其中一项是记录学生在一分钟内完成标准俯卧撑的次数。我坐在旁边一手拿秒表一手拿记录板眼睛死死盯着测试者的动作。前二十个还好到后面测试者动作开始变形我的注意力也开始涣散一个走神可能就少数或多记了一个。更别提同时测试好几个人的混乱场面了。那次经历让我深刻体会到依赖人工计数不仅效率低下、容易出错而且无法提供动作质量的分析。就在那时“机器视觉”这个词跳进了我的脑子。我们能不能让摄像头代替人眼让算法代替人脑去自动、准确、公正地完成计数甚至分析动作标准度这个想法成了我毕业设计的核心。它不仅仅是一个学术课题更是为了解决一个非常具体的实际问题如何将计算机视觉技术落地到一个贴近生活的健身场景中让科技服务于日常训练。基于机器视觉的俯卧撑计数模型其核心目标就是通过摄像头实时捕捉人体姿态识别俯卧撑动作的关键阶段如身体下降和撑起并据此完成自动计数。它涉及的关键技术栈包括人体关键点检测、动作时序分析和计数逻辑设计。这个项目对于计算机视觉的初学者来说是一个绝佳的练手项目目标明确、应用场景清晰、技术链条完整从数据采集、模型训练到部署应用都能走一遍。2. 技术选型为什么是MediaPipe而不是OpenPose实现人体姿态估计主流开源方案主要有两个OpenPose和Google的MediaPipe。在项目初期我花了大量时间对比两者最终选择了MediaPipe原因基于以下几个非常实际的考量2.1 性能与效率的压倒性优势OpenPose功能强大精度高但其模型复杂对计算资源要求极高。在普通笔记本电脑甚至中端显卡上很难达到实时30 FPS的处理速度。这对于需要实时反馈的俯卧撑计数应用来说是致命的。MediaPipe则采用了轻量级模型和高度优化的流水线在CPU上就能实现实时的全身姿态估计这对于降低部署门槛、在移动端或边缘设备上运行至关重要。2.2 开发与部署的便捷性OpenPose的安装和配置过程堪称“劝退指南”需要编译复杂的C库处理各种依赖冲突。MediaPipe提供了极其友好的Python API通过pip即可安装其核心的姿势估计模块mediapipe几行代码就能初始化并运行检测器。这种“开箱即用”的特性让我能把宝贵的时间集中在业务逻辑计数算法上而不是和环境搏斗。2.3 关键点定义的合理性MediaPipe的人体姿态模型定义了33个三维关键点涵盖了身体、面部和手部。对于俯卧撑计数我们主要关注其中一部分双肩11, 12、双髋23, 24、双膝25, 26、双踝27, 28以及鼻子0。这些点足以构建判断身体角度的向量。MediaPipe的关键点索引清晰、稳定其提供的坐标x, y, z和可见度visibility信息非常实用。注意这里有一个初学者极易忽略的细节。MediaPipe返回的坐标x, y是归一化到[0, 1]区间的分别表示相对于图像宽度和高度的比例。而z坐标是相对深度值越小表示离摄像头越近。在计算真实世界角度时必须先将x, y坐标乘以图像的宽和高转换为像素坐标。我的选择总结对于毕业设计或快速原型开发MediaPipe是更优解。它平衡了精度、速度和易用性。如果未来项目对精度有极端要求且拥有强大的GPU服务器可以再考虑优化版的OpenPose或其他学术模型。但在现阶段“快速实现、快速验证”是第一要务。3. 核心实现拆解一个俯卧撑的完整周期有了姿态估计工具接下来就是核心逻辑如何从一个动态的视频流中识别出一次完整的俯卧撑我将其分解为三个层次单帧姿态解析、时序状态判断和计数逻辑触发。3.1 单帧姿态解析计算躯干倾斜角计数的基础是判断身体是否处于“低位”胸口接近地面。最直观的指标是躯干与地面的夹角。我们可以利用肩部和髋部的关键点来近似代表躯干。假设我们获得了左肩shoulder_left、右肩shoulder_right、左髋hip_left、右髋hip_right的像素坐标。首先计算肩部中点和髋部中点# 伪代码示例 shoulder_center (shoulder_left shoulder_right) / 2 hip_center (hip_left hip_right) / 2然后构建从髋部中点指向肩部中点的向量torso_vector。在图像坐标系中向量可以表示为(dx, dy) (shoulder_center.x - hip_center.x, shoulder_center.y - hip_center.y)。接着我们需要一个参考水平向量。一个简单且有效的方法是假设摄像头是基本水平放置的那么图像的水平方向x轴方向就可以作为地面参考。我们定义水平向量horizontal_vector (1, 0)。最后计算两个向量之间的夹角。这里使用向量点积公式cosθ (torso_vector · horizontal_vector) / (|torso_vector| * |horizontal_vector|)由于|horizontal_vector| 1公式简化为cosθ dx / sqrt(dx^2 dy^2)。通过反余弦函数arccos即可得到夹角 θ。这个夹角在0到180度之间当身体直立时θ接近90度当身体水平时θ接近0度或180度取决于方向。3.2 时序状态判断设计一个状态机单帧的角度只是一个瞬时值。俯卧撑是一个连续动作我们必须引入时间维度设计一个有限状态机来跟踪整个运动过程。这是整个计数逻辑的大脑。我定义了四个状态高位UP身体伸直躯干角度大于某个阈值如60度。这是起始和结束位置。下降中GOING_DOWN从高位进入角度持续减小且速度角度变化率为负。低位DOWN身体降至最低点角度小于某个阈值如30度并且速度接近零处于短暂停顿。上升中GOING_UP从低位进入角度持续增大且速度为正。状态之间的转换由角度值及其变化率导数触发。例如UP - GOING_DOWN当角度小于“高位阈值”且角度变化率为负正在变小。GOING_DOWN - DOWN当角度小于“低位阈值”且角度变化率的绝对值很小即将停止下降。DOWN - GOING_UP当角度变化率为正开始上升。GOING_UP - UP当角度大于“高位阈值”且角度变化率很小即将停止上升。3.3 计数逻辑触发何时算完成一次计数触发点放在GOING_UP - UP这个状态转换的时刻。为什么不是从DOWN状态直接计数因为要防止在最低点抖动导致的误计数。只有当系统确认用户确实从最低点开始上升并成功回到了高位才认为完成了一次完整的、有效的俯卧撑。这里有一个非常重要的防抖动机制在状态转换条件中我加入了“持续帧数”判断。例如不能因为一帧的角度略高于阈值就立刻从GOING_UP跳转到UP而需要连续N帧比如5帧约0.17秒都满足高位条件才进行状态转换和计数。这能有效过滤掉动作不稳定或检测噪声带来的抖动。4. 从实验室到健身房工程化落地中的五大挑战把算法跑通Demo只是第一步要让模型在真实场景中稳定工作会遇到一系列在实验室里想不到的问题。这部分是我踩坑最多、也是收获最大的地方。4.1 光照与背景干扰预处理不是可有可无最初我在宿舍均匀灯光下测试效果很好。但一到健身房问题就来了屋顶射灯造成局部强光过曝阴影区域又太暗背景中走动的人和复杂器械形成了大量干扰。解决方案动态ROI感兴趣区域不要处理整张图。我先用几帧图像通过背景减除或运动检测粗略定位运动者的大致区域后续的姿态估计和计算只在这个区域内进行大幅减少了计算量和背景干扰。直方图均衡化对ROI区域进行CLAHE限制对比度自适应直方图均衡化处理。它能有效改善局部过曝或欠曝区域的对比度让MediaPipe提取的关键点更稳定。这一步对精度提升非常明显。背景简化建议在项目演示或初期部署时如果条件允许尽量选择纯色、静止的背景墙。这能从根本上减少干扰。4.2 摄像头视角与标定问题“角度阈值”设定为30度和60度这个值是基于摄像头正对测试者侧面这一理想视角。如果摄像头是从斜上方拍摄计算出的躯干向量与水平面的夹角就会失真导致阈值完全失效。解决方案视角固定这是最实用的方法。规定摄像头的安装位置和高度例如侧面镜头中心与测试者髋部同高距离1.5米。在应用说明中明确要求用户按此设置。简易标定程序启动时让用户先做一个“直立”和“平板支撑”姿势程序记录下这两个姿势对应的角度值作为动态的“高位基线”和“低位基线”。这样阈值就变成了相对于用户自身基线的偏移量而非固定值适应性更强。4.3 动作不标准与计数争议这是最核心的业务逻辑挑战。什么是“有效”的俯卧撑身体没完全下去算吗屁股撅太高算吗解决方案引入多角度联合判断。躯干角度主判断条件如前所述。膝关节角度计算大腿髋到膝和小腿膝到踝的夹角。如果这个角度过大比如160度说明腿没伸直可以给出“膝盖未伸直”的提示但不一定判定为无效这取决于规则松紧。躯干弯曲度除了肩髋连线还可以计算脊柱关键点如肩、髋、膝连线的弯曲程度判断是否塌腰或过弓。最终策略我采用了一个“扣分制”模型。一次动作躯干角度达标是得分的必要条件。膝角、脊柱曲度等作为附加检查项如果不达标则在计数结果旁显示“动作不标准膝盖弯曲”但依然计数。这样既保证了计数的连续性又提供了质量反馈。4.4 实时性与延迟优化在树莓派4B上部署时发现延迟高达2-3秒完全无法用于实时计数。瓶颈分析发现MediaPipe本身在CPU上运行尚可但图像采集、显示和GUI渲染占用了大量资源。解决方案降低处理分辨率将输入图像从1920x1080下采样到640x480MediaPipe的处理速度直接提升数倍而对关键点检测精度影响微乎其微。多线程流水线设计一个生产者-消费者模型。一个线程专门负责从摄像头抓取帧生产者放入一个容量为1的队列另一个线程从队列取帧进行姿态估计和计数计算消费者。这样抓取下一帧的操作不会被耗时的计算所阻塞显著提升了帧率。关闭非必要可视化在最终部署版本中可以只显示关键点连线和计数结果甚至只输出数字和语音提示关闭原始的、高分辨率的视频流显示。4.5 数据收集与模型微调进阶MediaPipe的通用模型在大多数情况下已经很好但对于一些极端姿势如穿着非常宽松的衣服、有部分遮挡或特定人群如儿童效果可能会下降。解决方案建立自己的微调数据集。数据收集录制不同体型、不同着装、在不同光照和背景下做俯卧撑的视频。关键点标注使用Labelme等工具手动标注出MediaPipe定义的33个关键点。这是一个费时但效果显著的工作。模型微调利用MediaPipe提供的模型训练框架或使用PyTorch/TensorFlow重建类似轻量级模型用自己的标注数据对模型进行微调。即使只用几百张标注图像也能让模型在特定场景下的鲁棒性大幅提升。这一步属于进阶优化但如果你想深入研究CV这是必经之路。5. 系统搭建与效果评估一份可运行的实现清单理论说了这么多我们来点实际的。下面是一个基于Python使用MediaPipe和OpenCV实现的简化版系统框架和评估方法。5.1 核心代码结构import cv2 import mediapipe as mp import numpy as np from enum import Enum class PoseState(Enum): UP 1 GOING_DOWN 2 DOWN 3 GOING_UP 4 # 初始化MediaPipe Pose mp_pose mp.solutions.pose pose mp_pose.Pose(static_image_modeFalse, model_complexity1, enable_segmentationFalse, min_detection_confidence0.5, min_tracking_confidence0.5) mp_drawing mp.solutions.drawing_utils # 状态机变量 current_state PoseState.UP rep_count 0 angle_buffer [] # 用于平滑角度和计算变化率 UP_THRESH 60 # 高位角度阈值 DOWN_THRESH 30 # 低位角度阈值 CONFIRM_FRAMES 5 # 状态确认所需连续帧数 def calculate_torso_angle(landmarks, image_shape): # 获取肩部和髋部关键点索引MediaPipe索引 l_shoulder landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER] r_shoulder landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER] l_hip landmarks[mp_pose.PoseLandmark.LEFT_HIP] r_hip landmarks[mp_pose.PoseLandmark.RIGHT_HIP] # 转换为像素坐标并计算中点 h, w image_shape[:2] shoulder_center np.array([(l_shoulder.x r_shoulder.x) * w / 2, (l_shoulder.y r_shoulder.y) * h / 2]) hip_center np.array([(l_hip.x r_hip.x) * w / 2, (l_hip.y r_hip.y) * h / 2]) # 计算向量和角度 torso_vec shoulder_center - hip_center # 注意图像y轴向下所以角度计算需注意。这里我们主要关心角度大小变化趋势。 angle np.degrees(np.arctan2(abs(torso_vec[1]), abs(torso_vec[0]))) # 一个简化的角度计算 return angle cap cv2.VideoCapture(0) # 打开摄像头 while cap.isOpened(): success, image cap.read() if not success: break # 转换为RGB并处理 image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results pose.process(image_rgb) if results.pose_landmarks: # 计算当前帧躯干角度 current_angle calculate_torso_angle(results.pose_landmarks.landmark, image.shape) angle_buffer.append(current_angle) if len(angle_buffer) 5: # 保留最近5帧用于计算平滑角度和速度 angle_buffer.pop(0) smoothed_angle np.mean(angle_buffer) # 简单计算角度变化率用最近两帧差值近似 if len(angle_buffer) 2: angle_velocity angle_buffer[-1] - angle_buffer[-2] else: angle_velocity 0 # 状态机逻辑简化版需完善防抖 if current_state PoseState.UP: if smoothed_angle UP_THRESH and angle_velocity -0.5: current_state PoseState.GOING_DOWN elif current_state PoseState.GOING_DOWN: if smoothed_angle DOWN_THRESH and abs(angle_velocity) 0.5: current_state PoseState.DOWN elif current_state PoseState.DOWN: if angle_velocity 0.5: current_state PoseState.GOING_UP elif current_state PoseState.GOING_UP: if smoothed_angle UP_THRESH and abs(angle_velocity) 0.5: current_state PoseState.UP rep_count 1 # 完成一次计数 # 在图像上绘制姿态和计数 mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) cv2.putText(image, fState: {current_state.name}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(image, fCount: {rep_count}, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(image, fAngle: {smoothed_angle:.1f}, (10, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2) cv2.imshow(Push-up Counter, image) if cv2.waitKey(5) 0xFF 27: break cap.release() cv2.destroyAllWindows()5.2 如何评估你的模型不能光说“看起来挺准”需要有量化的评估指标。我建议从两个层面进行计数准确率录制一段包含已知次数如20次标准俯卧撑的视频。用你的程序跑一遍记录计数结果。计算准确率 (程序计数 / 真实次数) * 100%。理想应接近100%。误差分析是漏计数没检测到还是多计数误触发结合状态机日志分析是哪个状态转换判断出了问题。鲁棒性测试不同速度快速俯卧撑和慢速俯卧撑。不同幅度半程俯卧撑和全程俯卧撑。干扰测试在背景中轻微走动穿着不同颜色/款式的衣服。边界测试动作在摄像头边缘光照突然变化。记录下每种情况下的准确率。这个测试过程不仅能评估模型更是发现算法缺陷、优化阈值和逻辑的最佳途径。6. 超越计数项目的延伸思考与优化方向完成基本计数后这个项目还有巨大的深化空间可以从一个“计数器”进化为一套“体能训练辅助系统”。6.1 从计数到质量评估可以计算并反馈更多指标节奏分析计算每次俯卧撑的下降和上升阶段分别用时提示用户保持稳定节奏如“下降过快”。不对称性检测比较左右肩、左右髋的高度差判断身体是否倾斜预防肌肉力量不平衡导致的损伤。耐力曲线绘制每次俯卧撑到达最低点的时间或速度随次数变化的曲线直观展示力竭过程。6.2 多模态反馈与交互语音提示使用pyttsx3或gTTS库在计数时播报数字在动作不标准时给出语音提醒“请保持核心收紧”。可视化报告训练结束后生成一个简单的报告页面展示总次数、平均速度、动作标准度曲线等。云端存储与历史对比将每次训练数据上传到数据库用户可以看到自己的历史进步曲线。6.3 部署到边缘设备让模型脱离电脑真正走进健身房树莓派小型触摸屏制作一个便携设备。优化代码后在树莓派上可以达到不错的实时性。安卓/iOS App利用MediaPipe的移动端库开发手机应用。用户将手机放在地面即可使用最为便捷。Web应用使用TensorFlow.js或ONNX Runtime Web配合浏览器的WebRTC调用摄像头无需安装任何软件打开网页即可使用。6.4 模型轻量化与加速如果对延迟有极致要求可以探索模型量化将MediaPipe的浮点模型转换为INT8精度在几乎不损失精度的情况下大幅提升推理速度。模型剪枝移除网络中不重要的连接或神经元得到一个更小、更快的模型。专用硬件使用带有NPU神经网络处理单元的开发板如华为昇腾Atlas 200 DK或英伟达Jetson Nano能获得飞跃式的性能提升。回过头看这个毕业设计项目带给我的远不止一个计数程序。它是一次完整的“问题定义 - 技术选型 - 算法实现 - 工程调优 - 评估部署”的微型项目演练。最大的体会是在机器视觉项目中算法只占一半另一半是工程。如何让算法在复杂、非受控的真实环境中稳定工作如何设计人性化的交互和反馈这些问题的挑战性和价值丝毫不亚于模型本身的调优。如果你也在做类似的课题希望我的这些踩坑经验和实现思路能帮你少走些弯路更快地做出一个既“炫酷”又“扎实”的作品。