开学第一堂体育课,体育老师要数全班每个人做了多少个俯卧撑。他站在队伍旁边,目光扫向正在做动作的同学——他盯住的不是衣服颜色、高矮胖瘦,而是头、肩膀、手腕、髋部这些“关节点”怎么移动。一个完整的俯卧撑:身体下去,再上来,算一次。老师看着身体起伏的节奏,把一个个起落数出来。如果同学做得快,他就数得快;如果同学中途停下来系鞋带,他就先按住不数,等继续了再接着数。 AI 做“重复动作计数”,学的就是这位体育老师的本事。第一步,看懂人体——从视频每一帧中提取“关键点骨架”;第二步,看起伏——把骨架的运动变成一条“身体高度随时间变化”的信号曲线;第三步,数拍子——从这条曲线里找到重复的周期,然后数出一共有多少个完整起落。听起来简单,但要在灯光变化、角度刁钻、动作快慢不一的现实视频里数得准,背后是一整套从姿态估计、周期检测到计数评估的方法。 这篇文章,就跟着这位“体育老师”把重复动作计数从头到尾学一遍。从任务定义、姿态关键点、信号构建,讲到周期检测、计数策略、数据集评估和落地部署,让你不仅看懂原理,还能动手搭建属于自己的计数系统。

岗位关联

掌握重复动作计数,你就能独立搭建健身 App 动作统计、康复训练评估、体育测试自动计分等实用功能,是视频理解技能树中最贴近真实产品的一环。

目录
  1. 可视化演示
  2. 任务总览:一双会数数的眼睛
  3. 姿态关键点:先看懂人体再数数
  4. 时序信号:把动作压成一条曲线
  5. 周期检测:给动作找心跳
  6. 计数策略:把波峰一个个数出来
  7. 数据集与评估:和标准答案对分
  8. 部署优化:火眼金睛也要省着用
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示重复动作计数的核心机制,通过基础/核心/应用/扩展四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

重复动作计数:任务定义 任务定义与核心挑战 周期检测与时序信号分析 核心机制可视化 重复动作计数整体架构与关键组件

任务总览:一双会数数的眼睛

体育老师上班第一天接到的任务是“数清楚全班每个人做了多少个俯卧撑”。他一开始也犯难:什么才叫“一次”?后来他总结出一条标准:身体从撑起的最高点,下降到最低点,再回到最高点——这样才算一次完整动作。这个定义听起来简单,却是整个计数任务的核心。

AI 的重复动作计数任务与此完全同构:输入是一段视频,输出是一个整数——动作重复的次数。从整个系统的高度看,任务通常被拆成一条流水线:先做姿态估计(从每帧视频中识别人体关节位置),再把骨架运动压成一条时序信号,接着做周期检测(估计动作的多长重复一次),最后在周期约束下数出次数。

难点在于“什么算一次”在不同场景里各不相同。俯卧撑看躯干升降,引体向上看下巴过杠,跳绳看脚离地;动作幅度、速度、拍摄视角都会变化,甚至同一个动作不同人做出来节奏完全不同。所以在工程上,这个任务一般建模为“姿态估计 → 信号构建 → 周期检测 → 计数”的分段流程,而不是端到端硬学。评价一个系统也分两个维度:一是数的准不准(准确性指标),二是算得快不快(实时性指标)。前者对应数据集与评估的知识,后者对应部署与优化的知识。

整条流水线的数学表达如下:

体育老师把“数俯卧撑”拆成“盯关节→找心跳→数起落”,AI 工程师也把任务拆成几段好分工的流水线。这样每一段都有明确、可解释的目标,出了问题也能定位到具体环节。

