TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

自動判定:サーブから勝者まで、そしてウォーミングアップ・練習と試合の切り分け方

ステータス:下書き(2026-10-26)、実測値を反映。 ルールは2026-10-25時点のリポジトリコード(ttscore/, backend/static/tt/play/)から読み取ったものであり、数値は公開された試合映像を用いて私たちが直接測定した値です(日付や条件は表ごとに異なります)。スマートフォンで撮影した実際の卓球場の映像については、ラベル付き映像がまだないため未測定です。 作成ルール:実測データを基本とし、測定できなかったものは「未測定」と表記します。

目次

1. 自動判定はスコアボードではなく「提案」である

最初に立場を明確にしておきます。カメラ型スコアボードの本当のスコアは、第2部で見た 人間が入力して修正した履歴です。自動判定はその履歴に対して「今回のラリーはAの得点、理由はアウト、信頼度0.83」のような 提案を1つずつ提示するだけであり、確定した提案のみが履歴の得点欄になります(backend/static/tt/play/lib/camauto.mjs まるがき「スコアボードは一つである」)。そのため、自動判定が間違っていたとしても、人間が修正した履歴と個別に合わせる必要はありません。

この記事では、ボール座標が入力された瞬間からチップがスコアになるまでを追っていきます。

  1. ボール座標 → イベント(バウンド・ネット・アウト・エンド)
  2. イベント → 審判ステートマシン → ラリーの勝者
  3. ラリーのまとめ、デッドボール、再同期
  4. 「確認必要」 判定
  5. チップと自動反映、人間と機械の役割分担
  6. その合間で ウォーミングアップや練習を試合と区別する仕組み

ボール座標がどのように生成されるかについては第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つの数値が、次のセクションの審判において、サーブ、リターン、偽のバウンドを区別します。

ネット、アウト、エンド

信頼度

イベントごとに信頼度が付与されます。 conf = min(검출 신뢰도, 공백 신뢰도) であり、空白の信頼度は 1 − 놓친 프레임 / (fps × 0.25초) です。直前にボールを0.25秒以上見失っていた場合は0になります。その間にバウンドを1つ見落とした可能性があるため信頼できないという意味です。検出の信頼度のみを別途(det_conf)も記録しており、サーブの最初のバウンドで使用されます。

3. 審判:「誰が打ったか」は見ない

서브 대기 서브 시작만 받는다 직행 서버 쪽 바운드를 못 봄 서브 진행 네트 닿음 = 렛 후보 랠리 기대하는 쪽만 들고 간다 (받아야 할 쪽 S) 넘겨주기·튀기기 손에 든 공은 버리고 기다림 서버 쪽 바운드 네트 쪽 ≥ 0.7 리시버 쪽, 서버 쪽에서 온 방향 ≥ 0.7 리시버 쪽 바운드 1초 안 서버 쪽에 리턴 기대한 쪽 바운드 → 반대쪽을 기대 또·끝: 서버 득점 S 쪽 선수가 못 받음 서브 실패 판정 → 점수판의 득점 같은 쪽에 또 바운드 → 투 바운드 · 밖으로 → 아웃 아무 일 없이 끝 → 못 받음 · 서브 실패 → 리시버 득점 렛 → 점수 없음 0.25초 안 같은 쪽 두 바운드와 렛은 다음 이벤트까지 미룸 「누가 쳤나」는 보지 않는다. 바운드 순서와 가로 속도(테이블 길이/초)만 본다.
図:審判のステートマシン:サーブ待ちからサーブ進行・直行、ラリー、エンドへ

審判(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)。

スコアボードのサーバー側とは反対側で「サーブのように」開始されるボールは受け付けません。 レシーバーがサーバーにボールを投げ返した場合、レシーバー側からネット方向へ弾み、サーバー側に落下します。形はサーブと同じですが、サイドが逆になります。両方をサーブとして受け付ける変形により、5試合の誤判定が3から16に増加しました。

例外が1つあります。レシーバー側に、 サーバー側から飛んできた方向へ(ネットから遠ざかりながら0.7以上)落下したボールです。サーバー側のバウンドを見落としたサーブである可能性や、サーバー側にバウンドしなかったサーブ失敗である可能性があります。これは direct(直行)状態として受け付け、信頼度を0.5に下げて確認必要として送信します。サーブの最初のバウンドは、トスやラケットに遮られて見落とされることがよくあります。そのため、直行の後にレシーバー側に再び落下するか、そのまま終了した場合、スイッチ(TTSCORE_DIRECT_MISS、オンになっている)に従って レシーバーがレシーブできなかったこと(サーバーの得点) とみなします。実際の映像実験(ホストE27)において、「再び落下」判定60件のうち48件、「終了」判定49件のうち36件がサーバーの得点であったためです。

サーブ進行:1秒以内にレシーバー側へ

serve1 では、レシーバー側のバウンドを待ちます。

レット:次のイベントまで持ち越す

サーブ中にネットに接触し(net)レシーバー側でバウンドしていればレットの候補です。すぐにレットを宣言せず、 次のイベントまで保留します。

当初はすぐにレットとしていましたが、レット判定後の「デッドボール」規則(第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)   # 같은 쪽에 또: 그 쪽 선수가 진다

アウトとエンド:最後のバウンド側の選手がレシーブできなかった

判定の信頼度

