TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

事件与裁判:从轨迹中提取弹跳并通过规则打分,从 66.9% 到 93.1%

状态:草案(2026-10-13),已反映实测数据。 准确率均为从 12 场 OpenTTGames 实际比赛(275 个已知发球方的拉力赛)中直接测得的值,每次迭代均附带了日期和条件。由于学习型事件检测和拉力赛结束判定重新设计的实验仍在进行中,故暂不记录结果。TT4D 和 PathCRF 的数值为论文中的值。 编写规则:以实测数据为基础,未能测得的数据标记为“未实测”。

目录

1. 从轨迹到事件

第一层给出的每个帧是一个 (시각, x, y, 신뢰도) 。为了打分,必须将其转换为事件序列。引擎会产生四个事件。

事件 定义(侧面摄像机)
bounce(弹跳) 在球台顶面条带内,球的屏幕 y 坐标先变大(下降)然后开始变小(上升)的帧。前提是保持横向方向不变
net(网球/触网) 球在网附近从球台高度上方通过,且横向速度降至 0.7 倍以下或发生反转
out(出界) 弹跳后球飞出球台边缘并落到球台高度以下
end(结束) 球连续 1 秒不可见,或最后一次弹跳后 1.5 秒内没有新的弹跳

直觉:弹跳是 V 字形,击球也是 V 字形

屏幕的 y 轴向下增大。当球落在球台上并弹起时,y 先增大后减小。这个“V”字形是弹跳的基本形态。问题在于 在球台上用球拍击打的球也会绘制出 V 字形。因此,我们需要观察另一个特征。落在球台上的球会继续朝同一方向飞行,而击中球拍的球则会折返。在 12 场实际比赛中,真正的弹跳有 97.9% 保持了横向方向,而击球前后 3 帧内的假弹跳有 73.8% 发生了横向方向反转。

# ttscore/events.py detect_events() 에서 줄임
going_down = before.x is not None and c.y > before.y      # 같은 y 가 이어지면 그 앞의 다른 y 와 비교
going_up = n.y < c.y
if (going_down and going_up and table.on_surface(c.x, c.y)
        and c.t - last_bounce_t >= MIN_BOUNCE_GAP_S         # 0.12초 — 같은 바운드를 두 번 세지 않게
        and keeps_direction(i)                              # 앞뒤 가로 이동이 둘 다 2px 이상이고 반대면 타구
        and not near_net_touch(i)):                         # 네트에 걸린 공의 V 는 버린다
    emit(Event(c.t, "bounce", table.ball_side(c.x, c.y), conf=min(p.conf, c.conf, n.conf),
               vx=vx_at(i), vx_in=vx_at(i, after=False)))

“如果相同的 y 持续存在,则与前面的其他 y 进行比较”这一简单的代码背后也有其原因。由于热图坐标是整数像素,在运动缓慢的区间内会出现相同的值,如果把“相同”当作“下降”,就会在正在上升的球中产生弹跳。

每个常数都有其依据

规则引擎的一个弱点是数字很容易凭感觉设定。因此,代码注释中记录了每个常数的实测依据。

常数 值 依据(12 场实际比赛,2026-10-11)
END_AFTER_BOUNCE_S 1.5秒 拉力赛中弹跳间隔的 98%(1,087 个中有 1,066 个)在 1.5 秒以内
REVERSAL_MIN_PX 2px 真实弹跳的 97.9% 保持方向不变,击球附近的假弹跳有 73.8% 方向反转
NET_SLOW_RATIO 0.7 触网的球(Extended 中 83 个 net 结束)的前后横向速度比为 −0.44…−0.03,直接从网上方通过的球(OpenTTGames 中 1,268 个 net)为 0.75…1.01
GAP_ZERO_S 0.25秒 如果未填补的空白达到这个长度,置信度为 0。超过填补极限(6 帧)则无法相信“没有漏掉弹跳”

与最后一行相配的是 fill_gaps 。如果球被身体遮挡而不可见的时间不超过 6 帧(在 60fps 下为 0.1 秒),则用直线填补,并将填补点的置信度削减为两端的一半。这是为了让该区间的弹跳自动被标记为“需人工确认”。

2. 裁判:不看“谁击的球”

