Préparer un jeu de données personnalisé
Organise les images et les annotations au format Ultralytics YOLO, convertis les boîtes en pixels en lignes d’annotation normalisées et repère les annotations incorrectes avant l’entraînement.
Un dossier mal nommé ou une annotation non normalisée ne fait pas échouer l’entraînement. Ultralytics ignore les images incorrectes et continue : tu entraînes donc un modèle sur une partie de tes données, obtiens un résultat médiocre et perds un après-midi à chercher la cause au mauvais endroit.
Cette leçon porte sur les aspects pratiques d’un jeu de données de détection : dossiers, lignes d’annotation, conversion et vérifications. Tu utiliseras le véritable jeu de données d’imagerie médicale de comprimés, composé de 115 images, sur lequel le reste de ce cours s’appuie pour l’entraînement. Le choix de ce qu’il faut collecter et de la façon de l’annoter est abordé dans Bonnes pratiques d’annotation.
Pratique
Récupérer le jeu de données d’exercice#

La structure ci-dessous est également acceptée sous forme d’archive ZIP par la boîte de dialogue « Nouveau jeu de données » d’Ultralytics Platform : le jeu de données que tu prépares ici peut donc être téléversé sans modification. Télécharge 92 images d’entraînement et 23 images de validation de comprimés sous blister, puis décompresse-les dans my_dataset :
curl -L -o medical-pills.zip https://github.com/ultralytics/assets/releases/download/v0.0.0/medical-pills.zip
unzip -q medical-pills.zip -d my_datasetmy_dataset/
├── images/
│ ├── train/ Frame_0.jpg, Frame_100.jpg, ... (92 images)
│ └── val/ Frame_144.jpg, ... (23 images)
└── labels/
├── train/ Frame_0.txt, Frame_100.txt, ... (92 files)
└── val/ Frame_144.txt, ... (23 files)Il n’y a pas de manifeste. Ultralytics trouve chaque annotation en remplaçant /images/ par /labels/ dans le chemin de l’image, et l’extension par .txt ; ainsi, images/val/Frame_144.jpg lit labels/val/Frame_144.txt. Deux conséquences :
- Les dossiers doivent s’appeler exactement
imagesetlabels, et se trouver côte à côte. - Une image dont le fichier d’annotation est vide ou manquant est une image d’arrière-plan : elle est valide et utile pour apprendre au modèle ce qu’il ne faut pas détecter, mais seulement si c’est bien ce que tu voulais.
Lire une ligne d’annotation#

