合成データ生成パイプライン実践ガイド — アノテーション工数ゼロで 5,000 枚

タイル素材 × 背景 × 配置ロジック × augmentation で YOLO ラベル付き合成データを大量生成する実装方針
作成日: 2026-05-14 目的: 訓練データ拡張 期待効果: mAP +5〜10% 前提: 実物 500 枚 + 合成 5,000 枚 (1:10)
← INDEX 設計ボトルネック 公開モデル素性 類似モデル 転移学習 卓上マスキング

1. なぜ合成データが必要か

核心: 実物 M-League フレームへの手動アノテーションは 1 枚あたり 1〜3 分 (34 種 × 14 個の牌 = 約 476 ラベル/フレーム)。 500 枚揃えるには 15〜25 時間 の単純作業が必要。 一方で合成データなら、ラベル生成は配置時に自動付与されるため、 5,000 枚を 計算時間のみ (人手 0) で得られる。

1.1 工数比較

方式枚数人手所要時間金額換算 (¥5,000/h)
手動アノテ (実物)500あり15〜25 h¥75,000〜¥125,000
Roboflow Auto-Label + 修正500あり (1/3 程度)5〜8 h¥25,000〜¥40,000
合成生成5,00002 h (計算)¥0 (電気代のみ)

1.2 合成データが向く問題

1.3 合成データが向かない問題

2. パイプライン全体像

[1. タイル素材ライブラリ]      ← 34 種 × N アングル (PNG, 透過背景)
       │
       ▼
[2. 背景ライブラリ]            ← 10〜30 種 (緑フェルト / 木目 / 単色 / 実フレーム)
       │
       ▼
[3. 配置サンプラ]              ← random / grid / hand-of-13 / discard-row パターン
       │
       ▼
[4. occlusion / rotation]      ← 手で隠す / 斜め / 重ね / 倒し
       │
       ▼
[5. augmentation]              ← brightness, blur, noise, JPEG re-encode, color jitter
       │
       ▼
[6. YOLO ラベル自動生成]       ← 配置時の bbox を YOLO 形式 (cls cx cy w h) で保存
       │
       ▼
[出力: image_NNNN.jpg + image_NNNN.txt 5,000 ペア]

2.1 ディレクトリ構成 (提案)

synthetic/
├── assets/
│   ├── tiles/            # 34 種 PNG (透過)
│   │   ├── m1.png ... m9.png
│   │   ├── p1.png ... p9.png
│   │   ├── s1.png ... s9.png
│   │   └── z1.png ... z7.png
│   └── backgrounds/      # 緑フェルト / 木目 / 実フレーム crop
│       └── *.jpg
├── output/
│   ├── images/           # image_00001.jpg ... image_05000.jpg
│   └── labels/           # image_00001.txt ... image_05000.txt
├── generate.py           # メインスクリプト
└── config.yaml           # クラス定義 / 配置パラメータ

2.2 想定 CLI

# 5,000 枚生成 (フル素材モード)
python scripts/generate-synthetic-mahjong.py \
    --tiles synthetic/assets/tiles \
    --backgrounds synthetic/assets/backgrounds \
    --out synthetic/output \
    --count 5000 \
    --seed 42

# 素材なし procedural モード (素材準備が間に合わない場合)
python scripts/generate-synthetic-mahjong.py \
    --procedural \
    --out synthetic/output \
    --count 2000

3. 先行事例: Augmented Startups Playing Cards

トランプ検出の Roboflow データセット augmented-startups/playing-cards-ow27d10,100 枚の合成 + augmentation で 24,240 枚に拡張した代表例。 手法は単純だが効果が高く、麻雀タイルにそのまま転用可能。

3.1 Augmented Startups の手法サマリ

段階処理枚数倍率
① ベース合成52 種カード × N アングル × ランダム配置10,100 枚
② 90°/180°/270° 回転4 倍化40,400 枚 (相当)
③ rotation ±15° / shear ±10°幾何 augmentation
④ brightness / contrast / hue色変動
⑤ blur / noise / cutout劣化系
最終: train/valid/test split24,240 / 1,300 / 700 (Roboflow 既定)
転移の根拠: 詳細は 転移学習可能性 Section 3 参照。 トランプと麻雀牌は「平面の長方形物体」「シンボル + 数字」「卓上に並べる」という共通点が強く、 pretraining として有効。本セクションはその合成データ生成手法を麻雀に展開する具体策。

3.2 麻雀との違い

項目トランプ麻雀
クラス数5234 (赤ドラ込みで 37)
形状薄い長方形 (2D)厚みのある直方体 (3D)
方向4 方向 (90° 単位)4 方向 + 倒れ (立/伏)
配置単位ハンド (5 枚) / 卓上ばら手牌 13/14 列 + 河 + 副露
背景緑フェルト / 木目緑フェルト (M-League 専属)

4. 麻雀での実装方針 (34 種 × 10 背景)

4.1 タイル素材の収集