N=Count(Period(S(Pose(V))))N = \text{Count}\left( \text{Period}\left( S\left( \text{Pose}(V) \right) \right) \right)
这个公式把“从视频到次数”的完整流程压缩成一层层函数的嵌套,每一层对应流水线中的一个环节。
  1. V(输入视频)一段按时间排列的视频帧序列,每一帧是一张图像。
    V=(v1,v2,…,vT)V = (v_1, v_2, \dots, v_T)
  2. Pose(V)(姿态估计)从每一帧图像中检测人体关键点,输出关键点坐标序列,这一步就是“看懂身体”。
    Pose(V)={pj(t):j=1,…,J, t=1,…,T}\text{Pose}(V) = \{ p_j^{(t)} : j=1,\dots,J, \ t=1,\dots,T \}
  3. S(·)(信号构建)从关键点序列中提取一个能反映动作阶段的数值,比如身体高度,形成一维信号。
    S(Pose(V))=(h1,h2,…,hT)S(\text{Pose}(V)) = (h_1, h_2, \dots, h_T)
  4. Period(·)(周期检测)估计动作重复的周期长度,用 τ* 表示,意思是“每隔多少帧动作重复一次”。
    τ∗=arg⁡max⁡τ R(τ)\tau^* = \arg\max_{\tau} \ R(\tau)
  5. Count(·)(计数)结合周期与信号中的波峰,输出最终的动作重复次数 N。

姿态关键点:先看懂人体再数数

老师从不数学生的衣服颜色,他只盯头、肩、手腕、髋这些“关节点”。AI 也一样,第一步是姿态估计——从一张图像中定位人体各关节的像素位置。这个环节决定了后续所有步骤的质量:关键点偏了,信号就歪了,数自然数不准。

姿态估计模型输出的是人体骨架。最通用的定义来自 COCO 数据集,它把人体的主要关节点归纳为 17 个关键点:鼻子、双眼、双耳、双肩、双肘、双腕、双髋、双膝、双踝。每个关键点用一个三维向量表示:横坐标、纵坐标、置信度。置信度告诉系统这个点有多可靠,比如身体被遮挡时,某个关键点的置信度会很低。

有了 17 个关键点,一帧视频就被压缩成 34 个数字。为什么不用原始视频帧直接数?因为计数任务关心的是“动态运动趋势”,而骨架恰好保留了运动信息,丢掉了衣服、背景、光照这些无关信息。就像老师不需要知道学生穿什么颜色,只需要关节位置。工程上还要对关键点做归一化:以躯干中心为原点、以肩髋距离为尺度缩放坐标,这样能消除身高差异和离镜头远近的影响。

每个关键点可以表示为:

体育老师脑海里也有一副“火柴人骨架”,他盯的就是骨架上那几个关节。AI 的姿态估计把这种能力变成了可计算的坐标,接下来就能把坐标变成“高度尺”上的读数了。

pj(t)=(xj(t),yj(t),cj(t)),j∈{0,1,…,16}p_j^{(t)} = (x_j^{(t)}, y_j^{(t)}, c_j^{(t)}), \quad j \in \{0,1,\dots,16\}
第 t 帧中第 j 个关键点由横坐标、纵坐标和置信度三个值描述,17 个关键点用 0~16 编号。
  1. j(关键点编号)17 个关键点用 0 到 16 编号,比如 0 是鼻子、5 是左肩、6 是右肩。
    j∈{0,1,…,16}j \in \{0,1,\dots,16\}
  2. x、y(像素坐标)(x, y) 是第 t 帧中该关键点在图像上的横纵坐标,单位是像素。
    (xj(t),yj(t))(x_j^{(t)}, y_j^{(t)})
  3. c(置信度)取值范围 0 到 1,表示模型对这个关键点的确定程度,低置信度的点可以直接丢弃。
    cj(t)∈[0,1]c_j^{(t)} \in [0,1]

时序信号:把动作压成一条曲线

老师要数俯卧撑,不会对着每一帧照片发呆,而是看“高度”:身体下去再上来。如果把身体离地高度随时间画出来,就是一条起起伏伏的曲线。AI 也是这样,把高维骨架信息压缩成一维信号。

具体做法:选取与动作方向最相关的关键点坐标。对于俯卧撑、深蹲这类上下运动,肩部和髋部的纵坐标最能反映动作阶段。把选中的关键点 y 坐标取平均,就得到某一帧的“身体高度”值。对每一帧都这样做,就得到一条随时间变化的高度信号曲线。

