资讯动态

YOLO姿态估计实战:ai_gym模块拆解与关键点动作计数实现

发布时间:2026/10/17 12:09:07 来源:尧图企业网站定制
姿态估计模型跑起来之后很多人都会问一句这些关键点画在人身上除了看着酷到底能拿来做什么ultralytics.solutions里的ai_gym算是一个很直接的答案——它把姿态估计接到了健身房里自动识别俯卧撑、引体向上、深蹲这些动作并且实时计数。ai_gym.py这个子模块的完整链路是用 YOLO 姿态模型检测人体关键点再用关键点坐标计算关节角度再根据角度变化判断动作阶段最终把计数、角度、骨架绘制到画面上。这篇文章就把这整条链路从代码层面拆开讲透包括模块设计、核心 API、实际接入方法以及我实测中踩过的坑。适合已经会跑 ultralytics 基础推理、想往具体应用落地走一走的开发者。1. 模块定位与设计思路ai_gym 到底帮你做了什么1.1 solutions 包里的“即插即用”应用模块ultralytics不只是提供 YOLO 模型官方在工程里还维护了一个solutions模块里面放了一堆“模型加业务逻辑加绘图”的完整应用样板。除了ai_gym你还能看到物体计数、速度估计、安全警报这类现成方案。它们的共同特点是你不需要从零写推理、画框、统计逻辑只要传入视频帧拿回的就是处理好的画面。ai_gym在这个包里的定位很清晰针对健身房场景的运动监测。它内部组合了姿态估计模型、关节角度计算、动作计数状态机、画面标注几部分。对你来说调用层非常薄——初始化一个对象每帧丢给它一张图返回一张被画好骨架和计数结果的图。但如果你以为它只是个“黑盒玩具”那就浪费了因为这模块的代码本身就是一份很好的姿态应用教学材料把逻辑拆开讲能帮你理解这类应用的通用套路。1.2 为什么姿态估计能用来计数目标检测不行要理解ai_gym的价值得先搞清楚姿态估计和目标检测的区别。目标检测给的输出是“人的外接矩形框”它只能告诉你画面里“有一个人”至于这个人是站着、蹲着还是趴着框是表达不清楚的。而姿态估计输出的是人体的“骨架关键点”以二维坐标形式标出鼻子、肩膀、手肘、手腕、髋部、膝盖、脚踝这些位置。有了这些坐标你才能计算关节角度。打个比方检测模型告诉你“这里有个人”姿态模型告诉你“这个人的手肘现在弯曲了 45 度还是 150 度”。健身动作的本质就是关节角度随时间变化所以姿态估计天然适合做动作计数。ai_gym的核心逻辑其实就是两件事算角度、看角度变化。1.3 三层结构模型层、逻辑层、展示层读ai_gym.py的源码时我建议你把它理解成三层结构。第一层是模型层负责通过 YOLO 姿态模型拿到所有人的关键点。第二层是逻辑层根据你选择的运动类型决定关注哪几个关键点、计算哪个关节的角度、维护当前动作阶段和计数结果。第三层是展示层用Annotator在画面上画骨架、关节角度、计数文字、阶段提示。这种分层思路特别值得学。很多初学者写视觉应用习惯把推理、业务、绘图全塞在一个循环里代码一百行不到就乱成一团。ai_gym用类组织状态把模型加载、逐帧监控、姿态绘制分得清清楚楚即便你最后不用这个模块这套组织方式也几乎可以直接抄到你自己的项目里。2. 核心数据结构与关键 API 拆解姿态关键点与角度计算2.1 先搞清楚 results 里的关键点长什么样不管是读源码还是自己改代码第一步一定是理解姿态模型输出的数据结构。用 ultralytics 跑一帧推理之后结果存在results对象里。最常用的数据是results[0].keypoints.data在常见模型下它的 shape 是torch.Size([1, 17, 3])。第 1 维检测到的人数通常一次推理可能有多人第 2 维关键点数量COCO 姿态数据集是 17 个关键点第 3 维每个关键点的(x, y, confidence)前两个是像素坐标最后一个是模型对这个点的置信度范围在 0 到 1 之间。这点很关键算角度时必须用置信度做过滤。如果模型本身对某个关键点没把握conf会很低这时候还硬拿错误坐标去算角度计数值就会乱跳。实际工程里我一般只让conf大于 0.5 的关键点参与计算低于这个值的帧直接跳过计数效果比强行纠偏稳定得多。COCO 17 个关键点的索引也是要记住的核心信息索引关键点索引关键点0鼻子9左腕1左眼10右腕2右眼11左髋3左耳12右髋4右耳13左膝5左肩14右膝6右肩15左踝7左肘16右踝8右肘ai_gym这个场景主要用的就是三组点肩、肘、腕组成的手臂链髋、膝、踝组成的腿部链。俯卧撑和引体向上看手臂链深蹲看腿部链。2.2 关节角度计算的实现原理有了三个关键点坐标怎么算关节角度数学上就是求两条向量的夹角。假设我们想算肘关节角度取肩膀坐标 a、手肘坐标 b、手腕坐标 c以 b 为顶点求角 abc。实际写代码时有个容易踩的坑图像坐标系里 y 轴是向下的但这对夹角计算没影响因为我们用的是向量点积和坐标轴方向无关。真正要注意的是三点可能退化成一条线导致向量模长为零。所以计算时最好给分母加一个极小值防止除零报错。import math def calculate_angle(a, b, c): 计算三个点之间以 b 为顶点的夹角返回角度制数值 # 构造向量 ba 和 bc ba (a[0] - b[0], a[1] - b[1]) bc (c[0] - b[0], c[1] - b[1]) # 点积 dot ba[0] * bc[0] ba[1] * bc[1] # 模长加 1e-6 防止分母为 0 norm_ba math.sqrt(ba[0] ** 2 ba[1] ** 2) 1e-6 norm_bc math.sqrt(bc[0] ** 2 bc[1] ** 2) 1e-6 # 夹角的余弦值clip 到 [-1, 1] 避免 arccos 入参越界 cos_theta max(-1.0, min(1.0, dot / (norm_ba * norm_bc))) angle math.degrees(math.acos(cos_theta)) return angle这个函数是整套动作计数的基础。ai_gym内部计算角度的思路和我上面这个函数基本一致区别只在于传参时取哪几个关键点以及最后求的是肘角还是膝角。2.3 计数状态机从角度到动作次数有了角度下一步要解决“怎么算一次完整动作”。这里必须引入状态机否则计数会非常不稳定。以俯卧撑为例人撑起的时候手臂接近伸直肘角大于 120 度身体下放之后肘角变小通常小于 60 度。但实际情况里角度不会稳稳待在一个值它在阈值附近上下抖动是常态。如果只用“当前角度小于某值就加一”一次动作可能会被重复计算三五次。所以工程上会维护一个动作阶段标志只有当角度从“小于下阈值”切换到“大于上阈值”时才认为完成了一次完整动作。简化逻辑是这样的# down: 当前处于下放阶段 if angle down_threshold and not down: down True elif angle up_threshold and down: count 1 down Falseai_gym.py里常见的做法是同时维护count_aup和count_adown两个计数分别记录“进入高位状态”和“进入低位状态”的次数。这种设计在只需要动作次数时似乎多此一举但在引体向上这类动作里特别有用。引体向上的拉升和下放阶段更容易被误判分别统计能让你在调试时看清楚到底是一个动作拆成了两次还是漏了一次。2.4 reveal 关键属性和方法读源码时关注几个常用属性和方法就够了。首先是构造函数里要传的model、pose_type、line_width。model是加载好的 YOLO 姿态模型pose_type决定内部用哪组关键点、按什么逻辑计数line_width是画骨架线条的宽度调大一点在视频里更醒目。每个视频帧的入口是monitor方法。传入当前帧im0经过推理、角度计算、计数更新、绘图之后返回处理好的帧。如果你只想拿数据完全可以只调它然后不显示直接从对象上取count、angle、count_aup、count_adown这几个属性。我自己调试阈值时就是这么干的打印gym.angle比看画面直观得多。3. 实操接入把 ai_gym 接进自己的视频/摄像头流程3.1 最小可运行示例先把整套流程跑起来再谈深度定制。下面这段代码是从视频文件读取、用ai_gym处理、实时显示的标准流程import cv2 from ultralytics import YOLO from ultralytics.solutions import ai_gym # 1. 加载姿态估计模型 model YOLO(yolo11n-pose.pt) # 2. 创建健身房监测对象运动类型先选 pushup gym ai_gym.AIGym( modelmodel, pose_typepushup, line_width2 ) # 3. 打开视频 cap cv2.VideoCapture(workout.mp4) while cap.isOpened(): success, frame cap.read() if not success: break # 4. 核心调用只传一帧拿回处理后的帧 frame gym.monitor(frame) cv2.imshow(AI Gym Monitor, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()补充一个经验不同版本的 ultralytics 在solutions包里的导出名略有差异有的直接能从ultralytics.solutions里导入AIGym有的则需要from ultralytics.solutions import ai_gym再取ai_gym.AIGym。这不是什么大问题你打开本地安装目录下的solutions/__init__.py看一眼导出了什么即可。调用逻辑基本一致入口方法都叫monitor。3.2 三种常见动作的参数建议pose_type是决定行为的关键参数。工程里最常见的取值是pushup、pullup、squat三种对应的关键点链和阈值逻辑可以参考这张表运动类型关键点链判断阶段的核心角度参考阈值俯卧撑 pushup肩 - 肘 - 腕肘关节角度下放 60°撑起 120°引体向上 pullup肩 - 肘 - 腕肘关节角度 / 下巴过杠拉起时肘角 80°下放 150°深蹲 squat髋 - 膝 - 踝膝关节角度下蹲 90°站起 150°这里要特别强调表格里的阈值是参考值不是金标准。你的相机安装位置、人的动作习惯、镜头畸变都会影响实际角度曲线。比如正面拍俯卧撑肘角的数值范围和侧面拍差别很大。我自己的习惯是先跑一段自己的动作视频把gym.angle实时打印出来观察角度曲线的峰谷再确定阈值。很多人的计数不准不是代码写错而是把参考阈值当成了固定真理。3.3 把处理结果保存成视频如果想把ai_gym的输出存下来加一个VideoWriter就够了。注意要在循环外初始化循环里写帧循环结束后释放fps cap.get(cv2.CAP_PROP_FPS) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter( output.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (width, height) ) while cap.isOpened(): success, frame cap.read() if not success: break frame gym.monitor(frame) writer.write(frame) writer.release()有一点要提醒monitor内部会重新处理输入帧如果传入的帧尺寸和VideoWriter初始化尺寸不一致写出来的视频会打不开或画面变形。尽量让输入保持一致的分辨率处理完直接写回。3.4 从视频切换到摄像头把VideoCapture参数从文件路径改成摄像头索引号比如cv2.VideoCapture(0)代码逻辑不用动。但实时场景下要考虑推理耗时。姿态模型比纯目标检测要贵如果直接跑大模型会明显卡顿。我的建议是优先用yolo11n-pose.pt或yolov8n-pose.pt这类轻量模型如果还是卡可以在循环里做跳帧处理每隔一帧调一次monitor计数精度不会有太大损失画面流畅度却能好不少。4. 常见问题与排查技巧实录4.1 计数重复和漏计问题总出在状态切换上我把这个放在第一位因为这是几乎所有动作计数应用的通病。现象是明明只做了 10 个俯卧撑屏幕上却显示 15 次或者只显示 7 次。排除代码 bug 之后最常见的原因就是角度在阈值附近反复穿越。比如做了个半程俯卧撑肘角恰好卡在 100 度上下状态机可能在一帧之内来回切换一次动作就被计成了多次。解决办法是给状态切换加“死区”让下放判定和撑起判定之间留出足够间隔另外可以加一个“连续帧数”要求比如连续 3 帧都满足条件才真正切换状态if angle 50: down_frames 1 else: down_frames 0 if down_frames 3 and not down: down True down_frames 0这个思路我在自己的项目里用了很多次对消除单帧噪声非常有效。4.2 关键点置信度低算出来的角度不可信身体遮挡、镜头距离远、动作角度刁钻都会让某个关键点的置信度掉得很低。关键点坐标一旦不准后续所有角度计算就都是错的而且这种错误比漏检更隐蔽。排查时不要只看画面要把results[0].keypoints.data里的置信度打出来看看。如果持续偏低优先考虑调整机位和距离而不是靠代码硬扛。代码层面的兜底方案是关键点conf低于 0.5 的关键点不参与角度计算当一条关键点链里存在低置信点的时候宁可跳过这一帧不更新计数也算不出一个“看起来很合理但实际是垃圾”的值。4.3 多人场景下只跟踪一个人怎么办ai_gym默认的处理逻辑通常只关注画面里的一个主要目标。原因很简单它要为“一个人”维护一套计数状态如果同时处理五个人就得给每个人各维护一套状态复杂度立刻上升。如果你想做多人同时计数我建议不要直接魔改ai_gym而是把它的思路拆出来先用姿态模型得到所有人的关键点再给每个人分配一个跟踪 ID为每个 ID 维护独立的angle和count状态。这样每个目标各算各的互不干扰。工程上可以搭配 ByteTrack 这类多目标跟踪器你自己写的话只需要按检测框中心距离做最简单的帧间匹配也能跑起来。4.4 机位问题让角度失真这锅不该模型背同样一个深蹲正面拍摄时髋膝踝三点几乎在一条竖直线上膝角变化很小根本看不出来下蹲深度侧面拍摄时角度变化就非常明显。这是几何问题不是模型问题。我做动作数据采集时的经验是摄像头尽量放在人的侧面和人体的运动平面成 45 度到 90 度角。俯卧撑、深蹲这类垂直面动作侧面机位最好。如果实在只能正面拍那就别依赖绝对角度尝试用角度变化量做计数也就是看“角度相对初始值变化了多少”能缓解一部分问题。4.5 性能吃紧先优化推理再优化逻辑姿态估计的耗时大头在模型推理角度计算和绘图可以忽略不计。想提速顺序是换轻量模型 - 调小推理尺寸 - 跳帧处理 - 开多线程读帧。我个人不太建议一开始就上 TensorRT 或者半精度那是推理速度还不够时才考虑的优化手段日常调试阶段先把模型和输入尺寸定好观察帧率能不能接受再决定要不要做更深层的加速。5. 更进一步如何用 ai_gym 的思路定制自己的动作5.1 自定义动作的本质把动作拆成角度循环ai_gym并没有真正“理解”俯卧撑或者深蹲它只是用一组关节角度变化来定义动作的阶段。所以任何能表达成“A 阶段 - B 阶段 - A 阶段”循环的运动都可以套用这套计数逻辑。举个例子哑铃弯举手臂伸直时肘角接近 180 度弯举起来后肘角明显变小然后再伸直。这就是一个标准的“大角度 - 小角度 - 大角度”循环。类似地仰卧起坐可以用肩髋角度变化来做康复训练里的抬腿次数、膝关节屈伸次数本质也是同一个套路。你换一个场景真正要改的只有两件事选哪几个关键点阈值设多少。5.2 一个简单的自定义计数器骨架如果你要做的动作不太复杂可以不用完整读透ai_gym的每个细节直接照着状态机写一个精简版。下面是一个最小骨架class CustomActionCounter: def __init__(self, threshold_down60, threshold_up120): self.threshold_down threshold_down self.threshold_up threshold_up self.count 0 self.is_down False def update(self, current_angle): # 进入下放阶段 if current_angle self.threshold_down and not self.is_down: self.is_down True # 回到撑起阶段完成一次计数 elif current_angle self.threshold_up and self.is_down: self.count 1 self.is_down False return self.count你需要自己补一个compute_angle函数从关键点数组里取对应索引算出角度。建议把关键点索引做成常量字典比如JOINTS { shoulder: 6, elbow: 8, wrist: 10, }这样代码可读性会好很多也方便以后换动作。5.3 用角度曲线来确定阈值而不是拍脑袋自定义动作最容易被忽略的环节是阈值标定。很多人拿到threshold_down60、threshold_up120就直接跑结果发现计数值和自己的主观感受对不上。我的建议是先录一段你自己的标准动作视频离线跑一遍姿态模型把每一帧的目标关节角度保存下来然后画出一条角度随时间变化的曲线。你会发现曲线里有规律的波峰和波谷波谷对应动作的最低点角度波峰对应最高点角度。阈值就取在波峰和波谷之间的中间位置。比如波谷是 50 度、波峰是 140 度那下放阈值可以设 70、上举阈值设 110。这样设置即使动作速度有快有慢计数也能保持稳定。5.4 把 ai_gym 当作模板而不是黑盒读完成套代码之后你会发现ai_gym里最值得带走的不是那些类名和属性名而是“模型给出关键点逻辑层算角度状态机判阶段绘图层做展示”这套工程范式。我自己后来做关节活动范围检测时就是照着这个思路重新组织代码推理部分用 YOLO 姿态模型数据分析部分自己写绘图部分复用Annotator。核心逻辑没变换的只是阈值和应用场景。我还建议你在自己的项目里把“计算逻辑”和“绘图逻辑”分开不要全都塞在同一个函数里。实测下来这样调试效率高很多改一个业务条件不用来回翻绘图代码甚至你可以直接去掉绘图只在后台跑计数性能会更好。5.5 这个思路还能延展到不少场景健身房只是最直接的应用场景。把同一套“关键点 - 关节角度 - 状态机”的思路往下推可以做康复训练时的关节活动度记录也可以做久坐场景下的起身次数提醒甚至连简单的人体异常姿态报警都可以用类似逻辑实现。核心都是先想清楚一件事你要监测的那个动作是靠哪几个关节、哪个角度的变化来定义的。想清楚这一点代码只是抄作业的问题。我个人在大量实测中的体会是ai_gym最值钱的地方不在那几行计数代码而是把姿态估计应用工程化的完整思路。自己动手改造一遍之后你会对“模型推理结果怎么变成业务数据”有更直觉的理解。最后再分享一个实操技巧别直接拿摄像头实时调阈值先把视频录下来离线把角度曲线打印出来看清楚再上实时。调参的速度会快非常多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