Ultralytics YOLO27 : Inscris-toi sur la liste d’attente.
Get Started
Fondamentaux de la vision par ordinateur·Jeux de données et annotations·Leçon 7/10
Leçondébutant

Qualité des annotations

Comprends le contenu d’une étiquette YOLO, vérifie les fichiers d’étiquettes pour repérer les lignes incorrectes et assure la cohérence des annotateurs avec des consignes et une session de calibration mesurée.

Un modèle entraîné sur des étiquettes incohérentes apprend cette incohérence. Cinquante mille boîtes dessinées selon une règle claire valent mieux qu’un demi-million dessinées selon vingt interprétations personnelles, et réannoter coûte presque toujours moins cher que d’utiliser un modèle plus gros. Cette leçon te fournit un vérificateur d’étiquettes, des consignes d’une page et un indicateur de l’accord entre annotateurs.

Résultat

Vérifie les fichiers d’étiquettes d’un jeu de données, rédige des consignes d’une page pour une classe et mesure l’accord entre deux annotateurs.

Parcours accéléré
Si tu connais déjà les bases, voici la version courte.
  1. Connais la ligne d’étiquette correspondant à ta tâche : boîte, polygone, points clés ou sommets.

  2. Vérifie chaque fichier d’étiquettes : nombre de colonnes, identifiants de classe, coordonnées de 0 à 1, boîtes en double.

  3. Consignes d’une page, avec une image d’exemple pour chaque règle et une liste des éléments à ne pas annoter.

  4. Commence par une session de calibration : tout le monde annote les mêmes 50 images ; passe à l’échelle supérieure uniquement si l’accord atteint le seuil requis.

  5. Les réviseurs rejettent les annotations en indiquant une raison ; ils ne les corrigent jamais sans le signaler.

Pratique

Ce qu’est réellement une étiquette#

Au format YOLO, chaque image possède un fichier .txt correspondant, avec une ligne par objet. Les coordonnées sont normalisées entre 0 et 1 en fonction de la largeur et de la hauteur de l’image, afin que les étiquettes restent valides après redimensionnement. La structure de la ligne dépend de la tâche :

TâcheUne étiquetteRemarques
Détectionclass x_center y_center width heightCentre et dimensions de la boîte
Segmentation d’instanceclass x1 y1 x2 y2 ... xn ynContour du polygone
Poseclass x y w h px1 py1 v1 ... pxn pyn vnBoîte, puis points clés avec visibilité facultative
OBBclass x1 y1 x2 y2 x3 y3 x4 y4Quatre sommets de la boîte orientée
ClassificationAucun fichier d’étiquettesImages réparties dans un dossier par classe
Segmentation sémantiqueUn masque PNG par imageChaque valeur de pixel correspond à un indice de classe
Estimation de la profondeurUn fichier PNG ou NPY de profondeur par imageChaque valeur de pixel correspond à une distance

Les indices de classe commencent à 0 et correspondent aux noms définis dans le YAML du jeu de données. Une image sans objet n’a pas besoin de fichier d’étiquettes — c’est ainsi que fonctionnent les images d’arrière-plan. Les pages sur les jeux de données au format de détection, la segmentation sémantique et la profondeur proposent des exemples fonctionnels.

Vérifie tes fichiers d’étiquettes#

Les lignes incorrectes peuvent passer inaperçues : une coordonnée supérieure à 1, un identifiant de classe non défini dans le YAML ou le même objet encadré deux fois deviennent autant de bruit que le modèle tente d’apprendre. Un petit vérificateur les repère avant l’entraînement :

from itertools import combinations
from pathlib import Path

from ultralytics.data.utils import check_det_dataset

def iou(a, b):
    """IoU of two normalized (x_center, y_center, width, height) boxes."""
    ax1, ay1, ax2, ay2 = a[0] - a[2] / 2, a[1] - a[3] / 2, a[0] + a[2] / 2, a[1] + a[3] / 2
    bx1, by1, bx2, by2 = b[0] - b[2] / 2, b[1] - b[3] / 2, b[0] + b[2] / 2, b[1] + b[3] / 2
    inter = max(0, min(ax2, bx2) - max(ax1, bx1)) * max(0, min(ay2, by2) - max(ay1, by1))
    return inter / (a[2] * a[3] + b[2] * b[3] - inter)