为什么要选而不是全用?十七个关键点全塞进去会造成维度过高,而且很多点的运动与动作重复无关。比如做俯卧撑时手腕基本不动,它对计数没有贡献。特征选择的本质是问:“这个动作的周期体现在哪个部位的运动上?”选对了关节,信号就干净;选错了,波形就混乱。

原始信号往往带有噪声——姿态估计的抖动、人体肌肉的晃动、摄像头的轻微震动。工程上常用滑动平均来平滑信号,消除高频抖动。这样波峰波谷的位置才稳定,后续计数才可靠。

某一帧的身体高度按下式计算:

这条高度曲线,就是体育老师心里的“高度尺”。曲线摆动一次,身体就起伏一次。下一步的关键是:怎么知道曲线多久摆动一轮?

ht=1∣S∣∑j∈Syj(t)h_t = \frac{1}{|S|} \sum_{j \in S} y_j^{(t)}
S 是参与构建信号的关节集合,|S| 是集合大小,h_t 是第 t 帧的身体高度值。
  1. S(关节集合)从 17 个关键点中挑出与动作起伏最相关的关节,比如双肩和双髋。
    S={5,6,11,12}S = \{5,6,11,12\}
  2. 求平均(身体高度)把这些关键点的 y 坐标加起来除以点数,得到该帧的代表性高度。
    1∣S∣∑j∈Syj(t)\frac{1}{|S|} \sum_{j \in S} y_j^{(t)}
  3. 连成曲线(时序信号)把每一帧的 h_t 按时间顺序连起来,得到动作高度的完整曲线。
    (h1,h2,…,hT)(h_1, h_2, \dots, h_T)

周期检测:给动作找心跳

老师先不着急数,而是先观察几秒钟节奏,心里默念“1、2、1、2……“找到了动作的”心跳频率”,再核对总数。AI 找周期也是类似:先看一段信号,判断它每隔多少帧重复一次。

最经典的工具是自相关函数。它的思想是:把一个信号和它自己“错开”一段时间后的版本做对比,看它们有多相似。一个周期性信号,当错开的长度恰好等于一个周期时,两个版本几乎完全重合,相似度最高。所以,只要在所有的滞后长度里找相关性最大的那个,就找到了周期。

具体计算时,先把信号去均值(让它围绕 0 波动),然后逐个滞后计算相关值:

另一个等价做法是把信号做傅里叶变换,能量最高的频率对应动作频率,其倒数就是周期。就像老师默念的拍子,τ* 就是动作的“拍子长度”——每隔多少帧出现一次动作。有了拍子,下一步就能按着拍子数数了。

R(τ)=∑t=1T−τ(h~t−hˉ)(h~t+τ−hˉ)∑t=1T(h~t−hˉ)2R(\tau) = \frac{\sum_{t=1}^{T-\tau} (\tilde{h}_t - \bar{h})(\tilde{h}_{t+\tau} - \bar{h})}{\sum_{t=1}^{T} (\tilde{h}_t - \bar{h})^2}
R(τ) 表示信号与自身错开 τ 帧后的相似度,数值越大说明这个滞后越接近真正的动作周期。
  1. 去均值(去直流)先让信号围绕 0 波动,避免信号的固定偏移量干扰相似度比较。
    h~t−hˉ\tilde{h}_t - \bar{h}
  2. 错位相乘(分子)把原始序列与滞后 τ 的序列逐点相乘再求和。如果两个序列变化节奏一致,乘积为正且数值最大。
    ∑t=1T−τ(h~t−hˉ)(h~t+τ−hˉ)\sum_{t=1}^{T-\tau} (\tilde{h}_t - \bar{h})(\tilde{h}_{t+\tau} - \bar{h})
  3. 归一化(分母)除以信号自身的能量,让 R(τ) 落在 [-1,1],方便对不同长度、不同幅度的信号互相比较。
    ∑t=1T(h~t−hˉ)2\sum_{t=1}^{T} (\tilde{h}_t - \bar{h})^2
  4. 找最大滞后(周期定位)在 τ=1 到 T/2 的范围内找到 R(τ) 最大的 τ*。
    τ∗=arg⁡max⁡τR(τ)\tau^* = \arg\max_{\tau} R(\tau)

