Zum Inhalt springen

So trainierst du YOLO COCO , ohne diese zu konvertieren

Warum direkt mit COCO trainieren?

Annotationen in COCO JSON Das Format kann direkt für Ultralytics YOLO Training, ohne zu konvertieren .txt zuerst die Dateien. Dies geschieht durch Erstellen einer Unterklasse YOLODataset um COCO in Echtzeit zu analysieren und sie über einen benutzerdefinierten Trainer in die Trainingspipeline einzubinden.

Bei diesem Ansatz bleibt die COCO die einzige verlässliche Quelle – keine convert_coco() Aufruf, keine Neuorganisation des Verzeichnisses, keine Zwischen-Label-Dateien. YOLO26 und alle anderen Ultralytics YOLO Detektionsmodelle werden unterstützt. Segmentierungs- und Pose-Modelle erfordern zusätzliche Label-Felder (siehe FAQ), oder Auto-Modus mit angegebener Auslastungsfraktion (

Möchten Sie stattdessen eine einmalige Konvertierung durchführen?

Siehe die Leitfaden zur COCO zu YOLO Konvertierung für den Standard convert_coco() Arbeitsablauf.

Architektur-Überblick

Es werden zwei Klassen benötigt:

  1. COCODataset — liest COCO und konvertiert Bounding Boxes während des Trainings in YOLO im Speicher
  2. COCOTrainer — Überschreibt build_dataset() zur Verwendung COCODataset anstelle der Standardeinstellung YOLODataset

Die Implementierung folgt dem gleichen Muster wie die integrierte GroundingDataset, der auch 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 von Labels. Anstatt zu lesen .txt Dateien aus einem Verzeichnis „labels“; es öffnet die COCO , durchläuft die nach Bildern gruppierten Annotationen und konvertiert jede Begrenzungsbox aus COCO [x_min, y_min, width, height] in das YOLO Center-Format [x_center, y_center, width, height]. Crowd-Annotationen (iscrowd: 1) und Boxen mit Nullfläche werden automatisch übersprungen.

Die get_img_files() Die Methode gibt eine leere Liste zurück, da die Bildpfade aus dem JSON-Datensatz ermittelt werden file_name Feld innen cache_labels(). Kategorie-IDs werden sortiert und auf nullbasierte Klassenindizes umgewandelt, 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"]

Die analysierten Bezeichnungen werden in einer .cache Datei neben der JSON-Datei (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 aufgebaut.

Verbinden des Datensatzes mit der Trainings-Pipeline

Die einzige Änderung, die im Trainer erforderlich ist, ist das Überschreiben build_dataset(). Der Standard DetectionTrainer baut ein YOLODataset das nach .txt Label-Dateien. Indem man es ersetzt durch COCODataset, liest der Trainer stattdessen aus dem COCO-JSON.

Der Pfad zur JSON-Datei wird aus einer benutzerdefinierten train_json / val_json Feld in der Datenkonfiguration (siehe Schritt 3). Während des Trainings, mode="train" beschließt, train_json; während der Validierung, mode="val" beschließt, val_json. Wenn val_json ist nicht festgelegt, greift 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,
        )

Konfiguration von dataset.yaml für COCO JSON

Die dataset.yaml verwendet den Standard path, trainund val Felder zum Auswählen der Bildverzeichnisse. Zwei weitere Felder, train_json und val_json, geben Sie die COCO-Annotationsdateien an, die COCOTrainer heißt. Das nc und names Die Felder legen die Anzahl der Klassen und deren Namen fest, entsprechend der sortierten Reihenfolge von 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 names

Erwartete Verzeichnisstruktur:

my_dataset/
  images/
    train/
      img_001.jpg
      ...
    val/
      img_100.jpg
      ...
  annotations/
    instances_train.json
    instances_val.json
  dataset.yaml

Lauftraining auf COCO

Sobald die Datensatzklasse, die Trainerklasse und die YAML-Konfiguration bereitstehen, läuft das Training nach dem Standardablauf ab model.train() Aufruf. Der einzige Unterschied zu einem normalen Trainingslauf ist der 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 gesamte Trainingspipeline läuft wie erwartet, einschließlich Validierung, Speichern von Checkpoints und Protokollierung von Metriken.

Vollständige Umsetzung

Der Einfachheit halber finden Sie unten die vollständige Implementierung als ein einziges Skript zum Kopieren und Einfügen. Es enthält den benutzerdefinierten Datensatz, den benutzerdefinierten Trainer und den Trainingsaufruf. Speichern Sie dieses Skript zusammen mit Ihrem dataset.yaml und direkt ausführen.

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)

Empfehlungen zu Hyperparametern finden Sie im Leitfaden „Tipps zum Modelltraining “.

FAQ

Was ist der Unterschied zwischen dieser Funktion und convert_coco()?

convert_coco() schreibt .txt Dateien als einmalige Konvertierung auf die Festplatte speichern. Bei diesem Ansatz wird das JSON zu Beginn jedes Trainingsdurchlaufs analysiert und die Annotationen werden im Arbeitsspeicher konvertiert. Verwenden Sie convert_coco() wenn permanente YOLOLabels bevorzugt werden; nutze diesen Ansatz, um die COCO als einzige verlässliche Quelle zu erhalten, ohne zusätzliche Dateien zu generieren.

Kann YOLO mit COCO JSON ohne benutzerdefinierten Code trainieren?

Nicht mit der aktuellen Ultralytics , die YOLO erwartet .txt standardmäßig Labels. Diese Anleitung enthält den minimal erforderlichen benutzerdefinierten Code – eine Datensatzklasse und eine Trainerklasse. Nach der Definition erfordert das Training lediglich einen Standard- model.train() Aufruf.

Unterstützt dies Segmentierung und Posenschätzung?

Dieser Leitfaden behandelt Objekterkennung durchzuführen. Um hinzuzufügen Instanzsegmentierung Unterstützung, einschließlich der segmentation Polygondaten aus COCO in der segments Feld jedes Etikettenwörterbuchs. Für Pose-Schätzung, umfassen keypoints. Der GroundingDataset Quellcode bietet eine Referenzimplementierung für die Verarbeitung von Segmenten.

Funktionieren Augmentierungen mit diesem benutzerdefinierten Datensatz?

Ja. COCODataset erweitert YOLODataset, sodass alle integrierten DatenerweiterungenMosaik, Mixup, Copy-Paste, und andere — laufen ohne Modifikation.

Wie werden Kategorie-IDs den Klassenindizes zugeordnet?

Kategorien sind sortiert nach id und sequenziellen Indizes ab 0 zugeordnet. Dies verarbeitet 1-basierte IDs (Standard COCO), 0-basierte IDs und nicht-zusammenhängende IDs. Der names Wörterbuch in dataset.yaml sollte derselben sortierten Reihenfolge wie das COCO folgen categories Array.

Gibt es im Vergleich zu vorab konvertierten Beschriftungen einen Leistungsaufwand?

Das COCO wird beim ersten Trainingsdurchlauf einmalig geparst. Die geparsten Labels werden in einer .cache Datei, sodass nachfolgende Durchläufe sofort geladen werden, ohne dass eine erneute Analyse erforderlich ist. Die Trainingsgeschwindigkeit entspricht der YOLO , da die Annotationen im Speicher gehalten werden. Der Cache wird automatisch neu aufgebaut, wenn sich die JSON-Datei ändert.



📅 Erstellt vor 7 Tagen ✏️ Aktualisiert vor 7 Tagen
raimbekovmglenn-jocher

Kommentare