TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

ボール検出:TrackNetからWASB、BlurBallまで — ヒットマップ、複数フレーム、ブレの中心

ステータス:ドラフト(2026-10-13)、実測値を反映。 私たちの数値は、2026-10-11〜12にサーバーGPU(24GBクラス)でBlurBallの事前学習済み重みを微調整なしで実行して測定した値です。論文の数値は「論文」として別途表記しました。引用したオリジナルのリポジトリコードはコミットを記載し、短く省略しています。 作成ルール:実測データを基本とし、測定できなかったものは「未実測」と表記します。

目次

1. なぜ難しいのか

卓球のボールは直径4cmです。サイドからテーブル全体が映るように撮影した1080pの研究室用動画(TT3D)で測定したボールの直径は中央で11〜12pxであり、モデルに入力するために512×288に縮小すると3pxほどになります。そのうえ、スピードが速いです。時速70km(約20m/s)で飛ぶボールは、1/120秒の露光中に17cm、1/60秒の露光中に33cm移動します。ボールの直径の4〜8倍の長さで ブレた痕跡が写るという意味です。その上を選手の腕やラケットがボールを隠し、天井の蛍光灯や白いシューズがボールのように見えてしまいます。

そのため、一般的な物体検出器(人間や自動車を見つけるバウンディングボックス検出器)をそのまま使ってもうまくいきません。この分野にはボールだけを探す独自の系統があり、TT Scoreが使用しているBlurBallはその最先端にあります。

2. 直感:座標を当てず、地図を描く

ボール検出モデルは「ボールは(x, y)にある」と直接2つの数字を出力しません。代わりに、入力と同じサイズの白黒画像、すなわち ヒットマップを描きます。ピクセルごとに「ここがボールである確率」を0〜1で塗ったマップです。正解データもボールの位置に小さなガウス分布の山を描いたマップとして作成し、ピクセルごとに比較します。

このようにする理由は3つあります。

  1. 位置の誤差をピクセルごとに学習する。 座標を2つだけ当てる場合、「どれくらい外れたか」というシグナルは2つの数字だけですが、マップであれば数万個のピクセルそれぞれが学習のシグナルを与えます。小さな物体ほどこの違いが大きくなります。
  2. 「ボールなし」が自然である。 マップ全体が暗ければボールがないということです。座標回帰は、ボールがないときでもどこかの座標を指し示してしまいます。
  3. 候補が複数存在し得る。 ボールと蛍光灯の両方が明るく塗られた場合、両方を候補として残し、後から軌跡がつながる方を選ぶことができます。

2つ目の考え方は、 複数のフレームを一緒に入力することです。1枚だけ見ると白い点がボールなのか蛍光灯なのかわかりません。連続する3枚を見ると、動いているものと静止しているものが区別がつきます。人間が静止画よりも動画のほうがボールを簡単に見つけられるのと同じです。

입력: 연속 3프레임(RGB) → 9채널 288×512 t−2, t−1, t HRNet 계열 몸통(149만 파라미터) 원래 해상도 1/8 해상도 갈래를 나란히 두고 계속 섞는다 출력: 히트맵 3장(0–1, 프레임마다 한 장) 실시간은 마지막 장만 쓴다 후처리: 0.5 로 자르기 → 이어진 덩어리 찾기 위치 = 히트맵 값으로 가중한 중심, 신뢰도 = 값의 합 덩어리 주축 길이·각도 = 번짐의 길이·방향 라벨을 어디에 찍나 — 번진 공 하나 끝점(최신 위치) 가운데(BlurBall) 노출 시간 동안 공이 지나간 자국 끝점은 셔터가 닫히는 순간에 달려 흔들리고, 가운데는 노출의 중간 시각이라 일정하다
図:連続する3フレームを入力して3枚のヒットマップを受け取り、塊に絞り込むプロセス、およびブレたボールのラベル位置

3. 系統:TrackNet → TTNet → TrackNetV2 → WASB → BlurBall

TrackNet(2019):ヒットマップと複数フレーム