计数策略:把波峰一个个数出来

老师找到了拍子,于是盯着身体高度的最高点:每到一个最高点,数一次。“最高点”在信号里就是波峰。计数策略可以简单概括为“找波峰、数波峰”,但在真实数据上还需要几个细节保护。

首先是动作分割。真实视频往往包含准备、停顿、结束等非动作片段。直接在整个信号上找波峰,会把准备阶段的小动作、结束时的手臂晃动都算进去。所以先做活动检测,只保留真正在“做动作”的片段,再在片段内计数。这就像老师看到学生系鞋带,会先暂停计数。

然后是波峰计数。在动作片段内,寻找局部极大值点——比左右邻居都高的点。但光找局部极大还不够,身体小幅晃动也会产生假波峰。所以要加两个约束:高度要超过一定阈值(比如最大峰值的 30%),相邻波峰间隔要大于周期的一半。两个约束把噪声抖动过滤掉。

最后是周期匹配。如果动作不是简单的上下起伏,比如跳绳中间的过渡动作,可以用模板匹配:取一个周期的信号作为模板,与后续信号做动态时间规整(DTW)比对,相似度高的片段才计为一次动作。

波峰计数的数学表达:

“先找拍子再数高点”的思路,避免了把身体晃动的小抖动也当成动作。体育老师之所以数得又快又准,就是因为他心里有节拍、眼睛盯着最高点——AI 的计数策略正是把这个直觉显式建模了出来。

N=∣{t | ht≥ht−1 且 ht≥ht+1 且 ht>θ}∣N = \left| \left\{ t \ \middle| \ h_t \geq h_{t-1} \ \text{且} \ h_t \geq h_{t+1} \ \text{且} \ h_t > \theta \right\} \right|
在一段动作信号中,满足“比左右邻居都高且超过阈值 θ”的采样点数量,就是动作重复次数。
  1. 局部极大(波峰候选)h_t 不小于前一个点、也不小于后一个点,说明这个点是信号的一个“山顶”。
    ht≥ht−1 且 ht≥ht+1h_t \geq h_{t-1} \ \text{且} \ h_t \geq h_{t+1}
  2. 高度阈值(排除噪声)只有身体确实上升到足够高的位置才触发计数,防止把小幅晃动误认为动作。
    ht>θh_t > \theta
  3. 集合大小(计数值)满足以上所有条件的时刻点 t 的个数,就是计数结果 N。
    N=∣{t | ht≥ht−1 且 ht≥ht+1 且 ht>θ}∣N = \left| \left\{ t \ \middle| \ h_t \geq h_{t-1} \ \text{且} \ h_t \geq h_{t+1} \ \text{且} \ h_t > \theta \right\} \right|

数据集与评估:和标准答案对分

学生做完 20 个俯卧撑,老师怎么知道自己数的 20 对不对?他需要一张“标准答案表”——也许是上次课已经数过的记录。AI 也需要这样的标准答案,这就是数据集。

重复动作计数领域最常用的数据集叫 RepCount。它从真实场景中采集了大量视频,每个视频都标注了其中重复动作的总次数,覆盖多种动作类型,比如健身、运动训练中的常见动作。研究者在这个数据集上训练模型,让模型学习“从视频到次数”的映射,再在测试集上检验它没见过视频上的表现。RepCount 在重复动作计数研究中的地位,就像 ImageNet 在图像分类中的地位——是公认的“考场”。

光有考场还不够,还需要统一的评分规则。计数任务最直接的评分是平均绝对误差(MAE),把每个视频预测次数与真实次数的误差取绝对值后求平均:

除了 MAE,另一个常用指标是 OTM(Off-By-One,允许偏差一次)。它计算预测次数与真实次数相差不超过 1 的样本占比:

这个指标更宽容,因为视频的边界帧可能正好卡在半次动作上,人工标注也有主观性。好比老师在心里默数时被打断了一下,数错一个也是情有可原的。

