Comment convertir des annotations COCO au format YOLO#
L'entraînement des modèles Ultralytics YOLO nécessite des annotations au format YOLO, mais de nombreux outils d'annotation populaires exportent plutôt au format COCO JSON. Ce guide t'explique comment convertir tes annotations COCO au format YOLO et commencer l'entraînement de modèles de détection d'objets, de segmentation d'instances et d'estimation de pose.
Pour entraîner directement sur COCO JSON sans générer de fichiers .txt, consulte Entraîner YOLO sur COCO JSON sans conversion.
Pourquoi convertir de COCO vers YOLO ?#
Le format COCO JSON stocke toutes les annotations dans un seul fichier, tandis que YOLO utilise un fichier texte par image avec des coordonnées normalisées. La conversion est nécessaire car :
- Les modèles YOLO nécessitent des fichiers d'étiquettes
.txt, avec un fichier par image contenantclass x_center y_center width heighten coordonnées normalisées. - COCO JSON utilise des coordonnées en pixels au format
[x_min, y_min, width, height], avec un seul fichier JSON pour toutes les images. - Les identifiants de classe diffèrent — COCO utilise des valeurs
category_idarbitraires, tandis que YOLO nécessite des identifiants de classe indexés à partir de zéro.
| Fonctionnalité | COCO JSON | YOLO TXT |
|---|---|---|
| Structure | Un seul fichier JSON pour toutes les images | Un fichier .txt par image |
| Format des BBox | [x_min, y_min, width, height] en pixels | class x_center y_center width height normalisé (0-1) |
| Identifiants de classe | category_id (peut commencer par n'importe quel nombre) | Indexés à partir de zéro (commencent à 0) |
| Segmentation | Tableaux de polygones dans le champ segmentation | Coordonnées des polygones après l'identifiant de classe |
| Points clés | [x, y, visibility, ...] en pixels | [x, y, visibility, ...] normalisés |
Démarrage rapide#
La méthode la plus rapide pour convertir les annotations COCO et commencer l'entraînement :
from ultralytics.data.converter import convert_coco
convert_coco(
labels_dir="my_dataset/annotations/", # directory containing your JSON files
save_dir="my_dataset/converted/", # where to save converted labels
cls91to80=False, # set False for custom datasets (see warning below)
)Après la conversion, organise la structure de tes répertoires, crée un fichier dataset.yaml et commence l'entraînement. Consulte le guide détaillé étape par étape ci-dessous.
La valeur par défaut cls91to80=True est conçue uniquement pour le jeu de données COCO standard avec 80 classes d'objets. Elle mappe 91 identifiants de catégorie non contigus vers 80 identifiants de classe contigus. Pour tout jeu de données personnalisé, tu dois définir cls91to80=False — sinon tes identifiants de classe seront mappés incorrectement sans avertissement et ton modèle apprendra de mauvaises classes.
Guide de conversion étape par étape#
1. Prépare ton jeu de données COCO#
Un jeu de données typique au format COCO exporté depuis des outils d'annotation possède la structure suivante :
my_dataset/
├── images/
│ ├── train/
│ │ ├── img_001.jpg
│ │ ├── img_002.jpg
│ │ └── ...
│ └── val/
│ ├── img_100.jpg
│ └── ...
└── annotations/
├── instances_train.json
└── instances_val.jsonChaque fichier JSON suit la spécification du format de données COCO avec trois champs obligatoires — images, annotations et categories :
{
"images": [{ "id": 1, "file_name": "img_001.jpg", "width": 640, "height": 480 }],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 1,
"bbox": [100, 50, 200, 150],
"area": 30000,
"iscrowd": 0
}
],
"categories": [
{ "id": 1, "name": "helmet" },
{ "id": 2, "name": "vest" }
]
}2. Convertis les annotations#
Utilise la fonction convert_coco() pour convertir tes annotations COCO JSON au format YOLO .txt :
from ultralytics.data.converter import convert_coco
convert_coco(
labels_dir="my_dataset/annotations/",
save_dir="my_dataset/converted/",
cls91to80=False,
)convert_coco() écrit un fichier .txt par image annotée dans un sous-répertoire labels/ nommé d'après chaque fichier JSON, après suppression du préfixe instances_ (ainsi, instances_train.json produit labels/train/). Les images sans annotations sont ignorées et ne reçoivent aucun fichier d'étiquettes ; l'arborescence labels/ peut donc ne pas refléter toutes les images :
my_dataset/converted/
├── images/ # created but left empty
└── labels/
├── train/ # from instances_train.json
│ ├── img_001.txt
│ └── ...
└── val/ # from instances_val.json
└── ...convert_coco() ne remplace jamais un save_dir existant : si my_dataset/converted/ existe déjà, une nouvelle exécution écrit plutôt dans my_dataset/converted-2/. Supprime la sortie précédente (ou modifie save_dir) avant de relancer la conversion, sinon les étapes suivantes liront d'anciennes étiquettes.
3. Organise la structure des répertoires#
Après la conversion, les fichiers d'étiquettes doivent être placés à côté de tes images. YOLO s'attend à trouver un répertoire labels/ qui reproduit la structure du répertoire images/ :
import shutil
from pathlib import Path
converted_dir = Path("my_dataset/converted/labels")
dataset_dir = Path("my_dataset")
# convert_coco names each subdirectory after its JSON file (minus the "instances_" prefix),
# so iterate the actual subdirectories instead of assuming "train"/"val".
for src in converted_dir.iterdir():
if not src.is_dir():
continue
dst = dataset_dir / "labels" / src.name
dst.mkdir(parents=True, exist_ok=True)
for f in src.glob("*.txt"):
shutil.move(str(f), str(dst / f.name))Ta structure finale de jeu de données devrait ressembler à ceci :
my_dataset/
├── images/
│ ├── train/
│ │ ├── img_001.jpg
│ │ └── ...
│ └── val/
│ └── ...
├── labels/
│ ├── train/
│ │ ├── img_001.txt
│ │ └── ...
│ └── val/
│ └── ...
└── dataset.yaml4. Crée dataset.yaml#
Crée un fichier de configuration dataset.yaml qui associe tes catégories COCO aux noms de classes YOLO. Ce fichier indique à YOLO où se trouvent tes données et quelles classes détecter :
import json
from pathlib import Path
import yaml
# Read categories from your COCO JSON
with open("my_dataset/annotations/instances_train.json") as f:
coco = json.load(f)
# Build class names matching convert_coco output (category_id - 1)
categories = sorted(coco["categories"], key=lambda x: x["id"])
names = {cat["id"] - 1: cat["name"] for cat in categories}
# NOTE: convert_coco maps class IDs as category_id - 1, so category_id must
# start from 1. If your categories start from 0, add 1 to each ID first.
# Create dataset.yaml
dataset = {
"path": str(Path("my_dataset").resolve()),
"train": "images/train",
"val": "images/val",
"names": names,
}
with open("my_dataset/dataset.yaml", "w") as f:
yaml.dump(dataset, f, default_flow_style=False)Le fichier YAML obtenu :
path: /absolute/path/to/my_dataset
train: images/train
val: images/val
names:
0: helmet
1: vestPour plus de détails sur le format YAML des jeux de données, consulte le guide de configuration des jeux de données.
5. Entraîne ton modèle YOLO#
Une fois ton jeu de données converti prêt, entraîne un modèle YOLO :
from ultralytics import YOLO
model = YOLO("yolo26n.pt") # load a pretrained model
results = model.train(data="my_dataset/dataset.yaml", epochs=100, imgsz=640)Pour obtenir des conseils et des bonnes pratiques d'entraînement, consulte le guide d'entraînement des modèles.
6. Vérifie ta conversion#
Avant l'entraînement, vérifie rapidement quelques fichiers d'étiquettes pour confirmer que les identifiants de classe et les coordonnées sont corrects :
from pathlib import Path
label_file = Path("my_dataset/labels/train/img_001.txt")
for line in label_file.read_text().strip().splitlines():
parts = line.split()
cls_id = int(parts[0])
coords = [float(v) for v in parts[1:5]]
assert cls_id >= 0, f"Negative class ID {cls_id} — category_id in your JSON may start from 0"
assert all(0 <= v <= 1 for v in coords), f"Coordinates out of [0, 1] range: {coords}"Si tu vois des identifiants de classe négatifs, ton COCO JSON utilise probablement category_id en commençant à 0. Ajoute 1 à toutes les valeurs category_id de ton JSON avant d'exécuter convert_coco(), car celui-ci mappe les identifiants de classe comme category_id - 1.
Résolution des problèmes courants#
Identifiants de classe incorrects après la conversion#
Si ton modèle s'entraîne mais détecte de mauvaises classes d'objets, tu utilises probablement cls91to80=True (par défaut) sur un jeu de données personnalisé. Cela mappe tes valeurs category_id via la table de correspondance COCO 91-vers-80, qui n'est correcte que pour le jeu de données COCO standard. Un category_id sans équivalent dans COCO-80 n'est associé à rien et déclenche TypeError: must be real number, not NoneType pendant la conversion au lieu de produire des étiquettes incorrectes.
Solution : utilise toujours cls91to80=False pour les jeux de données personnalisés.
Aucune étiquette trouvée pendant l'entraînement#
Si l'analyse des étiquettes signale 0 images, N backgrounds et que l'entraînement s'interrompt ensuite avec ValueError: train: No labels found in .../labels/train.cache, tes fichiers d'étiquettes ne se trouvent pas dans le répertoire attendu. convert_coco() enregistre les étiquettes dans un répertoire de sortie distinct (par exemple, save_dir/labels/train/), tandis que YOLO attend labels/ au même niveau que images/ dans ton répertoire de jeu de données.
Solution : déplace les fichiers d'étiquettes pour respecter la structure de répertoires attendue. Vérifie que labels/train/ est au même niveau que images/train/.
KeyError pendant la conversion#
Si tu obtiens KeyError: 'bbox' ou des erreurs similaires en exécutant convert_coco(), ton labels_dir contient probablement des fichiers JSON qui ne sont pas des annotations d'instances (par exemple, captions_train2017.json) et qui possèdent une structure d'annotation différente.
Solution : place uniquement des fichiers JSON d'annotations d'instances (par exemple, instances_train2017.json) dans labels_dir.
Fichiers d'étiquettes vides après la conversion#
Si la conversion se termine mais que les fichiers .txt sont vides ou manquants, toutes les annotations peuvent avoir iscrowd: 1 (cas fréquent avec les masques générés par SAM), ou les boîtes englobantes peuvent avoir une largeur ou une hauteur nulles. Exécuter avec use_keypoints=True sur un export limité à la détection produit le même résultat, car les annotations sans champ keypoints sont entièrement ignorées.
Solution : inspecte les valeurs iscrowd de tes annotations JSON. Si tu utilises des masques SAM, prétraite le JSON pour définir iscrowd: 0. Si tu as fourni use_keypoints=True, vérifie que tes annotations contiennent bien keypoints.
Polygones en forme de boîte issus d'annotations de masques#
Si use_segments=True affiche annotations without a usable polygon dans les journaux, certaines annotations ne contiennent aucune valeur segmentation, ou une valeur qui n'est pas une liste d'au moins trois paires de coordonnées. Les causes habituelles sont les exports limités à la détection, qui laissent le champ manquant ou vide, et l'encodage COCO par longueurs de séquences ({"counts": ..., "size": ...}), utilisé par les exportateurs de masques binaires tels que SAM ; une liste de coordonnées plate sans liste de polygones englobante, des contours à un ou deux points et d'autres valeurs mal formées sont traités de la même manière. Une annotation conserve les polygones restants et utilise une ligne de segment en forme de boîte englobante lorsqu'il n'en reste aucun. Les étiquettes restent donc valides, mais ces lignes ne contiennent aucun détail de masque.
Solution : réexporte les annotations avec des segmentations polygonales, décode les masques RLE en polygones avant d'exécuter convert_coco(), ou corrige les valeurs segmentation mal formées.
Écarts entre les identifiants de classe dans les étiquettes converties#
Si les identifiants de classe dans les fichiers d'étiquettes ne sont pas contigus (par exemple, 0, 4, 9 au lieu de 0, 1, 2), ton outil d'annotation utilise des valeurs category_id non contiguës.
Solution : vérifie que les identifiants de classe de tes fichiers .txt correspondent au dictionnaire names dans dataset.yaml. Remappe les identifiants vers des valeurs contiguës si nécessaire.
Pour consulter tous les détails de l'API et la description des paramètres, consulte la référence de l'API convert_coco.
FAQ#
Cela se produit parce que
convert_coco()enregistre les étiquettes dans un sous-répertoire desave_dir/labels/(par exemple,save_dir/labels/train/) au lieu de les placer directement danslabels/train/de ton jeu de données, à côté deimages/train/. YOLO attend que les étiquettes soient au même niveau que les images — par exemple,images/train/img.jpgdoit contenirlabels/train/img.txt. Déplace tes étiquettes converties pour respecter cette structure. Consulte Correction de la structure des répertoires.Le paramètre
cls91to80contrôle la façon dont les valeurs COCOcategory_idsont mappées vers les identifiants de classe YOLO. LorsqueTrue(par défaut), il applique la table de correspondancecoco91_to_coco80_class(), conçue pour le jeu de données COCO standard, qui possède 80 classes avec des identifiants non contigus (1-90). Pour les jeux de données personnalisés, définis toujourscls91to80=False— cela soustrait simplement 1 à chaquecategory_idafin de créer des identifiants de classe indexés à partir de zéro.Pas sans code personnalisé. Le pipeline d'entraînement par défaut attend des étiquettes YOLO
.txt, avec un fichier par image. Exécute doncconvert_coco()et suis ce guide étape par étape, ou crée une sous-classe du jeu de données pour analyser le COCO JSON à la volée — consulte Entraîner YOLO sur COCO JSON sans conversion. Pour en savoir plus sur les formats pris en charge, consulte les formats de jeux de données.Oui, utilise
use_segments=Truelors de l'appel deconvert_coco()pour inclure les masques de segmentation polygonaux dans les étiquettes YOLO converties. Cela produit des fichiers d'étiquettes compatibles avec les modèles de segmentation YOLO :from ultralytics.data.converter import convert_coco convert_coco(labels_dir="annotations/", save_dir="output/", use_segments=True, cls91to80=False)Utilise
use_keypoints=Truepour convertir les annotations de points clés COCO en vue de l'entraînement à l'estimation de pose :from ultralytics.data.converter import convert_coco convert_coco(labels_dir="annotations/", save_dir="output/", use_keypoints=True, cls91to80=False)Note que si
use_segmentsetuse_keypointssont tous deux définis surTrue, seuls les points clés seront écrits dans les fichiers d'étiquettes — les segments seront ignorés silencieusement.
Utilise la fonction
convert_coco()d'Ultralytics pour convertir les annotations COCO JSON au format YOLO.txt. Définiscls91to80=Falsepour les jeux de données personnalisés :Après la conversion, réorganise tes fichiers d'étiquettes afin que
labels/reproduise la structure du répertoireimages/, puis crée un fichierdataset.yaml. Consulte le guide étape par étape pour connaître le processus complet.