変換せずにCOCO YOLO 学習させる方法
なぜCOCO で直接トレーニングするのか
アノテーション で COCO JSON フォーマットは直接使用できます Ultralytics YOLO 変換せずにトレーニングを行う .txt まずファイルを処理します。これは、サブクラス化によって行われます。 YOLODataset COCO オンザフライで解析し、カスタムトレーナーを通じてトレーニングパイプラインに組み込む。
このアプローチでは、COCO を唯一の信頼できる情報源として維持します — いいえ convert_coco() 呼び出し、ディレクトリの再編成なし、中間ラベルファイルなし。 YOLO26 および他のすべてのUltralytics YOLO detectモデルがサポートされています。segmentおよびポーズモデルには追加のラベルフィールドが必要です (参照 よくある質問)。
その代わり、1回限りの変換をお探しですか?
以下を COCOからYOLOへの変換ガイド 標準的な用途には convert_coco() ワークフロー。
アーキテクチャの概要
2つのクラスが必要です:
COCODataset—COCO を読み込み、変換します バウンディングボックス トレーニング中にメモリ内でYOLO に変換するCOCOTrainer— 上書きbuild_dataset()使用するにはCOCODatasetデフォルトの代わりにYOLODataset
この実装は、組み込みの機能と同じパターンに従っています GroundingDataset、これもJSONアノテーションを直接読み込みます。3つのメソッドがオーバーライドされます。 get_img_files(), cache_labels()、および get_labels().
COCO JSONデータセットクラスの構築
The COCODataset クラスは以下を継承します YOLODataset ラベル読み込みロジックをオーバーライドします。読み込む代わりに .txt ラベルディレクトリからファイルを読み込む際、COCO JSONファイルを開き、画像ごとにグループ化されたアノテーションを反復処理し、各バウンディングボックスをCOCOピクセル形式から変換します [x_min, y_min, width, height] YOLO センター形式へ [x_center, y_center, width, height]。群衆アノテーション(iscrowd: 1)と面積ゼロのボックスは自動的にスキップされます。
The get_img_files() このメソッドは、画像のパスがJSONから解決されるため、空のリストを返します file_name 内のフィールド cache_labels()。カテゴリIDはソートされ、ゼロベースのクラスインデックスに再マッピングされるため、1ベース(標準COCO)と非連続IDスキームの両方が正しく機能します。
import json
from collections import defaultdict
from pathlib import Path
import numpy as np
from ultralytics.data.dataset import DATASET_CACHE_VERSION, YOLODataset
from ultralytics.data.utils import get_hash, load_dataset_cache_file, save_dataset_cache_file
from ultralytics.utils import TQDM
class COCODataset(YOLODataset):
"""Dataset that reads COCO JSON annotations directly without conversion to .txt files."""
def __init__(self, *args, json_file="", **kwargs):
self.json_file = json_file
super().__init__(*args, data={"channels": 3}, **kwargs)
def get_img_files(self, img_path):
"""Image paths are resolved from the JSON file, not from scanning a directory."""
return []
def cache_labels(self, path=Path("./labels.cache")):
"""Parse COCO JSON and convert annotations to YOLO format. Results are saved to a .cache file."""
x = {"labels": []}
with open(self.json_file) as f:
coco = json.load(f)
images = {img["id"]: img for img in coco["images"]}
# Sort categories by ID and map to 0-indexed classes
categories = {cat["id"]: i for i, cat in enumerate(sorted(coco["categories"], key=lambda c: c["id"]))}
img_to_anns = defaultdict(list)
for ann in coco["annotations"]:
img_to_anns[ann["image_id"]].append(ann)
for img_info in TQDM(coco["images"], desc="reading annotations"):
h, w = img_info["height"], img_info["width"]
im_file = Path(self.img_path) / img_info["file_name"]
if not im_file.exists():
continue
self.im_files.append(str(im_file))
bboxes = []
for ann in img_to_anns.get(img_info["id"], []):
if ann.get("iscrowd", False):
continue
# COCO: [x, y, w, h] top-left in pixels -> YOLO: [cx, cy, w, h] center normalized
box = np.array(ann["bbox"], dtype=np.float32)
box[:2] += box[2:] / 2 # top-left to center
box[[0, 2]] /= w # normalize x
box[[1, 3]] /= h # normalize y
if box[2] <= 0 or box[3] <= 0:
continue
cls = categories[ann["category_id"]]
bboxes.append([cls, *box.tolist()])
lb = np.array(bboxes, dtype=np.float32) if bboxes else np.zeros((0, 5), dtype=np.float32)
x["labels"].append(
{
"im_file": str(im_file),
"shape": (h, w),
"cls": lb[:, 0:1],
"bboxes": lb[:, 1:],
"segments": [],
"normalized": True,
"bbox_format": "xywh",
}
)
x["hash"] = get_hash([self.json_file, str(self.img_path)])
save_dataset_cache_file(self.prefix, path, x, DATASET_CACHE_VERSION)
return x
def get_labels(self):
"""Load labels from .cache file if available, otherwise parse JSON and create the cache."""
cache_path = Path(self.json_file).with_suffix(".cache")
try:
cache = load_dataset_cache_file(cache_path)
assert cache["version"] == DATASET_CACHE_VERSION
assert cache["hash"] == get_hash([self.json_file, str(self.img_path)])
self.im_files = [lb["im_file"] for lb in cache["labels"]]
except (FileNotFoundError, AssertionError, AttributeError, KeyError, ModuleNotFoundError):
cache = self.cache_labels(cache_path)
cache.pop("hash", None)
cache.pop("version", None)
return cache["labels"]
解析されたラベルは .cache JSONファイルの隣にファイルを配置します(例: instances_train.cache)。その後のトレーニング実行では、キャッシュが直接ロードされ、JSON解析がスキップされます。JSONファイルが変更された場合、ハッシュチェックが失敗し、キャッシュは自動的に再構築されます。
データセットをトレーニングパイプラインに接続する
トレーナーで必要な変更は、オーバーライドすることだけです build_dataset()。デフォルトの DetectionTrainer を構築します YOLODataset …をスキャンする .txt ラベルファイル。これを次のように置き換えることで COCODataset、トレーナーは代わりにCOCO JSONから読み込みます。
JSONファイルのパスは、カスタム train_json / val_json データ設定のフィールド(ステップ3を参照)。トレーニング中、 mode="train" ~することを決意する train_json; 検証中、 mode="val" ~することを決意する val_json。もし val_json が設定されていない場合、デフォルトでは train_json.
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import colorstr
class COCOTrainer(DetectionTrainer):
"""Trainer that uses COCODataset for direct COCO JSON training."""
def build_dataset(self, img_path, mode="train", batch=None):
json_file = self.data["train_json"] if mode == "train" else self.data.get("val_json", self.data["train_json"])
return COCODataset(
img_path=img_path,
json_file=json_file,
imgsz=self.args.imgsz,
batch_size=batch,
augment=mode == "train",
hyp=self.args,
rect=self.args.rect or mode == "val",
cache=self.args.cache or None,
single_cls=self.args.single_cls or False,
stride=int(self.model.stride.max()) if hasattr(self, "model") and self.model else 32,
pad=0.0 if mode == "train" else 0.5,
prefix=colorstr(f"{mode}: "),
task=self.args.task,
classes=self.args.classes,
fraction=self.args.fraction if mode == "train" else 1.0,
)
COCO JSON用のdataset.yamlの設定
The dataset.yaml 標準を使用します path, train、および val 画像ディレクトリを特定するためのフィールド。さらに2つのフィールド、 train_json および val_json、COCOアノテーションファイルを指定します。 COCOTrainer と書かれている。その nc および names フィールドはクラスの数とその名前を定義し、ソートされた順序に一致します categories JSON内で。
path: /path/to/images # root directory with train/ and val/ subfolders
train: train
val: val
# COCO JSON annotation files
train_json: /path/to/annotations/instances_train.json
val_json: /path/to/annotations/instances_val.json
nc: 80
names:
0: person
1: bicycle
# ... remaining class names
期待されるディレクトリ構造:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yaml
COCO でのトレーニングの実行
データセットクラス、トレーナークラス、およびYAML設定ファイルが整えば、トレーニングは標準の手順に従って実行されます model.train() 呼び出し。通常のトレーニング実行との唯一の違いは、 trainer=COCOTrainer 引数。これにより、Ultralyticsはデフォルトのデータセットローダーではなく、カスタムデータセットローダーを使用するように指示されます。
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)
検証、チェックポイントの保存、メトリクスの記録を含め、トレーニングパイプライン全体が想定通りに実行されています。
完全な導入
便宜上、完全な実装は単一のコピー&ペーストスクリプトとして以下に提供されています。これには、カスタムデータセット、カスタムトレーナー、およびトレーニング呼び出しが含まれます。これをあなたの[ファイル]と一緒に保存してください。 dataset.yaml 直接実行します。
import json
from collections import defaultdict
from pathlib import Path
import numpy as np
from ultralytics import YOLO
from ultralytics.data.dataset import DATASET_CACHE_VERSION, YOLODataset
from ultralytics.data.utils import get_hash, load_dataset_cache_file, save_dataset_cache_file
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import TQDM, colorstr
class COCODataset(YOLODataset):
"""Dataset that reads COCO JSON annotations directly without conversion to .txt files."""
def __init__(self, *args, json_file="", **kwargs):
self.json_file = json_file
super().__init__(*args, data={"channels": 3}, **kwargs)
def get_img_files(self, img_path):
return []
def cache_labels(self, path=Path("./labels.cache")):
x = {"labels": []}
with open(self.json_file) as f:
coco = json.load(f)
images = {img["id"]: img for img in coco["images"]}
categories = {cat["id"]: i for i, cat in enumerate(sorted(coco["categories"], key=lambda c: c["id"]))}
img_to_anns = defaultdict(list)
for ann in coco["annotations"]:
img_to_anns[ann["image_id"]].append(ann)
for img_info in TQDM(coco["images"], desc="reading annotations"):
h, w = img_info["height"], img_info["width"]
im_file = Path(self.img_path) / img_info["file_name"]
if not im_file.exists():
continue
self.im_files.append(str(im_file))
bboxes = []
for ann in img_to_anns.get(img_info["id"], []):
if ann.get("iscrowd", False):
continue
box = np.array(ann["bbox"], dtype=np.float32)
box[:2] += box[2:] / 2
box[[0, 2]] /= w
box[[1, 3]] /= h
if box[2] <= 0 or box[3] <= 0:
continue
cls = categories[ann["category_id"]]
bboxes.append([cls, *box.tolist()])
lb = np.array(bboxes, dtype=np.float32) if bboxes else np.zeros((0, 5), dtype=np.float32)
x["labels"].append(
{
"im_file": str(im_file),
"shape": (h, w),
"cls": lb[:, 0:1],
"bboxes": lb[:, 1:],
"segments": [],
"normalized": True,
"bbox_format": "xywh",
}
)
x["hash"] = get_hash([self.json_file, str(self.img_path)])
save_dataset_cache_file(self.prefix, path, x, DATASET_CACHE_VERSION)
return x
def get_labels(self):
cache_path = Path(self.json_file).with_suffix(".cache")
try:
cache = load_dataset_cache_file(cache_path)
assert cache["version"] == DATASET_CACHE_VERSION
assert cache["hash"] == get_hash([self.json_file, str(self.img_path)])
self.im_files = [lb["im_file"] for lb in cache["labels"]]
except (FileNotFoundError, AssertionError, AttributeError, KeyError, ModuleNotFoundError):
cache = self.cache_labels(cache_path)
cache.pop("hash", None)
cache.pop("version", None)
return cache["labels"]
class COCOTrainer(DetectionTrainer):
"""Trainer that uses COCODataset for direct COCO JSON training."""
def build_dataset(self, img_path, mode="train", batch=None):
json_file = self.data["train_json"] if mode == "train" else self.data.get("val_json", self.data["train_json"])
return COCODataset(
img_path=img_path,
json_file=json_file,
imgsz=self.args.imgsz,
batch_size=batch,
augment=mode == "train",
hyp=self.args,
rect=self.args.rect or mode == "val",
cache=self.args.cache or None,
single_cls=self.args.single_cls or False,
stride=int(self.model.stride.max()) if hasattr(self, "model") and self.model else 32,
pad=0.0 if mode == "train" else 0.5,
prefix=colorstr(f"{mode}: "),
task=self.args.task,
classes=self.args.classes,
fraction=self.args.fraction if mode == "train" else 1.0,
)
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)
ハイパーパラメータの推奨事項については、モデルトレーニングのヒントガイドを参照してください。
よくある質問
これとconvert_coco()の違いは何ですか?
convert_coco() と書いている .txt ラベル付きファイルをディスクに書き出すのは、一度限りの変換として行います。このアプローチでは、各トレーニング実行の開始時にJSONを解析し、メモリ内でアノテーションを変換します。使用方法 convert_coco() YOLO恒久的なラベルを使用する場合、このアプローチを採用することで、追加のファイルを生成することなく、COCO を唯一の信頼できる情報源として維持できます。
YOLOはカスタムコードなしでCOCO JSONでトレーニングできますか?
現在のUltralytics では、YOLO想定されているため、そうではありません .txt デフォルトではラベルが設定されます。このガイドでは、必要な最小限のカスタムコード(データセットクラス1つとトレーナークラス1つ)を紹介します。これらを定義すれば、トレーニングには標準的な model.train() 呼び出し。
これはセグメンテーションおよび姿勢推定をサポートしていますか?
このガイドでは、以下の内容を取り上げます 物体検出。追加するには インスタンスセグメンテーション サポート、以下を含める segmentation COCO からのポリゴンデータ segments 各ラベル辞書のフィールド。 姿勢推定、含める keypoints。 GroundingDataset ソースコード セグメントを処理するための参照実装を提供します。
このカスタムデータセットでデータ拡張は機能しますか?
はい。 COCODataset 拡張します YOLODataset、そのため、すべての組み込み データ拡張 — モザイク, mixup, コピー&ペースト、その他 — 変更なしで実行されます。
カテゴリIDはクラスインデックスにどのように対応付けられているのですか?
カテゴリは以下でソートされています id 0から始まる連番のインデックスにマッピングされます。これにより、1ベースのID(標準的なCOCO)、0ベースのID、および連続しないIDが処理されます。 names 辞書内の dataset.yaml COCOと同じ並べ替え順序に従うべきである categories 配列。
あらかじめ変換済みのラベルと比較して、パフォーマンス上のオーバーヘッドはありますか?
COCO は、最初のトレーニング実行時に一度だけ解析されます。解析されたラベルは .cache ファイルであるため、以降の実行では再解析なしで瞬時にロードされます。アノテーションはメモリに保持されるため、トレーニング速度は標準のYOLOトレーニングと同一です。JSONファイルが変更された場合、キャッシュは自動的に再構築されます。