体育老师用“往年的成绩表”核对,AI 用 RepCount 和 MAE/OTM 给“数数能力”打分。需要注意的是,在 RepCount 上数得准只是开始,真实场景还要看能不能落地跑得动,这就是下一节的话题。

MAE=1M∑i=1M∣Ni−N^i∣\text{MAE} = \frac{1}{M} \sum_{i=1}^{M} |N_i - \hat{N}_i|
MAE 越小越好,反映了平均每个视频数错了几次。OTM 则宽松一些,允许一次偏差。
  1. 差值(误差)第 i 个视频的预测次数 N̂_i 与真实次数 N_i 之差的绝对值。
    ∣Ni−N^i∣|N_i - \hat{N}_i|
  2. 求和平均(总误差)把所有视频的误差加起来除以视频总数 M,得到平均每个视频数错多少次。
    OTM=1M∑i=1M1[∣Ni−N^i∣≤1]\text{OTM} = \frac{1}{M} \sum_{i=1}^{M} \mathbb{1}[|N_i - \hat{N}_i| \leq 1]

部署优化:火眼金睛也要省着用

体育老师不光要数得准,还要能在课间四十秒内数完全班四十个人。AI 落地也一样:既要准,又要快、省电、不发热。这一节讲从“实验室模型”变成“手机 App 功能”的关键优化。

第一个优化方向是模型压缩。姿态估计网络往往又大又慢,直接在手机上跑不现实。常用手段有知识蒸馏(让大模型当老师,训练一个小模型学它的输出)、剪枝(砍掉不重要的神经元连接)和量化(把 32 位浮点的权重变成 8 位整数)。经过量化,推理延迟能从几百毫秒降到几十毫秒,模型体积也能缩小约四分之一,非常可观。

第二个优化方向是采样帧率。真实场景的视频流是连续的,但没必要每一帧都送进算法。动作重复本质是低频信号,深蹲、俯卧撑这类动作每秒大约重复 0.5~2 次。根据奈奎斯特采样定理,要捕捉一个频率为 f 的周期信号,采样率至少要达到 2f。反过来,如果动作频率只有 1Hz,那么用 2~8 FPS 的帧率就足够计数了:

第三个优化方向是流式处理。整段视频处理完再数,用户等一集广告都看不到结果。正确做法是设计“帧进帧出”的流水线:关键点检测每完成一帧,立刻更新计数状态;当一个波峰被确认后,计数立刻加一。用户看到的是“边做边数”,这才有实时反馈的体验。

老师练就一眼扫过就数完的本领,AI 则靠轻量模型、合理帧率和流式流水线在手机端跑起来。这一套组合拳做下来,一个能商用的重复动作计数器就诞生了。

fs≥2factionf_s \geq 2 f_{\text{action}}
f_action 是动作重复频率,f_s 是采样帧率。盲目上 30FPS 只会白白增加功耗,对计数精度几乎没有帮助。
  1. f_action(动作频率)每秒重复动作的次数,例如每秒做 1 个深蹲就是 1Hz。
    factionf_{\text{action}}
  2. f_s(采样帧率)每秒从视频流中取出多少帧送给计数算法处理。
    fsf_s
  3. 2 倍条件(奈奎斯特)采样率至少达到动作频率的两倍,才能保留完整的周期信息,否则会漏数动作。
    fs≥2factionf_s \geq 2 f_{\text{action}}

AI 应用场景

健身 App 智能教练

阶段:端侧实时推理 + 计数策略

解决的问题:传统健身 App 计数靠用户手动点击打卡,或依赖手环等穿戴设备。手动打卡不准确且打断训练节奏,穿戴设备无法识别具体动作类型。基于视觉的重复动作计数,让用户把手机放在地上对准自己,App 就能自动识别深蹲、俯卧撑并实时计数。

具体案例:用户在手机前做深蹲,App 画面中实时标出肩、髋、膝的关键点,屏幕上数字随每次下蹲-起身跳动。算法取髋部纵坐标作为身体高度信号,检测周期后数波峰,每确认一个波峰计数加一。用户做完一组,App 直接给出“深蹲 15 次,用时 42 秒”的总结,还能根据周期长度推算动作节奏是否过快。