34 種 × 複数アングルの PNG が必要。入手経路:

  1. 既存麻雀アプリのスプライト — 雀魂・天鳳のスクリーンショットから crop (要ライセンス確認)
  2. Wikimedia Commons の Mahjong tile set — public domain SVG (`File:MJ*.svg`) を PNG 化
  3. 手撮り素材 — 実物の麻雀牌セット 1 セットを buy & shoot (¥3,000〜¥10,000)
  4. 3D レンダリング — Blender で 34 種モデル + ライティング Variant (上級者向け)
推奨: Wikimedia の SVG セット (CC0/Public Domain) を 256×384 PNG にレンダリング、 + 実物 1 セットを 4 アングル (真上・斜め前・斜め後・水平) 撮影で組み合わせ。 初期投資 ¥5,000 + 半日作業で揃う。

4.2 配置パターン (4 種)

パターン名説明出力割合
hand-of-13手牌風 (13 枚 1 列 + ツモ 1 枚)30%
discard-row河 (6 列 × 1〜4 行)30%
meld副露 (ポン/チー/カン)15%
random-scatterランダム散布 (occlusion 学習用)25%

4.3 occlusion / rotation の付与

# 配置後にランダム occlusion を適用
def apply_occlusion(image, bboxes, occlusion_rate=0.15):
    """画像にランダム矩形 (手・腕・他牌) を描画して bbox を一部隠す"""
    h, w = image.shape[:2]
    n_occlusions = np.random.poisson(2)
    for _ in range(n_occlusions):
        ox = np.random.randint(0, w)
        oy = np.random.randint(0, h)
        ow = np.random.randint(40, 120)
        oh = np.random.randint(40, 120)
        color = (np.random.randint(20, 80),) * 3  # 暗色矩形 = 手の影
        cv2.rectangle(image, (ox, oy), (ox+ow, oy+oh), color, -1)
    # bbox の occlusion 率を計算し、>70% 隠れたものはラベルから削除
    return filter_visible_bboxes(image, bboxes, threshold=0.30)

4.4 perspective 変形

def random_perspective(tile_img, max_skew=0.15):
    """牌に台形変形を加えて斜め視点をシミュレート"""
    h, w = tile_img.shape[:2]
    src = np.float32([[0,0],[w,0],[w,h],[0,h]])
    dst = src + np.random.uniform(-max_skew, max_skew, (4,2)) * np.array([w,h])
    M = cv2.getPerspectiveTransform(src, dst.astype(np.float32))
    return cv2.warpPerspective(tile_img, M, (w, h), borderValue=(0,0,0,0))

4.5 YOLO ラベル自動生成

配置時の bbox を YOLO 形式 (正規化中心座標 + 幅高) で保存:

