Traccia gli oggetti con ByteTrack, BoT-SORT e TrackTrack
Assegna a ogni oggetto un ID stabile tra i fotogrammi, scegli un tracker e regola le sue soglie in base al tuo rilevatore.
Ogni conteggio, tempo di permanenza e stima della velocità dipende da una domanda: l'ID 17 in questo fotogramma identifica lo stesso oggetto dell'ID 17 nel fotogramma precedente? Nella stessa clip di 62 fotogrammi, con circa 30 pedoni e lo stesso rilevatore, ByteTrack ha trovato 32 ID e TrackTrack con la configurazione predefinita ne ha trovati 9. La scelta e la regolazione del tracker fanno parte del modello: se sbagli, tutti i numeri successivi diventano inaffidabili senza che te ne accorga.
Pratico
Cosa aggiunge un tracker#

Le etichette e le tracce in questo fotogramma sono il risultato prodotto da un tracker. Ogni ID è una traccia, cioè un'ipotesi relativa a un oggetto reale, e la traccia mostra la sua cronologia recente. A ogni fotogramma, il tracker prevede dove dovrebbe trovarsi ciascuna traccia usando un filtro di Kalman, quindi abbina le nuove rilevazioni a quelle previsioni in base alla sovrapposizione dei riquadri (e, facoltativamente, all'aspetto):
Le tracce che non trovano corrispondenza per track_buffer fotogrammi vengono eliminate. È tutto qui il meccanismo, e le sue soglie determinano tutto ciò che segue.
Scegli un tracker#
La modalità di tracciamento include sei tracker: BoT-SORT, ByteTrack, OC-SORT, Deep OC-SORT, FastTracker e TrackTrack (il valore predefinito se non passi tracker). Tre coprono la maggior parte delle esigenze:
| Tracker | Criteri di corrispondenza (configurazione predefinita) | Sceglilo quando |
|---|---|---|
| ByteTrack | Movimento e sovrapposizione dei riquadri, più un secondo passaggio che recupera i riquadri con bassa confidenza | Telecamere fisse, rilevatori piccoli o incerti, priorità alla produttività |
| BoT-SORT | Corrispondenza in stile ByteTrack più compensazione del movimento della telecamera (gmc_method: sparseOptFlow) | Telecamere in movimento o soggette a vibrazioni; imposta with_reid: True per folle e occlusioni prolungate |
| TrackTrack | Costo basato su più criteri (forma del riquadro, confidenza, angolo), inizializzazione consapevole delle tracce, compensazione del movimento della telecamera | Rilevazioni con elevata confidenza da un modello robusto e ben addestrato |
La corrispondenza basata sull'aspetto di BoT-SORT (re-identificazione) è disattivata per impostazione predefinita; con model: auto riutilizza le caratteristiche del rilevatore, quindi attivarla comporta un sovraccarico minimo.
Confronta i tracker su una clip#
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 IDsLa clip mostra dall'alto circa 30 persone che attraversano una piazza, quindi ByteTrack e BoT-SORT si avvicinano al conteggio reale. TrackTrack non è difettoso: avvia una traccia solo sopra new_track_thresh: 0.7 e cerca corrispondenze iniziali solo sopra track_high_thresh: 0.6, mentre la confidenza mediana delle persone rilevate da YOLO26n in questa clip è 0.53 e solo il 17% delle rilevazioni raggiunge 0.7. La maggior parte delle persone non riceve mai un ID. La regola decisionale: confronta le soglie del tracker con la distribuzione dei punteggi di confidenza del rilevatore prima di fidarti di un conteggio.
Usa gli ID in una 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 objectsUn set per classe conta ogni ID una sola volta, indipendentemente dal numero di fotogrammi in cui compare, quindi il risultato è il numero di oggetti, non di rilevazioni. stream=True restituisce i risultati un fotogramma alla volta, invece di tenere l'intero video in memoria. Il conteggio nelle regioni, le mappe di calore e il calcolo della distanza sono semplici livelli aggiunti sopra questo ciclo.
Traccia i fotogrammi che leggi tu#
Passare un percorso o un URL gestisce automaticamente lo stato del tracker. Se i fotogrammi provengono da un decoder, una webcam o una telecamera RTSP di tua scelta, passa persist=True affinché ogni chiamata continui la stessa sequenza (la documentazione della modalità di predizione elenca tutti i tipi di sorgente):
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 IDsIl risultato è lo stesso che passando il percorso, perché persist=True ha mantenuto attivo un unico tracker per tutte le 62 chiamate.
Regola un tracker in base al tuo rilevatore#
Copia una configurazione inclusa nel pacchetto, abbassa le soglie non adatte al tuo rilevatore e passa il nuovo file:
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 IDsImpostare le soglie appena sotto la confidenza mediana del rilevatore ha portato TrackTrack da 9 a 27 ID, un valore vicino al conteggio manuale. Le impostazioni condivise da tutti i tracker:
| Impostazione | Valore predefinito di ByteTrack | Aumentala per | Abbassala per |
|---|---|---|---|
track_high_thresh | 0.25 | Ignorare le rilevazioni deboli nel primo abbinamento | Tracciare con un rilevatore a bassa confidenza |
new_track_thresh | 0.25 | Eliminare le tracce false di breve durata | Avviare tracce per oggetti poco visibili |
track_buffer | 30 fotogrammi | Resistere a occlusioni più prolungate | Liberare prima gli ID degli oggetti usciti dalla scena |
match_thresh | 0.8 | Abbinare con maggiore flessibilità (movimento rapido, riquadri instabili) | Abbinare con maggiore rigore (folle) |
Insidie#
Sintomo: molti meno ID rispetto agli oggetti, soprattutto con un modello piccolo. Soluzione: stampa la confidenza mediana dei rilevamenti (vedi la soluzione) e imposta new_track_thresh e track_high_thresh appena al di sotto di essa.
Sintomo: gli ID si azzerano a ogni fotogramma quando chiami personalmente model.track(frame), oppure passano da un oggetto all'altro quando due telecamere alimentano un unico modello. Soluzione: passa persist=True per i fotogrammi consecutivi di un singolo flusso e assegna a ogni telecamera un'istanza del modello (oppure usa una sorgente .streams, che mantiene un tracker per flusso).
Sintomo: l'ID di un'auto parcheggiata cambia ogni pochi fotogrammi. Soluzione: il tracker avvia nuove tracce invece di abbinare quelle esistenti. Controlla prima la confidenza del rilevatore su quell'oggetto, poi aumenta match_thresh e track_buffer.
Mostra la soluzione
Prima misura i punteggi di confidenza del rilevatore, poi imposta le soglie appena al di sotto:
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%Per la clip della piazza, la tabella riporta: ByteTrack 32, BoT-SORT 33, TrackTrack 9, TrackTrack regolato 27, conteggio manuale circa 30. Solo il 17% delle rilevazioni supera new_track_thresh: 0.7 di TrackTrack, quindi abbassarlo a 0.35 (insieme a track_high_thresh: 0.3) è la correzione giusta; per questa telecamera fissa dall'alto, ByteTrack senza modifiche è la scelta più semplice.