运动康复评估

阶段:离线视频分析 + 周期检测

解决的问题:康复师要求患者在家每天完成固定次数动作,靠患者自报不可信。传统方案是让患者拍视频发给医生,由医生人工观看数次数,效率极低。重复动作计数模型可以自动统计动作次数并把结果连同视频一起推送给医生。

具体案例:骨科医生给膝关节术后患者布置“每日直腿抬高 20 次”。患者回家后用手机支架录一段视频,上传到康复系统。系统自动提取人体关键点、构建大腿高度信号、用自相关检测动作周期,最终输出“本次训练 23 次”的报告。医生在后台查看次数与实际视频片段,判断患者是否完成训练量。

体育体能测试自动计分

阶段:数据标注与评测 + 部署优化

解决的问题:中考体育、部队体能测试中,引体向上、仰卧起坐等项目的计数全靠人工裁判,一场测试下来裁判疲惫且容易误判。自动计数系统可以在固定机位实时统计次数,减少人力消耗。

具体案例:体能测试现场架设一台平板设备,考生在镜头前做引体向上。系统以 5FPS 的帧率采集视频,下巴过杠瞬间被检测为一个波峰。每次成功过杠,屏幕立即播报“第 8 个”。测试结束后,后台把计数结果和片段存档,方便复查申诉。

Python 演示

python48 行
import numpy as np  def generate_signal(frames=450, period=50, noise=0.05):    """模拟人做深蹲时身体高度随时间变化的信号。    period 是动作周期(帧数),噪声模拟关键点检测的抖动。"""    t = np.arange(frames)    height = 100 - 30 * (0.5 - 0.5 * np.cos(2 * np.pi * t / period))    height += np.random.normal(0, noise * 30, size=frames)    return height  def detect_period(signal, max_lag=200):    """用自相关函数估计动作周期(帧数)。"""    n = len(signal)    signal = signal - signal.mean()    best_lag, best_score = 1, -np.inf    for lag in range(1, max_lag + 1):        a = signal[: n - lag]        b = signal[lag:]        denom = np.sqrt((a * a).sum() * (b * b).sum())        if denom == 0:            continue        score = float((a * b).sum() / denom)        if score > best_score:            best_score, best_lag = score, lag    return best_lag  def count_reps(signal, period, min_gap=10):    """通过波峰计数统计重复动作次数。"""    peaks = []    i = 1    while i < len(signal) - 1:        if signal[i] >= signal[i - 1] and signal[i] > signal[i + 1]:            if not peaks or i - peaks[-1] >= min_gap:                peaks.append(i)        i += 1    return len(peaks), peaks  if __name__ == "__main__":    height = generate_signal(frames=450, period=50)    est_period = detect_period(height)    count, peak_idx = count_reps(height, est_period)    print(f"真实周期: 50 帧, 自相关检测周期: {est_period} 帧")    print(f"真实次数: 9, 波峰计数结果: {count}")    print(f"检测到的波峰位置: {peak_idx}")
目的:用合成数据完整演示“构建时序信号 → 自相关检测周期 → 波峰计数”的重复动作计数主流程。
  1. height = 100 - 30 * (0.5 - 0.5 * np.cos(...))模拟身体随动作周期起伏的信号,波峰为站直,波谷为蹲下。
  2. height += np.random.normal(0, noise * 30, size=frames)叠加随机噪声,模拟姿态估计的抖动。
  3. signal = signal - signal.mean()去均值,让信号围绕 0 波动,避免直流分量干扰自相关。
  4. for lag in range(1, max_lag + 1)遍历所有可能的滞后长度,寻找信号与自身错开后的相似度峰值。
  5. score = float((a * b).sum() / denom)归一化自相关计算,滞后等于真实周期时该值接近 1。
  6. if signal[i] >= signal[i - 1] and signal[i] > signal[i + 1]判断当前点是否为局部极大值,即潜在的动作波峰。
  7. if not peaks or i - peaks[-1] >= min_gap与上一个波峰保持最小间隔,防止噪声抖动被重复计数。

