YOLOファインチューニングテンプレート#
Ultralytics YOLOを使って、任意の物体検出データセットをファインチューニングするための公開用テンプレートです。データ本体は含めず、利用者が自分の動画、画像、YOLO形式ラベルを配置して実行する構成です。
本READMEでは、Ubuntu/Linux環境でGPUを使って学習する前提です。
前提条件#
本テンプレートは、以下の環境を前提とします。
OS: Ubuntu / Linux
Python: 3.11
パッケージ管理:
uvGPU: NVIDIA GPUを使用できる環境
データ形式: YOLO形式の物体検出データセット
アノテーション形式: Label StudioからYOLO形式でエクスポートしたラベル
Label Studioを使ったアノテーション環境の構築やエクスポート方法は、Label Studio 環境構築まとめ を参照してください。
GPUが認識されているかは、次のコマンドで確認できます。
# NVIDIA GPUが認識されているか確認する
nvidia-smi
ディレクトリ構成#
yolo_finetune_template/
├── configs/
│ └── dataset.yaml.example
├── videos/input/
├── frames/raw/
├── annotations/yolo_raw/
├── datasets/
├── scripts/
│ ├── extract_frames.py
│ ├── split_dataset.py
│ ├── make_data_yaml.py
│ ├── check_dataset.py
│ ├── train_yolo.py
│ ├── evaluate_yolo.py
│ ├── predict_images.py
│ ├── predict_video.py
│ └── list_prediction_errors.py
├── runs/experiments/
├── pyproject.toml
└── README.md
環境構築#
Python 3.11の仮想環境を作成し、必要なライブラリをインストールします。
# Python 3.11をインストールする
uv python install 3.11
# Python 3.11の仮想環境を作成する
uv venv --python 3.11
# 仮想環境を有効化する
source .venv/bin/activate
# pyproject.tomlに書かれた依存ライブラリをインストールする
uv sync
方法A: YOLO形式データセットから始める#
すでにYOLO形式データセットがある場合は、datasets/custom_yolo/ に配置します。
ここでは、検証用フォルダ名が valid のデータセットを例にします。
実際には自分のデータセットのディレクトリ構成に合わせて設定してください。
datasets/custom_yolo/
├── images/
│ ├── train/
│ ├── valid/
│ └── test/
├── labels/
│ ├── train/
│ ├── valid/
│ └── test/
画像ファイルとラベルファイルは、拡張子を除いたファイル名が対応している必要があります。
datasets/custom_yolo/images/train/sample_000001.jpg
datasets/custom_yolo/labels/train/sample_000001.txt
※一般的にdata.yamlは設定されていますが、ない場合は以下を参考にしてください。
data.yaml を生成します。
# images/train, images/valid, images/test を使うdata.yamlを生成する
uv run python scripts/make_data_yaml.py \
--dataset-dir datasets/custom_yolo \
--train images/train \
--val images/valid \
--test images/test \
--class-names target_object
引数:
--dataset-dir:data.yamlを作成するデータセットディレクトリ--train: 学習用画像ディレクトリ。--dataset-dirからの相対パス--val: 検証用画像ディレクトリ。--dataset-dirからの相対パス--test: test用画像ディレクトリ。--dataset-dirからの相対パス--class-names: クラスID順のクラス名
test データがない場合は、--test "" と指定します。
# test splitを書かないdata.yamlを生成する
uv run python scripts/make_data_yaml.py \
--dataset-dir datasets/custom_yolo \
--train images/train \
--val images/valid \
--test "" \
--class-names target_object
引数:
--dataset-dir:data.yamlを作成するデータセットディレクトリ--train: 学習用画像ディレクトリ相対パス--val: 検証用画像ディレクトリ相対パス--test "": test splitをdata.yamlに書かない指定--class-names: クラスID順のクラス名
複数クラスの場合は、クラスIDの順番にクラス名を並べます。
# 3クラス用のdata.yamlを生成する
uv run python scripts/make_data_yaml.py \
--dataset-dir datasets/custom_yolo \
--train images/train \
--val images/valid \
--test images/test \
--class-names class_a class_b class_c
引数:
--dataset-dir:data.yamlを作成するデータセットディレクトリ--train: 学習用画像ディレクトリ相対パス--val: 検証用画像ディレクトリ相対パス--test: test用画像ディレクトリ相対パス--class-names: クラスID順のクラス名。ここでは3クラス分を指定
生成される data.yaml の例です。
path: datasets/custom_yolo
train: images/train
val: images/valid
test: images/test
names:
0: target_object
方法B: 動画から画像を切り出して始める#
動画を videos/input/ に置きます。
videos/input/sample01.mp4
videos/input/sample02.mp4
1秒に1枚の間隔で画像を切り出します。
# videos/input/の動画から1秒ごとに画像を切り出す
uv run python scripts/extract_frames.py \
--video-dir videos/input \
--output-dir frames/raw \
--interval-sec 1.0
引数:
--video-dir: 入力動画を置いたディレクトリ--output-dir: 切り出した画像の保存先ディレクトリ--interval-sec: 画像を切り出す間隔。単位は秒
切り出した画像をLabel Studioでアノテーションし、YOLO形式でエクスポートしたラベルを annotations/yolo_raw/ に保存します。
frames/raw/sample01_000001.jpg
annotations/yolo_raw/sample01_000001.txt
未分割データを train/val/test に分割します。このスクリプトで作成する検証用フォルダ名は val です。
# frames/raw と annotations/yolo_raw を train/val/test に分割する
uv run python scripts/split_dataset.py \
--images-dir frames/raw \
--labels-dir annotations/yolo_raw \
--output-dir datasets/custom_yolo \
--val-ratio 0.2 \
--test-ratio 0.1 \
--seed 42
引数:
--images-dir: 分割前の画像ディレクトリ--labels-dir: 分割前のYOLO形式ラベルディレクトリ--output-dir: 分割後データセットの保存先ディレクトリ--val-ratio: 検証用データの割合--test-ratio: test用データの割合--seed: 分割結果を固定するための乱数シード
グループ単位で分割したい場合は、ファイル名の接頭辞をグループIDとして使えます。例えば sample01_000001.jpg なら sample01 がグループIDです。
# sample01をtest、sample02をvalに固定して分割する
uv run python scripts/split_dataset.py \
--images-dir frames/raw \
--labels-dir annotations/yolo_raw \
--output-dir datasets/custom_yolo \
--split-by-group \
--test-groups sample01 \
--val-groups sample02
引数:
--images-dir: 分割前の画像ディレクトリ--labels-dir: 分割前のYOLO形式ラベルディレクトリ--output-dir: 分割後データセットの保存先ディレクトリ--split-by-group: ファイル名の接頭辞をグループIDとして分割する指定--test-groups: testに固定するグループID--val-groups: valに固定するグループID
data.yaml を生成します。
# split_dataset.pyで作成した images/val 構成に合わせてdata.yamlを生成する
uv run python scripts/make_data_yaml.py \
--dataset-dir datasets/custom_yolo \
--train images/train \
--val images/val \
--test images/test \
--class-names target_object
引数:
--dataset-dir:data.yamlを作成するデータセットディレクトリ--train: 学習用画像ディレクトリ。--dataset-dirからの相対パス--val: 検証用画像ディレクトリ。split_dataset.pyの出力に合わせてimages/valを指定--test: test用画像ディレクトリ。--dataset-dirからの相対パス--class-names: クラスID順のクラス名
データセット確認#
学習前に、画像とラベルの対応、YOLO形式の値、クラス数を確認します。
# data.yamlを読み、train/val/testの画像とラベルを確認する
uv run python scripts/check_dataset.py \
--dataset-dir datasets/custom_yolo
引数:
--dataset-dir: 確認対象のデータセットディレクトリ。中のdata.yamlを読み取り
動画から切り出した直後の未分割データを確認したい場合は、次のようにします。
# frames/raw と annotations/yolo_raw の対応を確認する
uv run python scripts/check_dataset.py \
--check-raw
引数:
--check-raw:frames/rawとannotations/yolo_rawの対応確認に切り替える指定
学習#
# YOLOをGPU(device 0)でファインチューニングする
uv run python scripts/train_yolo.py \
--data datasets/custom_yolo/data.yaml \
--model yolo11n.pt \
--epochs 100 \
--patience 30 \
--imgsz 640 \
--batch 8 \
--device 0 \
--name custom_object_ep100
引数:
--data: 学習に使うデータセット設定ファイル--model: ファインチューニング元のYOLO重み--epochs: 最大学習エポック数--patience: 改善が止まったときに早期終了するまでの待機エポック数--imgsz: 入力画像サイズ--batch: バッチサイズ--device: 使用するGPU番号。0は1枚目のGPU--name: 実験名。結果保存ディレクトリ名に使用
主な出力先です。
runs/experiments/custom_object_ep100/train/results.csv
runs/experiments/custom_object_ep100/train/results.png
runs/experiments/custom_object_ep100/train/weights/best.pt
runs/experiments/custom_object_ep100/metadata/train_metadata.json
test評価#
# 学習済みbest.ptをtest splitで評価する
uv run python scripts/evaluate_yolo.py \
--model runs/experiments/custom_object_ep100/train/weights/best.pt \
--data datasets/custom_yolo/data.yaml \
--split test \
--conf 0.25 \
--iou 0.7 \
--name test_evaluation
引数:
--model: 評価に使う学習済み重み--data: 評価対象のデータセット設定ファイル--split: 評価対象の分割。ここではtest--conf: 検出結果として採用する信頼度の閾値--iou: NMSで重なったBBoxを統合するときのIoU閾値--name: 評価結果の保存名
出力先です。
runs/experiments/custom_object_ep100/test_evaluation/evaluation_metadata.json
runs/experiments/custom_object_ep100/test_evaluation/confusion_matrix.png
runs/experiments/custom_object_ep100/test_evaluation/confusion_matrix_normalized.png
runs/experiments/custom_object_ep100/test_evaluation/BoxPR_curve.png
画像推論#
# test画像に対して推論し、検出結果画像を保存する
uv run python scripts/predict_images.py \
--model runs/experiments/custom_object_ep100/train/weights/best.pt \
--source datasets/custom_yolo/images/test \
--conf 0.25 \
--name test_images_conf025
引数:
--model: 推論に使う学習済み重み--source: 推論対象の画像ディレクトリ--conf: 検出結果として採用する信頼度の閾値--name: 推論結果の保存名
動画推論#
# 入力動画に対して推論し、検出結果動画をmp4で保存する
uv run python scripts/predict_video.py \
--model runs/experiments/custom_object_ep100/train/weights/best.pt \
--source videos/input/sample01.mp4 \
--conf 0.25 \
--name sample01_conf025 \
--output-format mp4
引数:
--model: 推論に使う学習済み重み--source: 推論対象の動画ファイル--conf: 検出結果として採用する信頼度の閾値--name: 推論結果の保存名--output-format: 出力動画の形式
エラー分析#
正解ラベルと推論結果を比較し、間違えた画像、FP画像、FN画像をCSVと画像で出力します。
# test splitの推論結果を正解ラベルと比較し、誤検出・未検出を出力する
uv run python scripts/list_prediction_errors.py \
--model runs/experiments/custom_object_ep100/train/weights/best.pt \
--dataset-dir datasets/custom_yolo \
--split test \
--conf 0.25 \
--match-iou 0.5 \
--name test_error_conf025_iou05 \
--save-images
引数:
--model: 推論に使う学習済み重み--dataset-dir: 正解ラベルと画像を含むデータセットディレクトリ--split: エラー分析対象の分割。ここではtest--conf: 検出結果として採用する信頼度の閾値--match-iou: 正解BBoxと推論BBoxを同一物体とみなすIoU閾値--name: エラー分析結果の保存名--save-images: エラー画像を保存する指定
結果の保存場所#
runs/experiments/<experiment_name>/
├── train/
├── test_evaluation/
├── predictions/
│ ├── images/
│ └── videos/
├── error_analysis/
└── metadata/