def write_yolo_label(label_path, bboxes, img_w, img_h):
    """bboxes = [(cls_id, x1, y1, x2, y2), ...]"""
    with open(label_path, "w") as f:
        for cls_id, x1, y1, x2, y2 in bboxes:
            cx = (x1 + x2) / 2 / img_w
            cy = (y1 + y2) / 2 / img_h
            bw = (x2 - x1) / img_w
            bh = (y2 - y1) / img_h
            f.write(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")
重要: ラベル生成は 配置時の bbox をそのまま記録するだけ。 検出器を回す必要は一切ない。これが「アノテーション工数 0」の核心。

5. procedural mode (素材なしフォールバック)

用途: 素材ライブラリの収集が間に合わない場合、 あるいは「形状の汎化能力」を先に確認したい場合の中間ステップ。 本物の見た目ではないが、「平面の長方形 + シンボル」のジオメトリだけは学習できる

5.1 procedural モードの設計

  1. 背景: 緑フェルト風グラデーション (HSV: H=70±10, S=120±30, V=110±30)
  2. 牌: 白い角丸長方形 (60×85 px) + 上部にシンボル文字を描画
  3. シンボル文字:
    • 萬子: 漢数字 (一〜九) + 「萬」
    • 筒子: 円マーク × n 個 (Unicode `●`)
    • 索子: 縦線 × n 本 (1 索だけ鳥アイコン代用)
    • 字牌: 東/南/西/北/中/發/白 (1 文字)
  4. 影: 各牌の右下に半透明黒矩形でドロップシャドウ

5.2 procedural サンプルコード (短縮)

TILE_LABELS = {
    "m1": "一萬", "m2": "二萬", ..., "m9": "九萬",
    "p1": "①",   "p2": "②",  ..., "p9": "⑨",
    "s1": "鳥",   "s2": "‖",   ..., "s9": "‖‖‖‖‖‖‖‖‖",
    "z1": "東",   "z2": "南",   ..., "z7": "中",
}

def render_procedural_tile(cls_name, size=(60, 85)):
    img = np.full((size[1], size[0], 3), 240, dtype=np.uint8)  # ベージュ
    cv2.rectangle(img, (0,0), (size[0]-1, size[1]-1), (180,180,180), 2)
    label = TILE_LABELS[cls_name]
    # PIL で日本語文字描画 (cv2.putText は日本語不可)
    pil = Image.fromarray(img)
    draw = ImageDraw.Draw(pil)
    font = ImageFont.truetype("msgothic.ttc", 28)
    draw.text((size[0]//2 - 14, size[1]//2 - 18), label, font=font, fill=(40,40,40))
    return np.array(pil)

5.3 procedural モードの限界

procedural で生成した牌は 本物の麻雀牌と見た目が大きく異なる ため、 procedural のみで訓練したモデルは実 M-League フレームで検出率が低い (推定 mAP 0.15〜0.30)。 形状汎化の Pre-training 用途と割り切り、必ず本物素材データと混ぜて使う。

6. 既知の限界とドメインシフト対策

6.1 ドメインシフトの正体

合成データで訓練したモデルが実物データで性能を出さない現象は "sim-to-real gap" と呼ばれ、 主に以下の要因による:

要因合成の特徴実物の特徴
照明均一・きれいテレビ照明・反射・スポットあり
ぼけシャープカメラ動き・被写界深度のぼけ
圧縮無劣化 PNG/JPEG q=95H.264 動画 → JPEG q=70 で圧縮アーティファクト
ノイズほぼなしセンサーノイズ・露出変動
色再現RGB そのまま放送機器のカラーグレーディング

6.2 対策 (augmentation 強度を上げる)

# Albumentations による sim-to-real 緩和 augmentation
import albumentations as A

transform = A.Compose([
    A.MotionBlur(blur_limit=(3, 9), p=0.4),           # 動きぼけ
    A.GaussianBlur(blur_limit=(3, 7), p=0.3),
    A.GaussNoise(var_limit=(10, 50), p=0.5),          # センサーノイズ
    A.ImageCompression(quality_lower=50, quality_upper=85, p=0.7),  # JPEG 劣化
    A.RandomBrightnessContrast(brightness_limit=0.3, contrast_limit=0.3, p=0.6),
    A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=20, p=0.5),
    A.RGBShift(p=0.3),                                # 放送機器のカラー再現
    A.RandomShadow(p=0.2),
    A.CLAHE(p=0.2),
], bbox_params=A.BboxParams(format="yolo", label_fields=["class_labels"]))

6.3 段階的 augmentation 強度

落とし穴: Albumentations の bbox 変形は cutout / shear / rotate で bbox 範囲外の物体まで bbox にトリミングされる。 検証フェーズで「ラベルなのに物体がない」枚を 10 枚目視確認すること。

7. 実物データとの混合比率

推奨混合比: 実 500 + 合成 5,000 = 1:10
実物データで「本番ドメイン」を、合成データで「クラス均衡 + occlusion バリエーション」を担当させる分業。 純粋合成のみは sim-to-real gap で失敗するため、必ず実物を混ぜる。

7.1 比率と期待性能 (推定)

構成実物合成期待 mAP@0.5備考
実物のみ50000.60〜0.70クラス不均衡で字牌が弱い
合成のみ05,0000.15〜0.35sim-to-real gap で失敗
1:15005000.65〜0.75合成の効果が薄い
1:10 (推奨)5005,0000.78〜0.88クラス均衡 + 実物ドメイン両立
1:2050010,0000.75〜0.85合成が支配的になり実物軽視

7.2 学習スケジュール (2 段階)

  1. Stage 1: 合成で warm-up (50 epoch)
    • 5,000 枚合成のみで訓練、汎化を獲得
  2. Stage 2: 実物 + 合成混合で fine-tune (30 epoch)
    • 実物 500 + 合成 5,000 をバッチサンプリング比 1:1 でサンプリング
    • learning rate を Stage 1 の 1/10 に

7.3 evaluation の独立性

必須: 評価セット (test 100 枚) は 実物のみ。 合成データを評価に混ぜると本番性能を過大評価する。

8. 期待効果と評価方法

8.1 mAP 寄与の期待値 (推定)

条件mAP@0.5差分
ベースライン: 公開モデル test-upsgd/mahjong-tiles-oc9zz0.70
実物 500 のみで fine-tune0.78+0.08
実物 500 + 合成 5,000 (1:10)0.83〜0.88+0.13〜+0.18
+ 卓上マスキング前処理0.86〜0.91+0.16〜+0.21

8.2 A/B 評価設計

同じ評価セット 100 枚に対して以下 3 モデルを比較:

  1. Model A: 公開モデル (ベースライン)
  2. Model B: 実物 500 のみで fine-tune
  3. Model C: 実物 500 + 合成 5,000 で fine-tune

詳細な評価テンプレートは model-evaluation-template.html 参照。

8.3 デバッグ用 sanity check

8.4 ROI 試算

項目工数金額
素材収集 (Wikimedia + 実物撮影)4 h¥20,000 + 素材 ¥5,000
generate.py 実装8 h¥40,000
5,000 枚生成 (計算のみ)2 h¥0 (電気代)
sanity check + 調整3 h¥15,000
合計17 h¥80,000
これに対して実物 5,000 枚手動アノテだと150〜250 h¥750,000〜¥1,250,000
結論: 合成データ生成パイプラインは 10 倍以上のコスト効率を持つ。 手動アノテに頼らない訓練ループが構築できれば、モデル改善サイクルが大幅に加速する。