Ultralytics YOLO27 : Inscris-toi sur la liste d’attente.
Get Started
Ultralytics YOLO en production·Suivre les objets et créer des pipelines·Leçon 5/9
Leçonintermédiaire

Suivre des objets avec ByteTrack, BoT-SORT et TrackTrack

Attribue à chaque objet un ID stable d’une image à l’autre, choisis un tracker et règle ses seuils en fonction de ton détecteur.

Chaque décompte, durée de présence et estimation de vitesse dépend d’une question : l’ID 17 dans cette image désigne-t-il le même objet que l’ID 17 dans la précédente ? Sur le même extrait de 62 images, qui montre environ 30 piétons, et avec le même détecteur, ByteTrack a trouvé 32 ID et TrackTrack, avec sa configuration par défaut, en a trouvé 9. Le choix et le réglage du tracker font partie du modèle, et une erreur à ce niveau fausse silencieusement tous les chiffres qui en découlent.

Résultat

Exécute trois trackers sur une vidéo, explique leurs décomptes d’ID à partir des scores de confiance du détecteur, utilise box.id dans un pipeline et corrige un tracker mal adapté avec un fichier YAML réglé.

Parcours accéléré
Si tu connais déjà les bases, voici la version courte.
  1. model.track(source, stream=True, tracker="bytetrack.yaml") ; chaque boîte reçoit un id stable.

  2. Tu fournis toi-même les images ? Passe persist=True pour que les ID soient conservés d’un appel à l’autre ; utilise un tracker par caméra.

  3. La valeur par défaut est tracktrack.yaml ; ByteTrack est rapide et tolérant ; BoT-SORT ajoute une compensation du mouvement de la caméra, avec la réidentification activée en option.

  4. Pas assez d’ID : mesure les scores de confiance du détecteur et abaisse new_track_thresh et track_high_thresh dans une copie du fichier YAML.

Pratique

Ce qu’apporte un tracker#

