Comment convertir des annotations COCO au format YOLO
L’entraînement de 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 à entraîner des 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 COCO au format 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’annotations
.txt, à raison d’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]et stocke toutes les images dans un seul fichier JSON. - Les identifiants de classe diffèrent — COCO utilise des valeurs
category_idarbitraires, tandis que YOLO exige des identifiants de classe commençant à 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) |
| ID de classe | category_id (peut commencer par n'importe quel nombre) | Indexé à partir de zéro (commence à 0) |
| Segmentation | Tableaux de polygones ou masques RLE dans le champ segmentation | Coordonnées des polygones après l'ID de classe |
| Points clés | [x, y, visibility, ...] en pixels | [x, y, visibility, ...] normalisé |
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/", # répertoire contenant tes fichiers JSON
save_dir="my_dataset/converted/", # emplacement d'enregistrement des étiquettes converties
cls91to80=False, # définis la valeur sur False pour les jeux de données personnalisés (voir l'avertissement ci-dessous)
)Après la conversion, organise la structure de ton répertoire, 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 de cls91to80=True est conçue uniquement pour le jeu de données COCO standard, qui comprend 80 classes d'objets et associe 91 ID de catégorie non contigus à 80 ID de classe contigus. Pour tout jeu de données personnalisé, tu dois définir cls91to80=False ; sinon, tes ID de classe seront associés de manière incorrecte sans avertissement, et ton modèle apprendra des classes erronées.
Guide de conversion étape par étape#
1. Préparer ton jeu de données COCO#
Un jeu de données classique au format COCO exporté depuis un outil d'annotation présente 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 respecte les spécifications du format de données COCO et comporte 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. Convertir 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() crée un fichier .txt par image annotée dans un sous-répertoire labels/ portant le nom de chaque fichier JSON, après suppression du préfixe instances_ (ainsi, instances_train.json produit labels/train/). Les images sans annotation 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() n'écrase jamais un save_dir existant : si my_dataset/converted/ existe déjà, une nouvelle exécution écrit 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 des étiquettes obsolètes.
3. Organiser la structure des répertoires#
Après la conversion, les fichiers d'étiquettes doivent être placés à côté de tes images. YOLO attend un répertoire labels/ qui reflète le répertoire images/ :
import shutil
from pathlib import Path
converted_dir = Path("my_dataset/converted/labels")
dataset_dir = Path("my_dataset")
# convert_coco nomme chaque sous-répertoire d'après son fichier JSON (sans le préfixe "instances_"),
# parcours donc les sous-répertoires réels au lieu de supposer qu'ils s'appellent "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))La structure finale de ton jeu de données devrait ressembler à ceci :
my_dataset/
├── images/
│ ├── train/
│ │ ├── img_001.jpg
│ │ └── ...
│ └── val/
│ └── ...
├── labels/
│ ├── train/
│ │ ├── img_001.txt
│ │ └── ...
│ └── val/
│ └── ...
└── dataset.yaml4. Créer 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
from ultralytics.utils import YAML
# Lire les catégories depuis ton fichier JSON COCO
with open("my_dataset/annotations/instances_train.json") as f:
coco = json.load(f)
# Créer des noms de classes correspondant à la sortie de convert_coco (category_id - 1)
categories = sorted(coco["categories"], key=lambda x: x["id"])
names = {cat["id"] - 1: cat["name"] for cat in categories}
# REMARQUE : convert_coco associe les ID de classe selon la formule category_id - 1 ; category_id doit donc
# commencer à 1. Si tes catégories commencent à 0, ajoute d'abord 1 à chaque ID.
# Créer dataset.yaml
dataset = {
"path": str(Path("my_dataset").resolve()),
"train": "images/train",
"val": "images/val",
"names": names,
}
YAML.save("my_dataset/dataset.yaml", dataset)Le fichier YAML obtenu :
path: /absolute/path/to/my_dataset
train: images/train
val: images/val
names:
0: helmet
1: vestPour en savoir plus sur le format YAML des jeux de données, consulte le guide de configuration des jeux de données.
5. Entraîner 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") # charger un modèle préentraîné
results = model.train(data="my_dataset/dataset.yaml", epochs=100, imgsz=640)Pour obtenir des conseils et connaître les bonnes pratiques d'entraînement, consulte le guide d'entraînement des modèles.
6. Vérifier ta conversion#
Avant l'entraînement, vérifie quelques fichiers d'étiquettes pour confirmer que les ID 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 ID de classe négatifs, ton fichier JSON COCO utilise probablement des category_id commençant à 0. Ajoute 1 à toutes les valeurs category_id de ton fichier JSON avant d'exécuter convert_coco(), car cette fonction associe les ID de classe selon category_id - 1.
Résolution des problèmes courants#
ID de classe incorrects après la conversion#
Si ton modèle s’entraîne, mais détecte les mauvaises classes d’objets, tu utilises probablement cls91to80=True (par défaut) sur un jeu de données personnalisé. Cette option associe les valeurs category_id à la table de correspondance COCO de 91 à 80 classes, ce qui convient uniquement au jeu de données COCO standard. Une category_id sans équivalent parmi les 80 classes COCO ne correspond à aucune classe et déclenche TypeError: must be real number, not NoneType lors de la conversion, au lieu de produire des étiquettes erronées.
Solution : utilise toujours cls91to80=False pour les jeux de données personnalisés.
Aucune étiquette détectée pendant l'entraînement#
Si l'analyse des étiquettes indique 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/ se trouve au même niveau que images/train/.
KeyError pendant la conversion#
Si tu obtiens KeyError: 'bbox' ou des erreurs similaires lors de l'exécution de convert_coco(), ton labels_dir contient probablement des fichiers JSON qui ne correspondent pas à des annotations d'instances (par exemple, captions_train2017.json) et dont la structure d'annotation est 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 absents, toutes les annotations peuvent avoir iscrowd: 1, que convert_coco() ignore (CVAT, par exemple, exporte les annotations de masque sous forme de régions de foule), ou les boîtes englobantes peuvent avoir une largeur ou une hauteur nulle. L’exécution avec use_keypoints=True sur un export contenant uniquement des détections produit le même résultat, car les annotations sans champ keypoints sont entièrement ignorées.
Solution : Examine tes annotations JSON pour repérer les valeurs iscrowd. Lorsque chaque masque marqué comme foule délimite un seul objet, comme dans les exports de masques CVAT, définis iscrowd: 0 afin que use_segments=True convertisse son masque RLE ; laisse marquées les véritables régions de foule qui couvrent plusieurs objets. Si tu as transmis 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 consigne annotations without a usable polygon, certaines annotations ne contiennent aucune valeur segmentation, un encodage en longueur de plages COCO ({"counts": ..., "size": ...}) dont size ne correspond pas à celui de l’image [height, width], ou dont le masque est vide ou ne contient que des régions trop petites ou trop fines pour être délimitées par trois points, ou encore une valeur qui n’est ni un RLE ni une liste d’au moins trois paires de coordonnées. La cause habituelle est un export contenant uniquement des détections, qui laisse le champ absent ou vide ; une liste de coordonnées à plat 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 qui restent et, s’il n’en reste aucun, utilise à la place une ligne de segment ayant la forme de sa boîte englobante. Les étiquettes restent donc valides, mais ces lignes ne contiennent aucun détail de masque.
Les valeurs RLE dont counts est absent ou ne peuvent pas être décodées, par exemple une chaîne tronquée ou des séquences dont la somme n’est pas égale à size, interrompent la conversion avec une erreur au lieu d’entraîner un repli.
Solution : Réexporte les annotations avec des segmentations polygonales ou RLE, ou corrige les valeurs segmentation mal formées.
Lacunes dans les ID de classe des étiquettes converties#
Si les ID de classe des 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 ID de classe de tes fichiers .txt correspondent au dictionnaire names dans dataset.yaml. Si nécessaire, remappe les ID pour obtenir des valeurs contiguës.
Pour consulter les détails complets de l'API et les descriptions des paramètres, consulte la référence de l'API convert_coco.
FAQ#
Utilise la fonction
convert_coco()d'Ultralytics pour convertir les annotations COCO JSON au format YOLO.txt. Pour les jeux de données personnalisés, définiscls91to80=False:from ultralytics.data.converter import convert_coco convert_coco(labels_dir="path/to/annotations/", save_dir="output/", cls91to80=False)Après la conversion, réorganise tes fichiers d'étiquettes afin que
labels/reflète le répertoireimages/, puis crée un fichierdataset.yaml. Consulte le guide détaillé étape par étape pour découvrir le processus complet.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, au même niveau queimages/train/. YOLO attend que les étiquettes soient placées à côté des images ; par exemple,images/train/img.jpgdoit correspondre àlabels/train/img.txt. Déplace tes étiquettes converties pour respecter cette structure. Consulte la correction de la structure des répertoires.Le paramètre
cls91to80détermine comment les valeurs COCOcategory_idsont associées aux ID de classe YOLO. LorsqueTrue(valeur par défaut) est défini, il applique la table de correspondancecoco91_to_coco80_class(), conçue pour le jeu de données COCO standard, qui comprend 80 classes avec des ID non contigus (1-90). Pour les jeux de données personnalisés, définis toujourscls91to80=False: cette option soustrait simplement 1 à chaquecategory_idafin de créer des ID 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. Tu peux donc soit exécuterconvert_coco()et suivre ce guide détaillé étape par étape, soit créer une sous-classe du jeu de données pour analyser le fichier COCO JSON à la volée — consulte Entraîner YOLO avec 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 àconvert_coco()pour inclure les masques de segmentation dans les étiquettes YOLO converties. Les segmentations polygonales sont converties directement, et les masques à encodage par plages (RLE), compressés ou non, sont décodés avecfaster-coco-eval(installé automatiquement lorsqu’un fichier JSON en contient) pour obtenir les contours externes de leurs régions. Un objet réparti sur plusieurs polygones ou régions de masque est regroupé en une seule ligne polygonale et, comme les polygones YOLO ne peuvent pas représenter de trous, les trous des masques sont remplis. Les annotations de foule (iscrowd: 1) sont ignorées. 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 COCO de points clés destinées à 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 sans avertissement.
Commentaires
Pose une question, partage tes résultats ou aide quelqu’un d’autre.