常见误区

误区很多初学者认为计数很简单:把身体高度信号画出来,预设一个阈值,高度超过阈值就算一次。结果换个测试者、换个动作,计数就乱套了。

正解固定阈值完全无法应对不同人的动作幅度差异。正确思路是“先周期、后计数”——先用自相关或频域分析估计动作周期,再在周期约束下数波峰。这就像老师心里先有节拍再数动作,而不是拿一把固定高度的尺子去卡所有人。

为何常见:直观上“身体下降-上升”确实像一个开关,初学者很容易把连续信号二值化,忽略了阈值得随人、随动作动态调整。

误区姿态估计出了 34 个坐标值,有人直接把坐标拼成特征向量输入模型,发现效果很不理想,以为是模型不够强。

正解原始坐标混杂了人离镜头的远近、身高差异等无关信息。正确的特征工程是先做归一化(以躯干中心为原点、按肩髋距离缩放),再筛选与动作方向相关的关键点,最后才构建时序信号。模型再强也扛不住问题百出的输入。

为何常见:受“神经网络会自动学习特征”的思路影响,忽视了时序任务中特征选择的必要性。

误区觉得视频越流畅精度越高,于是把采样帧率设为 30FPS 甚至 60FPS,结果手机发热掉电,精度却没有任何提升。

正解动作是低频信号,俯卧撑、深蹲的动作频率一般不超过 2Hz。根据奈奎斯特准则,采样率只需达到动作频率的两倍即可完整保留周期信息。5~10FPS 对绝大多数计数任务完全够用。

为何常见:被“流畅=高清=好”的直觉误导,忽略了重复动作计数是一个低频周期性任务。

误区模型在公开数据集上误差很低,于是直接部署到实际产品,结果用户用了几次就发现计数不准,差评如潮。

正解RepCount 是相对干净的单人、固定视角数据集,真实场景有遮挡、视角变化、多人入镜、动作不规范等复杂情况。上线前需要额外做视角增强、遮挡处理,并在端侧验证推理速度。

为何常见:学术 benchmark 与真实产品之间有“最后一公里”的差距,初学者往往低估了这个 gap。

误区把任意的姿态信号丢给自相关函数,以为它一定能找到明显的周期峰值,找不出来就怀疑代码写错了。

正解自相关假设信号是平稳的周期信号。如果视频里有准备阶段、中途停顿、动作不完整,自相关结果会被这些非动作区间严重干扰。正确做法是先做动作分割,把真正运动的片段切出来,再在片段内做周期检测。

为何常见:只学了自相关的数学定义,忽略了它的适用前提——周期性本身要先存在。

面试题

Q1. 重复动作计数任务中,模型的输入通常是什么?

单选题 · easy

  • A.整段视频的原始像素序列
  • B.每帧人体关键点坐标序列← 正确答案
  • C.视频的音频信号以及文字
  • D.视频的拍摄时间等元数据

答案:B。主流方案先用姿态估计把视频压缩成骨架坐标序列,再在坐标序列上做周期检测与计数。原始像素冗余信息太多,音频和元数据与动作次数没有直接关系,这也对应文中“体育老师只看关键点不看衣服”的直觉。

Q2. COCO 人体姿态数据集标准定义了多少个关键点?

单选题 · easy

  • A.9 个
  • B.15 个
  • C.17 个← 正确答案
  • D.21 个

答案:C。COCO 标准定义了 17 个关键点:鼻子、双眼、双耳、双肩、双肘、双腕、双髋、双膝、双踝。这些点覆盖了人体主要的关节,足以表达俯卧撑、深蹲等动作的身体姿态变化。

Q3. 要从身体高度信号中估计动作周期,最常用的方法是?

单选题 · mid

  • A.计算信号绝对值的中位数
  • B.对信号做 K 均值聚类
  • C.自相关函数峰值定位← 正确答案
  • D.使用边缘检测算法

答案:C。自相关函数在滞后等于周期长度时会出现明显峰值,是周期检测的经典方法。中位数、聚类和边缘检测都不是测周期的工具。这就像听节拍声,把录音与自身错开一拍,两个版本对得最齐的那个偏移量就是节拍周期。

