目次
統合対象スクリプト一覧
--use-table-mask フラグの追加方法
フォールバック処理 (卓検出失敗時)
パイプライン順序 (マスキング → YOLO → 後処理)
訓練データへの適用 (Roboflow Auto-Label 精度向上)
A/B 評価方法
既知の問題と対策 (緑シャツ等)
改善方向 (ChArUco / SAM / 専用セグモデル)
1. 統合対象スクリプト一覧
方針: 検出スクリプトは 全 5 つ を統合対象とする。
各スクリプトに --use-table-mask オプションを追加し、デフォルトは OFF (互換性維持)。
本番運用が安定してから ON をデフォルト化する段階リリース。
スクリプト
役割
優先度
期待効果
scripts/detect-agari-visual.py
視覚アガリ検出 (本番)
最高
FP 50〜70% 削減
scripts/analyze-still-frame.py
静止画解析 (デバッグ・確認用)
高
誤検出表示が減り、可視化が読みやすくなる
scripts/render-3d-overlay-cf.py
CF 用 3D オーバーレイ動画
高
クラウドファンディング映像の品質向上
scripts/evaluate-agari-transition-detector.py
評価フレームワーク
高
dense 推論の精度向上 → 評価指標の信頼性 up
scripts/voice-cli-mahjong.py
音声 CLI (Phase α)
中
画像入力モードのみ適用
1.1 統合対象外
scripts/extract-roboflow-frames.py — フレーム抽出のみで検出しないため対象外
scripts/filter-roboflow-frames.py — quality filtering のみ
scripts/generate-image.mjs — 画像生成系で別ドメイン
2. --use-table-mask フラグの追加方法
2.1 共通パターン (argparse 拡張)
import argparse
parser = argparse.ArgumentParser()
# 既存引数...
parser.add_argument("--in-image", required=True)
parser.add_argument("--out-image", default=None)
# 新規: 卓マスキング前処理
parser.add_argument("--use-table-mask", action="store_true",
help="卓上領域マスキング前処理を有効化 (FP 削減)")
parser.add_argument("--mask-dilate-pct", type=float, default=5.0,
help="凸包の dilate 比率 (%) [default: 5.0]")
parser.add_argument("--mask-min-area-pct", type=float, default=5.0,
help="卓と判定する最小面積比率 (%) [default: 5.0]")
parser.add_argument("--mask-debug-out", default=None,
help="マスクデバッグ画像の出力パス (4 分割)")
args = parser.parse_args()
2.2 import 例
# scripts/mask-table-region.py から再利用可能関数を import
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from mask_table_region import (
table_region_mask, # マスク生成
apply_mask, # マスク適用
save_debug_grid, # デバッグ画像 (4 分割) 保存
)
注意: mask-table-region.py はファイル名にハイフンを含むため
Python パッケージとして直接 import mask-table-region は不可。
importlib.util.spec_from_file_location を使うか、
mask_table_region.py (アンダースコア版) にシンボリックリンク or リネームすると簡潔。
2.3 統合の最小コード (3 行差し込み)
# 既存: フレーム読み込み → YOLO 推論
frame = cv2.imread(args.in_image)
detections = model.predict(frame, confidence=0.40)
# 統合版: マスク前処理を挟む
frame = cv2.imread(args.in_image)
if args.use_table_mask: # 1
mask, info = table_region_mask( # 2
frame,
dilate_pct=args.mask_dilate_pct,
min_area_pct=args.mask_min_area_pct,
)
if not info["fallback"]: # 3
frame = apply_mask(frame, mask)
if args.mask_debug_out:
save_debug_grid(frame, mask, info, args.mask_debug_out)
detections = model.predict(frame, confidence=0.40)
2.4 ユースケース別 CLI 例
# 1. detect-agari-visual.py に統合後
python scripts/detect-agari-visual.py \
--video mleague/sample01.mp4 \
--start-frame 1800 --end-frame 2400 \
--use-table-mask \
--mask-debug-out mleague/output/_agari-mask-debug.png
# 2. analyze-still-frame.py に統合後
python scripts/analyze-still-frame.py \
--in-image mleague/frames/sample01-peak.jpg \
--use-table-mask
# 3. 評価フレームワーク
python scripts/evaluate-agari-transition-detector.py \
--eval-dir mleague/eval/ \
--use-table-mask \
--report-out mleague/eval/report-with-mask.json
3. フォールバック処理 (卓検出失敗時)
方針: 卓が映っていない / 検出失敗のフレームでは マスクを適用せず元画像を渡す 。
検出機会を失わない方を優先 (リコール重視のフェイルセーフ)。
3.1 フォールバック判定ロジック
def table_region_mask(frame, dilate_pct=5.0, min_area_pct=5.0):
h, w = frame.shape[:2]
total_area = h * w
# ... HSV → connectedComponents ...
if largest_area_pct < min_area_pct:
return None, {"fallback": True, "reason": "no_table_detected",
"largest_area_pct": largest_area_pct}
# ... 凸包 + dilate ...
return mask, {"fallback": False, "table_area_pct": table_pct, ...}
3.2 フォールバック発生パターン
発生シーン 挙動 対処
インタビューカメラ 卓なし → fallback 元画像で推論。アガリ検出は他の信号で代替
タイトルカード / オープニング 卓なし → fallback 同上。多くは検出 0 件で良い
俯瞰超ロングショット 卓小さい → 5% 未満で fallback --mask-min-area-pct 2.0 で閾値下げ
暗転 / フェード 緑領域なし → fallback そのまま fallback で問題なし
HSV 範囲外の緑 (照明異常) 緑として認識されず fallback HSV 範囲を動画ごとに調整 (sec4 参照)
3.3 フォールバック発生率の監視
評価フレーム群全体に対する fallback 発生率を 5% 以下 に保つことを目標とする。
それ以上なら HSV 範囲 / min_area_pct を見直す必要がある。
# 監視用ログ収集
fallback_count = 0
for frame in test_frames:
_, info = table_region_mask(frame)
if info["fallback"]:
fallback_count += 1
print(f"fallback rate: {fallback_count / len(test_frames) * 100:.1f}%")
4. パイプライン順序 (マスキング → YOLO → 後処理)
[入力動画フレーム]
│
▼
[1. 卓マスキング前処理] ← 本ガイドの主題
│ (fallback 時は素通し)
▼
[2. YOLO 推論 (公開モデル / MVP)]
│ - 34 牌種 bbox + 信頼度
│
▼
[3. orientation 分類] ← 立/伏 二値判定 (Pose3D 連携)
│
▼
[4. 鳴き数判定] ← 横向き bbox を副露としてカウント
│
▼
[5. アガリ判定 / 点数推定] ← 視覚 + 状態遷移
│
▼
[出力: 検出結果 JSON / 可視化 PNG]
4.1 順序の根拠
マスキングは推論前に : 探索空間を絞ることで YOLO の confidence threshold を下げてもFP が抑制される
orientation 分類は推論後に : マスクされた領域には牌がないため、ここでマスク考慮は不要
鳴き数判定は最後 : 副露の bbox が「卓端の枠外」にも存在することがあり、マスクで切らない方が安全
4.2 鳴き対応との相互作用
注意: 副露 (ポン/チー) は卓の縁に近い位置 に並べられることが多く、
dilate_pct=5.0 で十分含まれるが、念のため副露多発フレームで動作確認すること。
マスクされて副露が消える場合は --mask-dilate-pct 8.0 に上げて再評価。
4.3 動画 (時系列) パイプラインでの最適化
動画では卓位置がほぼ固定 (カメラ切替時のみ変動)。マスクを 1 秒間隔でキャッシュ することで
計算コストを削減できる:
cached_mask = None
cached_fnum = -10000
for fnum, frame in enumerate(video_frames):
if fnum - cached_fnum >= 30: # 30 fps 動画で 1 秒
cached_mask, info = table_region_mask(frame)
cached_fnum = fnum
if cached_mask is not None:
frame = apply_mask(frame, cached_mask)
detections = model.predict(frame)
5. 訓練データへの適用 (Roboflow Auto-Label 精度向上)
核心アイデア: Roboflow の Auto-Label (自動アノテーション) は
背景に含まれる文字・ロゴ に引きずられて誤ラベルを付ける。
マスク済画像を上げれば、Auto-Label は卓上だけを見るので精度が劇的に向上する。
5.1 Roboflow へのアップロード前処理
# 一括マスキング (PowerShell)
Get-ChildItem mleague/frames-roboflow-filtered/*.jpg | ForEach-Object {
phase0/.venv/Scripts/python.exe scripts/mask-table-region.py `
--in-image $_.FullName `
--out-image "mleague/frames-roboflow-masked/$($_.Name)"
}
# その後 Roboflow にアップロード (CLI)
roboflow upload \
--project mahjong-mleague-v2 \
--version pending \
--workspace shimanto-ai \
--image-dir mleague/frames-roboflow-masked/
5.2 Auto-Label 精度比較 (想定)
条件 Auto-Label 正解率 手動修正工数 (100 枚)
マスクなし 60〜70% 3〜5 h
マスクあり 80〜90% 1〜2 h
5.3 訓練と推論の一貫性
マスク済画像で訓練すれば、推論時もマスクを期待 するので一貫性が保たれる。
マスクなしで推論する場合は別途 fine-tune を実施 (混合訓練でドメイン頑健性を獲得)。
5.4 リスク
リスク: マスク済画像のみで訓練すると、フォールバック (マスクなし) 時に性能が落ちる。
対策: 訓練データの 20% はマスクなし を含める (augmentation 的に混ぜる)。
6. A/B 評価方法
6.1 評価フレーム群の準備
同じテストフレーム 100 枚を用意 (mleague/eval/ 配下)
各フレームに対して 2 通り実行: マスクあり / マスクなし
同一モデル ID で公平に比較 (test-upsgd/mahjong-tiles-oc9zz 等)
6.2 評価スクリプト雛形
def evaluate_ab(test_frames, model, output_dir):
results = {"with_mask": [], "without_mask": []}
for frame_path in test_frames:
frame = cv2.imread(frame_path)
# Without mask
dets_raw = model.predict(frame)
results["without_mask"].append({"path": frame_path, "n": len(dets_raw), "dets": dets_raw})
# With mask
mask, info = table_region_mask(frame)
frame_m = apply_mask(frame, mask) if not info["fallback"] else frame
dets_masked = model.predict(frame_m)
results["with_mask"].append({"path": frame_path, "n": len(dets_masked), "dets": dets_masked})
# 集計
summary = {
"without_mask_total": sum(r["n"] for r in results["without_mask"]),
"with_mask_total": sum(r["n"] for r in results["with_mask"]),
"reduction": sum(r["n"] for r in results["without_mask"]) -
sum(r["n"] for r in results["with_mask"]),
}
return results, summary
6.3 比較指標 (記録項目)
指標 マスクなし マスクあり 差分 備考
検出件数合計 — — — 絶対件数の総数
FP 件数 — — — GT 比較が必要
TP 件数 — — — マスクで TP が減らないことを確認
1 枚平均検出数 — — — —
fallback 発生率 — — — マスクありのみ
6.4 結果の判定基準
TP が 5% 以上減らない : マスクが牌を消していない (健全)
FP が 30% 以上減る : マスクの効果が出ている (採用条件)
fallback 発生率 5% 以下 : 安定動作の指標
7. 既知の問題と対策
7.1 緑シャツのプレイヤー問題
問題: プレイヤーが緑系のシャツ (チームカラー) を着ている場合、
シャツが卓と HSV 緑域で連結し、マスクに含まれてしまう可能性。
その結果、シャツ上の文字 / ロゴで FP が発生する。
7.2 連結回避の対策案
対策 実装難易度 効果
connectedComponents の connectivity=4 化
低 (1 行)
細い橋での連結を切断
shape 分析: 卓は楕円フィット率高、シャツは低
中
誤連結時に分割可能
top-N 連結成分の 2 番目 がシャツの場合は除外
中
シャツが第 1 候補にならない限り有効
ChArUco マーカーで卓の幾何を強制
高 (運用変更)
確実だが M-League 制作側の協力が必要
SAM (Segment Anything) でクラス無しセグ
高 (推論コスト)
緑連結問題を構造的に解消
7.3 ハイライト / 反射の問題
問題: スタジオ照明の反射で、卓の一部が「ハイライト = HSV 緑域外」になり、マスクに穴が空く。
対策: Step 4 (凸包 + dilate) が既に穴を埋める設計のため通常問題ないが、
ハイライトが広範な場合は dilate_pct を 8〜10% に上げる。
7.4 卓の左右端切れ問題
問題: カメラショットで卓が画面外まではみ出している場合、凸包が画面端で切れる。
対策: 凸包の代わりに 外接矩形 (axis-aligned bounding box) を使うと、
画面端まで矩形が広がる。トレードオフは余分な背景が含まれること。
7.5 倍速 / スロー再生時の動的ブレ
問題: 動的シーンでカメラがパンすると HSV 範囲が一時的にズレる。
対策: 時間軸スムージング (前後 N フレームのマスク平均) で対応。
Section 4.3 のキャッシュ機構と組み合わせると安定する。
8. 改善方向
8.1 短期改善 (1〜2 週間)
項目 工数 期待効果
connectivity=4 + 第 2 連結成分除外ロジック 2 h 緑シャツ問題の 70% を解消
動画ごとの HSV 範囲チューニング (sample01/02 個別) 3 h fallback 発生率を 5% → 2% に削減
時間軸スムージング (キャッシュ 1 秒) 4 h カメラ切替時のジッタ低減
8.2 中期改善 (1〜2 ヶ月)
項目 工数 期待効果
ChArUco マーカー運用 (物理マーカー設置) 運用調整 + 1 週 マスク精度 100% (運用協力が前提)
SAM (Segment Anything) ベースのセグ 1 週 (推論統合) 緑問題を構造的に解消、ただし推論コスト増
Roboflow で table vs non-table セグモデル訓練 2 週 (アノテ 100 枚 + 訓練) M-League 専用最適化
8.3 長期改善 (3〜6 ヶ月)
3D カメラキャリブレーション : 卓平面を 3D で同定し、視点変動に頑健化
専用セグモデル (YOLOv8-seg) : 卓・牌・手・背景を同時セグ。一括処理で高速
放送 API 連携 : M-League 制作側からカメラメタデータを取得して直接マスク生成
8.4 推奨ロードマップ
Week 1: 5 スクリプトに --use-table-mask 統合 (本ガイド)
Week 2: A/B 評価実施 (評価テンプレート 使用)
Week 3: 緑シャツ対策 (connectivity=4 + 第 2 連結成分除外)
Week 4: マスク済画像で Roboflow 再アノテ → MVP v2 訓練データ準備
Month 2-3: SAM ベース置換を検討。MVP v2 で十分なら見送り
結論: 本ガイドの統合作業は
1 週間 で完了する低リスク改善。
その後の A/B 評価で効果が確認できれば、
評価テンプレート の Section 8 (前処理 A/B) に結果を記録し、
MVP モデルとの相乗効果を本番展開の判断材料とする。