Aller au contenu principal

Module 9 — Annotation, augmentation et qualité des jeux

Les huit premiers modules ont supposé que le jeu de données existait déjà. Sur un projet réel, il n'existe presque jamais : il faut le fabriquer. Ce module traite ce que la moitié des articles de recherche passent sous silence — comment on annote, comment on augmente et surtout comment on évite la fuite entre entraînement et test qui gonfle artificiellement toutes les métriques.

Choisir un outil d'annotation

Le paysage des outils s'est stabilisé autour d'une poignée de choix :

OutilPoints fortsFormat natif
CVATouvert, boîtes, polygones, vidéoXML CVAT ou export COCO
Label Studioouvert, très polyvalent, multi-tâchesJSON
RoboflowSaaS, augmentation intégrée, export multi-formatstout
LabelImgminimaliste, boîtes seulesPascal VOC ou YOLO

Sur le carrefour, un projet d'une semaine à trois personnes tourne bien avec CVAT auto-hébergé : segmentation des voies avec polygones, boîtes sur les véhicules et piétons, export COCO directement consommé par pycocotools. Roboflow séduit sur les petits projets par sa facilité, mais verrouille les données dans un service tiers.

L'accord inter-annotateurs révèle les définitions floues

Quand deux personnes annotent la même image, elles n'obtiennent pas la même chose. Les désaccords ne sont pas des erreurs individuelles — ils révèlent des définitions floues dans les instructions.

  • Un vélo au fond de l'image : petit véhicule ou piéton actif ?
  • Un rétroviseur qui déborde d'une voiture stationnée : dans la boîte ou hors de la boîte ?
  • Deux motos superposées à distance : une seule boîte ou deux ?

Mesurer cet accord se fait par le coefficient kappa de Cohen pour la classification, ou par l'IoU moyen entre annotations sur les boîtes. Un IoU inter-annotateurs autour de 0,85 est bon ; sous 0,70, la qualité du jeu est plombée dès la source, et aucune amélioration du modèle ne compensera.

def iou_annotateurs(boites_a, boites_b, seuil=0.5):
"""Renvoie l'IoU moyen des paires appariees et la couverture."""
from scipy.optimize import linear_sum_assignment
import numpy as np
if not boites_a or not boites_b:
return 0.0, 0.0
m = np.zeros((len(boites_a), len(boites_b)))
for i, a in enumerate(boites_a):
for j, b in enumerate(boites_b):
m[i, j] = iou(a, b)
lignes, cols = linear_sum_assignment(-m)
ious = [m[i, j] for i, j in zip(lignes, cols) if m[i, j] >= seuil]
couverture = 2 * len(ious) / (len(boites_a) + len(boites_b))
return float(np.mean(ious)) if ious else 0.0, couverture

Une guide d'annotation écrite — deux pages, cinq exemples, dix contre-exemples — élimine 80 % des désaccords en une matinée de mise à niveau. C'est l'investissement le mieux rentabilisé de tout un projet d'annotation.

Augmentation compatible avec les boîtes

L'augmentation classique (rotation, découpe, changement de luminosité) demande une synchronisation entre l'image et les annotations. Faire tourner une image sans faire tourner ses boîtes produit un jeu silencieusement incorrect.

Albumentations est la bibliothèque de référence : chaque transformation sait mettre à jour boîtes et masques.

import albumentations as A
import cv2

pipeline = A.Compose(
[
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.3),
A.Affine(rotate=(-10, 10), translate_percent=0.05, p=0.7),
A.Resize(640, 640),
],
bbox_params=A.BboxParams(format="pascal_voc",
label_fields=["classes"]),
)

image = cv2.imread("carrefour_0001.jpg")
boites = [[100, 200, 250, 380], [420, 190, 560, 350]]
classes = ["voiture", "voiture"]

sortie = pipeline(image=image, bboxes=boites, classes=classes)
image_augmentee = sortie["image"]
boites_augmentees = sortie["bboxes"]

Les transformations à utiliser prudemment en vision de trafic :

  • Symétrie verticale : inverserait le sens des panneaux et fausserait les plaques.
  • Rotation de plus de 15° : les voitures penchées à 90° ne sont plus reconnaissables par un modèle préentraîné.
  • Changement de couleur agressif : peut faire disparaître un feu rouge.

Le principe : une augmentation doit produire une image plausible dans la distribution cible, jamais une bizarrerie qui n'apparaîtra jamais en production.

