资讯动态

基于YOLO姿态估计的健身动作智能计数系统实战

发布时间:2026/10/22 9:31:15 来源:尧图企业网站定制
从朋友让我帮他做一个“自动数俯卧撑”的小工具开始这个项目前前后后折腾了将近一个月。一开始我也以为这不就是“看着视频里人的轮廓变高变低”嘛但真正落到代码和实测之后才发现把一个健身动作拆成“可以量化的数字”里面全是细节。最后我选了 YOLO 系列姿态估计作为核心方案基于它构建了一套健身动作智能计数系统能同时覆盖俯卧撑和引体向上两种动作。这篇博文就把整个实战过程完整记录下来包括方案选型、数据处理、模型训练、动作判定、实时部署和一堆踩过的坑给同样想用视觉技术做运动计数、动作分析的朋友一个可以直接抄作业的参考。1. 项目需求拆解为什么偏要用姿态估计做计数1.1 智能计数的核心逻辑健身动作计数这件事本质上是“把连续的视频帧转换成离散的次数”。人的动作再复杂在物理上也可以抽象成一组关键点的空间位置随时间的变化。俯卧撑就是躯干下沉再撑起引体向上就是身体被拉上去再放下来。只要我们能稳定追踪到肩膀、手肘、手腕、髋、膝等关键点的坐标再通过角度、距离、相对高度这些几何量判断动作所处的阶段计数就是水到渠成的事。这里有两个关键点第一要知道人的身体部位在哪里也就是目标检测第二要知道这些部位之间的连接关系也就是关键点回归。YOLO系列姿态估计恰好把这两件事合并成一次推理输出目标框外加每个关键点的坐标和置信度比“先检测人再单独跑关键点模型”的两段式方案省掉一半时间对实时计数来说特别友好。1.2 技术选型对比视觉方案与传统方案的差距在做技术选型时我认真比较过几种常见路线方案类型优点缺点适合场景穿戴式传感器/加速度计精度高、不受遮挡影响需要额外硬件、用户嫌麻烦专业运动研究普通目标检测规则实现简单没有关节信息动作阶段难以细分只数人头、检测动作开始通用姿态估计框架关键点丰富、准确速度慢、部署成本高离线分析YOLO系列姿态估计速度快、关键点与检测一体、工程生态成熟对遮挡和极端视角仍敏感实时动作计数实测下来YOLO系列姿态估计的推理速度比其他通用姿态估计框架快一倍以上在普通独立显卡上即使不做过多的优化也能跑到几十毫秒一帧完全满足摄像头实时计数需求。2. 数据准备与关键点定义2.1 俯卧撑和引体向上的关键点建模训练一个能用的姿态估计模型第一步是明确关键点语义。官方姿态估计模型通常输出人体的多个关键点比如鼻子、眼睛、耳朵、双肩、双肘、双腕、双髋、双膝、双踝等。我们在计数任务中真正关心的子集是动作核心关键点作用俯卧撑肩膀、手肘、手腕、髋部、膝盖、脚踝判断肘关节角度、躯干是否平直、身体离地高度引体向上鼻子、肩膀、手肘、手腕、髋部、膝盖、脚踝判断下巴/鼻子是否超过横杠高度、手臂是否伸直实际标注时我采用了侧视角拍摄为主因为侧面对算法最友好。俯卧撑在侧面能看到手肘明显弯曲角度引体向上在侧面也能看到下巴过杠瞬间。我自己采集了一部分模拟数据同时补充了公开数据集里包含运动场景的图像最终整理出一份包含约两万张图像的数据集。标注工具用的是常见的开源标注工具每个关键点记录归一化坐标和可见性标志即关键点编号, x, y, 可见性这里有一个很重要的经验可见性标志不要随便填。关键点被身体遮挡时标注为不可见而不是强行标一个猜测坐标。模型训练初期如果可见性标注混乱推理阶段输出的关键点置信度会非常不稳定计数也就无从谈起。2.2 标注规范与数据增强标注规范直接影响模型效果。我的标注规范是四句话侧视角为主正面和斜侧面作为补充。所有关键点严格对应解剖学位置手肘不标在手臂外侧膝盖不标在裤腿褶皱处。遮挡时标为不可见宁可缺失不预测。每张图像至少保证双肩、双肘、双腕、双髋可见否则不进入训练集。数据增强方面我用了旋转、镜像翻转、随机缩放、亮度扰动和随机遮挡。这里特别注意俯卧撑和引体向上本身有上下方向语义不能做垂直翻转否则模型会混淆支撑和悬挂动作。随机遮挡用来模拟身体部分被器械或镜头边缘挡住的情况能明显提升模型在真实场景下的鲁棒性。我实际使用中发现增强比例控制在适中范围比较稳旋转角度在正负15度以内缩放比例在正负20%以内亮度扰动按原图灰度的10%左右。增强过猛会造成模型在合成分布上训练真实场景反而掉点。3. 模型训练与调优3.1 模型选择与输入设置YOLO系列姿态估计的模型体量有很多档从轻量级到高精度级都有。我的选型原则是实时计数对延迟敏感优先选轻量级和次轻量级。轻量级模型在普通独立显卡上速度很快我的笔记本无外部供电环境都能跑实时次轻量级精度更高适合对计数准确率更挑剔的场景。输入尺寸我选的 640 乘 640。这个尺寸是速度与精度的较好平衡点。如果你希望细节更好用 768 或 1024但推理时间会明显上涨。摄像头画面本身是横屏直接缩放会变形吗不会YOLO系列姿态估计会把图像保持比例后填充到目标尺寸推理后再把坐标映射回原图。所以代码里不要忘记做坐标的缩放回去处理否则画骨架时会错位。3.2 训练参数和优化技巧训练时我先把预训练权重加载进来让它基于通用姿态估计能力做微调。直接用随机权重从头训练两万张图像是不够的效果会差很多。下面是几个关键参数参数设定值说明输入尺寸640x640平衡速度与精度批次大小16根据显存调整越小越容易收敛但不稳训练轮数150用早停机制监控验证集损失初始学习率0.001配合余弦退火降学习率优化器AdamW比SGD收敛更快关键点回归更稳关键点损失权重适当加大关键点回归是本任务核心训练结果不能只看关键点精度我额外写了一个评估脚本直接统计模型输出在真实视频上触发计数动作的成功率。这个比单个关键点的像素误差更有实际意义。实测下来轻量级模型训练到后期验证集损失不再下降时单帧推理时间在显卡上约二三十毫秒精确率已经足够完成俯卧撑和引体向上的计数。4. 计数算法从关键点到动作判定4.1 俯卧撑计数逻辑有了姿势关键点之后最核心的问题就是“怎么算一次”。俯卧撑的标准动作可以定义为起始时手臂伸直身体呈平板状下降时肘关节弯曲到接近90度胸部靠近地面撑起时手臂重新伸直。从视觉上看最稳定的判断信号是肘关节角度。我定义了一个骨架角度计算函数输入三个关键点坐标输出它们形成的角度import math def compute_angle(point_a, point_b, point_c): # point_b 是角点例如手肘 ab_x point_a[0] - point_b[0] ab_y point_a[1] - point_b[1] cb_x point_c[0] - point_b[0] cb_y point_c[1] - point_b[1] dot ab_x * cb_x ab_y * cb_y norm_ab math.hypot(ab_x, ab_y) norm_cb math.hypot(cb_x, cb_y) if norm_ab 0 or norm_cb 0: return 0.0 cos_angle max(-1.0, min(1.0, dot / (norm_ab * norm_cb))) angle math.degrees(math.acos(cos_angle)) return angle俯卧撑时以肩膀为原点手腕、手肘一起算肘关节角度手肘-肩膀-手腕还是手腕-手肘-肩膀这里取决于实际输出。我用的核心角度是肩-肘-腕三点构成的肘角。支撑状态下肘角接近180度撑起时也接近180度下降到最低点时通常小于90度。但光靠一个角度还不够因为人可能半程发力肘角只到120度就撑起。为了增强鲁棒性我又加入了两个辅助条件肩部与髋部的连线是否保持平直防止塌腰或撅屁股。手肘位置相对于肩膀的垂直距离变化。当肘角小于90度且躯干倾斜角度小于设定容差时判定为下降到位当肘角大于150度时判定为上升到位。4.2 引体向上计数逻辑引体向上的判定和俯卧撑不同。引体向上的核心是“下巴过杠”。但直接用下巴检测不稳定因为下巴在侧视角经常被遮挡或显示不清晰。我在实际项目中改用两个替代信号的组合鼻子或头部关键点的纵坐标代表头部的最高位置。手腕关键点的纵坐标代表手握杠的位置。理论上引体向上拉起来时头部会明显高于手腕。因此我定义“下巴过杠”的判定为鼻子关键点纵坐标小于手腕关键点纵坐标且竖直差距超过某个像素阈值。又因为不同人的身高和手臂长度不同这个阈值不能是固定的像素值我会用肩宽或躯干长度做归一化。引体向上的完整状态机是初始 hanging手臂伸直当头部拉起超过手腕且肘角小于某个角度进入 up 状态随后头部下降到重新低于手腕回到 hanging 状态计数器加一。4.3 阈值判定与防抖动阈值的选取是整个系统最容易出问题的地方。阈值太小会把半程动作误判为完整动作阈值太大又会漏计。我采用的方案是滞回比较而不是单阈值。例如俯卧撑定义 down 触发阈值是90度up 触发阈值是150度。动作进入 down 状态后必须肘角小于90度才算有效动作回到 up 状态前肘角必须大于150度才算伸直。中间90度到150度这段是滞回区不产生状态切换。这样即使用户在最低点有些微抖动也不会来回计数。我还对角度做了平滑处理避免单帧关键点跳变引起误判smooth_angle alpha * raw_angle (1 - alpha) * smooth_anglealpha 取0.3到0.4之间。平滑之后动作状态切换的曲线很干净实测基本不会出现连续帧抖动导致的重复计数。5. 实时推理与系统集成5.1 摄像头帧处理流程整个实时系统的流程是这样的从摄像头读取一帧画面。将画面缩放到模型输入尺寸。执行 YOLO 系列姿态估计推理获得人体框和关键点。将关键点坐标映射回原图尺寸。根据模式俯卧撑/引体向上计算角度和位置关系。更新动作状态机必要时计数器加一。在原图上绘制骨架、关键点和计数值显示出来。摄像头采集和模型推理要避免放在同一个循环里同步等待。我用了双线程加队列的方案一个线程持续采集一个线程做推理。这样就算某帧推理偶尔超时也不会导致画面卡死或延迟越积越大。5.2 计数的流式状态机计数逻辑我单独封装成一个计数器类里面维护动作状态。这里贴一段伪代码完整表达了状态机的思路class ActionCounter: def __init__(self, mode): self.mode mode self.state up if mode pushup else hanging self.count 0 self.smooth_angle 180.0 if mode pushup else 0.0 def update(self, keypoints, frame_height): # 计算角度和位置特征 angle compute_elbow_angle(keypoints) self.smooth_angle 0.35 * angle 0.65 * self.smooth_angle if self.mode pushup: return self._update_pushup() else: return self._update_pullup(frame_height) def _update_pushup(self): if self.state up and self.smooth_angle 90: self.state down elif self.state down and self.smooth_angle 150: self.state up self.count 1 return self.count def _update_pullup(self): # 判定头部是否高于手腕 head_above nose_y wrist_y if self.state hanging and head_above: self.state up elif self.state up and not head_above: self.state hanging self.count 1 return self.count这个状态机的关键点是一次完整计数必须经历两个边缘而且每个状态都有明确的进入条件。只满足“下去”但没有“上来”计数器不会增加只满足“上来”但没有先进入“下去”也不会增加。这就杜绝了摆动身体造成的假计数。5.3 部署细节与性能优化实时推理的性能优化我按照优先级做了三件事推理尺寸保持640不盲目调大。推理时跳过置信度低于阈值的关键点不参与角度计算。如果模型较大或帧率仍然不够可以把相邻几帧的检测结果做时间维度的融合用上一帧的关键点位置平滑当前帧。实际测试中我在一款普通独立显卡的笔记本上做俯卧撑计数稳定跑满摄像头帧率CPU占用也不高。换到纯CPU环境后轻量级模型仍然能跑到接近实时只是帧率会降到接近15到20帧左右使用体验在可接受范围。6. 常见问题与排查实录6.1 典型问题速查表这个项目里我踩过的坑不少整理成一张速查表方便直接对照问题可能原因解决方法关键点频繁跳动光照变化、身体与背景颜色相近增加数据增强、切换更亮或对比度更高的环境俯卧撑计数偏多状态机没有锁定半程动作加入滞回区间提高 up 触发阈值俯卧撑计数偏少肘角阈值设置过大下调 down 触发角度例如从90度改到80度引体向上不计数鼻子/头部和手腕位置关系判断错误改用头部关键点加手腕位置组合判定加入置信度过滤推理速度太慢输入尺寸过大、模型体量过大切换到轻量级模型或降至 512 输入尺寸人离摄像头太远关键点尺度过小调整摄像头视角让人体高度占画面的40%以上多人同框互相干扰只取目标框最大的人根据人体框中心距离摄像头最近的原则选择一个目标背景复杂误检人形类间干扰加入跟踪机制只跟踪同一目标6.2 独家避坑心得第一摄像头摆放角度非常重要。俯卧撑尽量从侧面拍镜头高度和地面上的身体中线大致齐平引体向上从斜前方拍不要正对着人脸否则头部被杠铃遮挡后关键点容易丢失。我实测过正面视角同一个动作的计数成功率明显低于侧视角这在投入大量数据采集前就应该想明白。第二每个人做动作的幅度不一样。有人俯卧撑只做半程有人引体向上习惯轻微甩腰。我最后的方案是把角度阈值做成可配置参数在程序启动时允许用户根据自己的动作习惯微调。不要试图用一套固定阈值适配所有人这不是模型能力问题是人体多样性决定的。第三关键点置信度要充分利用。很多关键点在快速动作过程中会出现一两帧的错误预测。我的做法是任何参与角度计算的关键点置信度低于0.5就直接跳过该帧不更新状态机。宁可等待一两帧也不要因为一个错误点造成重复计数。7. 扩展方向与个人体会这个系统做完之后我最大的感受是模型推理只是整个系统的一小块真正的难点在于把“人体姿态”翻译成“符合实际语义的动作阶段”。角度计算、状态机设计、阈值滞回这些看似简单的代码才是决定计数准不准的核心。如果后续想扩展我建议可以先加动作质量评估。既然已经有了连续的关键点轨迹完全可以算出动作速度、幅度、关节角度变化曲线再结合健身标准给出“偏快”“幅度不足”“躯干不稳”这类提示。再往后可以做一个人运动档案长期跟踪每周动作次数和完成的规范性这就超出计数本身进入数字健康管理的范畴了。这个项目让我印象最深的反而是状态机设计。刚开始我也试图用纯粹的连续角度数值来计数结果半程动作、停顿、抖动全部变成误计。后来老老实实把动作拆成离散状态每一步都问“现在处于哪个阶段”整个系统忽然变得异常稳定。如果你也在做类似的计数系统建议先画出动作状态图再写代码。这个思路比换更厉害的模型更能解决问题。

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

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

免费获取报价 →
↑