TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

寻找乒乓球桌:从 SAM 2.1、EfficientTAM、SAM 3 到四个角与单应性

状态:草稿 (2026-10-13),已反映实测数据。 SAM 2.1 tiny 的准确率与速度是 2026-10-11 至 12 在服务器 GPU(与其他任务共享时)和 CPU 临时 Pod 上测得的值。EfficientTAM 和 SAM 3 未在我们的环境中运行(未实测),我们附带条件搬运了论文和官方仓库的数值。自动寻找乒乓球桌的功能正在其他任务中开发(进行中),有结果后会进行补充。 编写规则:实测数据为基础,未测得的标记为「未实测」。

目录

1. 为什么需要四个角

仅靠球的坐标无法进行计分。要想知道「球是否触台」、「触碰了哪半边」、「是否飞出了球桌边缘」,就必须知道球桌位于画面的什么位置。乒乓球桌是尺寸由规程规定的长方形,长 2.74m,宽 1.525m,网高 15.25cm。因此,只要在画面中找到 四个角,其余的都可以通过几何计算得出。

幸运的是,TT Score 假设相机是固定在三脚架上的。乒乓球桌不会动,所以只需在 比赛开始时找一次 即可。与每帧都需要运行的找球不同,这里花上几百毫秒也是完全可以接受的。这个差异改变了模型的选择。

当前的实时画面由 人工直接点击四个角来完成。这是最稳妥且不需要计算的方法。在此基础上再多点击球桌的两半各一次,即使在倾斜的相机角度下,引擎也能分辨出哪条边是底线(下方第 5 节)。本文中的模型正是为了「减少或省去点击」而探索的路径。

2. 提示词分割:单击变成提问

SAM 的思路

Segment Anything (SAM) 系列在训练时并不决定「涂抹什么」。相反,它接收人工给出的 提示词(点点击、框选、大致的掩码),并对该位置的物体进行涂抹。其结构分为三个部分:

  1. 图像编码器:较重的部分。对单张图片仅运行一次,生成特征图。
  2. 提示词编码器:将点击坐标或框选转换为小向量。
  3. 掩码解码器:较轻的部分。接收特征图与提示词,输出掩码。

由于只需运行一次沉重的编码器,并在每次点击时重新运行轻量的解码器,它非常适合人工更改点击进行修正的交互画面。在 TT Score 中, 人工触碰乒乓球桌的点击直接成为了提示词。

SAM 2:在视频中用记忆连接

SAM 2 (arXiv 2408.00714) 将相同的思路扩展到了视频中。在第一帧中被点选的物体需要在接下来的帧中被持续追踪。为此,它设置了 记忆机制。

直观理解就是「参考过去几张图中该物体的外观来寻找当前图中的物体」。即使运动员短暂遮挡了球桌边缘,由于存在记忆,掩码也不会中断。

官方仓库展示的视频 API 分为三个阶段。

# facebookresearch/sam2 README.md(Apache-2.0)에서
predictor = build_sam2_video_predictor(model_cfg, checkpoint)
with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16):
    state = predictor.init_state(<your_video>)
    frame_idx, object_ids, masks = predictor.add_new_points_or_box(state, <your_prompts>)
    for frame_idx, object_ids, masks in predictor.propagate_in_video(state):
        ...

这是 SAM 2.1 (2024-09) 按不同大小划分的数值(官方 README,单张数据中心级 GPU,所有组件均已编译)。

检查点 参数量 FPS(论文环境) SA-V 测试 J&F
tiny 3890万 91.2 76.5
small 4600万 84.8 76.6
base+ 8080万 64.1 78.2
large 2亿2440万 39.5 79.5

代码和检查点均采用 Apache-2.0 协议。我们选择了最小的 tiny。我们认为乒乓球桌是一个又大又简单的物体,大模型带来的收益很小,而且需要与找球等任务共享显存。

EfficientTAM:精简内存以适配手机端

EfficientTAM (arXiv 2411.18933,代码与检查点为 Apache-2.0) 展示了使 SAM 2 变得轻量化的两种方法:

  1. 平坦的 ViT 编码器。 不使用 SAM 2 的分层编码器 (Hiera),而是改用单一分辨率的小型 ViT (Tiny·Small)。
  2. 对内存交叉注意力进行粗化处理。 通过 2×2 窗口对内存的空间 token 进行平均,缩减为四分之一后再计算注意力。论文解释称,得益于相邻像素记忆彼此相似的「局部平滑性」,缩减后的 token 能够很好地模拟原本的注意力(与完整注意力的相对误差为 0.03)。object pointer 不会被缩减。