Chaque ligne correspond à un objet : un indice de classe entier, suivi du centre et de la taille de la boîte englobante exprimés en fractions de la largeur et de la hauteur de l’image. Voici une vraie ligne de labels/train/Frame_0.txt :
0 0.330729 0.816204 0.103125 0.1212960: indice de classe 0, que le YAML désignera parpill.0.330729 0.816204: le centre de la boîte se trouve à 33 % de la largeur et à 82 % de la hauteur de l’image.0.103125 0.121296: la boîte mesure environ 10 % de la largeur et 12 % de la hauteur de l’image.
Exemple pratique : convertir une boîte en pixels#
Les outils d’annotation exportent souvent les coordonnées des coins en pixels (x1, y1, x2, y2). Divise-les par la taille de l’image. La personne la plus à gauche dans bus.jpg de 810×1080, présenté dans la leçon précédente, s’étend de (49, 398) à (240, 902) :
| Champ | Formule | Valeur |
|---|---|---|
x_center | (x1 + x2) / 2 / width | (49 + 240) / 2 / 810 = 0.178395 |
y_center | (y1 + y2) / 2 / height | (398 + 902) / 2 / 1080 = 0.601852 |
width | (x2 − x1) / width | 191 / 810 = 0.235802 |
height | (y2 − y1) / height | 504 / 1080 = 0.466667 |
La plupart des outils d’annotation, ainsi que des outils comme Roboflow, peuvent exporter directement au format YOLO. Pour les fichiers JSON COCO, suis le guide d’entraînement avec JSON COCO ; le dépôt JSON2YOLO couvre les autres formats. Pour les fichiers XML Pascal VOC, la formule ci-dessus tient dans un court script :
import xml.etree.ElementTree as ET
from pathlib import Path
CLASSES = ["person", "bus"] # index = class id; must match names in data.yaml
def voc_to_yolo(xml_file: Path, out_dir: Path) -> None:
root = ET.parse(xml_file).getroot()
w, h = int(root.find("size/width").text), int(root.find("size/height").text)
lines = []
for obj in root.iter("object"):
name = obj.find("name").text
if name not in CLASSES:
continue
box = obj.find("bndbox")
x1, y1, x2, y2 = (float(box.find(k).text) for k in ("xmin", "ymin", "xmax", "ymax"))
xc, yc, bw, bh = (x1 + x2) / 2 / w, (y1 + y2) / 2 / h, (x2 - x1) / w, (y2 - y1) / h
lines.append(f"{CLASSES.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / f"{xml_file.stem}.txt").write_text("\n".join(lines) + "\n")
voc_to_yolo(Path("bus.xml"), Path("labels"))
print(Path("labels/bus.txt").read_text())Exécuté sur un bus.xml contenant les boîtes de la personne et du bus de la leçon précédente, le script affiche l’exemple détaillé et celui du bus :
0 0.178395 0.601852 0.235802 0.466667
1 0.495679 0.454167 0.991358 0.482407Vérifier avant l’entraînement#
Affiche quelques fichiers d’annotation sur leurs images. Une classe décalée, des axes x et y inversés ou des boîtes autour des ombres sautent aux yeux, mais restent invisibles dans les métriques :
from ultralytics.data.utils import visualize_image_annotations
visualize_image_annotations(
"my_dataset/images/train/Frame_0.jpg",
"my_dataset/labels/train/Frame_0.txt",
{0: "pill"},
)Ensuite, laisse Ultralytics vérifier chaque fichier. Lorsqu’il lit un jeu de données pour la première fois, il analyse les annotations et décide, ligne par ligne, si chaque image est exploitable :
La ligne de bilan indique le résultat : 23 images, 0 backgrounds, 0 corrupt correspond à un jeu de données sain. Tout autre résultat signale l’une des erreurs ci-dessous.
Éviter ces erreurs#
Symptôme : ignoring corrupt image/label: non-normalized or out of bounds coordinates [634. 881. 198. 163.] et un nombre corrupt non nul. Cause : les valeurs sont exprimées en pixels plutôt qu'en fractions. Solution : divise par la largeur et la hauteur de l'image. Toute valeur d'étiquette supérieure à 1 signifie que la boîte n'a jamais été normalisée.
Symptôme : yolo val parcourt 0 images, 23 backgrounds et affiche l'avertissement No labels found, puis l'entraînement s'arrête avec ValueError: train: No labels found — ici, parce que le dossier des étiquettes s'appelait annotations. Solution : nomme les dossiers exactement images et labels, avec des noms de fichiers correspondants.
Symptôme : Label class 1 exceeds dataset class count 1. Possible class labels are 0-0. Cause : l'outil d'exportation a numéroté les classes à partir de 1. Solution : les classes YOLO commencent à 0 ; soustrais 1 dans le script de conversion ou corrige le paramètre d'exportation.
Si les boîtes dessinées sont décalées d'une demi-boîte vers le bas et la droite, tu as écrit x1 y1 w h au lieu de x_center y_center w h. Dessiner une seule image permet de le repérer en quelques secondes ; aucun contrôle du jeu de données ne le peut.
Pour effectuer des vérifications systématiques sur un jeu de données entier, consulte Contrôle qualité des jeux de données. Les jeux de données intégrés comme coco8, coco128 et african-wildlife fournissent de bons exemples de structure ; la présentation des jeux de données les répertorie tous. L’entraîneur ajoute de l’augmentation des données à tes annotations : un rapide nettoyage des données à cette étape est donc bénéfique à chaque époque.
Afficher la solution
for s in train val; do
echo "$s: $(ls my_dataset/images/$s | wc -l) images, $(ls my_dataset/labels/$s | wc -l) labels"
done
# train: 92 images, 92 labels
# val: 23 images, 23 labelsfrom pathlib import Path
for txt in Path("my_dataset/labels").rglob("*.txt"):
for line in txt.read_text().splitlines():
values = [float(v) for v in line.split()[1:]]
if any(v < 0 or v > 1 for v in values):
print(txt, line)Si le script ne renvoie rien, toutes les coordonnées sont normalisées. Des nombres identiques par partition signifient que chaque image a un fichier d’annotation ; la ligne de bilan de la prochaine leçon confirmera qu’aucun fichier n’est corrompu.