Comment entraîner YOLO les données COCO sans conversion
Pourquoi s'entraîner directement sur COCO ?
Annotations dans COCO ce format peut être utilisé directement pour Ultralytics YOLO s'entraîner sans se convertir à .txt les fichiers en premier. Pour ce faire, on crée une sous-classe YOLODataset pour analyser COCO à la volée et l'intégrer dans le pipeline d'entraînement via un modèle d'entraînement personnalisé.
Cette approche fait du COCO la seule source de vérité — non convert_coco() appel, pas de réorganisation du répertoire, pas de fichiers d'étiquettes intermédiaires. YOLO26 et tous les autres modèlesYOLO Ultralytics sont pris en charge. Les modèles de segmentation et de pose nécessitent des champs d'étiquette supplémentaires (voir FAQ).
Vous préférez plutôt une conversion ponctuelle ?
Consultez le fichier Guide YOLO COCO YOLO pour la norme convert_coco() flux de travail.
Aperçu de l'architecture
Deux classes sont nécessaires :
COCOJSONDataset— lit COCO et le convertit boîtes englobantes YOLO en mémoire pendant l'entraînementCOCOJSONTrainer— remplacebuild_dataset()à utiliserCOCOJSONDatasetau lieu de la valeur par défautYOLODataset
La mise en œuvre suit le même schéma que la fonction intégrée GroundingDataset, qui lit également directement les annotations JSON. Trois méthodes sont redéfinies : get_img_files(), cache_labels(), et get_labels().
Création de la classe de jeu de données COCO
L'argument COCOJSONDataset la classe hérite de YOLODataset et remplace la logique de chargement des étiquettes. Au lieu de lire .txt à partir d'un répertoire « labels », il ouvre le fichier COCO , parcourt les annotations regroupées par image et convertit chaque cadre de sélection du format COCO [x_min, y_min, width, height] au format YOLO par rapport au centre [x_center, y_center, width, height]. Annotations participatives (iscrowd: 1) et les cases de surface nulle sont automatiquement ignorées.
L'argument get_img_files() La méthode renvoie une liste vide car les chemins d'accès aux images sont déterminés à partir du JSON file_name champ à l'intérieur cache_labels(). Les identifiants de catégorie sont triés et convertis en indices de classe à indexation zéro, ce qui permet aux schémas d'identifiants à indexation 1 ( COCO standard) et non contigus de fonctionner correctement.
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"]
Les étiquettes analysées sont enregistrées dans un .cache fichier situé à côté du fichier JSON (par exemple instances_train.cache). Lors des exécutions d'entraînement suivantes, le cache est chargé directement, sans passer par l'analyse du JSON. Si le fichier JSON est modifié, la vérification du hachage échoue et le cache est automatiquement reconstitué.
Connexion de l'ensemble de données au pipeline d'entraînement
La seule modification à apporter au programme d'entraînement consiste à remplacer build_dataset(). La valeur par défaut DetectionTrainer crée un YOLODataset qui recherche .txt fichiers d'étiquettes. En le remplaçant par COCOJSONDataset, le programme d'entraînement lit alors le fichier COCO .
Le chemin d'accès au fichier JSON est extrait d'un fichier personnalisé train_json / val_json field in the data config (see Step 3). During training, mode="train" décide de train_json; lors de la validation, mode="val" décide de val_json. Si val_json s'il n'est pas défini, le système utilise par défaut 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,
)
Configuration du fichier dataset.yaml COCO
L'argument dataset.yaml utilise la norme path, train, et val champs permettant de localiser les répertoires d'images. Deux champs supplémentaires, train_json et val_json, indiquez les fichiers COCO qui COCOJSONTrainer dit. Le nc et names Ces champs définissent le nombre de classes et leurs noms, en respectant l'ordre trié de categories dans le fichier 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
Structure de répertoires prévue :
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yaml
Formation à l'utilisation de COCO
Une fois le jeu de données, la classe d'entraînement et la configuration YAML en place, l'entraînement se déroule selon la procédure standard model.train() appel. La seule différence par rapport à une séance d'entraînement normale est la trainer=COCOJSONTrainer argument qui indique Ultralytics utiliser le chargeur de jeux de données personnalisé à la place de celui par défaut.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOJSONTrainer)
L'ensemble du processus d'entraînement se déroule comme prévu, y compris la validation, l'enregistrement des points de contrôle et la journalisation des métriques.
Mise en œuvre complète
Pour plus de commodité, vous trouverez ci-dessous l'implémentation complète sous la forme d'un script unique prêt à copier-coller. Elle comprend l'ensemble de données personnalisé, le modèle d'entraînement personnalisé et l'appel d'entraînement. Enregistrez ce fichier à côté de votre dataset.yaml et l'exécuter directement.
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)
Pour obtenir des recommandations sur les hyperparamètres, consultez le guide « Conseils pour l'entraînement des modèles ».
FAQ
Quelle est la différence entre cette fonction et convert_coco() ?
convert_coco() écrit .txt enregistrer les fichiers d'étiquettes sur le disque lors d'une conversion unique. Cette approche analyse le fichier JSON au début de chaque session d'entraînement et convertit les annotations en mémoire. Utilisez convert_coco() lorsque l'on privilégie les étiquettes YOLO permanent ; utilisez cette approche pour que le fichier COCO reste la seule source de référence, sans générer de fichiers supplémentaires.
Est-ce que YOLO peut YOLO sur COCO données COCO sans code personnalisé ?
Ce n'est pas le cas avec le Ultralytics actuel Ultralytics , qui s'appuie sur YOLO .txt étiquettes par défaut. Ce guide fournit le code personnalisé minimal nécessaire : une classe de jeu de données et une classe d'entraîneur. Une fois ces éléments définis, l'entraînement ne nécessite qu'un model.train() appel.
Cette fonctionnalité prend-elle en charge la segmentation et l'estimation de la pose ?
Ce guide traite détection d'objets. Pour ajouter segmentation d'instance prise en charge, inclure le segmentation données polygonales issues COCO dans le segments champ de chaque dictionnaire d'étiquettes. Pour estimation de pose, notamment keypoints. Le GroundingDataset code source fournit une implémentation de référence pour la gestion des segments.
Les augmentations fonctionnent-elles avec cet ensemble de données personnalisé ?
Oui. COCOJSONDataset s'étend YOLODataset, donc toutes les fonctions intégrées enrichissement des données — mosaïque, mixup, copier-coller, entre autres — fonctionnent sans modification.
Comment les identifiants de catégorie sont-ils mis en correspondance avec les indices de classe ?
Les catégories sont classées par id et mappés à des indices séquentiels commençant à 0. Cela permet de gérer les identifiants à partir de 1 (norme COCO), les identifiants à partir de 0 et les identifiants non contigus. Le names dictionnaire en dataset.yaml devrait respecter le même ordre de tri que le COCO categories tableau.
Y a-t-il une perte de performances par rapport aux étiquettes pré-converties ?
Le fichier COCO est analysé une seule fois lors du premier cycle d'entraînement. Les étiquettes analysées sont enregistrées dans un .cache fichier, de sorte que les exécutions suivantes se chargent instantanément sans nouvelle analyse. La vitesse d'entraînement est identique à celle de YOLO standard, car les annotations sont conservées en mémoire. Le cache est automatiquement reconstitué si le fichier JSON est modifié.