新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Python姿态估计的引体向上自动计数算法实现

发布时间:2026/10/2 3:15:45来源:尧图网络
基于Python姿态估计的引体向上自动计数算法实现
简介基于Python的引体向上计数算法实现配套完整源码与示例视频面向具备一定Python基础、希望学习计算机视觉姿态检测与运动轨迹分析的学生或开发者。算法通过人体关键点检测自动识别动作并依据轨迹特征统计次数可直接运行验证也可按需调整阈值与逻辑。压缩包共8个文件核心为2个Python脚本负责模型加载与关键点推断其余包含mp4示例演示、说明文档及备份文件整体约47.26MB。目前已有34人学习下载适合作为计算机科学、应用数学、电子工程等专业的课程实践或毕业设计参考。通过该资源可掌握从视频输入到动作计数的完整处理流程理解各功能模块的代码组织方式并基于实际视频观察识别效果为进一步优化算法提供良好起点。1. 引体向上计数不靠手感用 Python 把训练视频变成自动计数算法引体向上计数是力量训练里最容易被凑数的项目。我以前练完一组打开手机录像一帧一帧数数到一半就记混后来想让程序自动数第一反应是检测下巴过杠结果在实际视频里频频翻车。这份资源的核心思路完全不同用 Python 姿态估计提取肩、肘、腕关键点把连续视频帧转成肘关节角度曲线再用状态机判断是否完成一次循环最后在视频上实时叠加上计数结果。配套的示例视频可以直接拿来验证计数效果不需要额外准备数据。它适合想量化训练量的人、给体能测试做自动化记录的人也适合拿来当 Python 入门阶段的运动识别课程设计或者把同一个计数方法迁移到深蹲、俯卧撑、杠铃弯举等重复动作上。2. 算法选型与判定原理为什么看肘关节角度比看下巴过杠更稳引体向上计数看起来简单但真正写成算法难点不在「识别杠」而在「识别动作阶段」。这一章先讲清楚计数需求怎么拆解再对比几种常见方案的可用性最后给出基于关键点角度的状态机原理。2.1 拆解需求一次标准引体向上包含哪些可测特征标准引体向上从悬垂开始双臂基本伸直、身体自然下沉然后发力将身体拉起直到下巴超过杠面再下放回双臂接近伸直的位置。以程序视角看这里有两个关键的几何特征肘关节角度悬垂时接近 160°~180°拉起后明显变小能到 60°~90°。躯干相对杠的高度下巴或肩部在竖直方向上的位置变化明显。很多初学者会把逻辑做成「检测到下巴高于某个像素坐标就计数」。这个思路在真实视频里非常不稳定因为下巴受头部姿态影响太大低头、仰头、转头都会让下巴在图像里移动几十像素而且人脸检测模型在远距离运动场景下经常丢框。相反肩、肘、腕这三个点由姿态估计模型统一输出坐标稳定性远好于下巴检测肘关节角度又能直接反映手臂是否弯到位所以我最终选它作为计数主特征。2.2 备选方案对比肤色分割、背景差分 vs 姿态关键点有人会先想到肤色检测把皮肤区域找出来取区域的最高点变化来判动作。在健身房这类复杂背景里肤色阈值非常难调侧面拍摄时手和脸的肤色接近光线一变就破功。用 OpenCV 做背景差分也一样它要求镜头固定、画面里不能有人走动室内反光、窗外车流都能让差分结果变花。这些方法在实验室环境能跑放到真实训练视频里可用性低。姿态关键点的优势是MediaPipe Pose 这类模型已经在大规模人体数据上训练过对单人前景能稳定输出整组关键点坐标不依赖背景是否静态。它也有代价每帧多一轮神经网络推理普通 CPU 播放视频时只能跑 10~20 帧每秒但计数任务不需要看全所有帧丢几帧不影响最终计次。这份源码里用的就是 MediaPipe Pose 加上 OpenCV 做视频读写。2.3 关键点定义与角度计算用肩、肘、腕三点算夹角在 MediaPipe Pose 的 33 个关键点里我们只需要左右各三个左肩(11)、左肘(13)、左腕(15)右肩(12)、右肘(14)、右腕(16)。注意这些坐标是归一化的取值范围 0~1要乘上视频宽高才能得到像素坐标。肘关节角度的计算用的是标准三点夹角法肘所在点是顶点两边的向量分别指向肩和腕import math def angle_between(a, b, c): 计算三点夹角b 是顶点返回角度值域 0~180 ab [a[0] - b[0], a[1] - b[1]] cb [c[0] - b[0], c[1] - b[1]] dot ab[0] * cb[0] ab[1] * cb[1] norm_ab math.hypot(ab[0], ab[1]) norm_cb math.hypot(cb[0], cb[1]) cos_angle dot / (norm_ab * norm_cb 1e-6) return math.degrees(math.acos(max(-1.0, min(1.0, cos_angle))))这个函数本身没有黑匣子就是余弦定理。加上1e-6是为了防止两个向量长度恰好为零时针点重合导致除零报错。acos的输入被强制限制在 -1 到 1避免浮点误差产生 NaN。运行逻辑上肩到肘向量与腕到肘向量的夹角越小说明手臂越弯曲也就是引体向上拉得越高。2.4 从角度到状态机up/down 两状态加回滞仅凭单帧角度不可能稳定计数。如果按「角度小于 90 度就计一次大于 170 度就复位」来做在动作顶点附近轻微抖动就会重复计数。正确做法是设计一个两状态机状态只允许按固定方向迁移down悬垂状态。连续 N 帧肘角小于 up_angle才转入 up。up拉起状态。连续 N 帧肘角大于 down_angle才转入 down并计一次。这里的 N 就是hold_frames相当于给状态加了「确认期」单帧噪声不会触发状态迁移。up 与 down 的判定阈值刻意拉开距离这叫回滞。回滞的意义在于动作不会在临界点附近来回横跳只有真正完成「从下到上、再从上到下」的完整相位变化才会计数。状态机里的计数时机放在 up 转 down 的确认时刻不是拉起那一下这样能挡住做了一半就停在顶点的动作。2.5 阈值怎么初定从动作生理范围反推初始阈值不用猜直接从人体活动范围反推。悬垂时肘角通常在 160°~180°但侧拍视频里由于手臂不是完全与镜头平面平行实测经常只有 145°~155°所以 down_angle 建议用 150°。拉起时肘角根据握距不同在 60°~100° 之间我一般先用 75° 作为 up_angle。这两个数值不是通杀值只是基线。不同人的肩臂比例、握距、身体摆动幅度都会影响实测角度真正需要调的就是这两个角度模型检测置信度反而不太需要动。3. 源码包结构与核心实现从读取视频到输出计次结果这一章进入可复现阶段。拿到压缩包以后先把文件结构搞清楚再配环境然后我把整个主流程拆成「读视频 → 关键点提取 → 状态机计数 → 标注输出」四段每段都能独立验证。3.1 资源包文件结构压缩包内通常包含以下内容具体文件名以你实际拿到的为准文件作用pullup_counter.py主程序读取视频、计数、标注并输出angle_utils.py角度计算与角度平滑工具函数requirements.txtPython 依赖列表demo_video.mp4用于测试计数效果的示例视频output/脚本运行后保存结果视频的目录默认自动创建主程序入口是pullup_counter.py调用了angle_utils.py里的辅助函数。示例视频主要用来跑通流程验证阈值是否合适不要拿它当算法输出质量的绝对标准真实训练视频的机位和环境变化更大。3.2 环境准备Python 版本与依赖安装建议用 64 位 Python 3.8 或 3.9 运行。MediaPipe 在不同平台上的 wheel 支持范围不一样如果你在 Python 3.11 或 3.12 上安装失败换回 3.8/3.9 通常能解决。依赖包括opencv-python、mediapipe、numpy。用虚拟环境隔离是最稳妥的做法免得污染全局环境。mkdir pullup cd pullup python -m venv venv # Windows 下用 venv\Scripts\activate source venv/bin/activate pip install -r requirements.txt说明一下requirements.txt里面的版本号我建议不要直接全锁定。opencv-python装最新没问题numpy用 pip 自动解析版本即可。MediaPipe 首次运行时会自动下载姿态模型确保当前网络能访问正常的 Python 包源和模型缓存地址。简单判断是否成功在 Python 里执行import mediapipe没有报错就说明基础环境好了。3.3 主循环读取视频、抽帧、姿态推断视频处理的主循环用 OpenCV 的VideoCapture逐帧读取转成 RGB 后交给 MediaPipe 处理。MediaPipe 的 Pose 模型本身输入要求 RGB 顺序OpenCV 读出来的是 BGR必须做一次cvtColor否则关键点坐标会整体偏移。import cv2 import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose( static_image_modeFalse, model_complexity1, smooth_landmarksTrue, min_detection_confidence0.5, min_tracking_confidence0.5 ) cap cv2.VideoCapture(demo_video.mp4) fps cap.get(cv2.CAP_PROP_FPS) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) frame_height, frame_width frame.shape[:2] if results.pose_landmarks: lm results.pose_landmarks.landmark left_shoulder [lm[11].x * frame_width, lm[11].y * frame_height] left_elbow [lm[13].x * frame_width, lm[13].y * frame_height] left_wrist [lm[15].x * frame_width, lm[15].y * frame_height]这段代码的逻辑其实只有三步读帧、推理、取关键点。model_complexity1表示使用中等复杂度模型速度与精度相对均衡。smooth_landmarksTrue会对关键点做时间维度的平滑过滤减少抖动。这里不要盲目调低min_detection_confidence低于 0.3 后人脸或肢体误检率会显著上升计数反而乱。3.4 计数状态机核心计数类状态机是本项目最值得自己动手敲一遍的部分。它不复杂但状态迁移的写法直接影响计数是否稳定。下面这个类单独抽出来方便你在别的项目里复用class PullUpCounter: def __init__(self, up_angle75.0, down_angle150.0, hold_frames3): self.up_angle up_angle self.down_angle down_angle self.hold_frames hold_frames self.state down self.count 0 self._hold 0 def update(self, elbow_angle): 输入当前帧肘角返回当前总计数 if self.state down: if elbow_angle self.up_angle: self._hold 1 if self._hold self.hold_frames: self.state up self._hold 0 else: self._hold 0 elif self.state up: if elbow_angle self.down_angle: self._hold 1 if self._hold self.hold_frames: self.count 1 self.state down self._hold 0 else: self._hold 0 return self.count逻辑重点从 down 到 up 只改变状态不计数从 up 回到 down 时才count 1。这是为了确保一次完整循环真正结束避免「拉上去悬停一会儿」也被记录。_hold是连续满足条件的帧数计数器一旦出现一帧不满足就清零。hold_frames3的意思是在 30fps 视频里动作大约要在临界点附近保持 3 帧才确认切换这个值对常见视频足够。如果视频只有 15fps建议把这个值调成 2否则真实下放动作只要 1~2 帧就可能被漏掉。3.5 标注与输出把结果画在视频上计数逻辑跑完后要把结果可视化这样对账时一眼就能看出哪一帧被计了数、哪一帧没有。我习惯在画面左上角画一个大的计数框同时在肘部位置画出角度值。if results.pose_landmarks: left_angle angle_between(left_shoulder, left_elbow, left_wrist) right_angle angle_between(right_shoulder, right_elbow, right_wrist) elbow_angle min(left_angle, right_angle) count counter.update(elbow_angle) cv2.putText(frame, fCount: {count}, (30, 90), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 3) cv2.putText(frame, fAngle: {elbow_angle:.1f}, (30, 150), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 255), 2)取min(left_angle, right_angle)的理由引体向上过程中两侧手臂不可能完全对称其中一侧先到位是常态。取更弯曲的一侧作为判定依据能覆盖「一侧到位、另一侧差一点」的场景。如果只看固定一侧遇到拍摄角度让某侧关键点经常被遮挡计数就会断掉。这里有一个隐藏选择有些动作要求两臂都到位那就改成取两个角度的平均值具体取决于你要宽松计数还是严格计数。4. 用示例视频调参数阈值区间、验证方法和输出检查算法不是一次跑通就完事姿态估计算出的角度和人眼看到的角度存在偏差必须用示例视频闭环验证一遍。这一章教你如何系统调参而不是靠肉眼看着视频临时改数字。4.1 第一步先人工数出标准答案调参最忌讳上来就跑程序。先把示例视频完整看一遍数出你认为正确的次数记为expected_count。有条件的话再看一遍重点观察哪些动作「算不算准」有争议下巴刚过杠算不算半程就下降算不算。这一步决定你后面调参的方向因为很多偏差不是算法算错而是人对标准的定义不一样。把人工计数结果写在输出文件的第一行这句话听起来多余但实际排查时你会感谢自己记了这笔账。4.2 参数表与推荐区间主程序支持命令行参数通过调参可以不改代码直接跑不同组合。核心参数含义如下参数含义起始值调整方向--up-angle判定进入拉起状态的角度阈值75°计数偏多则调低偏少则调高--down-angle判定回到下放状态的角度阈值150°计数偏多则调高偏少则调低--hold-frames连续满足条件的帧数3角度抖动大就调大到 5--smooth-window角度平滑窗口大小3关键点抖动明显时调到 5~7--min-detection姿态检测置信度0.5检测不到人时调低到 0.4调整方向有一个通用逻辑所有误计都来自「状态切换得太容易」。如果示例视频里出现半程摆动被计数优先调大hold_frames或拉大 up/down 阈值的间距如果完整动作被漏计优先调小hold_frames而不是贸然改变角度阈值。4.3 运行示例视频的命令从终端进入项目目录激活虚拟环境后用下面这组命令跑通全流程python pullup_counter.py \ --video demo_video.mp4 \ --up-angle 75 \ --down-angle 150 \ --hold-frames 3 \ --output output/demo_result.mp4参数之间不要混用单位角度全是度不是弧度。output目录不存在时程序会自动创建。运行完成后对比输出视频里的计数数字和人工计数值是否一致。如果一致继续测下一个新视频如果不一致进入下一步。4.4 用逐帧状态日志定位误判点只对比总数不够还要知道误判发生在哪个时间点。打开源码里的--verbose开关程序会在状态切换时打印一行日志帧号、当前角度、切换前状态、切换后状态、累计计数。把日志输出到文本文件再对照人工计数结果逐段核对。python pullup_counter.py \ --video demo_video.mp4 \ --verbose count_log.txt日志里每一行都表示一次状态迁移。如果某次状态从 up 转 down 打印了但对应视频位置上动作明显没放到底说明down_angle定得太低导致下放途中角度已经超过阈值。反之如果视频里动作已经到底却没打印状态迁移说明down_angle定得过高程序认为还没复位。这种对账方式比反复重新播放输出视频快得多。4.5 多计与漏计的典型方向多计通常来源于「下降不到位就再次拉起」。回滞距离不够动作在临界区抖动状态机来回切换。解决办法是拉大 up 和 down 的间距比如 up_angle 降到 70、down_angle 升到 155。漏计通常来源于两个原因一是起拉速度太快满足 up 条件的帧数不够hold_frames需要把 hold 值调成 2二是动作顶点肘角仍然大于 up_angle比如宽握距引体向上时手臂弯曲角度偏大需要把 up_angle 上调到 85 左右。调参顺序永远是先看状态切换日志再结合视频确认最后改参数。5. 避坑与排查实测里最容易翻车的五个真实问题这一章全是排查经验每条都是真实踩过的坑。按「现象 → 原因 → 解决」记录你可以直接对照自己的现象找答案。5.1 计数在杠下摆动时乱跳现象人在杠下前后摆荡没有明显拉起动作计数却自动加一。原因摆荡时身体重心移动姿态估计输出的肩、肘、腕位置发生整体偏移肘角在某个瞬间低于 up_angle又在下落瞬间高于 down_angle状态机被摆荡骗过。解决为状态切换增加「最低持续时间」限制也就是调大hold_frames从 3 调到 5。如果仍然乱跳手动在画面中框一条最低高度线肩部 y 坐标低于该线时强制不允许状态切换。后面这个方案对「荡起来借力」的引体向上尤其有效。5.2 手机前置摄像头视频左右坐标翻转现象同样的动作用后置摄像头正常用前置摄像头拍的视频计数严重偏少且左右手角度差异变大。原因前置摄像头默认镜像输出MediaPipe 给的关键点坐标是镜像后的实际左右手对应的 landmark 索引等于被交换了。程序里如果没有做镜像检测固定取「左臂角度」就会取到画面右侧的物理手两者弯曲程度经常不一致。解决直接不依赖左右标签取min(left_angle, right_angle)这样镜像翻转不会造成实质影响。如果你要严格按同一侧判定就在程序里加一个--flip参数做一次水平翻转后再送入姿态模型或者交换左右 keypoint 取用逻辑。5.3 肘角在顶点附近来回抖现象视频画面稳定但打印出来的角度值在临界值附近 3°~5° 范围内反复横跳导致状态确认被反复清零。原因姿态估计的 landmark 本身有微小随机噪声即使画面静止肩或腕的坐标也会有 1~2 个像素的波动。角度函数对坐标变化敏感尤其是三点接近共线时微小坐标抖动会被放大成角度抖动。解决对角度序列做滑动平均。常见做法是在angle_utils.py里维护一个长度为 3~5 的队列每帧取队列平均值作为当前角度。更简单的方法是在状态机之前只更新每 2 帧计算一次角度。这个平滑操作只影响状态切换的瞬时值不会把真实动作拉变形。5.4 下降很快的视频漏计现象同样是标准引体向上慢速动作计数正常快速离心下放时少计或不计。原因下放速度太快实际肘角从 80° 到 160° 可能只用了 2~3 帧。如果持帧数还是 3程序还没来得及确认 up 转 down状态就被重置了。解决把hold_frames调成 1 或 2视频帧率越低越要调低。同时检查down_angle是否设置过高如果下放终点实测只有 140°阈值却设 150°就会永远无法触发复位需要把阈值降回 145°。5.5 画面里出现第二个人时计数错乱现象单人训练时没问题有人从背景路过计数突然增加或停止。原因MediaPipe Pose 默认返回画面中最显著的一人如果路过的人距离镜头更近显著度高关键点会瞬间从训练者切换到路人。解决先用results.pose_landmarks拿到当前人框的坐标计算相邻两帧肩部中心点的位移。如果位移超过画面宽度的 30%判定为跟踪对象切换暂停计数并重置状态机直到关键点回到合理位置。这个「跳变检测」逻辑非常朴素但对单人计数场景足够实用。6. 进阶用法用相位时间曲线做动作质量分析计数只是开始运动训练真正有价值的是动作质量。把状态机的状态切换时间记录下来就能统计出每一次引体向上的上升相位时长、下降相位时长以及总耗时这些数据能暴露动作是否借力、是否节奏失控。先扩展状态机记录状态发生的时间戳。比如 down 转 up 时写入self.up_start_timeup 转 down 时写入self.up_end_time则up_duration up_end_time - up_start_time。把这个值收集到列表里运行完视频后导出 CSV配合 matplotlib 画一张柱状图就能直观看到哪一次动作过于仓促import matplotlib.pyplot as plt durations [0.8, 0.9, 0.7, 1.2, 0.6, 1.5] plt.bar(range(len(durations)), durations, color#4C72B0) plt.axhline(ysum(durations) / len(durations), colorblack, linestyle--) plt.xlabel(Rep Number) plt.ylabel(Up Phase Duration (s)) plt.show()图表里黑色虚线是平均上升用时。如果某几次明显低于平均值说明那几组动作可能靠摆腿或爆发借力完成。慢速离心训练里下降相位时间更长这个指标也能反向验证训练强度。还有一个更实用的进阶技巧半程动作过滤。如果目标动作要求下巴过杠但状态机只靠肘角判断很容易放过半程。可以给程序加一个手动标注机制视频第一帧用鼠标点击杠的位置得到杠的像素坐标然后在后续帧计算下巴或鼻尖关键点的 y 坐标只有 y 坐标高于杠坐标时才允许 up 状态生效。改动量很小但对「标准度」的判定价值极大。我在自己的项目里加了这个逻辑后原先被计进去的借力半程动作基本消失。从那以后我每次拿到新的训练视频都强制自己先把人工计次结果写在表格第一行再跑脚本对比最后看状态切换日志。这套流程看起来多花三分钟实际上能筛掉绝大多数算法问题。引体向上计数这个项目代码本身不算难难的是让计数结果在任何机位下都稳定。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

