0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

YOLOでゲーム画面を構造化する:ローカル自動ラベリングと学習ループ実践

0
Posted at

あるモバイル対戦ゲーム画面をYOLOでローカル自動ラベリング→学習ループ(labelImg + オートラベル)

この記事は「ローカルでラベリングするにはどうしたらよいか?」から始まり、
labelImgで手ラベル → YOLO学習 → ローカル自動ラベリング(Roboflow Auto-label相当) → 修正 → 再学習
を回して、検出器を実用域まで持っていった手順メモです。


ゴール

  • MP4から切り出したフレーム(例:2400枚)をローカルでラベリングし、
  • YOLOv8 で学習 → 自動ラベリングで作業を激減させ、
  • 盤面構造化や下流タスク(状態抽出・時系列化)に進める。

1. 前提:データ構成(YOLO形式)

今回のディレクトリ例(Windows)

clash-royale-assistant/
  dataset/
    images/
      train/
      val/
      test/
    labels/
      train/
      val/
      test/
    data.yaml
  runs/
  tools/

data.yaml の例:

path: ..
train: C:/path/to/project/dataset/images/train
val:   C:/path/to/project/dataset/images/val
test:  C:/path/to/project/dataset/images/test

nc: 11
names:
  0: unit_structure
  1: resource_bar
  2: unit_fast
  3: unit_tank
  4: unit_small
  5: projectile_spell
  6: unit_ranged
  7: unit_swarm
  8: base_core
  9: base_left
  10: base_right

2. フレームは 330×752 のままラベリングして良い?

結論:そのままでOK

  • YOLOは学習時に imgsz=640 などへリサイズする
  • アノテーション(YOLO txt)は**正規化座標(0〜1)**なので解像度変更に強い

ただし、

  • 30fps で連番をそのまま切ると 似た絵が多すぎる
  • 手ラベルが辛いので 間引き(例:5fps/10fps) を推奨

3. ローカルでの手ラベリング:labelImg

インストール(例)

pip install labelImg

起動

labelImg

使い方(最低限)

  • Open Dirdataset/images/train
  • Change Save Dirdataset/labels/train
  • YOLO形式で保存

まずは 200枚くらいを目標に手ラベル。


4. GPU確認(PyTorch / CUDA)

nvidia-smi でGPUが見えるのに、PyTorchがCUDAを認識しないことがある。

確認:

python -c "import torch; print('CUDA available:', torch.cuda.is_available()); print('GPU:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'None')"

例:CUDA available: False なら、PyTorchのCUDAビルドが合ってない。

(環境依存なので、ここは各自の構成に合わせてPyTorchを入れ直す)


5. 初回学習(YOLOv8m)

手ラベル200枚程度でも、とりあえず学習して オートラベル用の初期モデルを作る。

yolo train `
  model="C:/path/to/project\yolov8m.pt" `
  data="C:/path/to/project\dataset\data.yaml" `
  epochs=50 `
  imgsz=640 `
  batch=16 `
  device=0

ありがちなエラー:val が空

val: No images found in ...\dataset\images\val

dataset/images/val に画像を入れる(まずは20〜100枚でもOK)。


6. Roboflowみたいな「オートラベル」が欲しい → ローカルで作る

Ultralyticsの yolo predict だけだと、

  • 大量画像を一気に投げるとRAM/GPUが溢れることがある

そこで 1枚ずつ推論してYOLO txtを書き出すスクリプトを用意する。

tools/autolabel_yolo.py(安定版:1枚ずつ推論)

重要:大量画像をまとめて predict すると OOM しやすいので、
1枚ずつ推論(source=str(img))で回避。

# tools/autolabel_yolo.py
# 1枚ずつ推論してYOLO txtを書き出す(Ultralyticsのメモリ肥大化回避)
import argparse
from pathlib import Path
import torch
from ultralytics import YOLO

IMG_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--model", required=True)
    ap.add_argument("--images", required=True)
    ap.add_argument("--labels", required=True)
    ap.add_argument("--conf", type=float, default=0.55)
    ap.add_argument("--iou", type=float, default=0.7)
    ap.add_argument("--imgsz", type=int, default=512)
    ap.add_argument("--device", default="0")
    ap.add_argument("--every", type=int, default=1, help="process every Nth image (debug)")
    args = ap.parse_args()

    model = YOLO(args.model)
    img_dir = Path(args.images)
    lbl_dir = Path(args.labels)
    lbl_dir.mkdir(parents=True, exist_ok=True)

    images = sorted([p for p in img_dir.iterdir() if p.is_file() and p.suffix.lower() in IMG_EXTS])
    if not images:
        raise RuntimeError(f"No images found in {img_dir}")

    images = images[:: max(args.every, 1)]
    total = len(images)
    print(f"Auto-labeling {total} images (single-image inference)...")

    device = int(args.device) if str(args.device).isdigit() else args.device

    for i, img_path in enumerate(images, 1):
        r = model.predict(
            source=str(img_path),
            conf=args.conf,
            iou=args.iou,
            imgsz=args.imgsz,
            device=device,
            verbose=False,
            save=False,
        )[0]

        h, w = r.orig_shape
        txt_path = lbl_dir / f"{img_path.stem}.txt"

        lines = []
        if r.boxes is not None and len(r.boxes) > 0:
            boxes = r.boxes.xyxy.cpu().numpy()
            classes = r.boxes.cls.cpu().numpy().astype(int)

            for (x1, y1, x2, y2), c in zip(boxes, classes):
                x1 = max(0.0, min(float(x1), w))
                x2 = max(0.0, min(float(x2), w))
                y1 = max(0.0, min(float(y1), h))
                y2 = max(0.0, min(float(y2), h))

                bw = (x2 - x1) / w
                bh = (y2 - y1) / h
                xc = (x1 + x2) / 2.0 / w
                yc = (y1 + y2) / 2.0 / h

                if bw > 0 and bh > 0:
                    lines.append(f"{c} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")

        txt_path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")

        if torch.cuda.is_available():
            torch.cuda.empty_cache()

        if i % 50 == 0 or i == total:
            print(f"  {i}/{total} done")

    print(f"Done. Labels written to {lbl_dir}")