根据论文表格(1024×1024 输入),EfficientTAM-S/2 拥有 3400 万参数,在 SA-V 上达到 74.0 J&F,数据中心级 GPU 上为 109.4 FPS,手机端(iPhone 15 Pro Max)每帧 450ms。与之对比的 SAM 2 拥有 8100 万参数、74.7 J&F、43.8 FPS(引自论文)。手机端测量的精度与转换方式未在论文中说明,也没有与 SAM 2.1 的对比。

对我们而言,它的意义在于「有朝一日在手机端寻找乒乓球桌的路径」。由于只需在比赛开始时运行一次,每帧 450ms 也是可以接受的。我们目前尚未在自己的环境中运行它(未实测)。

SAM 3:用文字呼唤

SAM 3 (arXiv 2511.16719, 2025-11) 提出了 基于提示词的概念分割。只要给定「table tennis table」之类简短的文字或示例图片,它就会寻找并涂抹出符合该概念的 所有 物体,并在视频中进行追踪。鉴于它无需点击即可找到乒乓球桌,这一点极具吸引力。

然而这次我们没有采用它。

3. 从掩码到四个角

分割模型给出的是掩码(被涂抹的像素集合)。而我们需要的是四个顶点。这个转换过程比想象中要棘手。

失败的两种方法

最初,我们将掩码的外轮廓简化为多边形来挑选顶点(approxPolyDP)。当球网将顶面分为两半时,它会把半个球桌当作整体;当运动员遮挡角点时,它会将内部错误的点当作角点输出。

接下来,我们尝试通过「掩码每一行的最上方 = 远端边,最下方 = 近端边」来对齐边。这在侧面视角下表现良好,但在乒乓球桌在画面中发生倾斜旋转的视频(TT3D 实验室的倾斜相机视角)中, 单个顶点偏移了 370–754px 。这是因为远端边被误读成了近端边。部分准确率从 0.99 下跌到了 0.80。

当前的方法:为每条边重新拟合直线

① 화면: 마스크의 바깥 경계 → 변마다 직선 → 교점 먼 오른쪽 가까운 왼쪽 가까운 오른쪽 선수 팔이 가림 먼 왼쪽 = 두 변의 교점 바운드 가려진 꼭짓점도 보이는 변 두 개의 교점으로 정해진다 호모그래피 H (네 점 대응) ② 테이블 평면: 2.74m × 1.525m L 끝 (u < 0) R 끝 (u > 0) 네트 = u 0 · 끝 = |u| 1.37m · 바운드의 쪽 = 닿은 점의 u 부호
图:在掩码的四条边上拟合直线以获取角点,并通过四点对应将其映射到球桌平面的过程

ttscore/calib.py 步骤如下:

  1. 收集掩码的外部边界点。
  2. 使用包围凸包的矩形大致框出四条边。
  3. 针对每一条边, 仅使用其附近的边界点重新拟合直线 。为了即使在球网支柱或运动员手臂导致近一半的点偏移时也能保持稳健,首先使用重复中位数(Siegel)斜率捕获直线,然后仅利用该直线 3px 范围内的点反复进行最小二乘法拟合。
  4. 将相邻两条边的 交点作为顶点。
  5. 不只使用单帧,而是通过中位数合并多个帧(实测为 5 张)的顶点。
# ttscore/calib.py _fit() 에서 줄임 — v = a + b·u 직선, 순수 파이썬
slopes = [_median([(vs[j] - vs[i]) / (us[j] - us[i]) for j in idx if us[j] != us[i]]) for i in idx]
b = _median(slopes)                       # 반복 중앙값: 점의 절반이 어긋나도 기울기가 버틴다
a = _median([vs[i] - b * us[i] for i in idx])
for _ in range(it):                        # 그 직선에서 tol(3px) 안의 점만으로 최소제곱을 되풀이
    keep = [i for i in range(n) if abs(vs[i] - (a + b * us[i])) <= tol]
    ...

