| 転移対象 | 判定 | 期待効果 |
|---|---|---|
| 合成データ生成パイプライン | 強く推奨 | 34 種 × 5,000 枚 = 5,000 枚を自動生成可能 (アノテ工数 = 0) |
| データ拡張レシピ | 強く推奨 | 回転 ±15°, 明度 ±25%, ブラー, 重なり生成 そのまま流用 |
| 透視変形・OBB ハンドリング | 強く推奨 (新発見) | 菱形変形・斜めからの平面物体検出を共有 |
| YOLOv8 ハイパーパラメータ | 推奨 | YOLOv8l, 50 epochs, batch=16, imgsz=640 から開始 |
| バックボーン重み | 条件付き | +1〜3% mAP (Catastrophic Forgetting リスクあり) |
| 検出ヘッド・分類層 | 不可 | 52 → 34 クラスでフル再構築必要 |
| 3D 直方体 (立て/倒し) 形状認識 | 不可 | カードは紙 1 枚、麻雀牌は厚みあり (高さ情報がない) |
Augmented Startups の playing-cards モデルは 10,100 枚を合成で生成している。 その合成データは以下の多様性を意図的に含む:
| 転移対象 | なぜ転移できるか | 麻雀への応用 |
|---|---|---|
| バックボーン汎用特徴 (CNN 早期層) | 「矩形物体のエッジ・コーナー」を検出する能力は完全に共通 | YOLOv8 の P1〜P3 層の重みを継承可能 (推定 60〜70% の特徴量共有) |
| 透視変形ロバスト性 | 菱形・台形・平行四辺形に変形した矩形を扱う訓練が積まれている | 立て牌 → 斜め視点で平行四辺形になる場合に直接適用可 |
| OBB 検出能力 (もし OBB ヘッド使用なら) | カードの斜め配置で θ (回転角) の推定を学習している | 副露 (横倒しチー・ポン) の検出に直接転用可 |
| 重なり・隠れの処理 | カード同士の重ね合わせを大量に学習している | 麻雀の捨て牌山・整列手牌・副露の隠れた牌に転用可 |
| 背景ノイズ耐性 | 緑フェルト・木目背景は両ドメインで共通 | 緑フェルトを「牌でないもの」と認識する能力が転移される |
| 合成データ生成手法 | 「単体素材 + 背景 + 合成」のパイプラインは構造的に同じ | 麻雀牌 34 種 × 背景 10 種 × 配置 × augmentation で数万枚生成可能 |
| 訓練ハイパーパラメータ | 同型タスクの最適化挙動は近い | YOLOv8l, 50 epochs, batch=16, imgsz=640 から開始すれば探索コスト減 |
| 転移対象 | 難点 | 緩和策 |
|---|---|---|
| 後期層の重み (P4, P5) | 「カードらしさ」が刷り込まれている — トランプ向けに最適化された表現 | 転移後に freeze=0 で全層 fine-tune (LR を 1/10 に) |
| NMS / Anchor 設定 | カードの典型サイズ (大) と牌のサイズ (小) が違う | Roboflow Generate Version 時に anchor 再計算 |
| クラス分布の偏り | カード 4 スート均等 vs 麻雀の手牌・捨て牌偏り | クラス重み付き損失で補正 |
| 転移対象 | 理由 |
|---|---|
| 検出ヘッド・分類層 | 52 クラス (トランプ) → 34 クラス (麻雀) で出力次元が違う。完全に作り直し |
| カード絵柄の認識 | A, K, Q, J, ♠ ♥ ♦ ♣ の絵柄パターンは麻雀の 1m〜7z と無関係 |
| 3D 直方体 (立て/倒し) の判断 | カードは「紙 1 枚 = ほぼ平面」、麻雀牌は「厚みのある直方体」。3D の立て倒しはカードにない概念 |
| 副露 / 暗槓のラベル付け | 麻雀ルール固有。クラス定義の問題 |
YOLOv8m pretrained on COCO をそのまま麻雀 500 枚で fine-tune。
0.80〜0.85 (アノテ品質次第)
COCO pretrained → Augmented Startups playing-cards で 1 段階 → 麻雀で 2 段階。
0.82〜0.88 (アプローチ A 比 +1〜3%)
重みは継承せず、Augmented Startups の「合成データ生成パイプライン」を麻雀向けに移植。
実物撮影 500 枚 + 合成 5,000 枚 を混合して訓練する。
0.88〜0.94 (アプローチ A 比 +5〜10%、特に副露 recall で大幅改善)
| 素材 | 枚数 | 取得方法 | 所要時間 |
|---|---|---|---|
| 麻雀牌 34 種 高解像度写真 | 34 + α | 手持ち牌セットを 1 枚ずつ正面撮影。立て・倒しの両方 | 1〜2 時間 |
| 背景画像 | 10 種 | 緑フェルト・木目卓・暗部・M-League 卓を実況なしフレーム抜粋 | 30 分 |
| マスク (前景切り抜き) | 34 + α | OpenCV GrabCut or Roboflow の自動マスキング | 30 分 |
| 合計 | 2〜3 時間 |
import cv2, numpy as np, random
from pathlib import Path
TILE_DIR = Path("synth/tiles/") # 34 種 PNG (アルファ付き)
BG_DIR = Path("synth/backgrounds/") # 10 種 JPG
OUT_DIR = Path("synth/output/")
def random_perspective(img, max_tilt=30):
"""ランダムな透視変形 — 菱形化を再現"""
h, w = img.shape[:2]
src = np.float32([[0, 0], [w, 0], [w, h], [0, h]])
jitter = lambda: random.uniform(-max_tilt, max_tilt)
dst = src + np.float32([
[jitter(), jitter()], [jitter(), jitter()],
[jitter(), jitter()], [jitter(), jitter()]
])
M = cv2.getPerspectiveTransform(src, dst)
return cv2.warpPerspective(img, M, (w, h), borderMode=cv2.BORDER_TRANSPARENT)
def generate_synthetic_image(seed=42):
random.seed(seed)
bg = cv2.imread(str(random.choice(list(BG_DIR.glob("*.jpg")))))
bg = cv2.resize(bg, (1280, 720))
bboxes = [] # YOLO 形式: (cls_id, cx, cy, w, h)
n_tiles = random.randint(13, 60)
for _ in range(n_tiles):
tile_path = random.choice(list(TILE_DIR.glob("*.png")))
tile = cv2.imread(str(tile_path), cv2.IMREAD_UNCHANGED)
# 1. ランダム回転 (-180 〜 +180°)
tile = rotate_image(tile, random.uniform(-180, 180))
# 2. ランダム透視変形 (菱形化)
tile = random_perspective(tile, max_tilt=20)
# 3. ランダムスケール (0.5x 〜 1.5x)
scale = random.uniform(0.5, 1.5)
tile = cv2.resize(tile, None, fx=scale, fy=scale)
# 4. ランダム明度
tile = adjust_brightness(tile, random.uniform(0.7, 1.3))
# 5. 既存 bbox と少し重ねた位置にペースト (occlusion 生成)
x, y = sample_position(bg, bboxes, allow_overlap=0.2)
bg = paste_with_alpha(bg, tile, x, y)
cls = tile_class_from_filename(tile_path)
bboxes.append((cls, x, y, tile.shape[1], tile.shape[0]))
# 6. 全体に最終 augmentation (ノイズ・ブラー)
bg = add_motion_blur(bg, k=random.randint(1, 3))
return bg, bboxes
for i in range(5000):
img, bboxes = generate_synthetic_image(seed=i)
cv2.imwrite(str(OUT_DIR / f"synth_{i:06d}.jpg"), img)
save_yolo_labels(OUT_DIR / f"synth_{i:06d}.txt", bboxes)
print(f"Generated 5000 synthetic images with auto-labels")
# 1. Roboflow に upload
upload to roboflow/mahjong-vision-mvp-v2 project:
- 500 枚 (実物 M-League 動画から filter 済)
- 5,000 枚 (合成データ)
total: 5,500 枚
# 2. Generate Version (Augmentation 設定)
preprocessing:
- resize: 640x640
- filter null: True
augmentation (Roboflow 側でさらに):
- rotation: ±15° (合成側で既に大きく振っているので軽め)
- brightness: ±10%
- blur: 1px
# 3. Train
model: YOLOv8m (Fast) または YOLOv8l (Accurate)
epochs: 80
batch: 16
imgsz: 640
initial weights: COCO pretrained (アプローチ A と同じ)
# ノウハウだけ転移、重みは継承しない
| 項目 | アプローチ A (現行) | アプローチ C (推奨) |
|---|---|---|
| 準備時間 | 0 時間 | 2〜3 時間 (素材撮影 + マスキング) |
| 合成スクリプト開発 | — | 半日 (200 行程度の Python) |
| 合成データ生成 | — | 1 時間 (5,000 枚 / Workstation) |
| アノテーション工数 | 500 枚 × 50 bbox = 25,000 → 修正 7,500 (62 時間) | 合成分は 0 時間、実物 500 枚分のみ (62 時間) |
| 外注の場合 | $375 (≒ ¥55,000) | $375 (実物分のみ) |
| 訓練時間 (Roboflow) | 2〜4 時間 | 4〜8 時間 (データ量 11 倍) |
| 追加コスト | — | 半日〜1日 |
| 期待 mAP@0.5 | 0.80〜0.85 | 0.88〜0.94 |
カメラ姿勢 (R, t) と物体平面 n · X = d から、
画像上の bbox は透視変形によって四辺形 (一般には 4 自由度の四角形) になる。
トランプも麻雀牌も平面に近い物体なので、この問題設定は完全に共有される。
2D 画像座標 (x, y) ← 透視変換 ← 3D 卓座標 (X, Y, Z)
↑
カメラ内部行列 K と外部姿勢 (R, t)
→ axis-aligned bbox では正確に囲えない (隅に空白ができる)
→ OBB (cx, cy, w, h, θ) で囲うのが正解
Augmented Startups の playing-cards モデルは合成データで以下の変形を全て学習している:
これらは麻雀の M-League カメラワーク (ズーム・パン・カット) で生じる変形と完全に同じ。 バックボーンが「これは透視変形した矩形だ」と認識する能力は転移可能。
本プロジェクトは現状 axis-aligned bbox を採用しているが、将来 OBB に切り替える場合:
OBB は本来「副露の横倒し・斜め倒し」検出に最適。 平行四辺形の角度 θ を正確に取れば、立/倒 二値判定の精度も上がる (現状は h/w aspect ratio に依存 = ノイズに弱い)。
本プロジェクトは 2026-04-29 に 斜め上 (oblique-overhead) 設置 に方針転換した。 これは天井真下視点ではなく、M-League 中継と同じ「選手の向かい側上方からの斜め視点」を採用。
この視点では麻雀牌は必ず台形・平行四辺形・菱形に変形して見える。 トランプ検出モデルが学習している「透視変形した矩形物体の検出」能力はそのまま使える環境になっている。
| 指標 | 現状 (公開モデル) | A: COCO→麻雀 | B: 2段階 | C: 合成+自前 ★ |
|---|---|---|---|---|
| mAP@0.5 | 0.50〜0.70 | 0.80〜0.85 | 0.82〜0.88 | 0.88〜0.94 |
| 副露 recall | 0.65 | 0.80 | 0.85 | 0.92 |
| 緑フェルト false positive | 多発 | 大幅減 | 大幅減 | ほぼゼロ |
| 必要データ規模 | — | 500 枚 | 500 枚 | 500 + 5,000 合成 |
| 準備工数 | 0 | 0 | 半日 | 1〜2 日 |