서브 대기 서브처럼 보이는 공만 받음 서버 쪽 바운드 + 네트 쪽 속도 ≥ 0.7 서브 진행 네트 닿음 = 렛 후보 리시버 쪽 바운드 1초 안 서버 쪽에 또 · 바운드 없이 끝 랠리 기대하는 쪽: S S 에 바운드 → 다음은 반대쪽 서브 실패 리시버 득점 S 쪽 선수가 진다 같은 쪽에 또 바운드 → 투 바운드 테이블 밖으로 떨어짐 → 아웃 아무 일 없이 끝남 → 못 받음 「누가 쳤나」는 보지 않는다. 바운드가 떨어진 쪽의 선수가 받아 넘겨야 할 차례다. 0.25초 안의 같은 쪽 두 바운드는 다음 이벤트까지 판정을 미룬다(가짜일 수 있다).
图:裁判的状态变化

裁判引擎(ttscore/referee.py) 的核心思想正如导言中的一句话: 不直接从视频中观察是谁击球,而是从弹跳顺序中进行推断。 如果球在 S 方一侧弹跳,那么接下来 S 方的运动员必须击球并让球在对方一侧落台一次。如果未能做到这一点(在同一侧再次弹跳、未弹跳直接出界、无缘无故结束),则 S 方运动员失分。发球是个例外,球必须先在发球方一侧落台,然后在接发球方一侧落台。

通过这种方式,我们可以在没有“球拍接触”这一视频中最难检测的事件的情况下进行计分。相反,如果漏掉一个弹跳或夹杂一个假弹跳,“轮到谁击球”就会完全错乱。因此,裁判的其余规则大多是 过滤假弹跳的装置。

横向速度这一单一数字

弹跳事件中包含弹跳刚结束时的横向速度 vx(球台长度/秒)。由于它是除以球台长度而不是屏幕像素的值,因此与摄像机距离和分辨率无关。裁判通过这个数字来区分三件事。

  1. 发球的开始。 必须是记分牌发球方一侧的弹跳,且球必须以 0.7 个球台/秒或以上的速度向球网方向移动,才能被当作发球。实际 271 个发球的第一次弹跳都在 1.07 以上,而拉力赛之外的发球方一侧的球(发球前在自己一侧弹跳、传给对手的球),除了被误认为发球的 4 个之外,都在 0.61 以下。
  2. 拉力赛中的假弹跳。 在拉力赛中,S 方的真实弹跳是由对方击过来的球,因此会朝着 S 方底线方向、即远离球网的方向运动。朝着球网方向运动的“弹跳”会被当作误读自网空通过的球或击球动作而丢弃。仅凭这一条规则,实际拉力赛胜者判定正确的数量就从 235 增加到了 241。
  3. 传球。 如果接发球方将球抛给发球方,球会从接发球方一侧朝球网方向弹跳并落在发球方一侧。它看起来“像发球”,但由于它始于记分牌发球方的反侧,因此不予接受。同时接受双方的变体使得实际 5 场比赛的错误判定从 3 个增加到了 16 个。

推迟快速二次弹跳的判定

如果同一侧的两次弹跳间隔小于 0.25 秒,则无法区分这确实是两次弹跳,还是将弹跳刚结束时的击球误读为了弹跳。在实测中,此类 39 个判定中有 16 个(41%)是错误的。其余判定的错误率为 8%。因此,此类判定将被 推迟到下一个事件发生时再处理。 如果下一次弹跳在对方一侧(回球落地),则说明第二个弹跳是假的,应予舍弃并继续拉力赛。通过这条规则,判定发生改变的 9 个实际拉力赛全部从错误变为正确。付出的代价是时间。该判定最多会延迟 1.5 秒得出。

置信度与“需人工确认”

拉力赛的置信度是该拉力赛各事件置信度中的 最小值。事件的置信度取检测置信度与前面空白处置信度两者中的较小值。如果低于 0.6,记分牌就会弹出“需人工确认”。判定结果会照常提交,但会要求人工检查一次。

在这里需要区分两个数字:

如果保守地扣减置信度,未经确认即出错就会减少,但需人工确认会增加,反之亦然。下面的历史演变也是这两个数字之间的一场拉锯战。

3. 从 66.9% 到 93.1%:每次迭代的改动与代价

条件(所有行相同):12 场 OpenTTGames 实际比赛,Extended OpenTTGames 拉力赛结束标签,275 个已知发球方的拉力赛,BlurBall 坐标,录像评分。日期均为 2026-10-11–12。