该方法的优点在于 被遮挡的顶点也能由可见的两条边决定。即使运动员遮盖了某个角点,只要构成该角点的两条边的其余部分可见,交点就会准确出现在原位。在相同的 SAM 掩码下,倾斜视频中顶点最大误差从 370–754px 缩减到了 25–28px ,部分准确率达到了 0.999。

由于 CI 中没有 numpy,该模块采用纯 Python 编写。掩码作为每一行涂抹的区间 {y: [(x0, x1), ...]} 传入。

只保留顶面

SAM 不仅会涂抹球桌顶面,还会连同侧板和桌腿一起涂抹,或者相反,因被球网阻挡而只涂抹了一半。因此,我们给出两次点击(被球网分隔的两半各一次), 仅保留与点击位置颜色相似的像素 ,从而将顶面与侧板分离开来。这个步骤对阴影的处理较为薄弱。在实验室的两个侧面视角视频中,色彩阈值切断了阴影边缘,导致顶点偏差了 87–97px。这种矩形无法被识别为长方形的检测机制(quad_ok)将其过滤掉了,从而回退到以前的规则,并保持了 0.99 的部分准确率。

录像视频分析工作器中尚未集成 SAM 2.1,因此目前在标签位置使用相似颜色生长的 色阶步骤 (batch/calib.py)。此方法的边缘准确度未进行测量(未实测),因此结果中会保留该方法,但画面上建议选择“直接指定四个角”。

4. 从四个角到球台平面:单应性(Homography)

直觉

当用相机拍摄平面时,矩形会变成梯形。这种“平面到平面”的透视变换被称为 单应性。它可以用一个 3×3 的矩阵表示,如果将最后一个元素固定为 1,则有 8 个未知数。由于一个点的对应关系会给出两个方程(x, y),因此 四个点即可精确求解。 只需将画面的四个角与球台的四个顶点 (0, 0) … (2.74, 1.525) 进行匹配即可。

我们的代码

虽然使用 OpenCV 的 getPerspectiveTransform(四个点)或 findHomography(多个点,RANSAC)只需一行代码,但引擎需要在没有 numpy 和 OpenCV 的环境下运行,因此我们直接求解 8×8 联立方程。

# ttscore/table.py
def homography(src, dst):
    """네 점 대응 src[i] → dst[i] 의 3×3 변환(h33 = 1)."""
    a, b = [], []
    for (x, y), (u, v) in zip(src, dst):
        a.append([x, y, 1, 0, 0, 0, -u * x, -u * y]); b.append(u)
        a.append([0, 0, 0, x, y, 1, -v * x, -v * y]); b.append(v)
    h = _solve(a, b)                       # 부분 피벗 가우스 소거
    return ((h[0], h[1], h[2]), (h[3], h[4], h[5]), (h[6], h[7], 1.0))

通过该变换,将屏幕上的点转换到球台平面坐标 (u, v)。u 是长度方向,中间(球网)为 0。这样无论视角如何,公式都是相同的。

两个陷阱

首先,平面投影仅在球触及球台的那一刻才准确。 如果将空中的球转换到平面上,就会得出“从相机出发穿过球的直线与球台平面的交点”,球越高,就会被推得越远。在远端相机中,球网高度处的球会被推开 50 厘米以上。因此,平面坐标仅用于 击球瞬间的区域判断,而空中区段的判定则由第 5 篇中的事件规则单独处理。即使在击球瞬间,球心也比接触点高出一个半径(20mm),因此需要计算其下方的 接触点来确定区域。仅凭这一项修正,合成斜角相机的胜者准确度就从 79.5% 提升到了 85.6%。

其次,仅靠四个点无法判断哪一边是端线(底线)。 因为任何四边形都可以通过两种方式转换为矩形。引擎按以下顺序进行确定:

  1. 如果数据或人工指明了端线,则以其为准。
  2. 如果在球台的两半各拍摄到一个标签点,则选择能使这两个点在 u 方向上拉开最远的配对。
  3. 如果已知画面尺寸,可将画面中心视为主要观测点,并在两个消失点正交的条件下求解焦距,以恢复实际的宽高比(恢复矩形)。与 2.74 : 1.525 接近的配对是正确的。
  4. 如果什么都没有,则遵循旧规则(侧面假设:x 较小的两个点构成一端)。