Q4. OTM 指标(Off-By-One)衡量的内容是?

单选题 · mid

  • A.预测次数与真实次数完全相同
  • B.预测与真实次数相差不超过一次← 正确答案
  • C.预测动作类别与真实类别相同
  • D.关键点置信度超过某一个阈值

答案:B。OTM 统计预测次数与真实次数相差不超过 1 的样本占比。与 MAE 相比它更宽容,因为视频边界可能卡在半次动作上,人工标注也有主观性。好比老师数跑动中的学生,允许前后差一个人的误差来考核估算能力。

Q5. 采样帧率与动作重复频率之间应满足什么关系才不会漏记?

单选题 · hard

  • A.采样帧率应恰好等于动作频率
  • B.采样帧率应高于动作频率的两倍← 正确答案
  • C.采样帧率应低于动作频率
  • D.采样帧率与动作频率没有关系

答案:B。根据奈奎斯特采样定理,要完整捕捉频率为 f 的周期信号,采样率必须不低于 2f。深蹲频率约 1Hz 时,用 5~10FPS 的采样率就足够。这也是部署优化章节强调“不要盲目上 30FPS”的原因。

Q6. 下面哪些举措可以提升重复动作计数模型的泛化能力?

多选题 · mid

  • A.在训练时对骨架坐标做随机尺度缩放← 正确答案
  • B.对不同身高的人做坐标归一化← 正确答案
  • C.固定使用训练集的唯一视频分辨率
  • D.在多种拍摄视角的数据上做增强← 正确答案

答案:ABD。尺度缩放增强和身高归一化能消除个体差异,多视角增强让模型适应拍摄角度变化;而固定单一分辨率会导致模型过拟合到采集设备,反而降低泛化能力。这与文中“特征降维和归一化消除无关因素”的思路一致。

Q7. 动作计数前先做“动作分割”的主要原因是?

单选题 · mid

  • A.让视频文件变小更易于存储
  • B.把非动作区间剔除,使周期检测只作用于动作片段← 正确答案
  • C.让关键点检测网络更容易训练
  • D.用灰度视频替代彩色视频减少计算

答案:B。真实视频常有准备、停顿、结束等非动作片段,如果直接对整个信号做自相关和波峰检测,会把无关的小动作也算进去。先分割出动作片段再检测周期,才能保证计数准确。这就像老师等学生系好鞋带再继续数。

Q8. 关于 RepCount 数据集,下列说法正确的是?

多选题 · hard

  • A.每条视频标注了重复动作的次数← 正确答案
  • B.只包含一个固定动作类型
  • C.常用于重复动作计数算法的评估← 正确答案
  • D.视频中不包含重复动作的计数标签

答案:AC。RepCount 是重复动作计数领域的通用基准,每个视频都有人工标注的重复次数,支持多种动作类型,模型在此数据集上训练和评估。B 说它只有单一动作类型,D 说它没有计数标签,都与事实相反。

Q9. 在身体高度信号中,一个完整动作周期对应的是?

单选题 · easy

  • A.信号中的单个采样点
  • B.相邻两个波峰之间的距离← 正确答案
  • C.信号的全部时间长度
  • D.信号的整体方差

答案:B。相邻两个波峰(或相邻两个波谷)之间的间隔就是一个动作周期。比如俯卧撑两次撑到最高点之间的帧数,就代表完成一次动作所需的时间。周期越长动作越慢,周期越短动作越快。

Q10. 端侧部署重复动作计数模型时,常采用的优化手段包括?

多选题 · hard

  • A.模型量化降低计算量← 正确答案
  • B.跳过关键点检测直接输入视频帧
  • C.降低采样帧率到满足需求即可← 正确答案
  • D.对模型做剪枝减小体积← 正确答案

答案:ACD。量化、剪枝是经典模型压缩手段;按奈奎斯特准则合理降低帧率能大幅省电。但不能跳过关键点检测,因为骨架坐标是计数特征的来源;直接让端侧模型吃原始视频帧,计算量会大到无法实时运行。