def check_labels(text, names):
    """Return a list of problems in one YOLO detection label file."""
    problems, boxes = [], []
    for i, row in enumerate(text.split("\n"), 1):
        values = row.split()
        if not values:
            continue
        if len(values) != 5:
            problems.append(f"row {i}: {len(values)} values, expected 5")
        elif int(values[0]) not in names:
            problems.append(f"row {i}: unknown class {values[0]}")
        elif not all(0 <= float(v) <= 1 for v in values[1:]):
            problems.append(f"row {i}: coordinates outside 0-1")
        else:
            boxes.append((i, int(values[0]), [float(v) for v in values[1:]]))
    for (i, ca, a), (j, cb, b) in combinations(boxes, 2):
        if ca == cb and iou(a, b) > 0.8:
            problems.append(f"row {j}: duplicate of row {i} (IoU {iou(a, b):.2f})")
    return problems

broken = "0 0.50 0.50 0.20 0.30\n0 0.51 0.50 0.20 0.30\n3 0.40 0.40 0.10 0.10\n0 1.20 0.50 0.10 0.10\n0 0.50 0.50 0.10"
print("\n".join(check_labels(broken, {0: "pill"})))

data = check_det_dataset("medical-pills.yaml")
files = sorted(Path(data["path"], "labels").rglob("*.txt"))
found = sum(len(check_labels(f.read_text(), data["names"])) for f in files)
print(f"medical-pills: {found} problems in {len(files)} label files")
row 3: unknown class 3
row 4: coordinates outside 0-1
row 5: 4 values, expected 5
row 2: duplicate of row 1 (IoU 0.90)
medical-pills: 0 problems in 115 label files

L’exemple créé à la main illustre chaque erreur détectée par le vérificateur ; le vrai jeu de données ne présente aucun problème. Exécute-le après chaque lot d’annotations et avant chaque entraînement — cela ne prend que quelques secondes.

Pourquoi les étiquettes divergent#

Éditeur d’annotations Ultralytics Platform en plein écran : des cartons sur des robots d’entrepôt sont délimités par des polygones, et la liste des classes est ouverte à côté

Observe le panneau des classes dans cet éditeur : les 14 polygones de cartons sont enregistrés sous Grapes, tandis qu’une classe Box apparaît plus bas dans la liste — et plane et Plane existent comme deux classes distinctes. Un vérificateur ne peut repérer aucun de ces problèmes, car chaque ligne est valide ; un réviseur qui compare les étiquettes aux consignes les repérerait tous les deux en quelques secondes. La plupart des erreurs d’étiquetage sont de ce type : des fichiers valides, mais des décisions incorrectes.

Les annotateurs produisent rarement de mauvaises annotations par négligence. Leurs annotations manquent de cohérence parce que les règles sont ambiguës et que chacun fait une supposition raisonnable, mais différente :

  • Où la boîte doit-elle s’arrêter ? À l’ombre, au sac porté, à la charge sur les fourches du chariot élévateur ?
  • Troncature. Une personne à moitié hors du cadre est-elle encore une person ? À partir de quelle fraction visible ?
  • Occultation. Faut-il annoter une voiture dont on ne voit qu’une roue ou l’ignorer ?
  • Limites entre les classes. Un pick-up est-il une car ou une truck ? Un scooter est-il une bicycle ou une other ?
  • Cas à ne pas annoter. Reflets, affiches et écrans représentant l’objet.

Chaque choix convient, à condition que tout le monde fasse le même.

Des consignes d’une page#

Elles tiennent sur une page, comportent une image d’exemple pour chaque règle et présentent d’abord les cas limites :

## Class: forklift

Include:
- Forklifts with at least 30% of the body visible, moving or parked, any size or color.

Do not label:
- Pallet jacks (no overhead mast) → class `pallet_jack`.
- Reflections in glass or polished floors, and forklifts on posters or screens.

Box rules:
- Include the mast and the full visible body.
- Exclude the load (pallets, boxes) — those have their own classes.
- For partly occluded forklifts, box the visible part only.

Mesurer l’accord lors d’une session de calibration#

Avant de passer d’un à vingt annotateurs, tout le monde annote les mêmes 50 images et tu compares les résultats :

L’accord correspond à la proportion d’objets que les deux annotateurs ont délimités avec la même classe et un IoU d’au moins 0,5 — une mesure pratique de la fiabilité inter-évaluateurs :

def iou(a, b):
    """IoU of two (x1, y1, x2, y2) pixel boxes."""
    w = max(0, min(a[2], b[2]) - max(a[0], b[0]))
    h = max(0, min(a[3], b[3]) - max(a[1], b[1]))
    inter = w * h
    return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter)

def agreement(boxes_a, boxes_b, threshold=0.5):
    """Share of objects both annotators drew with the same class and IoU >= threshold."""
    unmatched, matched = list(boxes_b), 0
    for cls_a, box_a in boxes_a:
        best = max(unmatched, key=lambda b: iou(box_a, b[1]) if b[0] == cls_a else -1, default=None)
        if best and best[0] == cls_a and iou(box_a, best[1]) >= threshold:
            unmatched.remove(best)
            matched += 1
    return matched / max(len(boxes_a), len(boxes_b))

