ステータス:ドラフト(2026-10-13)、実測値を反映。 私たちの数値は、2026-10-11〜12にサーバーGPU(24GBクラス)でBlurBallの事前学習済み重みを微調整なしで実行して測定した値です。論文の数値は「論文」として別途表記しました。引用したオリジナルのリポジトリコードはコミットを記載し、短く省略しています。 作成ルール:実測データを基本とし、測定できなかったものは「未実測」と表記します。
目次
- 1. なぜ難しいのか
- 2. 直感:座標を当てず、地図を描く
- 3. 系統:TrackNet → TTNet → TrackNetV2 → WASB → BlurBall
- 4. HRNet:解像度を最後まで維持する
- 5. 私たちのコードでは
- 6. 実測:どれほど検出できるか
- 7. なぜ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つあります。
- 位置の誤差をピクセルごとに学習する。 座標を2つだけ当てる場合、「どれくらい外れたか」というシグナルは2つの数字だけですが、マップであれば数万個のピクセルそれぞれが学習のシグナルを与えます。小さな物体ほどこの違いが大きくなります。
- 「ボールなし」が自然である。 マップ全体が暗ければボールがないということです。座標回帰は、ボールがないときでもどこかの座標を指し示してしまいます。
- 候補が複数存在し得る。 ボールと蛍光灯の両方が明るく塗られた場合、両方を候補として残し、後から軌跡がつながる方を選ぶことができます。
2つ目の考え方は、 複数のフレームを一緒に入力することです。1枚だけ見ると白い点がボールなのか蛍光灯なのかわかりません。連続する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ライセンス)。変更されたのは主に ラベルです。
- ボールがブレている場合、痕跡の 中心に点を打ちます。終点(TrackNet方式)はシャッターが閉じるタイミングによって前後に揺れますが、中心は露光の中間時刻であるため一定です。フレームごとに同じ「時刻のボール」を指し示すため、軌跡が滑らかになります。
- ブレの 方向θと半分の長さl も一緒にラベルとして付与します。正解ヒットマップは1つの点ではなく、痕跡全体を覆う細長い山状になります。
- バックボーンの各ブロックにチャンネル注意機構(SEブロック)を追加し、損失関数をQuality Focal Lossに変更しました。
入力と出力の形状は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つあります。
- 同じWASBでも、中央ラベルで学習した方が端点ラベルよりも優れている。 速いボールにおいて0.855対0.513です。ブレの中央ラベルに効果があるという直接的な根拠となります(OpenTTGamesのマーカーが中央寄りに近いことも一因です)。
- 性能向上分のほとんどは、モデルを追加したからではなく、閾値を下げたことによって生じたものである。 BlurBall単体でも閾値を0.3に下げるだけで、再現率が0.979から0.985に上がります。WASBを加えて生じた純粋な利得は、再現率+0.35%pです。
- きれいな映像では、ボールをさらに探してもスコアは上がらなかった。 テスト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なのか、そして次へ
- 60fpsのスマホ映像に適したラベル。 シャッターが長いほどブレが長くなり、その際に中央ラベルの利点が大きくなります。
- 小さく速い。 149万パラメータ、1ウィンドウ10.5ms。同じカードで複数のセッションを受け付ける余地があります。
- コードはMIT。 ただし、重みと学習データにはライセンス表記がありません。論文の付録には「クリエイティブ・コモンズ・ライセンスの公開オンライン映像」でデータを作成したと記載されており、学習映像のリストが公開されていないため、OpenTTGamesと重複しているかは不明です。そのため、私たちの実測値を 汎化性能として捉えていません。
次の段階として、遮蔽に強いモデル(遮蔽フレームでのみ有効にする条件付きモデル)と、スマホ映像によるファインチューニングを調査・実験しています。結果が出次第、この記事に追記します(進行中)。スマホ映像の人手による正解ラベルセットがまだ存在しないことが最大の課題です。
ティティの一言。 同じWASBモデルですが、ブレたボールの端の代わりに「中央」を捉えるよう学習させたところ、高速なボールの再現率が0.51から0.86になりました。ラベルの約束事ひとつ分の力です。
原理をさらに読む。 ヒートマップと解像度を最後まで維持する構造については ビジョンモデルを読む 第1・3回:U-NetとHRNet、ヒートマップ損失と学習手法については 第4回:損失関数と学習手法に元の論文とともにまとめました。
参考
- TrackNet: https://arxiv.org/abs/1907.03698
- TTNet: https://arxiv.org/abs/2004.09927
- WASB: https://arxiv.org/abs/2311.05237, https://github.com/nttcom/WASB-SBDT (MIT)
- BlurBall: https://arxiv.org/abs/2509.18387, https://github.com/cogsys-tuebingen/BlurBall (コードはMIT、重み・データライセンスの表記なし)
- HRNet: https://arxiv.org/abs/1902.09212
- 私たちの測定:リポジトリ
docs/tt/PERFORMANCE.md2.1・2.8・2.9、リアルタイムデモ設計文書 第9節