ステータス:下書き(2026-10-26)、実測値を反映。 ルールは2026-10-25時点のリポジトリコード(
ttscore/,backend/static/tt/play/)から読み取ったものであり、数値は公開された試合映像を用いて私たちが直接測定した値です(日付や条件は表ごとに異なります)。スマートフォンで撮影した実際の卓球場の映像については、ラベル付き映像がまだないため未測定です。 作成ルール:実測データを基本とし、測定できなかったものは「未測定」と表記します。
目次
- 1. 自動判定はスコアボードではなく「提案」である
- 2. ボール座標からイベントへ
- 3. 審判:「誰が打ったか」は見ない
- 4. ラリーのまとめ:デッドボール、勝者、再同期
- 5. 「確認必要」:4つの分岐
- 6. ウォーミングアップ・練習と試合をどのように区別するか
- 7. チップ:人間と機械の役割分担
- 8. 実測:現在どこまで正確か
- 9. まとめ
- 参考文献
- 関連記事
1. 自動判定はスコアボードではなく「提案」である
最初に立場を明確にしておきます。カメラ型スコアボードの本当のスコアは、第2部で見た 人間が入力して修正した履歴です。自動判定はその履歴に対して「今回のラリーはAの得点、理由はアウト、信頼度0.83」のような 提案を1つずつ提示するだけであり、確定した提案のみが履歴の得点欄になります(backend/static/tt/play/lib/camauto.mjs まるがき「スコアボードは一つである」)。そのため、自動判定が間違っていたとしても、人間が修正した履歴と個別に合わせる必要はありません。
この記事では、ボール座標が入力された瞬間からチップがスコアになるまでを追っていきます。
- ボール座標 → イベント(バウンド・ネット・アウト・エンド)
- イベント → 審判ステートマシン → ラリーの勝者
- ラリーのまとめ、デッドボール、再同期
- 「確認必要」 判定
- チップと自動反映、人間と機械の役割分担
- その合間で ウォーミングアップや練習を試合と区別する仕組み
ボール座標がどのように生成されるかについては第4部で、前シリーズの 第5部ではこのエンジンが66.9%から93.1%に向上した歴史を扱っています。この記事は 現在の運用で動作しているルールの全体像です。
2. ボール座標からイベントへ
ワーカーはボール座標を直近の2.5秒間のみ保持し(EventFeed, KEEP_S=2.5)、新しいサンプルが入力されるたびに短い空白(3サンプル以下)を埋めてイベントを再抽出します(ttscore/events.py の detect_events)。リアルタイム処理であるため未来を見ることはできず、 直近の区間を継続的に再読み込みする方式をとっています。
バウンド:5つの条件をすべて満たすこと
バウンドは、以下の条件を すべて 満たす場合にのみ発生します。
| 条件 | 内容 | 理由 |
|---|---|---|
| V字 | 画面のyが増加し(下降)、減少する(上昇) | 跳ねるボールの基本的な形状 |
| 卓球台の帯の内側 | 卓球台の上面の四角形の内部。各辺に8pxの余裕、エンド側は平面上で0.15m外側まで | バウンド瞬間のボールの中心は接触点よりも半径(20mm)分高いため、平面に投影するとズレが生じる |
| 間隔 | 直前のバウンドと0.12秒以上離れていること | 同じバウンドを二重にカウントしないため |
| 横方向の動きの維持 | 前後の水平移動がどちらも2px以上であり、かつ方向が逆の場合は破棄 | 卓球台で跳ねたボールは進み続け、ラケットに当たったボールは引き返していく。ラケットでの打球もV字を描く |
| ネット付近のV字の破棄 | ネットのx ±2%の範囲内であり、ネット接触の±2フレーム以内のV字は破棄 | ネットにかかったボールの揺れ |
エンド側から撮影した画面(カメラがエンドラインの後方にある場合)では、もう1つの項目を確認します。長さ方向の速度が0.3m/s以上でなければなりません。エンド側の画面では、画面のy座標に高さと距離が混ざり合って偽のV字が生成されやすいためです。
バウンドのサイド(L・R)は平面上で判定する
バウンドがどちらのプレイヤー側に落ちたかは、画面の左右ではなく 卓球台の平面座標によって決定します。卓球台の4つの頂点から画面から平面(2.74m × 1.525m)へのホモグラフィを作成し(第4部)、ボールが接触した点を平面に投影して、長さ方向の座標uの符号によりサイドを決定します(ttscore/table.py の ball_side)。このようにすれば、サイドから撮影しようとエンドから撮影しようと同じ数式になります。四角形が長方形として読み取れないほど歪んでいる場合(頂点が誤っている可能性)は、従来のルール、すなわちネットのx基準線の左右に戻ります。歪んだ四角形の平面変換は、ネット線を傾けてサイドを反転させてしまうためです。
平面投影は ボールが卓球台に接触した瞬間のみ 正しく機能します。空中のボールを平面に投影すると、カメラからボールを通る直線が平面と交わる点が求められ、ボールが高いほど遠くにずれます。そのため、平面座標はバウンドのサイドにのみ使用し、空中区間の判定は別に行います。
横方向の速度:判定の鍵
各バウンドには、長さ方向の水平速度が一緒に記録されます。バウンドの直後 vx、直前 vx_inであり、単位は テーブルの長さ/秒です。ピクセルではなく卓球台の長さで割った値であるため、カメラの距離や解像度に依存しません。この1つの数値が、次のセクションの審判において、サーブ、リターン、偽のバウンドを区別します。
ネット、アウト、エンド
- ネット:ボールがネットのx ±4%を通過し、前後6フレームの水平速度比が0.7を下回った場合(前方の速度が0.3以上の場合のみ)、ネットに接触したとみなします。サイドからの映像でのみ発生します。
- アウト:バウンドの後でのみ有効になります。ボールが卓球台のエンドを超えて飛行し、上面の帯の下に落ちた場合はアウトであり、サイドはその通過したエンド側になります。
- エンド:最後のバウンドから 1.5秒間 新しいバウンドがない場合、またはボールが 1秒間 検出されなかった場合、ラリーが終了したとみなします。1.5秒という値は、実際のラリー内のバウンド間隔の98%が1.5秒以内であるという実測値に基づいています(公開試合12試合、2026-10-11)。
信頼度
イベントごとに信頼度が付与されます。 conf = min(검출 신뢰도, 공백 신뢰도) であり、空白の信頼度は 1 − 놓친 프레임 / (fps × 0.25초) です。直前にボールを0.25秒以上見失っていた場合は0になります。その間にバウンドを1つ見落とした可能性があるため信頼できないという意味です。検出の信頼度のみを別途(det_conf)も記録しており、サーブの最初のバウンドで使用されます。
3. 審判:「誰が打ったか」は見ない
審判(ttscore/referee.py の RallyReferee)の中核となる考え方は、前文の最初の行にあります。 誰が打ったかは映像から直接確認せず、バウンドの順序から推論する。 ボールがS側にバウンドした場合、次はS側のプレイヤーが打ち返して反対側に1回落としさなければなりません。それができなければ(同じ側に再びバウンドする、バウンドせずに外に出る、何事もなく終了するなど)、S側のプレイヤーの失点となります。サーブのみ例外として、サーバー側に1回、そしてレシーバー側に1回落ちる必要があります。
ラケットとの接触は、映像から検出するのが最も困難なイベントです。ボールがラケットに隠れたり、ぶれたり、選手の体と重なったりするためです。この設計はそのイベントなしでスコアリングを行います。その代償も明確であり、1つのバウンドを見落としたり、1つの偽のバウンドが混入したりすると、「どちらの番か」が全体的に狂ってしまいます。そのため、以下のルールの大部分は 偽のバウンドやラリー外のボールを除外する仕組みになっています。
状態は4つあります。 serve0(サーブ待ち) → serve1(サーブ進行)または direct(直行) → rally(ラリー) → done(エンド)。
サーブ待ち:サーブの開始のみを受け付ける
ラリーとラリーの間も、ボールは多く動きます。ボールを拾いに行ったり、相手に返したり、サーブの前に自分の台で何度か弾ませたり、手に持ったボールが揺れたりします。これらがイベントとして捉えられると、存在しないスコアが生まれてしまいます。実際に試合全体をエンジンに入れた最初のテストでは、49件の判定のうち20件がラリー中ではありませんでした(ボールの返却13、サーブ前の自陣でのバウンド5...、2026-10-11、 docs/tt/PERFORMANCE.md 2.6).
そのため、サーブ待ちの状態では サーブの開始のように見えるボールのみ を受け付けます。
# ttscore/referee.py _start() 에서 줄임
f = self._forward(ev, self.server, serve=True) # 서버 쪽에서 네트 쪽으로 가는 가로 속도(바운드 앞뒤 중 큰 쪽)
if ev.side == self.server:
if f is not None and f < SERVE_MIN_VX: # 0.7 테이블 길이/초
return False # 서브가 아니다 — 버리고 계속 기다린다
self.phase = "serve1"
return True
if f is not None and f >= TOWARD_NET_VX: # 리시버 쪽에, 서버 쪽에서 날아온 방향으로 떨어진 공
self.phase, self.expect = "direct", self.server
self.conf = min(self.conf, AMBIGUOUS_CONF) # 0.5 — 서버 쪽 바운드를 못 봤으니 확인 필요
return True
return False
条件は2つあります。 スコアボードが認識しているサーバー側に落下し、, ネット方向へ0.7テーブルの長さ/秒以上 移動している必要があります。それ以外のボールは破棄され、信頼度も減点されません。0.7という数値の根拠は実測値です(公開試合12試合、2026-10-11)。
- 実際のサーブ271本の最初のバウンドは、ネット方向の速度がいずれも1.07以上でした(バウンドの前後で大きい方)。
- 0:0から続く5つの試合において、ラリー外のサーバー側のボール55個のうち、サーブのように見えた4個(試合前の練習サーブ、ラベルなしのサーブ)を除くと、0.61以下でした。返球されたボールはマイナスであり、自陣でのバウンドは0付近です。
スコアボードのサーバー側とは反対側で「サーブのように」開始されるボールは受け付けません。 レシーバーがサーバーにボールを投げ返した場合、レシーバー側からネット方向へ弾み、サーバー側に落下します。形はサーブと同じですが、サイドが逆になります。両方をサーブとして受け付ける変形により、5試合の誤判定が3から16に増加しました。
例外が1つあります。レシーバー側に、 サーバー側から飛んできた方向へ(ネットから遠ざかりながら0.7以上)落下したボールです。サーバー側のバウンドを見落としたサーブである可能性や、サーバー側にバウンドしなかったサーブ失敗である可能性があります。これは direct(直行)状態として受け付け、信頼度を0.5に下げて確認必要として送信します。サーブの最初のバウンドは、トスやラケットに遮られて見落とされることがよくあります。そのため、直行の後にレシーバー側に再び落下するか、そのまま終了した場合、スイッチ(TTSCORE_DIRECT_MISS、オンになっている)に従って レシーバーがレシーブできなかったこと(サーバーの得点) とみなします。実際の映像実験(ホストE27)において、「再び落下」判定60件のうち48件、「終了」判定49件のうち36件がサーバーの得点であったためです。
サーブ進行:1秒以内にレシーバー側へ
serve1 では、レシーバー側のバウンドを待ちます。
- レシーバー側のバウンドがサーバーのバウンド後 1.0秒以内 に到達すれば正常なサーブです(実際の最大値は0.55秒、30fps・スローサーブの余裕)。超過した場合はサーブ失敗(レシーバーの得点)となります。
- サーバー側に再び落下したが、ボールが依然としてネット方向へ(0.7以上)進んでいる場合は、ネットの上を通過する際に捉えられた偽のバウンドとみなし、サーブの監視を継続します(実測5件)。停止するか引き返してきた場合は、ネットに引っかかって自陣に落下したサーブ失敗です(実測2件、水平速度が0付近)。
- バウンドなしでアウトやエンドが発生した場合は、サーブ失敗(
serve_out)となります。
レット:次のイベントまで持ち越す
サーブ中にネットに接触し(net)レシーバー側でバウンドしていればレットの候補です。すぐにレットを宣言せず、 次のイベントまで保留します。
- 次のバウンドがサーバー側であれば、レシーバーがレシーブしたボールが落下したということです。ネットの検知が誤っていたか、選手がレットをコールせずに続行したため、レットを取り下げてラリーを継続しつつ、要確認(0.5)とします。
- そうでなければレットです。得点はありません(
winner_side=None).
当初はすぐにレットとしていましたが、レット判定後の「デッドボール」規則(第4節)によって継続した実際のラリーのバウンドがすべて破棄され、そのラリーの得点が消滅するという現象がリアルタイムのパイプラインで2回再現されました(2026-10-13)。判定を保留する方式によりその問題が修正され、オフラインのラリー単位の結果は変わりませんでした。
ラリー:期待する側を切り替えながら
ラリーでは「次のバウンドが落ちるべき側」(expect)のみを追跡します。
# referee.py feed() 의 랠리 부분에서 줄임
f = self._forward(ev, ev.side) # 바운드 뒤 가로 속도
if f is not None and f >= TOWARD_NET_VX: # 네트 쪽으로 가는 「바운드」 는 가짜 — 버린다
return None
if ev.side == self.expect: # 기대한 쪽: 정상. 다음은 반대쪽
self.expect = flip(self.expect)
return None
if ev.t - prev_bounce_t < DOUBLE_BOUNCE_SURE_S: # 0.25초 안의 같은 쪽 두 바운드 — 미룬다
self.pending = ev
return None
return self._call(flip(ev.side), "double_bounce", ev) # 같은 쪽에 또: 그 쪽 선수가 진다
- ネット方向に向かうバウンドは偽物。 ラリー中のS側における本当のバウンドは相手が打ったボールであるためS側のエンドに向かって、すなわちネットから離れる方向に進みます。ネット方向へ0.7以上進む「バウンド」は、ネット上を通過するボールや打球を誤読したものです(実際のラリー1,094件中56件)。この単一のルールにより、実際のラリー勝者が235から241に増加しました。
- ツーバウンド。 同じ側に再び落下した場合、その側の選手がレシーブできなかったことになります。
- 速いツーバウンドは保留する。 同じ側の2つのバウンドの間隔が0.25秒未満である場合、それが本当に短いボールなのか、バウンド直後の打球をバウンドとして誤読したのかを判別できません。実測において、このような判定39件中16件(41%)が誤りでした(残りの判定の誤答率は8%)。そのため信頼度を0.5に下げ、次のイベントまで保留します。次のバウンドが反対側であれば(リターンが落下)、2番目のバウンドは偽物であったため破棄してラリーを続行します。このルールにより判定が変更された実際のラリー9件は、すべて不正解から正解に変わりました。
アウトとエンド:最後のバウンド側の選手がレシーブできなかった
- アウト:直前にバウンドした側のエンドを越えるアウトは無視します。その側の選手が卓球台の高さより下でレシーブしようとしているボール(ツッツキ、中陣ドライブ)であるためです。レシーブできなければエンド(end)が決定します。
- その他のアウトやエンドは「最後にバウンドした側の選手が返球できなかった」ことを意味します。その選手が失点し、理由は
outまたはno_returnです。
判定の信頼度
1つの判定の信頼度は、そのラリー内のイベント信頼度の 最小値です。ただし、サーブの最初のバウンドは、空白期間を除いた検出信頼度(det_conf)で計算します。サーブの最初のバウンドの前にある空白期間は、トスやラケットによって遮られたために生じたものであり、その間に見逃されるバウンドは存在しないためです。これを取り除く前は、サーブの最初のバウンドの70%が信頼度0であり、それが「要確認86%」の主な原因でした(PERFORMANCE 2.6)。
4. ラリーのグループ化:デッドボール、勝者、再同期
1つの審判は1つのラリーを担当します。ラリーをつなぎ合わせてスコアボードを作成する作業は、 ttscore/analyze.py の LiveScorer が行います。
- 試合開始前は破棄する。 「試合開始」の時刻(
start_t)より前のイベントはすべて破棄します。第6節の主題です。 - デッドボール2秒。 判定が下されると、その後の2秒間(イベントが継続する場合は継続して延長)、またはエンド(end)が来るまでイベントを破棄します。判定がバウンドやアウトで下された場合、ボールはまだ動き続けています。弾み続けるサーブ、ツーバウンド後に転がるボール、レットの後に打ち返されたボールなどがこれに該当します。それらを次のラリーのサーブとして読み取ってしまうと、存在しない得点が生まれてしまいます。
- サイドから選手へ。 審判はL・R側に対して勝者を決定します。スコアボード(
rules.Scoreboard)はコートチェンジを反映し、その側に現在誰が立っているかに応じて得点を加算します。次のラリーの審判におけるサーバー側は、スコアボードのサーブ順序に従って新しく決定します。 - 再同期信号。 スコアボードが1点ズレるとサーブ順序がズレ、その結果、実際のサーブが「サーバーの反対側から開始されたボール」として破棄されてしまいます。そのため、エンジンはラリーごとに 実際に確認されたサーブ側を個別に計測します。前のラリーの終了後から今回のラリーの開始+0.5秒までの間の最後の「サーブ形状のペア」(S側のバウンドがネット方向へ0.7以上 → 1.0秒以内の反対側のバウンド)におけるSです。これがスコアボードのサーバー側と 一度でも 異なる場合、信号を出力します。スコアは変更せず、人間に対して「サーブは合っていますか?」と確認を促すのみです。実測において、ラベル付けされたサーブの±0.8秒以内のペアは、ラベル付けされたサーバーと267件中266件が一致していました。「2回連続でズレた場合」にしなかった理由は、スコアボードが1点ズレるとサーブが2点ごとに交代するため、サーバー側はラリーごとに交互にズレることになり、2回連続で発生することはほとんどないためです(ズレたラリー64件のうち、2回連続ルールで捕捉できたのは10件のみでしたが、1回ルールでは60件を捕捉し、誤警報は2件でした。PERFORMANCE 2.7)。
5. 「要確認」:4つの分岐
判定ごとに「要確認」マークが付く場合があります。判定はそのままにして、人間に一度確認してもらうためのものです。マークが付く経路は4つあります。
| 分岐 | 条件 | 理由(why) |
|---|---|---|
| ① キャリブレーション回帰 | 学習したロジスティック回帰による「判定が誤っている確率」 > 0.0442(キャリブレーション v2)。キャリブレーションをオフにすると、従来のルール「信頼度 < 0.6」 | cal +最も大きく影響を与えた特徴量の組み合わせ2つ(ボールが弱い・ボールを見失った・長いラリー・エンドが不明瞭・サーブを確認できなかった・サーブ側が異なる・判定の種類) |
| ② エンドライン帯 | 着地がエンドラインの境界±0.08m以内 | edge |
| ③ 信号の不一致・ボールが弱い | ボールモデル間で結果が一致しない、またはラリー区間でボールが検出されたフレームの割合が0.5未満の場合、信頼度を0.59に引き下げる | conflict など |
| ④ 卓球台 | ラリーが卓球台を再検出中であるか、見失った区間にまたがっている | table |
ここに再同期信号(resync)が付いた判定も、チップ側で別途確認が行われます。
なぜ1つの信頼度だけでは不十分なのか
初期のルールは「ラリー信頼度 < 0.6 ならば要確認」でした。2026-10-14にこの信頼度が本当に誤りを区別できるかを検証したところ、 区別できませんでした(原典 E20、リアルタイムパイプラインそのまま、公開テスト7試合×劣化11条件、判定597件)。信頼度0.0〜0.1の区間の正解率は0.86、0.9〜1.0の区間も0.86であり、誤りを判別するAUCは0.51(コイントスと同等)でした。30fpsのリアルタイムパイプラインでは、ボールを一度でも見失うだけで最小値が0.6を下回ってしまうためです。
そのため、判定の瞬間にエンジンがすでに把握している19個の値を用いて、小規模なロジスティック回帰を学習させました(ttscore/reviewcal.py)。信頼度、検出信頼度の最小値、バウンドの平均信頼度、バウンド・イベント数、弱いイベント・空白イベント数、ラリーの長さ、最後が「エンド」であるか、サーブ形状のペアを確認できたか、その側がスコアボードと異なるか、判定の理由などが含まれます。計算は判定ごとに内積が1回のみであるため、リアルタイムの負荷はありません。原理と学習については第4部で取り上げます。
| 判定 | 条件 | 確認なしの誤り(†) | 要確認 | 誤り判別AUC |
|---|---|---|---|---|
| 信頼度 < 0.6 | テスト7試合×11条件(判定597、E20) | 26 | 395 | 0.51 |
| キャリブレーション v1 | 同等 | 12 | 430 | — |
| キャリブレーション v2(運用) | テスト7試合×11条件(判定661、ラベル修正を反映、E25) | 20 → 8 | 450 → 497 | 0.71 → 0.73 |
v2行の「→」の前は同一条件のv1です。†v2行は確認なしでスルーされたゴースト判定も含めた数値であるため、上の2行とは直接比較できません。キャリブレーションは、 要確認を少し増やす代わりに、人間が信頼して見過ごしてしまう誤答を大幅に減らす 方向にしきい値を選定しました。しきい値は、学習用試合単位の5分割交差検証において「要確認が現在以下となるしきい値の中で見逃した誤答が最小のもの」として決定しました。
理由を付与する理由も実測から導き出されました。キャリブレーション前は理由が空欄であったため、チップが何を根拠に確認を求めているのかを伝えることができませんでした(原典 E19-2)。現在は、回帰分析によって要確認と判定された理由ごとに「誤っている方向に最も強く影響を与えた特徴量」を人間の言葉の組み合わせで最大2つまで含めて送信しています。
エンドライン帯
ボールがエンドラインに当たったか(エッジ)、すぐ外側に落ちたのかは、1台のカメラで判別するのが最も難しい判定です。着地位置がエンドラインの境界±0.08m以内である場合、勝者はそのままで要確認として送信します(TTSCORE_EDGE_REVIEW、原典 E34)。
6. ウォームアップ・練習と試合の判別方法
エンジンは練習のサーブを判別できない
卓球の試合はウォームアップのラリーから始まります。お互いに数分間ボールを打ち合い、練習用のサーブも出してみます。問題は、 練習のサーブが本番のサーブと同じ形状をしている点です。サーバー側に一度、レシーバー側に一度落下し、ネット上の速度も本番と同じです。
実測の事例があります。公開テストの1試合(test_4)では、開始2.05秒の時点で試合前の練習サーブが捉えられていました。サーバー側からネット方向へ4.2テーブル長/秒であり、ボールはネットを越えました。第3節のサーブ認定ルールでは、本番のサーブと区別する方法がありません。エンジンはこれを最初の得点としてカウントしてしまい、その1点によって試合終了までサーブの順番がズレてしまったため、この試合の「ラリー後の得点一致」は21ラリー中0でした(PERFORMANCE 2.6)。
映像のみで練習を判別する方法も検証しました。「スコアボードの最初のサーバー側の最初の完全なサーブの前を破棄する」(N1)および「どちらの側であれ最初のサーブ形状のペアの前を破棄する」(N2)です。どちらも改善されませんでした(信号なしのエンジンと比較して、5試合の得点一致は24 → 24、勝者は41 → 40、第12部の全体判定なしは18 → 21)。test_4の本番での最初のサーブはネットに引っかかったサーブ失敗であったため「完全なサーブ」ではなく、どちらのルールも本番の最初の得点まで破棄してしまいました。練習サーブの事例が1件しかなかったため、データを基にルールを定めることもできませんでした。 そのため、実装しませんでした。
結論はシンプルです。 試合がいつ始まったのかは人間が教える。 選手にとっては簡単なことですが、機械にとってはほぼ不可能なことです。
「試合開始」信号の経路
カメラのスコアボードのウォームアップ状態(第2部)は、まさにこの信号を受け取るための入り口です。
- 画面。 カメラを起動するとウォームアップ状態になります(
CamMatch.startedAt === null)。得点欄をタップしても得点は加算されず、「試合開始」ボタンが点滅します。リモコンの開始キーはウォームアップ中のみ有効です。 - 開始。 「試合開始」を押すと、
startMatch()が試合開始時刻を履歴に記録し、tt:match-startイベントとして{type:'score', on:true, start, server_side}を出力します。 - 時刻合わせ。 自動スコア画面(
cam-auto.js)がそれを受け取ってscoreOn()を呼び出し、デバイスの時計(Date.now)で記録された開始時刻を フレーム時計(作業者と接続した瞬間を0とする)に変換します(startOffset)。作業者はデバイスの時計を知らず、フレームに付与された時刻しか知らないためです。 - 作業者。 ウェブ中継を経由して作業者のセッションが
scorer.start(server_side, t)を呼び出し、LiveScorerを新規作成します。過去の判定と区間は破棄し、この時刻より前のイベントはすべて無視します。 - 二重ロック。 ブラウザは試合が開始されていない場合はこの信号を送信せず、作業者は信号を受け取るまで判定を行いません(ボールの位置とバウンドの表示のみ送信)。ブラウザ側も、ウォームアップ中や試合終了後に届いた判定は提案として作成しません(
autoProposalがnull、コードのコメント「ウォームアップラリーは得点ではない」)。
サーバー側も追従します。スコアボードが変更されるたびに(得点、取り消し、人間によるサーブ権の修正)、現在のサーバー側作業者に送信し、エンジンはそれを用いて次のラリーのサーバー側を調整します。 人間が修正したサーバー側が優先されます。
実測:1つの信号の効果
公開試合において 0:0 からルール通りに繋がる5試合(49ラリー)で、「試合開始」の信号を人間が最初のサーブの直前に押したと模して測定した値です(2026-10-11, PERFORMANCE 2.7)。
| 指標(5試合 49ラリー) | 信号なし | 信号あり |
|---|---|---|
| ラリー後のスコア(得点・ゲーム)一致 | 27 / 49 | 36 / 49 |
| 勝者一致 | 44 / 49 | 45 / 49 |
| ラリーの判定ではない判定(偽のスコア) | 3 | 2 |
test_4 の一つが 0/21 から 9/21 に上がりました。信号を最初のサーブの 0.3・1・2・3秒前に押しても結果は同じで、 5秒前(練習サーブより前)に押すと効果がありませんでした。そのため画面では「ウォーミングアップが終わり、最初のサーブの直前に」押すよう案内します。
試合中の「練習のようなボール」
試合が始まった後もラリーの間のボールは動き続けます。これらは第3・4節の装置が除外します。
- サーブ待機の除外: 送球、自陣でのバウンド、手に持ったボール、床のボールを破棄します。同じ5試合で偽の判定が 30 から 5 に、ラリー後のスコア一致が 8/49 から 24/49 に変わりました(PERFORMANCE 2.6、サーブ認定ルールの前・後)。
- デッドボール2秒: 判定直後にバウンドするボールを破棄します。
- 卓球台の区間: 卓球台を外したか、再び捉えるまでのボールは判定に含めず、その区間にまたがるラリーは要確認に送ります。
残された限界
- 本物のサーブの形をしたボールは依然としてスコアになります。 試合中のレットを選手同士でもう一度やり直したり、ラリーの間に練習のつもりでサーブを入れたりすると、エンジンは本物のサーブと区別できません。サーブ認定ルールのみを入れたとき、5試合に残った5つの偽判定のうち3つがこのようなボールでした(練習サーブ、レットと推定されるラベルなしのサーブ)。
- 「試合開始」を押さないと ウォーミングアップのラリーがスコアになります。手動スコアボードではウォーミングアップ中の得点欄を開始ボタンを揺らして知らせますが、人間が開始を早すぎるタイミング(練習サーブの前)に押すと意味がありません。
- ゲーム間の休憩時間には独自の「一時停止」信号がありません。この時間のボールはサーブ待機の除外だけに頼ります。
スコアボードとは関係のない「練習」機能
名前が似ていて紛らわしい2つの機能は、スコアボード・自動判定と 連動していません。
- 反応速度ラリー(
/tt/play/reaction-rally): 画面のボールがフォア・バック × ショート・ロングの4つのエリアのいずれかに飛んでくる、一人で行う反応ゲームです。カメラ判定を使用しません。 - 練習ルーチン(
/tt/practice): レベルと時間(15〜180分)を選ぶと、ウォーミングアップ・技術・ゲーム・整理の順序で練習計画を作成してくれます。記録は練習記録として残り、レーティングとは別個です(第6部)。
7. チップ: 人間と機械の役割分担
判定がチップになるまで
作業者が判定を送信すると、ブラウザはそれを その時点の左側の選手基準で 変換してチップとして表示します。作業者は元映像の L・R で話し、画面は鏡像である可能性があり、位置はゲームごとに変わるためです。レットと判定なしは提案しません。
チップには4つのボタンがあります。「一致」、「(左側の選手)得点」、「(右側の選手)得点」、「無効」。
回答がなければそのまま反映する
遠くに立てたスマートフォンをラリーごとに押す人はいません。そのため、回答がなければそのまま反映します(backend/static/tt/play/lib/scorestatus.mjs).
| 判定 | 回答なしで待つ時間 | 根拠 |
|---|---|---|
| 確実 | 4秒 | — |
| 要確認 | 9秒 | 人間がチップを見て回答する時間(約2.5秒 + 「ちょっと待って」3秒)よりも余裕があり、実際の映像の判定間の間隔(3.8〜17.4秒)の範囲内 |
このルールには背景があります。2026-10-13 に公開試合の映像を運営作業者に流してみたところ、カメラのスコアボードは50秒間に4つの判定を受け取っても 0:0 のままでした。判定の大部分が要確認であり、画面は要確認を人間が回答するまで反映せず、次の判定が来ると前の判定を破棄していました。そして、なぜ反映されないのかを説明しませんでした。現在は要確認も9秒後に反映し、9秒になる前に次の判定が来たらその場で前の判定を反映します。
チップの列は最大2つまでです。3番目の判定が来ると、最も古いものから順にそのまま反映し、一度表示されたチップは最低2.5秒間表示します。
回答の処理
| 回答 | 履歴に入る欄 | 修正された記録 |
|---|---|---|
| 「一致」 | 得点(ソース auto) | confirm |
| 時間が経過してそのまま | 得点(ソース auto) | 残さない(人間が確認したものではないため) |
| 判定と同じ選手 | 得点(ソース auto) | confirm |
| 判定と異なる選手 | 得点(ソース tap) | flip |
| 「無効」 | 反映しない | void |
手がチップの回答になる
最も重要なルールはこれです。 チップが表示されている間が得点欄をタップすると、その操作がチップの回答になります(handOverride)。同じ選手であれば一致、異なる選手であれば修正になります。このルールがないと、人間が習慣通りに手動でスコアを押し、4秒後に自動判定も反映されて 1ラリーで2点になってしまいます。
まとめると、役割は次のように分かれています。
| 人間 | 機械 | |
|---|---|---|
| 試合がいつ始まったか | 決定する(「試合開始」) | それ以前のものを破棄する |
| どちらがサーブするか | 修正でき、修正すると勝つ | スコアボードから受け取って使用し、異なる場合は「サーブは合っていますか?」と尋ねる |
| ラリーの勝者 | チップに回答するか手動で押す(手が回答) | 提案する。回答がなければ4〜9秒後に反映する |
| 曖昧な判定 | 確認する | 要確認と理由を付与する |
| 間違って入ったスコア | 履歴から削除する(第2部) | — |
人間がチップに回答した記録(修正された記録)はサーバーに残る場合があります。映像は保存せず、学習への使用に同意したものだけを出力します。その記録がどこにいき、これまでどのように使われてきたかは第6部で扱います。
8. 実測: 現在どこまで合っているか
すべての行は公開試合の映像(OpenTTGames テスト7試合)で測定した値です。学習としきい値の選定には別の試合を使用し、テスト試合は最後に見ます。ただし、ルールエンジン自体は 2026-10-11〜12 にテスト試合を見ながら調整されたため、ルール基準線のテストスコアは楽観的です(オーナーのデータ漏洩の警告)。
| 測定 | 条件 | 結果 |
|---|---|---|
| 運営フロー固定基準表 | テスト7試合 × 劣化11条件、ラベル 638、運営と同じパス + バウンド除外・補正 v2・直行ルール(2026-10-16, オーナー E25・E27) | 一致 508、 確認なしの不一致 8、要確認 497、ゴースト(ボールがないのに出した得点) 51、判定遅延 p90 1.32秒 |
| カメラのスコアボード最初から最後まで | テスト7試合 851秒を偽カメラ 30fps で実際のブラウザ・作業者コードに流す、ラベル 58ラリー(2026-10-17, オーナー E35) | 一致 47、不一致 7、判定なし 4、ゴースト 9、 確認なしの不一致 0、要確認 53/65 |
| 条件別公開映像 | 撮影条件8種類 27クリップ(2026-10-14〜16, PERFORMANCE 2.16〜2.17) | 縦長に立てたスマートフォンの映像は、ボールが映っていたフレーム 5%、判定 0 |
| スマートフォンで撮影した実際の卓球場 | — | 未測定(ラベル付き映像 0本) |
2つの行が現在の性質を物語っています。 確認なしで間違えることは稀ですが、要確認が多いです。 カメラのスコアボードの最初から最後のテストで、65個の判定のうち53個が要確認でした。9秒の自動反映がその代償を人間の代わりに払っている形であり、「要確認は減らしつつ、確認なしの不一致は増やさないこと」が次回の改善の目標です。
9. まとめ
- 自動判定はスコアボードではなく提案です。本物のスコアは人間が入力し修正した1つの履歴です。
- 審判はラケットを見ずにバウンドの順序と 横方向の速度 0.7 という1つの数値で、サーブ・送球・偽のバウンドを区別します。
- 練習サーブは本物と同じ形をしているため、エンジンは区別できません。そのため「試合開始」は人間が押し、その信号が画面 → フレーム時計 → 作業者 → エンジンへと伝わり、その前のすべてを破棄します。この1つの信号により、5試合のスコア一致が 27/49 から 36/49 になりました。
- 「要確認」は信頼度単体では不一致を判別できないため(AUC 0.51)、学習された補正に変更され、チップは回答がなければ4〜9秒後にそのまま反映され、人間の手がチップの回答になります。
ティティの一言。 ウォーミングアップのサーブと本物のサーブは僕には同じに見えるよ。だから「試合開始」は必ず最初のサーブの直前に押してね。その一回が十点よりも重要なんだから。
参考文献
- International Table Tennis Federation. ITTF Statutes 2026, Chapter 2: The Laws of Table Tennis, §§2.10–2.15. https://db.ittf.com/document
- R. Voeikov, N. Falaleev, R. Baikulov. "TTNet: Real-time Temporal and Spatial Video Analysis of Table Tennis." CVPR Workshops, 2020. arXiv:2004.09927. ニューラルネットワークでバウンドやネットを直接検出する手法の代表的な先行研究ですが、私たちはイベントをボールの軌跡のルールから抽出する点が異なります。
- Extended OpenTTGames(ラリー終了の原因・勝者ラベル). arXiv:2512.19327. この記事のラリー勝者の正解。
- 私たちの測定: リポジトリ
docs/tt/PERFORMANCE.md2.6・2.7・2.16・2.17、研究ノート E20・E25・E27・E34・E35. コードttscore/events.py·referee.py·analyze.py·reviewcal.py,backend/static/tt/play/lib/camauto.mjs·scorestatus.mjs.
あわせて読みたい
- この記事の 第2部: 手動スコアボードと試合ルールエンジン: 「状態ではなく履歴」、ウォーミングアップの状態。
- この記事の 第4部: モデルの原理と論文: バウンド除外1D CNNと要確認補正回帰をどのように学習したか。
- 前回のシリーズ TT Scoreモデル解剖 第5部: イベントと審判: 66.9% から 93.1% まで、試合ごとに変更したものと行ったこと。
- ビジョンモデルを読む 2、第10部: 精密なイベント検出: イベントを学習によって見つける方法。