if __name__ == "__main__":
    main()

実行例(2380枚をオートラベル)

python tools\autolabel_yolo.py `
  --model "C:/path/to/project\runs\detect\train2\weights\best.pt" `
  --images "C:/path/to/project\dataset\images\train" `
  --labels "C:/path/to/project\dataset\labels\train" `
  --conf 0.55 --imgsz 512 --device 0

7. 学習→オートラベル→修正→再学習 のループ

コツ

  • 最初は200枚くらい手ラベル

  • すぐ学習して best.pt を作る

  • 以降は

    • オートラベル
    • labelImgで直すだけ
    • 修正が数百枚溜まったら再学習

8. v4/v5 の学習(継続学習)

これは何をしている?

  • ファインチューニング(転移学習)

    • COCO(一般物体検出のための大規模公開データセット)などの事前学習済み重み(yolov8m.pt)から開始
    • 目的ドメイン(ゲーム画面)で微調整
  • 継続事前学習ではない(自己教師・再構成目的などは使っていない)

  • LoRAでもない(adapter差分だけ学習しているわけではない)

v4(例:batch=32)

yolo train `
  model="C:/path/to/project\runs\y8m_11cls_v3\weights\best.pt" `
  data="C:/path/to/project\dataset\data.yaml" `
  epochs=80 imgsz=640 batch=32 device=0 `
  project="C:/path/to/project\runs" `
  name="y8m_11cls_v4_b32"

v5(例:短めに40epoch)

yolo train `
  model="C:/path/to/project\runs\y8m_11cls_v4_b32\weights\best.pt" `
  data="C:/path/to/project\dataset\data.yaml" `
  epochs=40 imgsz=640 batch=32 device=0 `
  project="C:/path/to/project\runs" `
  name="y8m_11cls_v5_b32"

9. y8m と y8n、どっちが良い?(2400フレームの場合)

  • 精度優先(小物・希少クラスを安定させたい)→ y8m
  • 推論軽量化(モバイル/CPU/常時リアルタイム)→ y8n

今回は

  • 小物(例:projectile_spell / unit_small / unit_swarm)
  • 希少クラス(例:unit_ranged)

が課題になりやすいので、y8m継続が堅い


10. クラス別インスタンス数を数える(train labels をスキャン)

PowerShellワンライナー:

Get-ChildItem "C:/path/to/project\dataset\labels\train" -Filter *.txt |
  Get-Content |
  Where-Object { $_ -match '^\d+' } |
  ForEach-Object { ($_ -split '\s+')[0] } |
  Group-Object |
  Sort-Object Count -Descending |
  Select-Object @{n='class_id';e={$_.Name}}, Count

実際の例(抜粋):

class_id Count
10       4829
8        4800
9        4752
1        2402
...
5         160  # projectile_spell
6         167  # unit_ranged
7         165  # unit_swarm

→ 小物・希少クラスは 300〜500例くらいまで増やすと安定しやすい。


11. 新しい動画を撮った方がいい?

結論:

  • 同じ動画内に目的クラスが十分出ているなら、新撮は必須ではない

  • ただし

    • projectile_spell などがそもそも出ない
    • 新端末/新UI/新解像度で運用したい

なら、目的クラスを意図的に増やした新動画は効果が高い。


12. 「ヒーロー版ユニット(通常ユニットの強化版)」など新種が出た。ラベル分ける?

基本方針:今は分けない

  • 見た目が近いと、クラス分割は不安定になりやすい
  • まずは unit_tank として検出できれば盤面構造化には十分

将来的に分けるなら条件:

  • 通常 / ヒーローそれぞれ 数百例
  • 見た目差がフレームで安定して識別可能
  • 下流タスクで区別が必須

まとめ

  • 手ラベル200枚程度でも まず学習してオートラベルを回す
  • tools/autolabel_yolo.py(1枚ずつ推論)で ローカルAuto-labelを安定運用
  • 修正→再学習を回すと、短時間で実用域に到達する
  • ボトルネックはモデルより 希少クラスの例数

付録:環境メモ

  • Ultralytics 8.4.7
  • torch nightly(GPU: RTX 5060 Ti 16GB)
  • expandable_segments がWindowsで効かないことがある → 1枚推論で回避
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?