Suivi du trafic autoroutier avec Ultralytics YOLO : chaque voiture, bus et camion conserve un ID numéroté (voiture #12, bus #21, camion #16) et laisse une ligne colorée le long de son parcours récent

Les étiquettes et les trajectoires de cette image sont le résultat du travail d’un tracker. Chaque ID est une piste, une hypothèse sur un objet réel, et la trajectoire correspond à son historique récent. À chaque image, le tracker prédit où chaque piste devrait se trouver à l’aide d’un filtre de Kalman, puis associe les nouvelles détections à ces prédictions en fonction du chevauchement des boîtes (et, éventuellement, de l’apparence) :

Les pistes qui ne correspondent à aucune détection pendant track_buffer images sont supprimées. C’est tout le mécanisme, et ses seuils déterminent tout ce qui suit.

Choisir un tracker#

Le mode Track propose six trackers : BoT-SORT, ByteTrack, OC-SORT, Deep OC-SORT, FastTracker et TrackTrack (le tracker par défaut si tu ne passes pas de tracker). Trois d’entre eux répondent à la plupart des besoins :

SuiveurCritères d’association (configuration par défaut)À privilégier quand
ByteTrackMouvement et chevauchement des boîtes, avec une seconde passe qui récupère les boîtes à faible score de confianceCaméras fixes, détecteurs de petite taille ou incertains, priorité au débit
BoT-SORTAssociation de type ByteTrack avec compensation du mouvement de la caméra (gmc_method: sparseOptFlow)Caméras mobiles ou tremblantes ; définis with_reid: True pour les foules et les longues occultations
TrackTrackCoût multicritère (forme de la boîte, confiance, angle), initialisation tenant compte des pistes, compensation du mouvement de la caméraDétections fiables issues d’un modèle robuste et bien entraîné

L’association par apparence de BoT-SORT (réidentification) est désactivée par défaut ; avec model: auto, elle réutilise les caractéristiques du détecteur lui-même, donc son activation ajoute peu de surcharge.

Comparer les trackers sur un même extrait#

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"

for tracker in ("bytetrack.yaml", "botsort.yaml", "tracktrack.yaml"):
    ids, frames = set(), 0
    for r in model.track(video, stream=True, tracker=tracker, classes=[0], verbose=False):
        frames += 1
        if r.boxes.is_track:
            ids.update(r.boxes.id.int().tolist())
    print(f"{tracker:16s} {frames} frames, {len(ids)} unique person IDs")
bytetrack.yaml   62 frames, 32 unique person IDs
botsort.yaml     62 frames, 33 unique person IDs
tracktrack.yaml  62 frames, 9 unique person IDs

L’extrait montre environ 30 personnes traversant une place, filmée d’en haut ; ByteTrack et BoT-SORT sont donc proches de la réalité. TrackTrack n’est pas défectueux : il ne crée une piste qu’au-dessus de new_track_thresh: 0.7 et n’effectue la première association qu’au-dessus de track_high_thresh: 0.6, alors que le score de confiance médian des personnes détectées par YOLO26n dans cet extrait est de 0,53 et que seulement 17 % des détections atteignent 0,7. La plupart des personnes n’obtiennent jamais d’ID. La règle à suivre : compare les seuils du tracker à la distribution des scores de confiance de ton détecteur avant de te fier à un décompte.

Utiliser les ID dans un pipeline#

from collections import defaultdict

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"

counts = defaultdict(set)
for r in model.track(video, stream=True, tracker="bytetrack.yaml", verbose=False):
    if not r.boxes.is_track:  # no confirmed tracks in this frame yet
        continue
    for cls, tid in zip(r.boxes.cls.int().tolist(), r.boxes.id.int().tolist()):
        counts[r.names[cls]].add(tid)

for cls, ids in counts.items():
    print(f"{cls}: {len(ids)} unique objects")
person: 32 unique objects

Un ensemble par classe comptabilise chaque ID une seule fois, quel que soit le nombre d’images où il apparaît ; le résultat représente donc des objets, et non des détections. stream=True renvoie les résultats image par image au lieu de garder toute la vidéo en mémoire. Le comptage par région, les cartes thermiques et le calcul de distance sont de simples couches ajoutées à cette boucle.

Suivre les images que tu lis toi-même#

Si tu passes un chemin ou une URL, l’état du tracker est géré automatiquement. Si les images proviennent de ton propre décodeur, d’une webcam ou d’une caméra RTSP, passe persist=True pour que chaque appel poursuive la même séquence (la documentation du mode Predict répertorie tous les types de sources) :

import cv2

from ultralytics import YOLO
from ultralytics.utils.downloads import safe_download

model = YOLO("yolo26n.pt")
video = safe_download("https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4")
cap = cv2.VideoCapture(str(video))  # or 0 for a webcam, or an rtsp:// URL

seen = set()
while True:
    ok, frame = cap.read()
    if not ok:
        break
    r = model.track(frame, persist=True, tracker="bytetrack.yaml", classes=[0], verbose=False)[0]
    if r.boxes.is_track:
        seen.update(r.boxes.id.int().tolist())  # your logic: dwell time, trajectories, alerts
cap.release()
print(f"{len(seen)} unique IDs")
32 unique IDs

Le résultat est identique à celui obtenu en passant le chemin, car persist=True a conservé un seul tracker actif pendant les 62 appels.

Régler un tracker en fonction de ton détecteur#

Copie une configuration fournie avec le paquet, abaisse les seuils qui ne conviennent pas à ton détecteur et passe le nouveau fichier :

from pathlib import Path

from ultralytics import YOLO
from ultralytics.utils import ROOT

cfg = (ROOT / "cfg/trackers/tracktrack.yaml").read_text()  # packaged defaults
for old, new in (
    ("track_high_thresh: 0.6", "track_high_thresh: 0.3"),
    ("track_low_thresh: 0.25", "track_low_thresh: 0.1"),
    ("new_track_thresh: 0.7", "new_track_thresh: 0.35"),
):
    cfg = cfg.replace(old, new)
Path("tracktrack_tuned.yaml").write_text(cfg)

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
for tracker in ("tracktrack.yaml", "tracktrack_tuned.yaml"):
    ids = set()
    for r in model.track(video, stream=True, tracker=tracker, classes=[0], verbose=False):
        if r.boxes.is_track:
            ids.update(r.boxes.id.int().tolist())
    print(f"{tracker:22s} {len(ids)} unique person IDs")
tracktrack.yaml        9 unique person IDs
tracktrack_tuned.yaml  27 unique person IDs

En réglant les seuils juste en dessous du score de confiance médian du détecteur, TrackTrack est passé de 9 à 27 ID, un résultat proche du décompte manuel. Les paramètres communs à tous les trackers :

ParamètreValeur par défaut de ByteTrackAugmente-le pourAbaisse-le pour
track_high_thresh0.25Ignorer les détections faibles lors de la première associationSuivre les détections d’un détecteur peu fiable
new_track_thresh0.25Éliminer les pistes fausses de courte duréeCréer des pistes pour les objets peu visibles
track_buffer30 imagesMieux résister aux occultations prolongéesLibérer plus tôt les ID des objets qui ont quitté la scène
match_thresh0.8Effectuer des associations plus souples (mouvements rapides, boîtes instables)Effectuer des associations plus strictes (foules)

Pièges#

Seuils supérieurs à la confiance du détecteur

Symptôme : beaucoup moins d'ID que d'objets, surtout avec un petit modèle. Solution : affiche la confiance médiane des détections (voir la solution) et règle new_track_thresh et track_high_thresh juste en dessous.

Oublier persist ou le partager

Symptôme : les ID se réinitialisent à chaque image quand tu appelles toi-même model.track(frame), ou passent d'un objet à l'autre lorsque deux caméras alimentent un même modèle. Solution : transmets persist=True pour les images consécutives d'un même flux, et attribue une instance de modèle à chaque caméra (ou utilise une source .streams, qui conserve un suivi par flux).

Scintillement des ID sur les objets lents

Symptôme : l'ID d'une voiture garée change toutes les quelques images. Solution : le suivi démarre de nouvelles pistes au lieu de faire correspondre les anciennes. Vérifie d'abord la confiance du détecteur sur cet objet, puis augmente match_thresh et track_buffer.

Essayer

Prends une vidéo de 30 à 60 secondes issue de ton propre domaine et compte manuellement les objets d’une classe. Exécute compare_trackers.py sur la vidéo, mesure les scores de confiance du détecteur avec le script de solution et règle un fichier YAML de tracker pour rapprocher son décompte d’ID de ton décompte manuel. Fournis un tableau avec le tracker, le nombre d’ID uniques et le décompte manuel.

Terminé lorsque
Tu as terminé la leçon lorsque tous les éléments suivants sont réunis.
  • Tu as les décomptes d’ID uniques de ByteTrack, BoT-SORT et TrackTrack sur la même vidéo, accompagnés d’un décompte manuel.

  • Tu peux expliquer le décompte le plus faible à partir des seuils du tracker et du score de confiance médian du détecteur.

  • Un fichier YAML de tracker réglé a modifié le décompte dans le sens que tu avais prédit avant l’exécution.

Afficher la solution

Mesure d’abord les scores de confiance du détecteur, puis règle les seuils juste en dessous :

import torch

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
video = "https://github.com/ultralytics/assets/releases/download/v0.0.0/solutions_ci_demo.mp4"
conf = torch.cat([r.boxes.conf for r in model.predict(video, stream=True, classes=[0], verbose=False)])
print(f"median {conf.median():.2f}, share >= 0.7: {(conf >= 0.7).float().mean():.0%}")
median 0.53, share >= 0.7: 17%

Pour l’extrait de la place, le tableau indique : ByteTrack 32, BoT-SORT 33, TrackTrack 9, TrackTrack réglé 27, décompte manuel d’environ 30. Seules 17 % des détections dépassent le seuil new_track_thresh: 0.7 de TrackTrack ; l’abaisser à 0,35 (avec track_high_thresh: 0.3) est donc la bonne correction. Pour cette caméra fixe en plongée, ByteTrack sans réglage particulier est le choix le plus simple.

Et ensuite ?

Une fois les ID stabilisés, la prochaine leçon présente les pipelines de production les plus courants qui s’appuient dessus : décomptes, zones, cartes thermiques et vitesses.