尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Python与MediaPipe实现引体向上自动计数:从姿态估计到状态机逻辑

基于Python与MediaPipe实现引体向上自动计数:从姿态估计到状态机逻辑 简介本资源是一套基于Python的引体向上动作自动计数算法实现方案面向计算机科学、电子工程及应用数学等相关专业本科生与研究生解决健身动作识别与运动次数量化中的姿态检测与轨迹分析问题适用于课程实践、学期项目或毕业设计等中阶编程与CV结合场景。压缩包共8个文件含2个核心Python脚本姿态检测与计数逻辑、2段MP4演示视频原始动作与算法输出效果、1个说明文档及辅助配置文件整体大小为47.26MB结构简洁、开箱即用。已有34人学习下载资源突出算法可复现性与代码可扩展性——提供完整推理流程、关键参数注释及常见优化入口便于读者理解人体关键点建模思路、调试阈值逻辑并基于自身数据集进行迁移适配。1. 项目概述从健身痛点到一个Python程序员的解决方案最近在健身房我总能看到一些朋友在做引体向上时要么自己数着数着就忘了要么得依赖手机录像事后回放计数既麻烦又打断了训练的连贯性。作为一个常年泡在代码里的程序员我就在想能不能用自己最熟悉的工具——Python来解决这个小小的、但很实际的健身烦恼于是就有了这个“基于Python的引体向上计数算法实现”的项目。这个项目的核心目标很简单让电脑通过摄像头“看懂”你的引体向上动作并自动、准确地为你计数。它不依赖任何昂贵的智能健身设备只需要一台普通的电脑甚至是一台树莓派和一个摄像头笔记本自带的或USB摄像头都行。你只需要在单杠前开始训练程序就会在后台默默工作实时在视频画面上标注你的身体关键点并在你完成一次标准动作后自动增加计数。听起来是不是有点像那些高端健身镜或智能健身App的功能没错原理是相通的。但我们的实现完全开源、透明你可以清楚地知道每一行代码在做什么如何调整算法以适应你自己的动作习惯甚至把它改造成深蹲计数、俯卧撑计数。这对于健身爱好者、计算机视觉的初学者或者任何想将编程应用于实际生活场景的朋友来说都是一个绝佳的练手项目。它涉及了Python编程、OpenCV图像处理、以及轻量级的人体姿态估计等多个有趣的技术点。接下来我就把自己从零搭建这个项目的完整思路、踩过的坑和最终可运行的源码毫无保留地分享给你。2. 核心思路与技术选型为什么是“姿态估计”而不是“目标检测”当你决定用计算机视觉来计数引体向上时第一个面临的技术抉择就是我们到底要识别什么最直观的想法可能是“识别人”或者“识别单杠”。这就是目标检测的思路比如用YOLO或SSD模型在画面里框出人和单杠。但这个方法对于计数来说精度远远不够。因为计数依赖的是动作的周期性变化仅仅知道“那里有个人”是不够的我们需要知道这个人身体各部位的具体位置和角度变化。举个例子一个人挂在单杠上轻微晃动和完成一次标准的引体向上下巴过杠在目标检测框里看起来可能差不多但关节点的运动轨迹却有天壤之别。因此我选择了人体姿态估计作为本项目的技术基石。它的目标是识别出人体的一系列关键点例如鼻子、左右肩、左右肘、左右腕、左右髋、左右膝、左右踝等。通过追踪这些关键点尤其是肩、肘、腕在垂直方向上的运动轨迹我们就可以清晰地定义出一个“引体向上”动作的周期。技术栈的确定编程语言Python。这是计算机视觉和机器学习领域的事实标准库生态极其丰富从快速原型开发到性能优化都有成熟的工具链。核心视觉库OpenCV。用于视频流的读取、图像的基本处理缩放、裁剪、颜色转换、以及最终结果的绘制画关键点、画计数文字和输出。姿态估计模型MediaPipe Pose。这是本项目成功的关键。为什么选择它而不是OpenPose或HRNet轻量级与高效率MediaPipe是Google推出的一个跨平台框架其Pose解决方案在CPU上就能达到实时推理的速度30FPS完全不需要GPU。这对于我们希望在普通笔记本电脑上流畅运行的需求至关重要。开箱即用的精度MediaPipe Pose提供了33个3D人体关键点对于引体向上这种大肢体运动其2D投影的精度已经绰绰有余且模型鲁棒性很好在部分遮挡如侧面拍摄时一只手被身体挡住下也能保持稳定输出。极简的API几行代码就能完成模型的初始化和推理让我们能把精力集中在动作逻辑的判断上而不是复杂的模型部署上。项目工作流程简述输入摄像头实时视频流或本地视频文件。处理对每一帧图像使用MediaPipe Pose模型检测出人体的33个关键点。分析提取关键点如双肩中点、手腕的Y轴垂直方向坐标计算其相对位置和运动速度。逻辑判断根据关键点的运动轨迹设计一个状态机来判断用户当前处于“悬挂起始”、“上升”、“顶峰锁定”、“下降”中的哪个阶段从而在完成一个完整周期后计数。输出在原始视频帧上绘制出骨骼关键点和连线并实时显示当前计数最后输出处理后的视频。这个流程清晰地将一个复杂的视觉任务分解为可逐个击破的模块接下来我们就进入具体的实现环节。3. 环境搭建与依赖安装一步到位的配置指南工欲善其事必先利其器。为了避免大家在不同操作系统上遇到各种奇怪的依赖问题我整理了一份经过验证的、清晰的环境配置清单。我们使用conda来创建独立的Python环境这是管理项目依赖的最佳实践。步骤1创建并激活虚拟环境打开你的终端Windows用Anaconda Prompt或CMDMac/Linux用Terminal执行以下命令。环境名pullup_counter你可以随意替换。# 创建名为 pullup_counter 的 Python 3.8 环境3.7-3.10均可推荐3.8 conda create -n pullup_counter python3.8 -y # 激活环境 conda activate pullup_counter激活后你的命令行提示符前面应该会显示(pullup_counter)表示你已经在这个独立的环境中工作了。步骤2安装核心依赖库我们将使用pip来安装所需的包。请按顺序执行以下命令# 1. 安装OpenCV。这是处理图像和视频的基础。 pip install opencv-python # 2. 安装MediaPipe。这是实现姿态估计的核心。 pip install mediapipe # 3. 可选但推荐安装NumPy。MediaPipe和OpenCV都会用到它通常会自动安装但明确安装可以避免版本问题。 pip install numpy注意关于MediaPipe安装的坑如果你在安装MediaPipe时遇到编译错误或版本冲突大概率是因为Python版本或系统架构问题。最稳妥的解决方案是直接安装预编译的wheel包。访问 https://pypi.org/project/mediapipe/ 查看与你Python版本和系统匹配的版本号然后用pip install mediapipe[版本号]来安装。对于绝大多数用户直接pip install mediapipe就能成功。步骤3验证安装创建一个简单的Python脚本test_env.py输入以下内容import cv2 import mediapipe as mp import numpy as np print(fOpenCV version: {cv2.__version__}) print(fMediaPipe version: {mp.__version__}) print(fNumPy version: {np.__version__}) print(环境测试成功所有核心库均已就绪。)运行它python test_env.py如果成功输出版本号且没有报错那么恭喜你最磨人的环境配置环节已经顺利通过。接下来就是激动人心的编码部分了。4. 代码实现深度解析从关键点提取到状态机逻辑让我们打开代码编辑器新建一个名为pull_up_counter.py的文件。我将分模块、逐行解释核心代码并穿插我在开发过程中总结的注意事项。4.1 初始化与视频流读取首先导入必要的库并初始化MediaPipe Pose模型。import cv2 import mediapipe as mp import numpy as np # 初始化MediaPipe Pose解决方案 mp_drawing mp.solutions.drawing_utils # 用于绘制关键点和连线 mp_pose mp.solutions.pose # 姿态估计模型 # 创建Pose模型实例 # static_image_mode: 对于视频流设为False以提升速度 # model_complexity: 复杂度0快1中2慢且准。引体向上用1或0足够。 # smooth_landmarks: 平滑关键点减少抖动必须设为True # min_detection_confidence: 检测置信度阈值高于此值才认为检测到人体 # min_tracking_confidence: 跟踪置信度阈值用于视频流中维持跟踪 pose mp_pose.Pose(static_image_modeFalse, model_complexity1, smooth_landmarksTrue, min_detection_confidence0.5, min_tracking_confidence0.5) # 初始化计数器与状态 counter 0 stage None # 动作阶段up, down实操心得参数调优min_detection_confidence和min_tracking_confidence是影响体验的关键参数。设得太低如0.3容易产生误检测画面里走过一个人都可能被计数设得太高如0.8在动作快速或光线稍暗时容易丢失跟踪。0.5是一个在大多数场景下都比较稳健的折中值。如果在你自己的环境中计数不稳定可以优先调整这两个参数。接下来设置视频源。你可以使用摄像头cv2.VideoCapture(0)或者本地视频文件cv2.VideoCapture(‘your_video.mp4’)。# 使用默认摄像头编号0。如果是外接摄像头可能需要尝试1或2。 cap cv2.VideoCapture(0) # 设置摄像头分辨率更高的分辨率有助于提升关键点检测精度但会增加计算量。 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 如果你有一个录制好的引体向上视频用于测试可以注释掉上面两行用下面这行 # cap cv2.VideoCapture(pullup_demo.mp4)4.2 关键点提取与核心逻辑如何定义一次“引体向上”这是整个项目的“大脑”。我们需要从MediaPipe返回的33个关键点中选取合适的点来计算和判断。MediaPipe Pose的关键点索引定义可以在其官方文档找到。对于我们计数引体向上最有用的是左肩11右肩12左腕15右腕16左肘13右肘14一个稳健的策略是使用双肩中点和双腕中点来判定。因为有些人做引体向上时身体会扭转单侧手腕可能被遮挡取中点可以增加鲁棒性。while cap.isOpened(): ret, frame cap.read() if not ret: break # 如果视频流结束退出循环 # 为了提升性能可以将图像尺寸缩小。但注意缩小会影响远处小目标的检测精度。 # frame cv2.resize(frame, (640, 360)) # MediaPipe需要RGB格式的图像但OpenCV默认是BGR image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image.flags.writeable False # 设置为False可以提升一点性能 # 核心进行姿态估计检测 results pose.process(image) # 改回BGR格式以便OpenCV显示 image.flags.writeable True image cv2.cvtColor(image, cv2.COLOR_RGB2BGR)现在results.pose_landmarks里就包含了检测到的关键点坐标归一化到[0,1]的坐标。我们来提取并计算try: # 1. 提取关键点坐标 landmarks results.pose_landmarks.landmark # 获取左右肩和左右腕的坐标 left_shoulder [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y] right_shoulder [landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x, landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y] left_wrist [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y] right_wrist [landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x, landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y] # 2. 计算双肩中点和双腕中点的Y坐标图像坐标系原点在左上角Y向下增大 shoulder_center_y (left_shoulder[1] right_shoulder[1]) / 2 wrist_center_y (left_wrist[1] right_wrist[1]) / 2 # 3. 计算“手腕相对于肩膀的高度差”。这是判断上升下降的核心指标。 # 注意Y坐标越大位置越靠下。所以当手腕在肩膀下方时这个差值为正。 vertical_diff wrist_center_y - shoulder_center_y # 4. 状态机逻辑判断 # 阈值需要根据你的拍摄距离和身高进行调整这是调参的重点。 DOWN_THRESHOLD 0.05 # 手腕低于肩膀多少时算作“下放到底”悬挂状态 UP_THRESHOLD -0.02 # 手腕高于肩膀多少时算作“上升到顶”下巴过杠 # 状态判断 if vertical_diff DOWN_THRESHOLD: current_stage down elif vertical_diff UP_THRESHOLD: current_stage up else: # 处于中间过渡状态保持原阶段不变 current_stage stage if stage is not None else None # 5. 计数逻辑完成一个从“down” - “up” - “down”的循环计数1 if stage down and current_stage up: # 检测到从下到上的转换表示开始上升 pass elif stage up and current_stage down: # 检测到从上到下的转换表示完成了一次完整的引体向上 counter 1 print(f计数当前总数{counter}) # 在控制台也输出便于调试 # 更新阶段状态 stage current_stage except Exception as e: # 如果检测不到人体landmarks为None或者计算出错就跳过这一帧 # print(fError: {e}) # 调试时可以打开 pass核心难点与调参经验阈值THRESHOLD的设定这是整个算法最需要根据实际场景调整的部分。DOWN_THRESHOLD和UP_THRESHOLD不是固定值。它们代表什么代表手腕Y坐标 - 肩膀Y坐标的归一化差值。因为坐标是归一化的所以这个值与你的实际身高、距离摄像头的远近无关只与动作幅度有关。如何确定最好的方法是打印日志。在调试阶段在while循环里加上print(f垂直差值: {vertical_diff:.3f}, 阶段: {stage})然后你对着摄像头做几个引体向上观察在“悬挂最低点”和“下巴过杠最高点”时vertical_diff的典型值是多少。将“最低点”的值稍加一点余量作为DOWN_THRESHOLD将“最高点”的值稍减一点余量作为UP_THRESHOLD。一个常见的误区不要设得过于苛刻。因为人体在运动时会有晃动阈值设得太接近实际极值可能导致状态在阈值附近来回跳动造成误计数。适当放宽阈值利用状态机的“记忆性”只有完整的状态转换才计数是保证计数稳定的关键。4.3 结果可视化与输出逻辑判断完成后我们需要把结果直观地展示在视频画面上。# 6. 使用MediaPipe提供的工具绘制姿态骨架和关键点 if results.pose_landmarks: mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, mp_drawing.DrawingSpec(color(245,117,66), thickness2, circle_radius2), # 关键点样式 mp_drawing.DrawingSpec(color(245,66,230), thickness2, circle_radius2)) # 连线样式 # 7. 在画面上绘制计数和状态信息 # 绘制一个半透明的矩形作为文字背景 cv2.rectangle(image, (0,0), (300, 120), (245,117,16), -1) # -1表示填充矩形 # 显示计数 cv2.putText(image, REPS, (15,25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,0), 2, cv2.LINE_AA) cv2.putText(image, str(counter), (15,80), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255,255,255), 3, cv2.LINE_AA) # 显示当前阶段 cv2.putText(image, STAGE, (130,25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,0), 2, cv2.LINE_AA) cv2.putText(image, stage if stage else N/A, (130,80), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255,255,255), 3, cv2.LINE_AA) # 显示垂直差值调试用正式版可注释掉 cv2.putText(image, fDiff: {vertical_diff:.3f} if vertical_diff in locals() else Diff: N/A, (10, image.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 1, cv2.LINE_AA) # 8. 显示最终图像 cv2.imshow(Pull-up Counter, image) # 按‘q’键退出 if cv2.waitKey(10) 0xFF ord(q): break # 释放资源 cap.release() cv2.destroyAllWindows()至此一个完整的、实时的引体向上计数器就完成了运行python pull_up_counter.py对着摄像头试试看吧。5. 性能优化与高级功能扩展基础版本已经能工作但要做到“好用”我们还需要考虑更多。这里分享几个优化和扩展的方向。5.1 提升计数准确性的滤波技巧原始的关键点坐标可能存在抖动导致vertical_diff值波动进而引发状态误判。一个简单有效的改进是加入滑动平均滤波。在初始化部分加入import collections # 创建一个队列来存储最近的垂直差值 diff_history collections.deque(maxlen5) # 保存最近5帧的值 filtered_diff 0在计算完vertical_diff后进行滤波diff_history.append(vertical_diff) filtered_diff sum(diff_history) / len(diff_history) # 计算简单移动平均 # 后续的状态判断使用 filtered_diff 代替 vertical_diff if filtered_diff DOWN_THRESHOLD: current_stage down elif filtered_diff UP_THRESHOLD: current_stage up else: current_stage stage if stage is not None else None这样偶然的单帧跳动就会被平滑掉状态转换会更加稳定。5.2 支持离线视频分析与结果保存很多时候我们可能想分析一段已经录好的训练视频。修改视频源很简单但更进一步我们可以将处理后的视频保存下来方便分享或复盘。在初始化cv2.VideoCapture之后定义视频写入器# 获取原视频的帧宽、帧高和帧率 frame_width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps int(cap.get(cv2.CAP_PROP_FPS)) if fps 0: # 有些摄像头获取不到FPS给一个默认值 fps 30 # 创建VideoWriter对象用于保存处理后的视频 # ‘XVID’是编码器输出文件为‘output_pullup_counted.avi’ fourcc cv2.VideoWriter_fourcc(*XVID) out cv2.VideoWriter(output_pullup_counted.avi, fourcc, fps, (frame_width, frame_height))在主循环中将最终要显示的image帧写入文件# ... (所有处理逻辑之后imshow之前) out.write(image) # 将绘制好的帧写入视频文件 cv2.imshow(Pull-up Counter, image)程序结束后记得释放写入器cap.release() out.release() # 释放视频写入器 cv2.destroyAllWindows()5.3 动作标准度评估的初步思路一个更有挑战性也更有价值的扩展是评估动作标准度。例如检测是否“耸肩”、“摆动借力”或“下落未放直”。这需要引入更多的关键点和几何计算检测耸肩计算肩膀1112与耳朵78在垂直方向上的距离。在引体向上过程中这个距离不应显著缩短。检测身体摆动计算髋部中点2324的横向X轴移动速度或幅度。如果幅度过大可以给出“摆动借力”的提示。检测下落幅度我们已经在用vertical_diff和DOWN_THRESHOLD判断是否下放到底。可以增加一个“动作幅度不足”的警告如果vertical_diff始终小于一个更小的阈值则提示“未完全下放”。实现这些功能需要更复杂的状态管理和阈值设定但核心方法论是一致的提取关键点 - 计算几何或运动特征 - 设定逻辑规则进行判断。这为将本项目升级为一个初级的“AI健身教练”提供了可能。6. 常见问题排查与实战调试技巧在实际运行中你几乎一定会遇到一些问题。下面是我在开发和测试中遇到的典型问题及解决方法。问题现象可能原因排查与解决思路程序报错无法导入mediapipe1. 虚拟环境未激活或安装不正确。2. Python版本不兼容。1. 确认终端前有(pullup_counter)环境标识。2. 执行pip list检查mediapipe和opencv-python是否存在。3. 尝试使用Python 3.8或3.9重新创建环境。摄像头黑屏或打不开1. 摄像头被其他程序占用。2. 摄像头索引号错误。1. 关闭其他可能使用摄像头的软件微信、Zoom等。2. 尝试将cv2.VideoCapture(0)中的0改为1或2。3. 在代码开头加入cap cv2.VideoCapture(0, cv2.CAP_DSHOW)仅Windows试试。检测不到人体没有骨架绘制1. 人物离摄像头太远或光线太暗。2. 置信度阈值min_detection_confidence设得太高。3. 背景杂乱干扰模型。1. 确保人物在画面中清晰可见光线充足。2. 尝试将min_detection_confidence和min_tracking_confidence暂时调低至0.4。3. 尝试面对纯色墙壁进行测试。计数不准漏数或多计1.UP_THRESHOLD和DOWN_THRESHOLD设置不合理。2. 关键点抖动导致状态跳变。3. 动作不标准如摆动太大。1.这是最可能的原因务必使用“打印日志法”调试阈值。在循环中打印vertical_diff和stage观察动作极值点对应的数值重新设定阈值。2. 引入滑动平均滤波见5.1节平滑数据。3. 调整摄像头角度尽量保持人物正面或侧面避免严重遮挡。程序运行很卡帧率很低1. 图像分辨率太高。2.model_complexity参数设为2。3. 电脑性能不足。1. 将cv2.VideoCapture读取的分辨率降低如640x480。2. 将model_complexity设为0最快或1。3. 在主循环中可以对图像进行缩放如frame cv2.resize(frame, (640, 360))注意缩放要在pose.process之前进行且缩放后绘制骨架的坐标需要相应换算。保存的视频无法播放或花屏1. 编解码器‘XVID’不支持。2. 写入的帧尺寸与VideoWriter初始化尺寸不匹配。1. 尝试更换编解码器如‘MJPG’fourcc cv2.VideoWriter_fourcc(*MJPG)文件后缀改为.avi。2. 确保out.write(image)中的image尺寸与创建VideoWriter时指定的(frame_width, frame_height)完全一致。一个稳妥的做法是使用image.shape[1]和image.shape[0]来获取尺寸。终极调试心法可视化与日志当程序行为不符合预期时最强大的工具就是print()和cv2.putText()。把关键数据画在屏幕上就像示例代码中显示vertical_diff一样把stage、counter、甚至左右手腕的原始Y坐标都实时显示出来。这样你一眼就能看到程序“认为”的数值是什么。在控制台打印详细日志在状态改变、计数等关键节点打印信息方便你复盘程序逻辑的执行流程。录制一小段测试视频用手机录制一段自己做引体向上的视频10-15秒即可然后用程序分析这个本地视频文件。这样可以让你反复、暂停地观察程序在每一帧的处理结果是调整阈值和逻辑的黄金手段。7. 项目总结与未来展望通过这个项目我们完成了一个从实际问题出发利用现代计算机视觉库快速构建解决方案的完整流程。它麻雀虽小五脏俱全从环境配置、模型调用、数据提取、逻辑设计、到可视化交互。最重要的是它提供了一个可运行的、能够解决真实需求的代码原型。我个人在反复调试这个项目后最深的体会是在AI应用开发中数据和逻辑的质量往往比模型本身更重要。MediaPipe Pose已经提供了一个非常强大的“眼睛”但如何用好它提供的“关键点”数据设计出鲁棒的业务逻辑状态机才是项目成败的关键。这其中的阈值设定、滤波处理、异常处理都需要开发者对业务引体向上动作本身有深刻的理解并通过大量的实验和观察来打磨。这个过程是任何预训练模型都无法替代的。这个项目完全可以作为一个起点进行更多有趣的扩展多动作识别修改状态判断逻辑可以同样实现俯卧撑、深蹲、仰卧起坐的计数。你只需要重新定义“完成一次动作”所对应的关键点运动模式。部署到移动端MediaPipe本身就有Android和iOS的SDK。理论上你可以将这套逻辑移植到手机App上实现随身携带的健身计数器。云端分析与历史记录将每次训练的次数、时间戳甚至关键点数据上传到服务器可以生成长期的健身数据报表和分析图表。结合语音提示在计数时或动作不标准时用语音合成TTS库给出实时反馈体验会更像健身教练。希望这份超详细的指南和源码不仅能帮你实现一个引体向上计数器更能让你感受到用代码解决生活问题的乐趣和成就感。编程不只是冰冷的逻辑也可以是充满活力的创造。如果你在实现过程中有任何新的发现或有趣的改进也欢迎分享出来。本文还有配套的精品资源点击获取
返回列表