Zum Inhalt springen

So trainierst du YOLO COCO , ohne diese zu konvertieren

Warum direkt mit COCO trainieren?

Anmerkungen in COCO 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 Neuordnung des Verzeichnisses, keine temporären Label-Dateien. YOLO26 und alle anderenYOLO Ultralytics werden unterstützt. Für Segmentierungs- und Posenmodelle sind zusätzliche Beschriftungsfelder erforderlich (siehe FAQ), oder Auto-Modus mit angegebener Auslastungsfraktion (

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

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

Architektur-Überblick

Es werden zwei Klassen benötigt:

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

Die Implementierung folgt dem gleichen Muster wie die integrierte GroundingDataset, das auch JSON-Annotationen direkt ausliest. Es werden drei Methoden überschrieben: get_img_files(), cache_labels()und get_labels().

Erstellen der COCO -Datensatzklasse

Die COCOJSONDataset Klasse erbt von YOLODataset und überschreibt die Logik zum Laden der Beschriftungen. 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 Felder mit einer Fläche von Null 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(). Die Kategorie-IDs werden sortiert und auf null-indizierte Klassenindizes abgebildet, sodass sowohl 1-basierte ( 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 COCOJSONDataset(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, wobei die JSON-Analyse übersprungen wird. Ändert sich die JSON-Datei, schlägt die Hash-Prüfung fehl und der Cache wird automatisch neu aufgebaut.

Den Datensatz mit der Trainingspipeline verbinden

Die einzige Änderung, die im Trainer erforderlich ist, ist das Überschreiben build_dataset(). Der Standardwert DetectionTrainer erstellt eine YOLODataset das nach .txt Label-Dateien. Indem man es ersetzt durch COCOJSONDataset… liest der Trainer stattdessen aus der COCO -Datei.

Der Pfad zur JSON-Datei wird aus einer benutzerdefinierten train_json / val_json field in the data config (see Step 3). During training, 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 COCOJSONTrainer(DetectionTrainer):
    """Trainer that uses COCOJSONDataset 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 COCOJSONDataset(
            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 COCO

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 dateien an, die COCOJSONTrainer 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() Anruf. Der einzige Unterschied zu einem normalen Trainingslauf besteht darin, dass trainer=COCOJSONTrainer Argument, das Ultralytics anweist Ultralytics den benutzerdefinierten Datensatz-Loader anstelle des Standard-Loaders Ultralytics verwenden.

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOJSONTrainer)

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 es 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 COCOJSONDataset(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 COCOJSONTrainer(DetectionTrainer):
    """Trainer that uses COCOJSONDataset 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 COCOJSONDataset(
            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=COCOJSONTrainer)

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 ohne benutzerdefinierten Code auf COCO YOLO ?

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() Anruf.

Unterstützt dies die Segmentierung und die 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, einschließlich keypoints. Der GroundingDataset Quellcode bietet eine Referenzimplementierung für die Verarbeitung von Segmenten.

Funktionieren Erweiterungen mit diesem benutzerdefinierten Datensatz?

Ja. COCOJSONDataset erweitert YOLODataset, also alle integrierten DatenerweiterungenMosaik, Mixup, Copy-Paste, und andere — laufen ohne Änderungen.

Wie werden Kategorie-IDs den Klassenindizes zugeordnet?

Die Kategorien sind sortiert nach id und auf fortlaufende Indizes ab 0 abgebildet. Dies unterstützt 1-basierte IDs ( COCO), 0-basierte IDs und nicht zusammenhängende IDs. Die 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 0 Tagen ✏️ Aktualisiert vor 0 Tagen
glenn-jocherraimbekovm

Kommentare