YOLO Vision 2026 :

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.

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 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 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], avec un seul fichier JSON pour toutes les images.
  • Les identifiants de classe diffèrent — COCO utilise des valeurs category_id arbitraires, tandis que YOLO nécessite des identifiants de classe indexés à partir de 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)
Identifiants de classecategory_id (peut commencer par n'importe quel nombre)Indexés à partir de zéro (commencent à 0)
SegmentationTableaux de polygones dans le champ segmentationCoordonné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.

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

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

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

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() é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
        └── ...
Une nouvelle exécution crée un nouveau dossier de sortie

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

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

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

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

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#

  • Utilise la fonction convert_coco() d'Ultralytics pour convertir les annotations COCO JSON au format YOLO .txt. Définis cls91to80=False pour les jeux de données personnalisés :

    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/ reproduise la structure du répertoire images/, puis crée un fichier dataset.yaml. Consulte le guide étape par étape pour connaître 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, à côté de images/train/. YOLO attend que les étiquettes soient au même niveau que les images — par exemple, images/train/img.jpg doit contenir labels/train/img.txt. Déplace tes étiquettes converties pour respecter cette structure. Consulte Correction de la structure des répertoires.

  • Le paramètre cls91to80 contrôle la façon dont les valeurs COCO category_id sont mappées vers les identifiants de classe YOLO. Lorsque True (par défaut), il applique la table de correspondance coco91_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 toujours cls91to80=False — cela soustrait simplement 1 à chaque category_id afin 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 donc convert_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=True lors de l'appel de convert_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=True pour 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_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 silencieusement.

Commentaires