テニス中継でボールを見つけるために作られたモデルです(arXiv 1907.03698)。VGG-16の前半13層で特徴量を抽出し、デコンボリューションで再度拡大して640×360のヒットマップを出力します。1枚を入力するバージョン(Model I)と連続3枚を入力するバージョン(Model II)を比較したところ、精度と再現率が95.7% / 89.6%から99.8% / 96.6%に向上しました(論文、同じ試合を7:3に分割して評価)。複数フレームの効果を数値で示した最初の事例です。

デコードは単純です。ヒットマップを128(0〜255のうち)で閾値処理して白黒画像にし、ハフ変換(Hough)で円を検出し、 正確に1つの場合のみ その中心をボールとして出力します。同じ論文での、試合を混ぜていない10分割交差検証では再現率が75.7%でした。同じ試合内ではうまく機能していたモデルが、初めて見る試合では大きく性能が低下するという、この分野で繰り返される教訓がここですでに現れています。

ラベルのルールも記憶に値します。TrackNetは、ボールがブレている場合、痕跡の**最新の位置(前方先端)**に点を打っていました。BlurBallがまさにこのルールに疑問を投げかけます。

TTNet(2020):卓球、120fps、そしてOpenTTGames

卓球の試合をリアルタイムで分析するために作られたモデルであり(arXiv 2004.09927)、私たちが評価に使用している OpenTTGamesデータセットがこの論文とともに公開されました。120fpsのフルHD動画から連続9フレームをチャンネル方向へ積み重ねて(入力27チャンネル)入力します。画面全体を320×128に縮小しておおよその位置を見つけ、元の解像度でその位置を切り抜いて精密に検出する2段階構造です。

出力が興味深いです。2Dヒットマップではなく、 長さ320と128の2つの1Dベクトル(x軸、y軸)です。2Dマップを2つの軸の1D分布に分割すると計算量が減ります。のちのRTMPose(SimCC)も同じ考え方を使用しています。論文では、イベント認識が97.0%、ボール検出が97.5%、パイプライン全体で6.0ms(デスクトップ向けGPU 1枚)と報告されています(論文)。私たちの60fpsスマートフォン動画環境では未実測です。

TrackNetV2(2020):3枚入力して3枚受け取る

バドミントン用に修正したバージョンです。U-Net構造に変更し、 3枚を入力して3枚のヒットマップを一度に受け取る(MIMO)方式により、速度を2.6 FPSから31.8 FPSに向上させました(論文アブストラクト)。入力サイズの288×512と「3枚入力して3枚受け取る」という仕様は、以降WASBとBlurBallにそのまま引き継がれます。

WASB(2023):小さなHRNetと塊の後処理

サッカー・テニス・バドミントン・バレーボール・バスケットボールの5種目で広く使用するために作られたベースラインモデルです(BMVC 2023, arXiv 2311.05237, コードはMITライセンス)。卓球はその5種目に入っていません。バックボーンは小さなHRNet(後述のセクション4)であり、パラメータ数は150万個です。入力は (B, 9, 288, 512)であり、すなわちRGB 3チャンネル×3フレームであり、出力はシグモイド関数を通過した3枚のヒットマップです。

後処理には、TrackNetの円検出の代わりに 連続した塊の検出を使用します。オリジナルのリポジトリの該当部分です。

# nttcom/WASB-SBDT src/detectors/postprocessor.py @923462ca, 49–59행(MIT, NTT Communications)
if np.max(hm) > self._score_threshold:
    th, hm_th        = cv2.threshold(hm, self._score_threshold, 1, cv2.THRESH_BINARY)
    n_labels, labels = cv2.connectedComponents(hm_th.astype(np.uint8))
    for m in range(1,n_labels):
        ys, xs = np.where( labels==m )
        ws     = hm[ys, xs]
        if self._use_hm_weight:
            score  = ws.sum()
            x      = np.sum( np.array(xs) * ws ) / np.sum(ws)
            y      = np.sum( np.array(ys) * ws ) / np.sum(ws)

0.5で閾値処理し、塊ごとにヒットマップの値で重み付けした中心を位置とし、値の 和をスコアとします。和を用いることで、大きくて濃い塊が優先されます。フレーム間を繋ぐトラッカーについて、論文では「位置+速度+加速度/2」で次の位置を予測すると説明されていますが、公開コードでは予測を使用せず、 直前の位置から300px(オリジナル解像度基準)以内の候補の中でスコアが最も高いものを選択します。論文とコードが異なる場合、コードが実際の動作となります。