# Two annotators label the same calibration image
alice = [("forklift", (100, 200, 400, 600)), ("person", (450, 220, 520, 480)), ("person", (600, 230, 660, 470))]
bob = [("forklift", (100, 120, 400, 600)), ("person", (452, 218, 522, 478)), ("pallet_jack", (600, 230, 660, 470))]
print(f"agreement: {agreement(alice, bob):.0%}")
agreement: 67%

Deux types de désaccord se cachent dans ces 67 %. La boîte du chariot élévateur de Bob commence 80 pixels plus haut — il a inclus le sommet du mât — mais correspond quand même à un IoU de 0,83 : cela compte donc comme un accord ; précise la règle de délimitation si ton produit mesure la hauteur. Le troisième objet révèle un véritable conflit : Alice indique person, Bob indique pallet_jack. Il faut une règle plus claire et une image d’exemple, puis refaire une session. Conserve les 50 images de calibration : elles serviront de test d’intégration pour chaque nouvel annotateur.

Les réviseurs rejettent les étiquettes, ils ne les corrigent pas en silence

Un réviseur qui corrige discrètement les étiquettes supprime la boucle de rétroaction, et les annotateurs n’apprennent jamais ce qu’ils ont fait de travers. Rejette l’étiquette en indiquant la raison, et laisse l’annotateur la corriger. C’est plus lent pour les cent premières images, mais beaucoup plus rapide ensuite.

Éléments à suivre#

IndicateurCe qu’il t’indique
Débit (objets par heure)Capacité et coût
Taux de rejetÉvolution de la qualité
Accord sur un ensemble commun de recouvrementsLes annotateurs appliquent-ils toujours les mêmes règles ?

Quand l’accord baisse, quelque chose a changé : un nouvel annotateur, une nouvelle caméra, un lot ambigu, la fatigue. Si le modèle échoue ensuite dans une condition précise, renvoie exactement ces images à l’annotation — c’est la boucle qu’automatise l’apprentissage actif, et la principale source de gains de précision dans le monde réel. Parcours et filtre les erreurs dans tes jeux de données sur Ultralytics Platform, et consulte Contrôle qualité des jeux de données et Bonnes pratiques d’annotation pour appliquer cette méthode à grande échelle.

Essayer

Exécute le vérificateur sur le dossier d’étiquettes de ton jeu de données. Rédige ensuite des consignes d’une page pour ta classe la plus ambiguë, avec une image d’exemple par règle et une liste des éléments à ne pas annoter. Donne ces consignes et 10 images non annotées à un collègue, sans rien lui expliquer ; annote les mêmes images toi-même, puis calcule l’accord avec le script. Note le score et chaque règle que tu as modifiée à la suite d’un désaccord.

Terminé lorsque
Tu as terminé la leçon lorsque tous les éléments suivants sont réunis.
  • Le vérificateur ne signale aucun problème dans tes étiquettes, ou tu as corrigé tous ceux qu’il a détectés.

  • Tu disposes de consignes d’une page avec un exemple pour chaque règle et une liste des éléments à ne pas annoter.

  • Tu disposes d’un score d’accord entre deux annotateurs et d’une liste des règles que tu as modifiées en conséquence.

Afficher la solution

Consignes pour la classe pill de medical-pills, rédigées après une session de calibration sur 10 images :

## Class: pill

Include:
- Every tablet inside a blister cavity, including half-visible ones at the image edge.
- Tablets seen through clear plastic, even when glare covers part of them.

Do not label:
- Empty cavities, torn foil, or tablets outside the pack.
- Reflections of tablets in the foil.

Box rules:
- Box the tablet itself, not the plastic bubble around it.
- If two tablets touch, draw two boxes.

Comment interpréter le score d’accord : supposons que le premier tour obtienne 82 %. Trie les objets sans correspondance selon leur cause. Pour les blisters, les deux cas habituels sont les boîtes dessinées autour de la bulle en plastique au lieu du comprimé (corrigées par la première règle de délimitation, avec une image d’exemple) et les comprimés masqués par les reflets qu’un annotateur a ignorés (corrigés par la deuxième règle d’inclusion). Recommence sur 10 images nouvelles ; passe à l’échelle supérieure uniquement lorsque le score dépasse le seuil de 90 %.

Et ensuite ?

Ensuite : répartir les données annotées pour que tes métriques restent fiables — en commençant par une fuite cachée dans ce jeu de données même.