Ultralytics YOLO27 :
Get Started

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.

Tu préfères éviter la conversion ?

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, contenant class x_center y_center width height en 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_id arbitraires, tandis que YOLO exige des identifiants de classe commençant à zéro.
FonctionnalitéCOCO JSONYOLO TXT
StructureUn seul fichier JSON pour toutes les imagesUn fichier .txt par image
Format des BBox[x_min, y_min, width, height] en pixelsclass x_center y_center width height normalisé (0-1)
ID de classecategory_id (peut commencer par n'importe quel nombre)Indexé à partir de zéro (commence à 0)
SegmentationTableaux de polygones dans le champ segmentationCoordonné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.

Jeux de données personnalisés : utilise toujours `cls91to80=False`

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.json

Chaque 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 :

Convertir COCO au format YOLO
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
        └── ...
Une nouvelle exécution crée un nouveau dossier de sortie

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))

Ta structure finale du jeu de données devrait ressembler à ceci :

my_dataset/
├── images/
│   ├── train/
│   │   ├── img_001.jpg
│   │   └── ...
│   └── val/
│       └── ...
├── labels/
│   ├── train/
│   │   ├── img_001.txt
│   │   └── ...
│   └── val/
│       └── ...
└── dataset.yaml

4. 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

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,
}

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: vest

Pour 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 :

Entraîner le modèle sur des données COCO converties
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}"
Conseil

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 des classes d'objets incorrectes, tu utilises probablement cls91to80=True (valeur par défaut) sur un jeu de données personnalisé. Cette fonction associe tes valeurs category_id à l'aide de la table de correspondance COCO 91 vers 80, qui ne convient qu'au 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 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 manquants, il se peut que toutes les annotations aient iscrowd: 1 (fréquent avec les masques générés par SAM) ou que les boîtes englobantes aient une largeur ou une hauteur nulle. Le même résultat est obtenu en exécutant avec use_keypoints=True sur un export contenant uniquement des détections, car les annotations sans champ keypoints sont entièrement ignorées.

Solution : examine 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 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 signale annotations without a usable polygon, certaines annotations ne comportent aucune valeur segmentation, ou en comportent une qui n'est pas une liste d'au moins trois paires de coordonnées. Les causes habituelles sont les exports ne contenant que des détections, où le champ est absent ou vide, et le codage par plages de valeurs (RLE) COCO ({"counts": ..., "size": ...}), utilisé par les exportateurs de masques binaires tels que SAM ; les listes de coordonnées plates sans liste de polygones englobante, les contours à un ou deux points et les autres valeurs mal formées sont traités de la même manière. Une annotation conserve les polygones valides restants et, s'il n'en reste aucun, utilise 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.

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.

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éfinis cls91to80=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épertoire images/, puis crée un fichier dataset.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 de save_dir/labels/ (par exemple, save_dir/labels/train/) au lieu de les placer directement dans labels/train/ de ton jeu de données, au même niveau que images/train/. YOLO attend que les étiquettes soient placées à côté des images ; par exemple, images/train/img.jpg doit 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 cls91to80 détermine comment les valeurs COCO category_id sont associées aux ID de classe YOLO. Lorsque True (valeur par défaut) est défini, il applique la table de correspondance coco91_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 toujours cls91to80=False : cette option soustrait simplement 1 à chaque category_id afin 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écuter convert_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=True lors de l'appel à convert_coco() pour inclure les masques de segmentation polygonaux dans les étiquettes YOLO converties. Les fichiers d'étiquettes obtenus sont 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=True pour 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_segments et use_keypoints sont tous deux définis sur True, seuls les points clés seront écrits dans les fichiers d'étiquettes ; les segments seront ignorés sans avertissement.

Commentaires