BlurBall(2025–2026):ブレの中心

卓球用にWASBを修正したモデルです(arXiv 2509.18387, CVPRW 2026, コードはMITライセンス)。変更されたのは主に ラベルです。

入力と出力の形状はWASBと同じです((B, 9, 288, 512) → 3枚のヒットマップ)。ブレの長さや角度は、専用の出力ヘッドではなく、 ヒットマップの塊の形状から後処理で読み取ります。

# cogsys-tuebingen/BlurBall src/detectors/blurball_postprocessor.py @2f0f5496, 48–50·60–71행에서 줄임(MIT)
angle = 0.5 * np.arctan2(2 * moments["mu11"], moments["mu20"] - moments["mu02"])
mean, eigenvectors = cv2.PCACompute(coords.astype(np.float32), mean=None)
principal_axis = eigenvectors[0]                 # 가장 긴 축
projected = np.dot(coords - mean, principal_axis)
l = projected.max() - projected.min()            # 79행에서 l / 2 를 반길이로 저장

ここにも論文とコードの間で2つの違いがあります。論文では塊の信頼度をヒットマップの 平均と説明していますが、コードはWASBと同様に 和です。角度についても、論文は主軸ベクトルで、コードは画像モーメントで求めています。

論文の数値(独自の卓球テストセット、288×512、デスクトップ向けGPU)はF1スコア96.52、79 FPSとして頻繁に一緒に引用されますが、 これら2つの数値は異なる設定の値です。96.52はウィンドウを1フレームずつ進める1ステップ推論、79 FPSは3フレームずつ進める3ステップ推論です(3ステップのF1スコアは96.16)。1ステップのFPSは論文の表に記載されていません。

4. HRNet:解像度を最後まで維持する

WASBとBlurBallのバックボーンはHRNet(CVPR 2019, arXiv 1902.09212)系統です。通常の分類モデルは画像を1/2、1/4、… 1/32へと縮小しながら「それが何であるか」を圧縮します。3pxのボールは1/8に縮小しただけでも1ピクセルより小さくなってしまいます。再び拡大しても位置がぼやけてしまいます。

HRNetは、 元の解像度のブランチを最後まで維持し、低解像度のブランチを1つずつ横に追加して並行して実行し、ブランチ間で情報を繰り返し統合します。 低解像度のブランチは「この近辺に人がいてテーブルがある」という広いコンテキストを、高解像度のブランチは「正確にこのピクセル」を担当します。私たちが使用している設定は、ブランチが4つ、チャンネル数が16・32・64・128であり、最初Stemのストライドを1に設定して入力解像度をそのままブランチに入力します(私たちのコードの "STEM": {"STRIDES": [1, 1]}).

5. 私たちのコードでは

モデルのコード(MITライセンス)はリポジトリに含めず、サーバーのストレージ上に配置して読み込みます。他人のコードを取り込むと、そのコピーを自分たちで追従して修正しなければならないためです。リポジトリにはその周辺部分のみが存在します(live/blurball.py, batch/model_blurball.py).

重みの検証。 ダウンロードした重みはsha256を照合した後に torch.load(weights_only=True) としてのみ読み込みます。PyTorchのチェックポイントはpickle形式であるため、任意のコードを実行する可能性があります。照合が異なる場合や weights_only で読み込めないと起動を停止します。

前処理。 RGB、512×288、ImageNetの平均・標準偏差で正規化します。ブラウザがすでに512×288に縮小して送信するため、サーバー側でサイズ変更は行いません。以前、痛い目に遭ったことがあります。デコードした配列を transpose で軸を入れ替えて積み重ねたところ、テンソルが channels_last のバッチになってしまい、cuDNNがウォームアップ時とは異なる計算パスを通ってしまいました。ウォームアップ後の最初の実フレームに 12.5秒 かかりました。 np.ascontiguousarray 1行で修正しました。

