基于人体姿态估计的俯卧撑自动计数:从MediaPipe到状态机的完整实现

发布时间:2026/8/27 19:32:38
基于人体姿态估计的俯卧撑自动计数:从MediaPipe到状态机的完整实现 1. 项目概述从“数到眼花”到“一眼看穿”做俯卧撑最烦人的环节是什么除了肌肉的酸痛恐怕就是计数了。自己数容易分心让人帮忙数又麻烦用手机App吧要么得把手机放地上角度刁钻要么就是识别不准做个几十个还得自己心里再核对一遍。这个毕业设计的核心就是想用机器视觉技术彻底解决这个“小事儿”带来的大麻烦——实现一个高精度、非接触式的俯卧撑自动计数模型。简单来说这个项目就是让电脑“学会看人做俯卧撑”。它通过摄像头实时捕捉人体运动视频利用深度学习模型自动识别出人体的关键骨骼点比如肩膀、手肘、髋部然后像裁判一样根据这些点的位置变化规律精准判断每一次“下压”和“推起”的完整动作并自动累加计数。这背后融合了计算机视觉、目标检测、姿态估计和简单的逻辑判断算法是一个典型的“AI轻量化应用”场景。别看它目标明确实现起来却是一个麻雀虽小五脏俱全的完整AI项目流水线。你需要搞定数据采集与标注、模型选型与训练、前后端部署与交互等一系列环节。它非常适合计算机、人工智能相关专业的同学作为毕业设计既能展示你对前沿技术的理解又能体现完整的工程实现能力。对于健身爱好者或想入门机器视觉的开发者来说这也是一个绝佳的练手项目从理论到实践打通AI落地的“最后一公里”。2. 项目核心思路与技术选型解析2.1 为什么选择“关键点检测”而非“目标检测”这是整个项目的第一个关键决策。面对“俯卧撑计数”这个问题初学者很容易想到直接用目标检测模型如YOLO去框出整个人体然后通过检测框的上下移动来判断动作。这个思路直观但存在明显缺陷。首先俯卧撑过程中人体的整体轮廓变化并不剧烈尤其是在侧面视角下检测框的垂直位置y坐标波动可能很小容易受到拍摄角度、人体体型的影响导致计数阈值难以统一设定。其次检测框无法提供人体关节的精细角度信息而俯卧撑动作规范性的核心恰恰在于肘关节的角度变化。因此更优的方案是采用**人体姿态估计Human Pose Estimation**技术。它不关心“人在哪”而是关心“人的关节在哪”。通过定位出人体关键的骨骼点如鼻子、颈部、左右肩、左右肘、左右髋、左右膝、左右踝等我们就能获得一个结构化的、富含运动信息的“火柴人”模型。基于这个模型我们可以轻松计算肘关节角度判断手臂是否弯曲到标准角度通常小于90度视为有效下压。躯干倾斜度判断身体是否保持笔直避免塌腰或撅屁股。髋关节高度作为辅助判断身体是否接近地面的指标。这种基于角度的判断逻辑远比基于边界框位置变化的逻辑要鲁棒和精确得多。它抓住了动作的本质特征。2.2 技术栈全景图从数据到应用为了实现这个思路我们需要一套完整的技术栈。下图清晰地展示了从原始视频输入到最终计数输出的完整流程与核心组件flowchart TD A[摄像头视频流] -- B[视频帧预处理br缩放、归一化] B -- C{核心推理引擎br姿态估计模型} C -- D[方案一轻量级模型br如 MoveNet, BlazePose] C -- E[方案二高精度模型br如 HRNet, OpenPose] D -- F[获取人体关键点坐标br17或33个点] E -- F F -- G[关键点后处理br滤波、补全] G -- H[动作逻辑判断br计算肘/肩角度 判断状态] H -- I[计数与反馈br显示计数、播报、纠错]这个流程中每一个环节都有技术选型考量姿态估计模型选型这是核心。对于毕业设计或实时应用需要在精度和速度间权衡。轻量级方案推荐MediaPipe BlazePose或TensorFlow.js PoseNet/MoveNet。它们专为实时性能优化模型小几MB在CPU或普通GPU上也能达到每秒30帧以上非常适合在笔记本或树莓派上部署。BlazePose提供了33个关键点比标准的17点模型如COCO数据集多了手、脸细节对于俯卧撑的手部支撑状态判断更有优势。高精度方案HRNetHigh-Resolution Net或OpenPose。这类模型精度更高对遮挡、复杂背景更鲁棒但模型体积大、计算开销高更适合对实时性要求不高的离线分析或作为精度对比的基准。前后端框架选择快速原型使用Python OpenCV MediaPipe库。几行代码就能调用BlazePose模型配合OpenCV处理视频流开发效率极高是验证想法和完成毕业设计演示的首选。Web应用使用TensorFlow.js或MediaPipe for JavaScript。将模型直接运行在浏览器中用户打开网页即可使用无需安装任何软件便捷性无敌。移动端App使用MediaPipe 的 Android/iOS API或PyTorch Mobile。可以开发成独立的手机App利用手机摄像头和NPU加速体验最佳。计数逻辑设计这是项目的“大脑”。最简单的逻辑是监测一侧肘关节的角度。我们可以定义两个状态“UP”手臂伸直肘角160度和“DOWN”手臂弯曲肘角90度。当状态从UP进入DOWN再回到UP时计为一个完整的俯卧撑。更健壮的逻辑可以同时监测双肘和肩髋连线避免因身体侧倾导致的误判。3. 核心模块实现与实操详解3.1 数据准备没有数据一切免谈尽管我们可以直接使用预训练的姿态估计模型它们已在百万级的人体图像数据集上训练过但为了让模型在我们的特定场景如特定的拍摄角度、光照、着装下表现更好收集并标注自己的小规模数据是有益的。这也能让你的毕业设计报告更有分量。实操步骤采集视频用手机或电脑摄像头从不同角度正面、侧面、不同光照条件、穿着不同服装短袖、背心录制多段自己做俯卧撑的视频。同时可以邀请身材不同的朋友帮忙录制以增加数据的多样性。建议总时长不少于10分钟包含正常速度、快速、慢速等多种节奏。视频抽帧使用OpenCV或FFmpeg将视频按固定频率如每秒10帧抽取成图片。不需要每一帧因为相邻帧相似度太高。import cv2 video_path pushup.mp4 output_dir ./frames/ cap cv2.VideoCapture(video_path) frame_count 0 save_interval 3 # 每3帧保存一帧 while cap.isOpened(): ret, frame cap.read() if not ret: break if frame_count % save_interval 0: cv2.imwrite(f{output_dir}frame_{frame_count:04d}.jpg, frame) frame_count 1 cap.release()关键点标注可选但建议使用标注工具如LabelMe、CVAT或COCO Annotator。你需要在一部分抽出的图片上手动标出人体的关键点遵循你所用模型的关键点定义如COCO的17点。这部分工作耗时但对于后续可能的模型微调至关重要。如果仅使用预训练模型可跳过此步。3.2 模型部署与关键点提取这里以最流行的MediaPipe BlazePose和Python环境为例展示核心代码。环境准备pip install opencv-python mediapipe核心代码解析import cv2 import mediapipe as mp import math # 初始化MediaPipe姿态估计模型 mp_pose mp.solutions.pose pose mp_pose.Pose(static_image_modeFalse, # 设为False用于视频流 model_complexity1, # 复杂度0轻量1标准2高精度 smooth_landmarksTrue, # 平滑关键点减少抖动 enable_segmentationFalse, min_detection_confidence0.5, min_tracking_confidence0.5) mp_drawing mp.solutions.drawing_utils # 打开摄像头 cap cv2.VideoCapture(0) # 0代表默认摄像头 # 状态变量 counter 0 stage None # “up” 或 “down”关键点提取与角度计算函数def calculate_angle(a, b, c): 计算由三点a, b, c构成的夹角顶点为b a np.array(a) # 起点如髋 b np.array(b) # 顶点如肩 c np.array(c) # 终点如肘 radians np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0]) angle np.abs(radians * 180.0 / np.pi) if angle 180.0: angle 360 - angle return angle # 主循环 while cap.isOpened(): success, image cap.read() if not success: break # 转换颜色空间MediaPipe需要RGB image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image_rgb.flags.writeable False # 提升性能 results pose.process(image_rgb) # 转换回BGR用于显示 image.flags.writeable True image cv2.cvtColor(image_rgb, cv2.COLOR_RGB2BGR) if results.pose_landmarks: # 绘制关键点和连线 mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) # 提取所需关键点坐标 landmarks results.pose_landmarks.landmark # 获取左肩、左肘、左腕的坐标以左臂为例 shoulder [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y] elbow [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x, landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y] wrist [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y] # 计算肘关节角度肩-肘-腕 angle calculate_angle(shoulder, elbow, wrist) # 可视化角度 cv2.putText(image, fAngle: {int(angle)}, tuple(np.multiply(elbow, [640, 480]).astype(int)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA) # 核心计数逻辑 if angle 160: # 手臂基本伸直 stage up if angle 90 and stage up: # 手臂弯曲且之前是伸直状态 stage down counter 1 print(fCount: {counter}) # 或播放提示音 # 在图像上显示计数和状态 cv2.putText(image, fCount: {counter}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA) cv2.putText(image, fStage: {stage}, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA) cv2.imshow(Push-up Counter, image) if cv2.waitKey(5) 0xFF 27: # 按ESC退出 break cap.release() cv2.destroyAllWindows()注意上述角度阈值160和90是经验值可能需要根据你的动作标准和模型输出进行微调。更严谨的做法是同时监测双肘并加入“中间状态”以避免抖动引起的误计数。3.3 状态机设计与计数逻辑优化简单的“if-else”判断在动作快速或抖动时容易出错。一个更稳健的方法是引入一个有限状态机Finite State Machine。我们可以定义四个状态START、GOING_DOWN、DOWN、GOING_UP。计数触发条件不是简单的角度阈值而是状态的完整转移START - GOING_DOWN - DOWN - GOING_UP - START完成一次循环才计数。这可以有效过滤掉手臂在临界点附近抖动造成的多次触发。优化后的逻辑伪代码状态 START 计数 0 对于每一帧 角度 计算肘关节角度() 如果 状态 START 且 角度 100: 状态 GOING_DOWN 否则如果 状态 GOING_DOWN 且 角度 80: 状态 DOWN 否则如果 状态 DOWN 且 角度 100: 状态 GOING_UP 否则如果 状态 GOING_UP 且 角度 160: 状态 START 计数 1此外还可以加入时间约束例如规定从GOING_DOWN到DOWN必须在0.5秒内完成否则重置状态这可以防止因中途停顿导致的逻辑混乱。4. 工程化与性能优化要点4.1 提升实时性与鲁棒性一个可用的演示和一個健壯的产品之间差的就是这些优化细节。关键点平滑滤波原始的关键点坐标每一帧都会有微小抖动直接用于计算角度会导致计数不稳定。常用**指数移动平均EMA或卡尔曼滤波器Kalman Filter**进行平滑。# 简单的EMA滤波示例 smoothed_angle 0 alpha 0.3 # 平滑因子越小越平滑但延迟越大 while True: raw_angle get_raw_angle() smoothed_angle alpha * raw_angle (1 - alpha) * smoothed_angle # 使用 smoothed_angle 进行状态判断丢帧与补全处理在快速运动或遮挡时模型可能短暂丢失对某些关键点的检测。不能直接使用None值会导致程序崩溃。需要根据历史帧进行插值补全或暂时使用上一次的有效值。多线程处理图像采集I/O和模型推理CPU/GPU计算是瓶颈。可以使用生产者-消费者模型一个线程专门抓取视频帧放入队列另一个线程从队列取帧进行推理和显示避免因推理速度慢导致视频卡顿。4.2 从脚本到应用部署方案毕业设计不能只是一个跑在IDE里的.py文件。你需要把它打包成一个可交互的应用。桌面图形界面GUI使用Tkinter、PyQt或Dear PyGui为你的代码套一个壳。增加开始/停止计数按钮、计数显示框、角度实时曲线图、历史记录查看等功能瞬间提升专业度。Web应用部署这是展示和分享最方便的方式。后端API使用FastAPI或Flask搭建一个Web服务器。接收前端传来的图片Base64编码或二进制流调用Python版的MediaPipe模型进行推理返回关键点坐标和计数结果。前端界面使用HTML/CSS/JavaScript配合TensorFlow.js或直接通过API与后端通信。在网页上实时显示摄像头画面、覆盖骨骼点、显示计数。一体化方案使用Gradio或Streamlit。这两个Python库可以用极少的代码构建出功能完善的机器学习Web界面非常适合快速原型演示。模型轻量化与加速如果考虑在树莓派或手机端运行需要对模型进行优化。TensorFlow Lite将训练好的模型转换为.tflite格式并进行量化如INT8量化可以大幅减小模型体积、提升推理速度。ONNX Runtime支持跨平台硬件加速在Intel CPU、NVIDIA GPU、ARM NPU上都能获得不错的性能。5. 常见问题排查与效果提升技巧在实际开发中你一定会遇到各种问题。下面是一些“踩坑”经验的总结。5.1 问题一计数不准乱跳数这是最常见的问题。原因1角度阈值设置不合理。每个人的手臂长度、身体比例不同完全伸直的角度未必是180度。解决增加一个“校准”功能。让用户在开始前做两个标准动作完全伸直和下到底程序记录这两个状态下的角度作为该用户的个性化阈值。原因2关键点抖动。解决务必加入滤波平滑如前面提到的EMA或卡尔曼滤波。原因3逻辑过于简单。解决采用状态机FSM代替简单的if-else并引入时间窗口判断例如“必须在DOWN状态维持至少0.2秒才算有效”。原因4拍摄角度问题。侧面视角是最佳视角正面视角下肘关节角度的2D投影变化不明显。解决在UI中提示用户“请使用侧面视角”。5.2 问题二检测不到人或关键点漂移原因1光照条件太差或背景复杂。解决建议用户在有均匀光照、背景简洁的环境下使用。可以在预处理阶段增加图像增强如直方图均衡化。原因2人体超出画面或被严重遮挡。解决在画面中绘制一个“建议区域框”提示用户将身体置于框内。对于短暂遮挡使用关键点预测如用上一帧位置来保持跟踪。原因3模型复杂度选择不当。解决MediaPipe的model_complexity参数。如果环境简单但对精度要求高可以尝试设为2。如果追求速度设为0。5.3 问题三无法区分“俯卧撑”和其他类似动作比如有人做了一半停下或者在做平板支撑。单纯的肘关节角度可能无法区分。解决引入多关节联合判断。肩髋相对位置计算肩部和髋部中点的连线与水平线的夹角。在标准俯卧撑中这条线应基本保持水平。如果夹角过大屁股撅起或塌腰可以给出姿势警告。腕、髋、踝的相对高度可以判断身体是否整体在上下移动。平板支撑时这三个点的高度基本不变。动作频率真正的俯卧撑有一个合理的速度范围如每次1-3秒。过快或过慢的周期性运动可以过滤掉。5.4 高级功能拓展思路想让你的毕业设计脱颖而出可以考虑加入这些功能姿势规范性评估不仅计数还当教练。实时判断“塌腰”、“手臂未伸直”、“下潜深度不足”等问题并通过语音或文字提示用户。多人体同时计数修改逻辑对results.pose_landmarks检测到的多个人体实例results.pose_world_landmarks分别进行跟踪和计数。这需要为每个检测到的人体分配一个唯一的ID并进行持续跟踪。数据持久化与可视化将每次训练的次数、日期、平均速度保存到数据库如SQLite或文件中。后期可以绘制训练曲线图分析进步情况。支持多种健身动作将核心逻辑抽象化。通过配置文件定义不同动作如深蹲、引体向上、仰卧起坐所需监测的关键点和角度阈值打造一个通用的“视觉健身教练”。6. 项目总结与避坑指南回顾走完整个项目你会发现一个看似简单的“计数”功能背后涉及了计算机视觉项目的完整生命周期问题定义、方案选型、数据准备、模型应用、逻辑开发、性能优化、部署上线。每一个环节都有值得深挖的细节。最重要的几点心得不要一开始就追求完美模型MediaPipe BlazePose这类预训练模型已经非常强大足以支撑一个优秀的毕业设计。你的核心价值应体现在如何利用好模型的输出设计出鲁棒、智能的业务逻辑上。过早陷入模型训练和调参的泥潭可能会让你迟迟看不到成果打击信心。可视化是调试的最佳工具一定要把关键点、连线、计算出的角度、当前状态、计数都实时画在画面上。眼睛看到的问题比打印在日志里的数字直观一万倍。它能帮你快速定位是检测问题、角度计算问题还是逻辑问题。逻辑的健壮性远高于单一指标的精度一个99%准确率但会偶尔疯狂跳数的计数器用户体验是灾难性的。而一个95%准确率但极其稳定的计数器才是可用的产品。多花时间在状态机设计、滤波和异常处理上。考虑边缘情况人可能会离开画面又回来可能会中途休息可能会做不标准动作。你的程序在面对这些情况时是崩溃、乱计数还是能优雅地处理如暂停计数、提示用户这体现了工程的成熟度。这个项目最迷人的地方在于它让你手中的代码真正地“看见”并“理解”了物理世界。当你第一次看到屏幕上的骨骼线随着自己的身体同步舞动并准确地报出“1, 2, 3...”时那种成就感是纯粹的。它不仅是一个毕业设计更是一个通往更广阔AI应用世界的、非常扎实的起点。