So trainierst du YOLO auf COCO JSON, ohne Konvertierung
Warum direkt auf COCO JSON trainieren?
Annotationen in COCO JSON das Format kann direkt für Ultralytics YOLO Training verwendet werden, ohne zuerst in .txt Dateien zu konvertieren. Dies geschieht durch Subclassing von YOLODataset, um COCO JSON im laufenden Betrieb zu parsen und es über einen benutzerdefinierten Trainer in die Trainings-Pipeline einzubinden.
Dieser Ansatz behält COCO JSON als einzige Quelle der Wahrheit bei – kein convert_coco() Aufruf, kein Neuorganisieren von Verzeichnissen, keine Zwischen-Label-Dateien. YOLO26 und alle anderen Ultralytics YOLO Detektionsmodelle werden unterstützt. Segmentierungs- und Pose-Modelle erfordern zusätzliche Labelfelder (siehe FAQ).
Siehe den COCO zu YOLO Konvertierungsanleitung für das Standard-convert_coco() Workflow.
Architektur-Übersicht
Zwei Klassen werden benötigt:
COCODataset– liest COCO JSON und konvertiert Bounding Boxes während des Trainings im Speicher in das YOLO-FormatCOCOTrainer– überschreibtbuild_dataset(), umCOCODatasetanstelle des Standard-YOLODataset
Die Implementierung folgt demselben Muster wie die integrierte GroundingDataset, die ebenfalls JSON-Annotationen direkt liest. Drei Methoden werden überschrieben: get_img_files(), cache_labels(), und get_labels().
Erstellen der COCO JSON Dataset-Klasse
Die COCODataset Klasse erbt von YOLODataset und überschreibt die Logik zum Laden der Labels. Anstatt .txt Dateien aus einem Label-Verzeichnis zu lesen, öffnet sie die COCO JSON-Datei, iteriert über die nach Bildern gruppierten Annotationen und konvertiert jede Bounding Box vom COCO-Pixelformat [x_min, y_min, width, height] in das normalisierte YOLO-Mittelpunktformat [x_center, y_center, width, height]. Crowd-Annotationen (iscrowd: 1) und Boxen mit Flächeninhalt null werden automatisch übersprungen.
Die get_img_files() Methode gibt eine leere Liste zurück, da Bildpfade aus dem JSON file_name Feld innerhalb cache_labels() aufgelöst werden. Kategorie-IDs werden sortiert und auf nullbasierte Klassenindizes neu zugeordnet, sodass sowohl 1-basierte (Standard COCO) als auch nicht zusammenhängende ID-Schemata korrekt funktionieren.
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"]Geparste Labels werden in einer .cache Datei neben dem JSON gespeichert (z. B. instances_train.cache). Bei nachfolgenden Trainingsläufen wird der Cache direkt geladen, wodurch das JSON-Parsing übersprungen wird. Wenn sich die JSON-Datei ändert, schlägt die Hash-Prüfung fehl und der Cache wird automatisch neu erstellt.
Verbinden des Datasets mit der Trainings-Pipeline
Die einzige notwendige Änderung im Trainer ist das Überschreiben von build_dataset(). Der Standard DetectionTrainer baut einen YOLODataset auf, der nach .txt Label-Dateien scannt. Durch das Ersetzen durch COCODataset liest der Trainer stattdessen aus dem COCO JSON.
Der Pfad zur JSON-Datei wird aus einem benutzerdefinierten train_json / val_json Feld in der Datenkonfiguration gezogen (siehe Schritt 3). Während des Trainings wird mode="train" zu train_json aufgelöst; während der Validierung zu mode="val" zu val_json. Wenn val_json nicht gesetzt ist, greift es auf 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,
)Konfigurieren von dataset.yaml für COCO JSON
Die dataset.yaml verwendet die Standard path, train, und val Felder, um Bildverzeichnisse zu finden. Zwei zusätzliche Felder, train_json und val_json, spezifizieren die COCO-Annotationsdateien, die COCOTrainer liest. Die nc und names Felder definieren die Anzahl der Klassen und deren Namen, passend zur sortierten Reihenfolge der categories im 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 namesErwartete Verzeichnisstruktur:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yamlTraining auf COCO JSON ausführen
Mit der Dataset-Klasse, der Trainer-Klasse und der YAML-Konfiguration funktioniert das Training über den Standard model.train() Aufruf. Der einzige Unterschied zu einem normalen Trainingslauf ist das trainer=COCOTrainer Argument, das Ultralytics anweist, den benutzerdefinierten Dataset-Loader anstelle des Standard-Loaders zu verwenden.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)Die vollständige Trainings Pipeline läuft wie erwartet, inklusive Validierung, Checkpoint-Speicherung und Metrik-Protokollierung.
Vollständige Implementierung
Der Einfachheit halber wird die vollständige Implementierung unten als kopierbares Skript bereitgestellt. Es enthält das benutzerdefinierte Dataset, den benutzerdefinierten Trainer und den Trainingsaufruf. Speichere dies zusammen mit deiner dataset.yaml und führe es direkt aus.
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)Für Hyperparameter Empfehlungen, siehe die Tipps zum Modelltraining Leitfaden.
FAQ
Was ist der Unterschied dazu und zu convert_coco()?
convert_coco() schreibt .txt Label-Dateien als einmalige Konvertierung auf die Festplatte. Dieser Ansatz parst das JSON zu Beginn jedes Trainingslaufs und konvertiert Annotationen im Speicher. Nutze convert_coco() wenn permanente Labels im YOLO-Format bevorzugt werden; nutze diesen Ansatz, um COCO JSON als einzige Quelle der Wahrheit zu behalten, ohne zusätzliche Dateien zu generieren.
Kann YOLO auf COCO JSON ohne eigenen Code trainieren?
Nicht mit der aktuellen Ultralytics Pipeline, die standardmäßig YOLO .txt Labels erwartet. Diese Anleitung bietet den minimal notwendigen Code – eine Dataset-Klasse und eine Trainer-Klasse. Einmal definiert, erfordert das Training nur einen Standard model.train() Aufruf.
Unterstützt dies Segmentierung und Pose-Schätzung?
Diese Anleitung behandelt Objekterkennungs. Um Instanzsegmentierung Unterstützung hinzuzufügen, füge die segmentation Polygon-Daten aus COCO-Annotationen in das segments Feld jedes Label-Wörterbuchs ein. Für Pose-Schätzung, füge keypoints. Das GroundingDataset Quellcode bietet eine Referenzimplementierung für die Handhabung von Segmenten.
Funktionieren Augmentierungen mit diesem benutzerdefinierten Dataset?
Ja. COCODataset erweitert YOLODataset, sodass alle integrierten Mit dem Ultralytics SDK ist das Training von Modellen außergewöhnlich unkompliziert. Das System handhabt automatisch komplexe — mosaic, mixup, copy-paste und andere – ohne Modifikation funktionieren.
Wie werden Kategorie-IDs auf Klassenindizes abgebildet?
Kategorien werden nach id sortiert und auf sequenzielle Indizes ab 0 abgebildet. Dies handhabt 1-basierte IDs (Standard COCO), 0-basierte IDs und nicht zusammenhängende IDs. Das names Wörterbuch in dataset.yaml sollte der gleichen sortierten Reihenfolge folgen wie das COCO categories Array.
Gibt es einen Performance-Overhead im Vergleich zu vorkonvertierten Labels?
Das COCO JSON wird beim ersten Trainingslauf einmal geparst. Geparste Labels werden in einer .cache Datei gespeichert, sodass nachfolgende Läufe sofort ohne erneutes Parsen laden. Die Trainingsgeschwindigkeit ist identisch mit dem Standard YOLO Training, da die Annotationen im Arbeitsspeicher gehalten werden. Der Cache wird automatisch neu erstellt, falls sich die JSON-Datei ändert.