六款常用工具断网离线能力实测与分层解析 2026/10/2 5:51:43

六款常用工具断网离线能力实测与分层解析

1. 项目概述:当网络突然消失,你手里的工具还剩多少真实战斗力?“断网之后,六款工具还剩什么”——这个标题乍看像一句调侃,实则直击IT从业者、运维工程师、开发人员甚至普通办公族最真实的日常痛点。我做过十年一线技术…

阅读更多 →
WeKnora实战:基于RAG的企业知识库搭建与调优指南 2026/10/2 5:51:43

WeKnora实战:基于RAG的企业知识库搭建与调优指南

上个月帮团队做内部知识库选型的时候,我把 WeKnora 装到一台 Windows 11 笔记本上试了两周。说实话,刚开始我对这种“大厂开源、号称 RAG 知识库”的项目已经有点麻了,大多数都是把 LangChain 包一层壳,传个 PDF 进去问两句话就露…

阅读更多 →
开源物联网平台选型实战:设备接入、数据引擎与业务扩展 2026/10/2 5:51:43

开源物联网平台选型实战:设备接入、数据引擎与业务扩展

1. 开源物联网平台:不是“搭个Web界面连几台ESP32”就叫平台你搜“开源物联网平台”,首页跳出来的可能是GitHub上星标500的某个Python脚本,也可能是某位大学生毕设里用VueNode.js写的带登录页的控制面板——但这些离真正能用在产线、能扛住百…

阅读更多 →
MySQL面试题深度解析:从慢查询到索引、事务与锁的实战指南 2026/10/2 5:51:43

MySQL面试题深度解析:从慢查询到索引、事务与锁的实战指南

简介:这是一份面向后端开发求职者与在校学生的 MySQL 面试知识点总结文档,围绕数据库原理与索引机制展开,帮助读者系统梳理高频考点、补齐知识盲区。内容涵盖关系型与非关系型数据库的区别、一条 SQL 语句从连接器到执行器的完整执行流程、索…

阅读更多 →
PL0编译器扩充实战:从for循环到数组的完整改造 2026/10/2 5:51:43

PL0编译器扩充实战:从for循环到数组的完整改造

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
开源物联网平台选型与高可用部署实战指南 2026/10/2 5:51:37

开源物联网平台选型与高可用部署实战指南

1. 什么是真正能落地的开源物联网平台“开源物联网平台”这六个字,最近两年在技术社区、高校实验室和中小硬件创业团队里出现频率极高,但很多人第一次听到时,下意识反应是:这不就是个带Web界面的MQTT服务器?或者——是…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