あるモバイル対戦ゲーム画面を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 Dir→dataset/images/train -
Change Save Dir→dataset/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枚推論で回避