版本 改动内容 胜者 需人工确认 未经确认即出错 无判定 弹跳精确度 / 召回率
最初 球坐标 → 填补空白 → 事件 → 裁判 66.9% (184) 76.4% 2.9% 10.2% 0.789 / 0.971
2 击球≠弹跳(仅在保持横向方向时弹跳),最后一次弹跳后 1.5 秒即结束 79.6% (219) 85.1% 1.8% 1.5% 0.831 / 0.958
3 允许扩大边界 “8px + 球半径”(数据集掩码边缘) 82.5% (227) 85.5% 1.5% 0.7% 0.825 / 0.965
4 SAM 2.1 边缘(3 场) 84.7% (233) 85.8% 1.5% 0.0% 0.823 / 0.965
5 引入发球规则,丢弃拉力赛中朝向网的“弹跳”,重新设计需人工确认 88.7% (244) 41.5% 5.1% 0.4% 0.823 / 0.965
6 网判定(发球触网),推迟快速二次弹跳 93.1% (256) 41.1% 3.6% — 0.832 / 0.963

记录每次迭代付出的代价如下。

以及 2026-10-13 基准线

版本 6 的 93.1% 是使用 SAM 边缘测得的,但该边缘文件已经不存在了。随着研究重新开始,我们使用数据集掩码边缘和最新引擎重新设定了基准线,这一次我们评估了 实际部署的实时路径(采用每隔一段时间重新回溯最近 2.5 秒的方式)进行了测量。

路径 胜者 未经确认即判定错误 需要确认 无判定 弹跳精度 / 召回率
录像评分 91.3% (251) 13 108 3 0.835 / 0.965
部署实时路径 90.2% (248) 15 129 2 0.800 / 0.970

使用相同的引擎和相同的球坐标,仅仅改变路径,3 个回合的结果就发生了变化。这是因为录像是在看完全部回合后才做出判断,而实时模式则仅查看最近 2.5 秒并即时做出判断。 录像评分会夸大实时性能。 因此,接下来的所有实验都会将这两种路径一并记录。

4. 误差出现在哪一层:代入正确答案进行测试

由于划分了层级,我们可以用三种不同的输入来运行相同的评分。分别是模型检测到的球、数据集的标准球坐标,以及数据集的标准弹跳。如果代入了标准球仍然出错,那么该误差就不是由找球导致的。如果代入了标准弹跳仍然出错,则也不是由事件提取导致的。

引擎版本 模型球 标准球 标准弹跳 范围
2.0 版(79.6% 版) 79.6% 85.8% 90.2% 模型球为 275 个回合,标准输入为包含标准答案的回合
5.0 版(88.7% 版) 88.7% 90.2% 93.5% 模型球为 275 个回合,标准输入为包含标准答案的 246 个回合
2026-10-13 基准线 93.5% 89.0% 90.2% 三者均为相同的 246 个回合

在 2.0 版和 5.0 版中,输入的正确答案越多,准确率就越高。因此,我们得出结论:「剩余的大部分误差并非源于找球,而是源于 事件提取」,事实上,在 5.0 版的 25 起假弹跳中,有 11 起将击球误读为了弹跳,5 起在网边颠倒了方向,4 起漏掉了弹跳。

然而在最新版本的引擎中, 模型球的表现优于标准球和标准弹跳。 这就是为什么不能将这些数字解读为「上限」的原因所在。

也就是说,标准输入并非「完美的输入」,而是「不同的输入」。由于引擎已经针对模型球的特性(延伸至事件之外的坐标、横向速度)进行了微调,因此与标准输入的比较仅可用作区分各层误差的工具,而不能用作上限。

当前剩余的误差

这是在 2026-10-13 基准线(录像评分,275 个回合)中出错或无判定的 27 个案例的分类。

类型 数量
假弹跳 7
漏掉弹跳 4
结束判定方向偏移 4
未出现结束(end) 3
将击球误读为弹跳 2
方向颠倒·过早判定·网边方向颠倒·出界误判 各 1

事件(弹跳)方向有 27 起中的 14 起,回合结束方向有 8 起。按失分原因来看,出界占 92.8%(138 个中的 128 个),触网占 92.6%(81 个中的 75 个),制胜分占 84.4%(32 个中的 27 个),两次弹跳占 75.0%(4 个中的 3 个)。

恶劣条件下的表现

对 7 场比赛(57 个回合)施加画质恶化后进行了测量(录像评分,胜者匹配数)。