为什么要转换到平面

旧引擎通过对角线交点的 x 坐标(“球网 x”)将画面左右分割。这在侧面视角下没问题,但存在两个问题。对角线交点会将边缘的垂直误差放大约 8 倍(实测:边缘 6–15px 误差 → 球网 x 27–36px)。并且在远端相机中,该垂直线会变成球台宽度中央的白线,导致区域判定变成抛硬币(合成远端相机 0.55,实际转播视频 0.60)。转换为平面坐标后,无论视角如何,都可以应用相同的规则。

5. 实测

SAM 2.1 tiny 的边缘(2026-10-11)

条件:OpenTTGames 实际比赛 12 场,2 个标签点 + 颜色仅限顶部 + 四边直线拟合,5 帧中位数。对比对象是由数据集提供的分割掩码求得的边缘(1920×1080)。

项目 数值
边缘差异 中位数 / 90% / 最大 4.7px / 11.2px / 21px
仅使用单帧时 60 帧中有 6 帧偏差超过 20px(运动员手臂遮挡端线的帧)
正确击球处于“球台顶带”之外 容差 8px 时为 0.73%,8px + 球半径时为 0.11%

数据集的掩码并不总是正确的。通过 3 倍放大目视比对球网底线,在两场比赛(test_1, game_5)中,SAM 的结果更为准确。

速度与内存

条件 数值
服务器 GPU,与其他任务共享时(2026-10-11) 图像编码 40ms,提示词预测 26ms,视频追踪单个物体 36–38ms/帧,GPU 内存 0.68–0.96GB
CPU 临时 Pod(2026-10-12) 每帧中位数 1.14 秒。在 GPU 显卡碎片被占满的其他日子里为 5.9 秒

如果在比赛开始时仅运行一次,几秒钟内即可在 CPU 上完成。由于不需要在每一帧都运行,这些数字意味着没有必要非得使用 GPU。

对得分的影响

在同一天、同一引擎下仅更换边缘时,实际比赛 275 个回合的胜者准确度为 82.5%(掩码边缘)→ 84.7%(SAM 边缘) 。远端边缘附近的击球落入带内是主要原因。此后随着引擎的更改,SAM 边缘的准确度提升到了 93.1%,但由于该边缘文件未保留,2026-10-13 的基准线(91.3%,录像评分)重新使用掩码边缘进行了测量。由于两者的引擎也不同,无法单独剥离出边缘的贡献率。这意味着“记分牌决定服务器”的整场比赛模式下,掩码边缘的数据明显低于 SAM 记录。这说明边缘质量对记分牌的走势有着巨大影响。

未采用的内容

我们曾尝试用 SAM 2 处理乒乓球台以外的事物,但由于没有实际测量收益而未采用。

尝试 结果 判断
使用 SAM 2 追踪球 与运动员重叠的球的召回率 0.879 → 0.926。但击球精度下降,对回合胜者无增益 未采用
使用运动员掩码判定“被遮挡而不可见” 只能捕捉到 60% 重叠的球 未采用
用物理轨迹填补空白 回合胜者 −2 ~ −6 未采用

在 OpenTTGames 之外

视频 结果
国际比赛转播 30 个剪辑(640×360) 数据边缘与顶点最大差中位数 4.5px,90% 为 16px。有一个剪辑连观众席也被涂色,达到 158px
实验室斜角相机 3 个 顶点最大误差 25–28px(约 20° 的尖锐顶点导致微小的直线误差将交点推得很远)
实验室侧面相机 3 个 其中两个因为阴影导致 87–97px。 quad_ok 被过滤,区域准确度达到 0.99

6. 总结与展望

无需标签即可自动查找球台的功能(在比赛视频中查找并追踪球台)正在其他任务中开发。该结果以及 EfficientTAM 的实测数据得出后,将补充到本文中(正在进行中)。

TT 点评。 球台边缘被运动员的手臂遮挡也没关系。因为只需将可见的两条边延长并找到它们的交点即可。

延伸阅读原理。 SAM 系列的原始论文和学习原理已整理在 视觉模型阅读 第 1、9 篇:Segment Anything, 第 10 篇:SAM 2 与 EfficientTAM, 第 11 篇:SAM 3中。

参考

登录后即可点赞

评论

还没有评论。

登录后即可发表评论