Mosaic et MixUp : les augmentations lourdes

Deux augmentations spécifiques à la détection ont été popularisées par YOLO :

  • Mosaic assemble quatre images en une seule 640 × 640, avec leurs annotations. Le modèle apprend à voir plus d'objets par image et devient plus robuste aux tailles variées.
  • MixUp mélange deux images pixel par pixel avec un coefficient aléatoire ; les annotations sont additionnées avec le même poids.

Ces deux techniques améliorent la mAP de 1 à 3 points sur COCO, mais elles produisent des images non naturelles. Elles se désactivent dans les dernières époques d'entraînement, pour que le modèle voie de vraies images avant de terminer.

La fuite par images quasi identiques

C'est le piège le plus coûteux de la vision. Un jeu de trafic filmé par une caméra fixe contient des milliers d'images très similaires : deux images consécutives à 25 images par seconde ne diffèrent que de quelques pixels.

Un simple train_test_split aléatoire répartit ces images voisines dans les deux splits. Le modèle voit à l'entraînement des images presque identiques à celles du test, et sa mAP en évaluation est massivement gonflée. En production, sur une nouvelle vidéo, la précision s'effondre.

Trois défenses cohabitent :

  1. Split par vidéo source : toutes les images d'une vidéo vont dans le même split. C'est la règle minimale.
  2. Split par plage horaire : réserver certaines heures ou certains jours au test — matinée pour l'entraînement, soirée pour le test.
  3. Suppression des doublons perceptuels : une empreinte perceptuelle (imagehash) permet de détecter les images à moins de dd bits de distance et de n'en garder qu'une seule.
from imagehash import phash
from PIL import Image

def dedupliquer(chemins, distance_max=6):
empreintes = {}
conserves = []
for chemin in chemins:
e = phash(Image.open(chemin))
double = any(e - autre <= distance_max for autre in empreintes)
if not double:
empreintes[chemin] = e
conserves.append(chemin)
return conserves

Sur un jeu de 100 000 images de carrefour, cette déduplication réduit souvent le jeu utile à 8 000 à 15 000 images distinctes. C'est ce chiffre qu'il faut prendre au sérieux pour dimensionner l'entraînement, pas les 100 000 bruts.

Contrôle qualité continu

Un jeu propre au premier jour se dégrade au fil des ajouts. Trois vérifications automatiques préviennent la dérive :

  • Statistiques par classe : histogramme des tailles et des positions. Une nouvelle classe qui n'a que de grosses boîtes signale un biais d'annotation.
  • Boîtes hors image ou de taille 0 : filtrage automatique avant l'entraînement, avec un rapport des cas rejetés.
  • Fréquence des classes : un déséquilibre trop fort recommande un échantillonnage pondéré ou une focal loss.
La fuite temporelle est invisible aux métriques standards

Un jeu séparé par images voisines produit une mAP@0,5 de 0,95, alors que le modèle ne généralise pas du tout. La seule façon de la détecter est d'évaluer sur une vidéo complètement inconnue, prise à un autre moment ou d'un autre carrefour. Cet écart entre la mAP de test et la mAP réelle est le meilleur diagnostic de fuite qu'on puisse construire.

Annoter en boucle avec le modèle courant

Après un premier entraînement sur 1 000 images, faites tourner le modèle sur les 5 000 suivantes et corrigez ses prédictions plutôt que de tout annoter de zéro. Le coût par image chute de 60 % à 80 %, et le modèle apprend spécifiquement sur ses erreurs restantes.

En résumé

  • Les outils d'annotation ouverts (CVAT, Label Studio) suffisent pour la plupart des projets ; l'accord inter-annotateurs mesuré par IoU moyen est la seule métrique fiable de qualité de source.
  • Albumentations synchronise l'augmentation avec boîtes et masques ; les augmentations doivent produire des images plausibles dans la distribution cible, jamais des bizarreries.
  • Mosaic et MixUp aident la détection mais se désactivent en fin d'entraînement pour que le modèle voie des images naturelles.
  • La fuite par images quasi identiques est le piège le plus coûteux de la vision vidéo : split par source, split temporel et déduplication perceptuelle sont les trois défenses à combiner.

Module suivant : le projet final — la chaîne complète sur le carrefour, affinage YOLOv8 et analyse des erreurs par classe et par taille.