後処理。 オリジナルリポジトリと同じ塊の検出に加え、画面表示用の値をもう1つ出力します。

# live/blurball.py blobs() 에서 줄임
_, th = cv2.threshold(hm, SCORE_T, 1, cv2.THRESH_BINARY)          # SCORE_T = 0.5
n, labels = cv2.connectedComponents(th.astype(np.uint8))
for m in range(1, n):
    ys, xs = np.where(labels == m); ws = hm[ys, xs]; sw = float(ws.sum())
    x, y = float((xs * ws).sum() / sw), float((ys * ws).sum() / sw)
    pts = np.stack([xs, ys], 1).astype(np.float64); pts -= pts.mean(0)
    vals, vecs = np.linalg.eigh(np.cov(pts.T))                     # 주축 = 번짐 방향
    pm = pts @ vecs[:, 1]; length = float(pm.max() - pm.min()) / 2.0
    out.append({"x": x, "y": y, "score": sw, "peak": float(ws.max()), "len": length, ...})

score(和)はトラッカーが候補を選ぶ際に、 peak(最大値、0–1)は画面上の信頼度表示に使用します。和は塊のサイズに応じて1を超えるため、人間に提示する値としては適していません。角度はオリジナルリポジトリとは異なり、共分散の主軸から求めます。

リアルタイムと録画済みの違い。 リアルタイムでは新しいフレームごとに直近3枚のウインドウを回し、 最後のヒートマップのみ を使用します(OUT_INDEX = 2)。録画済みでは重複しない3枚のウインドウで3枚すべてを使用します。そのため、リアルタイム処理ではフレームあたりのGPUの仕事量が3倍になります(第1部第3節)。

6. 実測:どれくらい検出できるか

基準線(2026-10-11)

条件:OpenTTGamesテスト7試合、オリジナル120fpsの偶数フレームのみ(60fps)、マーカー付きフレーム3,483枚のみ、512×288で正解から5px以内であれば正解。重みは事前学習のまま。

指標 値
適合率 / 再現率 0.9817 / 0.9785
位置誤差(RMSE) 0.594px(512基準)、オリジナル1920基準で2.23px
境界前後±2フレームの再現率 0.988
時間(ウインドウ1つ、録画方式) フレームあたり3.42ms(293fps)、8ウインドウバッチ2.81ms
時間(ウインドウ1つ、リアルタイム方式、空カード) 10.5ms(fp32)、8.0ms(fp16)
GPUメモリ(torchの最大予約量) fp32 ウインドウ1つ 0.35GB、8つ 2.6GB、16つ 5.2GB。fp16 ウインドウ1つ 0.22GB
モデル 重み 5.8MB、149万パラメータ

弱点は3つありました。

ボールの状態 再現率
ほぼ静止したボール(60fpsのフレーム間隔の元データで10px未満) 0.790
選手と重なったボール(データセットの人物マスクと重なる) 0.863
非常に速いボール(80px以上) 0.855

静止したボールは大部分がサーブ前の手に持ったボールであるため、スコアへの影響は少なくなります。重なったボールと速いボールが実際に痛いところです。

test_7の適合率は0.882と単独で低かったですが、モデルのせいではありませんでした。 この試合には「ボールなし」のマークが104箇所ありますが、そのうち41箇所は実際にボールが見えています(6枚を目視で確認)。モデルが正しく見つけたボールが「存在しないボールを見つけた」と採点されたのです。この41箇所を除くと0.9937になります。データセットの話は第7部でさらに詳しく説明します。

他のモデルとの比較(2026-10-11~12)

BlurBallの著者が公開した比較モデルの重みを用いて、同一条件で測定しました。

モデル 適合率 / 再現率 重なったボール 静止したボール 速いボール
BlurBall(閾値 0.5) 0.982 / 0.979 0.863 0.790 0.855
BlurBall(閾値 0.3) 0.980 / 0.985 0.903 0.862 0.868
WASB、中央ラベルで学習 0.985 / 0.964 0.879 0.707 0.855
WASB、端点ラベルで学習 0.976 / 0.934 0.774 0.696 0.513
MonoTrack 0.983 / 0.949 0.798 0.646 0.829
TrackNetV2 0.992 / 0.822 0.669 0.398 0.566
ResTrackNetV2 0.993 / 0.712 0.532 0.160 0.237
BlurBall + WASB、ヒートマップの最大値で結合(閾値 0.3) 0.980 / 0.989 0.935 0.895 0.882