条件 正确 / 57 未经确认即判定错误
清晰 50 3
黑暗 35 2
30fps(引擎也为 30fps) 41 9
低分辨率 320×180 42 2
长曝光 46 2
手机 360p 60fps 500kbps 压缩 50 1

尽管 30fps 下找球准确率几乎没有下降(第 2 部分),但得分却大幅下降,并且未经确认即判错的情况最多。这是因为弹跳瞬间的时间分辨率减半了。 该引擎崩溃时往往会掩盖错误答案。 因此,拍摄指南建议使用 60fps。

5. 未来展望:3D 极值规则与约束维特比(Viterbi)

TT4D 式 3D 极值规则

目前的规则是在 2D 画面中寻找「V 字」。由于画面中的 y 坐标混合了球的高度和与摄像机的距离,因此当视点改变时,规则就会动摇(第 3 部分末尾的摄像机问题)。

TT4D(arXiv 2605.01234, ACM MM 2026)将单摄像机转播视频中的球轨迹提升到 3D 空间 后寻找事件。其规则简单得令人惊叹。

在具有 3D 标准答案的数据(合成数据与 TT3D)中,报告称 ±2 帧标准下的弹跳 F1 值为 0.92,击球 F1 值为 0.83(论文中无单独的精度和召回率)。关于击球数值较低的原因,作者认为是因为在运动员附近很难看清球。

与我们引擎的 2D 规则相比,这是同一思路的 3D 版本。「V 字」即 z 的极小值,而「横向方向颠倒即为击球」则是 x 的极值。升维到 3D 后,无论视点如何规则都保持不变,将空中之球错误映射到平面的问题也随之消失。取而代之的是,需要新增从单摄像机恢复 3D 的步骤。能否在我们侧面的手机视频中实现这种恢复,是下一个实验要探讨的问题(正在进行中,未实测)。由于同系列的部分公开实现采用 GPL 协议,我们不打算直接拿来代码,而是计划自行实现其方法。

约束维特比:将规则转化为「允许的转移」

目前的引擎是逐个接收事件并当即做出决定。一旦混入一个假弹跳,就会导致「接下来该谁击球」的顺序颠倒,从而使后续的所有判断全部出错。人类在遇到这种情况时会回顾过去并修正当前:「如果这个弹跳是真的,那么随后的顺序在规则上是不可能的。因此,这个弹跳一定是假的。」

将这种思路转化为计算的就是 约束维特比解码。

  1. 我们会对每个时间点的 状态候选(如「发球方一侧的弹跳」、「接发球方一侧的弹跳」、「击球」、「无事发生」)进行评分。这些分数来自球、声音、姿态等信号。
  2. 我们将乒乓球规则编写为 允许的转移。例如:发球 → 发球方一侧的弹跳 → 接发球方一侧的弹跳 → 击球 → 发球方一侧的弹跳……是允许的,而在同一侧连续两次弹跳后继续回合则是禁止的(必须产生得分)。我们将被禁止的转移的分数设得极低(实际上为 −∞)。
  3. 利用维特比算法寻找 分数字和最大、且符合规则的唯一路径。由于采用动态规划,其运行时间与回合长度成正比。
  4. 如果最佳路径与第二佳路径的分数差很小,则该回合属于「需要确认」。这比现有的「最低置信度」更有依据。

在足球领域展现出相同思路的 PathCRF(arXiv 2602.12080, KDD 2026) 在保持模型不变的情况下仅改变解码方式 ,就将事件 F1 值从 65.34% 提升至 73.14%,并将规则违规率从 2.80% 降至 0%(论文数据)。由于乒乓球的规则比足球严格得多,我们期望(推测)其效果会更加显著。由于 PathCRF 代码采用 MPL-2.0 协议,我们不打算直接套用,而是试图根据我们的引擎重新实现该方法。

正在进行的实验

由于以下实验尚无结果,故暂不记录(进行中)。

实验原则已经确定。超参数仅在未用于训练的验证比赛中进行选择,7 场测试比赛只在最后查看一次。此外,由于规则引擎是在查看测试比赛的过程中不断完善的,因此在与新方法进行比较时,我们会一并说明 规则基准线的测试得分偏向乐观这一事实。

乒乓球小贴士。 裁判不看球拍。仅凭数清球落在了哪里就能猜对大半。由于一个假弹跳就会彻底打乱顺序,因此绝大部分规则都用在了过滤假弹跳上。

参考

登录后即可点赞

评论

还没有评论。

登录后即可发表评论