Détection d'Objets : YOLO & Faster-RCNN
“De la classification d'images à la localisation et l'étiquetage de chaque objet dans la scène”
Des fenêtres glissantes aux détecteurs single-shot — IoU, boîtes d'ancrage, NMS, mAP et le compromis architectures deux-étapes vs une-étape.
Prérequis
Concepts Couverts
∑Formules Clés
IoU
Intersection sur Union — mesure de qualité de la boîte englobante ; IoU > 0,5 est conventionnellement une détection correcte
mAP
Précision Moyenne Moyenne — aire sous la courbe Précision-Rappel, moyennée sur toutes les classes
Perte YOLO
Somme pondérée : régression de boîte + confiance d'objectivité + probabilités de classe
NMS
Garder uniquement la boîte la plus confiante quand plusieurs boîtes se chevauchent fortement sur le même objet
▶Simulation Interactive
Au-Delà de la Classification : Où et Quoi ?
La classification d'images répond à « y a-t-il un chat ? ». La détection répond à « où sont les chats, et y a-t-il aussi des chiens ? ». Ce passage d'une seule étiquette à un nombre variable de sorties (classe, boîte englobante) est ce qui fait de la détection d'objets la tâche centrale en conduite autonome, imagerie médicale, caisse de commerce et surveillance. Chaque voiture autonome exécute un détecteur en temps réel traitant 30+ images par seconde. L'évolution des classificateurs à fenêtre glissante (DPM, 2010) → détecteurs à deux étapes (RCNN, 2014 ; Faster-RCNN, 2015) → détecteurs à une étape (YOLO v1, 2016 → v8, 2023) est l'un des domaines les plus dynamiques de la vision par ordinateur.
L'Autopilot de Tesla fait passer 8 caméras par un réseau de détection personnalisé à 36 FPS sur une puce à 72 TOPS. L'intégralité du modèle doit tenir dans un budget de latence serré tout en détectant des objets à 200m.
Deux Étapes vs Une Étape : Le Compromis Fondamental
**Détecteurs à deux étapes (Faster-RCNN) :** Étape 1 — le Réseau de Propositions de Régions (RPN) suggère ~300 régions candidates pouvant contenir des objets. Étape 2 — une tête de classification + régression affine chaque proposition. Avantage : haute précision. Inconvénient : lent (étapes séquentielles). **Détecteurs à une étape (YOLO, SSD) :** Divisez l'image en une grille. Chaque cellule prédit directement les décalages de boîte englobante, le score d'objectivité et les probabilités de classe en un seul passage avant. Avantage : rapide (capable de temps réel). Inconvénient : plus difficile à entraîner, rate les petits objets qui se chevauchent. **Basé sur ancrage vs sans ancrage :** YOLO v1-v3 utilisait des boîtes d'ancrage. YOLO v8 / FCOS / CenterNet sont sans ancrage — prédisent directement centre + largeur/hauteur de la boîte, plus simple et souvent meilleur.
YOLO = 'You Only Look Once.' L'idée : au lieu d'exécuter un classificateur à des milliers de positions de fenêtre glissante, prédire toutes les boîtes simultanément en un seul passage du réseau.
Pipeline d'Inférence YOLO
Divisez l'image d'entrée en une grille S×S (ex. 13×13 pour une entrée de 416px dans YOLO v2).
Pour chaque cellule : prédisez B boîtes englobantes (chacune : x, y, l, h relatifs à la cellule, + score d'objectivité) et C probabilités de classe.
Coordonnées de boîte : x, y sont des décalages depuis le centre de la cellule (0–1), l/h sont des décalages d'échelle logarithmique depuis les tailles d'ancrage.
Objectivité × probabilité de classe = score de confiance spécifique à la classe pour chaque boîte.
Appliquez la Suppression Non Maximale (NMS) : pour chaque classe, triez les boîtes par confiance, conservez la plus haute, supprimez les boîtes avec IoU > 0,5 avec la boîte conservée, répétez.
Sortie finale : liste de longueur variable de tuples (classe, confiance, x1, y1, x2, y2).
Détection d'Objets avec YOLOv8 (Ultralytics)
class="tok-comment"># pip install ultralytics from ultralytics import YOLO import numpy as np import cv2 class="tok-comment"># ── class="tok-num">1. Charger YOLO v8 préentraîné ──────────────────────────────────────────── modele = YOLO(class="tok-str">"yolov8n.pt") class="tok-comment"># modèle nano (class="tok-num">3,2M params, le plus rapide) class="tok-comment"># Autres tailles : yolov8s.pt, yolov8m.pt, yolov8l.pt, yolov8x.pt class="tok-comment"># ── class="tok-num">2. Inférence sur une seule image ───────────────────────────────────────── resultats = modele(class="tok-str">"chemin/vers/image.jpg", conf=class="tok-num">0.25, iou=class="tok-num">0.5) for r in resultats: boites = r.boxes for boite in boites: x1, y1, x2, y2 = boite.xyxy[class="tok-num">0].tolist() class="tok-comment"># coordonnées absolues en pixels conf = boite.conf[class="tok-num">0].item() class="tok-comment"># score de confiance cls = int(boite.cls[class="tok-num">0].item()) class="tok-comment"># indice de classe label = modele.names[cls] print(fclass="tok-str">"{label} : {conf:.2f} à ({x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f})") class="tok-comment"># ── class="tok-num">3. Affinage sur un jeu de données personnalisé ──────────────────────────── class="tok-comment"># Format du jeu de données : format texte YOLO class="tok-comment"># data.yaml : class="tok-comment"># train: /chemin/vers/images_entrainement class="tok-comment"># val: /chemin/vers/images_validation class="tok-comment"># nc: class="tok-num">3 class="tok-comment"># names: [class="tok-str">'chat', class="tok-str">'chien', class="tok-str">'voiture'] modele = YOLO(class="tok-str">"yolov8s.pt") class="tok-comment"># démarrer depuis préentraîné ImageNet resultats = modele.train( data=class="tok-str">"data.yaml", epochs=class="tok-num">50, imgsz=class="tok-num">640, batch=class="tok-num">16, lr0=class="tok-num">0.01, class="tok-comment"># taux dclass="tok-str">'apprentissage initial lrf=class="tok-num">0.01, class="tok-comment"># fraction du lr final augment=True, class="tok-comment"># mosaïque, retournement, échelle device=class="tok-num">0, class="tok-comment"># GPU class="tok-num">0 ) print(f"mAP50 : {resultats.metrics.mAP50:.4f}") class="tok-comment"># ── class="tok-num">4. Calcul de l'IoU depuis zéro ─────────────────────────────────────────── def iou(boite1, boite2): class="tok-str">"""boite = [x1, y1, x2, y2]""" x1 = max(boite1[class="tok-num">0], boite2[class="tok-num">0]); y1 = max(boite1[class="tok-num">1], boite2[class="tok-num">1]) x2 = min(boite1[class="tok-num">2], boite2[class="tok-num">2]); y2 = min(boite1[class="tok-num">3], boite2[class="tok-num">3]) inter = max(class="tok-num">0, x2-x1) * max(class="tok-num">0, y2-y1) aire1 = (boite1[class="tok-num">2]-boite1[class="tok-num">0]) * (boite1[class="tok-num">3]-boite1[class="tok-num">1]) aire2 = (boite2[class="tok-num">2]-boite2[class="tok-num">0]) * (boite2[class="tok-num">3]-boite2[class="tok-num">1]) return inter / (aire1 + aire2 - inter + class="tok-num">1e-6) verite_terrain = [class="tok-num">100, class="tok-num">50, class="tok-num">250, class="tok-num">200] prediction = [class="tok-num">110, class="tok-num">60, class="tok-num">260, class="tok-num">210] print(fclass="tok-str">"\nIoU = {iou(verite_terrain, prediction):.4f}") class="tok-comment"># ── class="tok-num">5. NMS manuel ───────────────────────────────────────────────────────────── def nms(boites, scores, seuil_iou=class="tok-num">0.5): class="tok-str">"""boites : (N,class="tok-num">4) xyxy, scores : (N,)""" ordre = np.argsort(scores)[::-class="tok-num">1] garder = [] while len(ordre) > class="tok-num">0: i = ordre[class="tok-num">0] garder.append(i) ious = np.array([iou(boites[i], boites[j]) for j in ordre[class="tok-num">1:]]) ordre = ordre[class="tok-num">1:][ious < seuil_iou] return garder boites_test = np.array([[class="tok-num">100,class="tok-num">50,class="tok-num">250,class="tok-num">200],[class="tok-num">105,class="tok-num">55,class="tok-num">255,class="tok-num">205],[class="tok-num">200,class="tok-num">100,class="tok-num">350,class="tok-num">250]]) scores_test = np.array([class="tok-num">0.95, class="tok-num">0.87, class="tok-num">0.72]) gardees = nms(boites_test, scores_test) print(fclass="tok-str">"Boîtes conservées : {gardees}") class="tok-comment"># [class="tok-num">0, class="tok-num">2] — boîte class="tok-num">1 supprimée
mAP et le Piège du Seuil IoU
mAP@0,5 (seuil IoU 0,5) et mAP@0,5:0,95 (moyenne sur les seuils IoU de 0,5 à 0,95 par pas de 0,05) racontent des histoires très différentes. Un modèle avec un excellent mAP@0,5 mais un mauvais mAP@0,5:0,95 localise les objets de manière lâche — acceptable pour les tâches grossières, mauvais pour la saisie robotique. De plus, mAP traite toutes les classes de manière égale, ce qui cache les mauvaises performances sur les classes rares. Pour les jeux de données déséquilibrés, reportez l'AP par classe séparément. Pièges courants : (1) Oublier de normaliser les coordonnées de boîte par la taille de l'image. (2) Utiliser un seuil de confiance trop bas pendant NMS — gardez conf_threshold ≈ 0,25 lors de l'inférence. (3) Surapprentissage sur les petits jeux de données — utilisez toujours une augmentation forte.
Une amélioration de 1% de mAP sur le benchmark COCO représente des mois de recherche — le contexte importe pour comparer les modèles dans votre domaine.
?Vérification des Connaissances
La progression est sauvegardée dans votre navigateur — aucun compte requis.
Besoin d'un ingénieur IA ou data scientist ?
Je conçois des modèles ML sur mesure, des agents IA, de la vision par ordinateur et de l'automatisation — de l'idée à la production.