1つの判定の信頼度は、そのラリー内のイベント信頼度の 最小値です。ただし、サーブの最初のバウンドは、空白期間を除いた検出信頼度(det_conf)で計算します。サーブの最初のバウンドの前にある空白期間は、トスやラケットによって遮られたために生じたものであり、その間に見逃されるバウンドは存在しないためです。これを取り除く前は、サーブの最初のバウンドの70%が信頼度0であり、それが「要確認86%」の主な原因でした(PERFORMANCE 2.6)。

4. ラリーのグループ化:デッドボール、勝者、再同期

1つの審判は1つのラリーを担当します。ラリーをつなぎ合わせてスコアボードを作成する作業は、 ttscore/analyze.py の LiveScorer が行います。

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. ウォームアップ・練習と試合の判別方法

① 휴대폰 화면 몸풀기: 득점 칸을 눌러도 점수 안 들어감 「경기 시작」 → 시작 시각을 이력에 적음 ② 자동 점수 모듈 기기 시계 → 프레임 시계(연결 순간 = 0) {type: score, on, start, server_side} ③ 웹 중계 로그인 · 몫 확인 뒤 그대로 전달 ④ 실시간 작업자 scorer.start(server_side, t) 지난 판정·구간을 잊고 새로 시작 ⑤ 엔진 시각 t 앞의 이벤트는 모두 버림 그 뒤에만 심판이 판정 이중 잠금: 시작 전에는 브라우저가 신호를 안 보내고 작업자도 판정하지 않는다(공 위치만 보냄). 서버 쪽은 점수판이 바뀔 때마다 따로 보낸다 — 사람이 고친 쪽이 이긴다.
図:「試合開始」信号がスマホ画面からエンジンに届くまで

エンジンは練習のサーブを判別できない

卓球の試合はウォームアップのラリーから始まります。お互いに数分間ボールを打ち合い、練習用のサーブも出してみます。問題は、 練習のサーブが本番のサーブと同じ形状をしている点です。サーバー側に一度、レシーバー側に一度落下し、ネット上の速度も本番と同じです。

実測の事例があります。公開テストの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部)は、まさにこの信号を受け取るための入り口です。

  1. 画面。 カメラを起動するとウォームアップ状態になります(CamMatch.startedAt === null)。得点欄をタップしても得点は加算されず、「試合開始」ボタンが点滅します。リモコンの開始キーはウォームアップ中のみ有効です。
  2. 開始。 「試合開始」を押すと、 startMatch() が試合開始時刻を履歴に記録し、 tt:match-start イベントとして {type:'score', on:true, start, server_side} を出力します。
  3. 時刻合わせ。 自動スコア画面(cam-auto.js)がそれを受け取って scoreOn() を呼び出し、デバイスの時計(Date.now)で記録された開始時刻を フレーム時計(作業者と接続した瞬間を0とする)に変換します(startOffset)。作業者はデバイスの時計を知らず、フレームに付与された時刻しか知らないためです。
  4. 作業者。 ウェブ中継を経由して作業者のセッションが scorer.start(server_side, t) を呼び出し、 LiveScorer を新規作成します。過去の判定と区間は破棄し、この時刻より前のイベントはすべて無視します。
  5. 二重ロック。 ブラウザは試合が開始されていない場合はこの信号を送信せず、作業者は信号を受け取るまで判定を行いません(ボールの位置とバウンドの表示のみ送信)。ブラウザ側も、ウォームアップ中や試合終了後に届いた判定は提案として作成しません(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節の装置が除外します。

残された限界

スコアボードとは関係のない「練習」機能

名前が似ていて紛らわしい2つの機能は、スコアボード・自動判定と 連動していません。

7. チップ: 人間と機械の役割分担

判定がチップになるまで

작업자 판정 승자 쪽(L·R) · 이유 · 신뢰도 · 확인 필요와 사유 칩 — 그때의 왼쪽·오른쪽 선수로 바꿔 띄움 줄은 2개까지 · 최소 2.5초 보임 · 렛과 판정 없음은 안 띄움 칩에 답 맞음 선수 이름 무효 득점 칸을 누름 그 손이 칩의 답 (2점 막기) 답 없음 확실: 4초 뒤 확인 필요: 9초 뒤 그대로 넣음 이력(진짜 점수)에 득점 칸 맞음·같은 선수 → confirm · 다른 선수 → flip 무효 → 넣지 않음, void 시간이 지나 들어간 판정 → 고친 기록 남기지 않음 고친 기록은 영상 없이, 동의 표시와 함께 서버로
図: 判定がチップとしてポップアップし、得点欄になるまでのパスと時間ルール

作業者が判定を送信すると、ブラウザはそれを その時点の左側の選手基準で 変換してチップとして表示します。作業者は元映像の 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. International Table Tennis Federation. ITTF Statutes 2026, Chapter 2: The Laws of Table Tennis, §§2.10–2.15. https://db.ittf.com/document
  2. R. Voeikov, N. Falaleev, R. Baikulov. "TTNet: Real-time Temporal and Spatial Video Analysis of Table Tennis." CVPR Workshops, 2020. arXiv:2004.09927. ニューラルネットワークでバウンドやネットを直接検出する手法の代表的な先行研究ですが、私たちはイベントをボールの軌跡のルールから抽出する点が異なります。
  3. Extended OpenTTGames(ラリー終了の原因・勝者ラベル). arXiv:2512.19327. この記事のラリー勝者の正解。
  4. 私たちの測定: リポジトリ docs/tt/PERFORMANCE.md 2.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.

あわせて読みたい

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

コメント

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

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