読み取るべきポイントは3つあります。

  1. 同じWASBでも、中央ラベルで学習した方が端点ラベルよりも優れている。 速いボールにおいて0.855対0.513です。ブレの中央ラベルに効果があるという直接的な根拠となります(OpenTTGamesのマーカーが中央寄りに近いことも一因です)。
  2. 性能向上分のほとんどは、モデルを追加したからではなく、閾値を下げたことによって生じたものである。 BlurBall単体でも閾値を0.3に下げるだけで、再現率が0.979から0.985に上がります。WASBを加えて生じた純粋な利得は、再現率+0.35%pです。
  3. きれいな映像では、ボールをさらに探してもスコアは上がらなかった。 テスト7試合57ラリーの勝者正解数は、BlurBallが53、閾値0.3が52、両モデルの統合も52でした。閾値を下げると偽のバウンドも増えるためです。

悪い条件下で(2026-10-11〜12)

同じテスト映像に決定的な劣化を加えて測定しました(ボール検出再現率)。

条件 BlurBall単体 BlurBall + WASB統合(0.3)
きれい 0.979 0.989
暗い + センサーノイズ 0.685 0.820
低解像度(320×180に縮小してから拡大) 0.898 0.957
長時間の露光(1/60秒のブレ) 0.841 0.869
ボールの遮蔽(肌色の楕円) 0.842 0.868
ボールに似た障害物 0.969 0.927
30fps 0.984 0.985

暗い場所では2つのモデルの統合が大きく役立ちましたが(テスト57ラリーの勝者36 → 45)、ボールに似た障害物の前ではWASBが偽の検出を多く出してしまい、かえって悪化しました。 常に勝つ組み合わせはありませんでした。 そして統合するとGPU時間が倍になります(1ウィンドウ10.5 → 20.0ms)。そのため現在はBlurBallひとつだけを常時稼働させ、「ボール信号が弱い時だけ2番目のモデルを起動する」方法を実験中です(進行中)。

OpenTTGames以外で(2026-10-11)

映像 適合率 / 再現率 何が間違っていたのか
OpenTTGamesテスト(基準) 0.982 / 0.979 —
TT3Dラボ、側面50fps 0.929 / 0.860 見逃したものの大部分はサーブ前に手に持ったボール
TT3Dラボ、斜め60fps 0.894 / 0.820 遠くの不正解20枚のうち15枚が 天井の蛍光灯。画面上の帯を除くと適合率0.956
LATTE-MV国際試合中継、30fps 640×360 0.815 / 0.722(一致度) 不一致40枚のうち25枚は 同じブレ痕のもう一方の端を指していたもの

最後の行がこの記事の主題をそのまま示しています。LATTE-MVのボール座標は人間のラベルではなく、そのデータパイプラインの追跡結果であり、痕跡の一方の端を指しています。BlurBallは中央を指しています。両者が「異なる」と採点されたものの62%は、実際には同じボールでした。ラベルのルールが異なれば、同じモデルでも採点結果が変わります。

7. なぜBlurBallなのか、そして次へ

次の段階として、遮蔽に強いモデル(遮蔽フレームでのみ有効にする条件付きモデル)と、スマホ映像によるファインチューニングを調査・実験しています。結果が出次第、この記事に追記します(進行中)。スマホ映像の人手による正解ラベルセットがまだ存在しないことが最大の課題です。

ティティの一言。 同じWASBモデルですが、ブレたボールの端の代わりに「中央」を捉えるよう学習させたところ、高速なボールの再現率が0.51から0.86になりました。ラベルの約束事ひとつ分の力です。

原理をさらに読む。 ヒートマップと解像度を最後まで維持する構造については ビジョンモデルを読む 第1・3回:U-NetとHRNet、ヒートマップ損失と学習手法については 第4回:損失関数と学習手法に元の論文とともにまとめました。

参考

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます