Link to this sectionSo trainierst du YOLO mit COCO JSON ohne Konvertierung#
Link to this sectionWarum du direkt mit COCO JSON trainieren solltest#
Annotations im COCO JSON-Format können direkt für das Ultralytics YOLO-Training verwendet werden, ohne sie vorher in .txt-Dateien umzuwandeln. Dies geschieht, indem du die Klasse YOLODataset erweiterst, um das COCO JSON während der Laufzeit zu parsen und es über einen benutzerdefinierten Trainer in die Trainings-Pipeline einzubinden.
Dieser Ansatz hält das COCO JSON als einzige Quelle der Wahrheit (Single Source of Truth) bei — kein convert_coco()-Aufruf, keine Neuordnung von Verzeichnissen, keine Zwischendateien für Labels. YOLO26 und alle anderen Ultralytics YOLO-Detektionsmodelle werden unterstützt. Segmentierungs- und Pose-Modelle erfordern zusätzliche Labelfelder (siehe FAQ).
Im COCO-zu-YOLO-Konvertierungshandbuch findest du den standardmäßigen convert_coco()-Workflow.
Link to this sectionArchitektur-Übersicht#
Es werden zwei Klassen benötigt:
COCODataset— liest COCO JSON und konvertiert Bounding Boxes während des Trainings im Arbeitsspeicher in das YOLO-Format.COCOTrainer— überschreibtbuild_dataset(), umCOCODatasetanstelle des standardmäßigenYOLODatasetzu verwenden.
Die Implementierung folgt dem gleichen Muster wie das integrierte GroundingDataset, das JSON-Annotationen ebenfalls direkt liest. Drei Methoden werden überschrieben: get_img_files(), cache_labels() und get_labels().
Link to this sectionErstellen der COCO JSON Dataset-Klasse#
Die Klasse COCODataset erbt von YOLODataset und überschreibt die Logik zum Laden der Labels. Anstatt .txt-Dateien aus einem 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-Zentrumformat [x_center, y_center, width, height]. Crowd-Annotationen (iscrowd: 1) und Boxen mit einer Fläche von Null werden automatisch übersprungen.
The get_img_files() method returns an empty list because image paths are resolved from the JSON file_name field inside cache_labels(). Category IDs are sorted and remapped to zero-indexed class indices, so both 1-based (standard COCO) and non-contiguous ID schemes work correctly.
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):
"""Initialize the dataset with a COCO JSON annotation file."""
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"]Die geparsten 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 Parsen des JSON entfällt. Wenn sich die JSON-Datei ändert, schlägt die Hash-Prüfung fehl und der Cache wird automatisch neu erstellt.
Link to this sectionAnbindung des Datasets an die Trainings-Pipeline#
Die einzige notwendige Änderung im Trainer ist das Überschreiben von build_dataset(). Der standardmäßige DetectionTrainer erstellt ein YOLODataset, das nach .txt-Labelfiles sucht. Indem du es durch COCODataset ersetzt, liest der Trainer stattdessen aus dem COCO JSON.
Der Pfad zur JSON-Datei wird aus einem benutzerdefinierten train_json- bzw. val_json-Feld in der Datenkonfiguration abgerufen (siehe Schritt 3). Während des Trainings wird mode="train" zu train_json aufgelöst; während der Validierung wird mode="val" zu val_json aufgelöst. Wenn val_json nicht festgelegt ist, wird auf train_json zurückgegriffen.
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):
"""Build a COCODataset for the given split using the JSON file from the data config."""
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,
)Link to this sectionKonfigurieren der dataset.yaml für COCO JSON#
Die dataset.yaml verwendet die Standardfelder path, train und val, um Bildverzeichnisse zu finden. Zwei zusätzliche Felder, train_json und val_json, geben die COCO-Annotationsdateien an, die vom COCOTrainer gelesen werden. Die Felder nc und names definieren die Anzahl der Klassen und deren Namen, entsprechend der 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.yamlLink to this sectionTraining auf COCO JSON ausführen#
Sobald die Dataset-Klasse, die Trainer-Klasse und die YAML-Konfiguration vorhanden sind, erfolgt das Training über den standardmäßigen model.train()-Aufruf. Der einzige Unterschied zu einem normalen Trainingslauf ist das Argument trainer=COCOTrainer, 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 gewohnt ab, einschließlich Validierung, Speichern von Checkpoints und Metrik-Logging.
Link to this sectionVollständige Implementierung#
Der Einfachheit halber ist die vollständige Implementierung unten als einzelnes Skript zum Kopieren und Einfügen 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):
"""Initialize the dataset with a COCO JSON annotation file."""
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, saving results 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"]}
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):
"""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"]
class COCOTrainer(DetectionTrainer):
"""Trainer that uses COCODataset for direct COCO JSON training."""
def build_dataset(self, img_path, mode="train", batch=None):
"""Build a COCODataset for the given split using the JSON file from the data config."""
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)Empfehlungen zu Hyperparametern findest du im Handbuch Tipps zum Modelltraining.
Link to this sectionFAQ#
Link to this sectionWas ist der Unterschied zu convert_coco()?#
convert_coco() schreibt .txt-Labelfiles als einmalige Konvertierung auf die Festplatte. Dieser Ansatz parst das JSON zu Beginn jedes Trainingslaufs und konvertiert die Annotationen im Arbeitsspeicher. Verwende convert_coco(), wenn dauerhafte Labels im YOLO-Format bevorzugt werden; verwende diesen Ansatz, um das COCO JSON als einzige Quelle der Wahrheit zu behalten, ohne zusätzliche Dateien zu erzeugen.
Link to this sectionKann YOLO ohne benutzerdefinierten Code auf COCO JSON trainieren?#
Nicht mit der aktuellen Ultralytics-Pipeline, die standardmäßig YOLO .txt-Labels erwartet. Diese Anleitung bietet den minimalen benutzerdefinierten Code, der benötigt wird — eine Dataset-Klasse und eine Trainer-Klasse. Sobald diese definiert sind, erfordert das Training nur noch einen standardmäßigen model.train()-Aufruf.
Link to this sectionWird Segmentierung und Pose-Schätzung unterstützt?#
This guide covers object detection. To add instance segmentation support, include the segmentation polygon data from COCO annotations in the segments field of each label dictionary. For pose estimation, include keypoints. The GroundingDataset source code provides a reference implementation for handling segments.
Link to this sectionFunktionieren Augmentierungen mit diesem benutzerdefinierten Dataset?#
Ja. Da COCODataset von YOLODataset erbt, funktionieren alle integrierten Datenaugmentierungen — Mosaic, Mixup, Copy-Paste und andere — ohne Änderungen.
Link to this sectionWie werden Kategorie-IDs auf Klassenindizes abgebildet?#
Kategorien werden nach id sortiert und auf sequentielle Indizes beginnend bei 0 abgebildet. Dies funktioniert bei 1-basierten IDs (Standard-COCO), 0-basierten IDs und nicht zusammenhängenden IDs. Das names-Dictionary in der dataset.yaml sollte die gleiche sortierte Reihenfolge wie das COCO-categories-Array haben.
Link to this sectionGibt es im Vergleich zu vorkonvertierten Labels einen Performance-Nachteil?#
Das COCO JSON wird beim ersten Trainingslauf einmalig geparst. Geparste Labels werden in einer .cache-Datei gespeichert, sodass nachfolgende Läufe sofort ohne erneutes Parsen geladen werden können. Die Trainingsgeschwindigkeit ist identisch mit dem Standard-YOLO-Training, da die Annotationen im Arbeitsspeicher gehalten werden. Der Cache wird automatisch neu erstellt, wenn sich die JSON-Datei ändert.