Sigue objetos con ByteTrack, BoT-SORT y TrackTrack
Asigna a cada objeto un ID estable entre fotogramas, elige un rastreador y ajusta sus umbrales a tu detector.
Cada recuento, tiempo de permanencia y estimación de velocidad depende de una pregunta: ¿el ID 17 de este fotograma corresponde al mismo objeto que el ID 17 del anterior? En el mismo clip de 62 fotogramas, con unos 30 peatones y el mismo detector, ByteTrack encontró 32 ID y TrackTrack con la configuración predeterminada encontró 9. Elegir y ajustar el rastreador forma parte del modelo, y equivocarse rompe silenciosamente todos los valores posteriores.
Práctico
Qué aporta un rastreador#

Las etiquetas y los rastros de este fotograma son el resultado del rastreador. Cada ID es una trayectoria, una hipótesis sobre un objeto real, y el rastro es su historial reciente. En cada fotograma, el rastreador predice dónde debería estar cada trayectoria con un filtro de Kalman y, a continuación, empareja las nuevas detecciones con esas predicciones según el solapamiento de las cajas (y, opcionalmente, la apariencia):
Las trayectorias que no se emparejan durante track_buffer fotogramas se eliminan. Ese es todo el mecanismo, y sus umbrales determinan todo lo que viene después.
Elige un rastreador#
El modo de seguimiento incluye seis rastreadores: BoT-SORT, ByteTrack, OC-SORT, Deep OC-SORT, FastTracker y TrackTrack (el predeterminado si no pasas tracker). Tres cubren la mayoría de las necesidades:
| Rastreador | Criterios de emparejamiento (configuración predeterminada) | Cuándo elegirlo |
|---|---|---|
| ByteTrack | Movimiento y solapamiento de cajas, más una segunda pasada que recupera las cajas con baja confianza | Cámaras fijas, detectores pequeños o poco fiables; cuando importa el rendimiento |
| BoT-SORT | Emparejamiento al estilo ByteTrack más compensación del movimiento de cámara (gmc_method: sparseOptFlow) | Cámaras en movimiento o con vibraciones; establece with_reid: True para multitudes y oclusiones prolongadas |
| TrackTrack | Coste basado en varios criterios (forma de la caja, confianza, ángulo), inicialización consciente de las trayectorias y compensación del movimiento de cámara | Detecciones fiables de un modelo potente y bien entrenado |
La coincidencia por apariencia de BoT-SORT (reidentificación) está desactivada de forma predeterminada; con model: auto reutiliza las propias características del detector, por lo que activarla apenas añade sobrecarga.
Compara rastreadores en un mismo 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 IDsEn el clip se ven unas 30 personas cruzando una plaza desde arriba, por lo que ByteTrack y BoT-SORT se acercan al recuento real. TrackTrack no está roto: solo inicia una trayectoria por encima de new_track_thresh: 0.7 y la empareja por primera vez por encima de track_high_thresh: 0.6, mientras que la confianza mediana de YOLO26n para las personas en este clip es 0,53 y solo el 17 % de las detecciones llega a 0,7. La mayoría de las personas nunca obtiene un ID. Regla para decidir: compara los umbrales del rastreador con la distribución de confianza de tu detector antes de fiarte de ningún recuento.
Usa los ID en 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 objectsUn conjunto por clase cuenta cada ID una sola vez, independientemente del número de fotogramas en los que aparezca, así que el resultado son objetos, no detecciones. stream=True genera resultados de uno en uno, por fotograma, en lugar de mantener todo el vídeo en memoria. El recuento por regiones, los mapas de calor y el cálculo de distancias son capas ligeras sobre este bucle.
Sigue los fotogramas que lees por tu cuenta#
Al pasar una ruta o una URL, el estado del rastreador se gestiona automáticamente. Si los fotogramas proceden de tu propio decodificador, una webcam o una cámara RTSP, pasa persist=True para que cada llamada continúe la misma secuencia (la documentación del modo Predict enumera todos los tipos de origen):
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 IDsLa respuesta es la misma que al pasar la ruta, porque persist=True mantuvo activo un único rastreador durante las 62 llamadas.
Ajusta un rastreador a tu detector#
Copia una configuración incluida, reduce los umbrales que no se ajusten a tu detector y pasa el nuevo archivo:
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 IDsAl establecer los umbrales justo por debajo de la confianza mediana del detector, TrackTrack pasó de 9 a 27 ID, cerca del recuento manual. Los parámetros comunes a todos los rastreadores:
| Parámetro | Valor predeterminado de ByteTrack | Auméntalo para | Redúcelo para |
|---|---|---|---|
track_high_thresh | 0.25 | Ignorar detecciones débiles en el primer emparejamiento | Seguir con un detector de baja confianza |
new_track_thresh | 0.25 | Eliminar trayectorias falsas de corta duración | Iniciar trayectorias para objetos poco visibles |
track_buffer | 30 fotogramas | Mantenerse durante oclusiones más prolongadas | Liberar antes los ID de los objetos que se han marchado |
match_thresh | 0.8 | Emparejar con más flexibilidad (movimiento rápido, cajas inestables) | Emparejar de forma más estricta (multitudes) |
Errores habituales#
Síntoma: hay muchos menos ID que objetos, especialmente con un modelo pequeño. Solución: muestra la confianza mediana de las detecciones (consulta la solución) y establece new_track_thresh y track_high_thresh justo por debajo de ese valor.
Síntoma: los ID se reinician en cada fotograma cuando llamas a model.track(frame) por tu cuenta, o saltan entre objetos cuando dos cámaras alimentan un mismo modelo. Solución: pasa persist=True para los fotogramas consecutivos de una misma secuencia y asigna a cada cámara su propia instancia del modelo (o usa una fuente .streams, que mantiene un rastreador por secuencia).
Síntoma: el ID de un coche aparcado cambia cada pocos fotogramas. Solución: el rastreador está iniciando nuevas trayectorias en lugar de asociar las antiguas. Comprueba primero la confianza del detector para ese objeto y, después, aumenta match_thresh y track_buffer.
Mostrar solución
Mide primero las confianzas del detector y, después, establece los umbrales justo por debajo de ellas:
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%Para el clip de la plaza, la tabla indica: ByteTrack, 32; BoT-SORT, 33; TrackTrack, 9; TrackTrack ajustado, 27; recuento manual, unos 30. Solo el 17 % de las detecciones supera new_track_thresh: 0.7 de TrackTrack, así que reducirlo a 0,35 (con track_high_thresh: 0.3) es la solución adecuada; para esta cámara fija situada en alto, ByteTrack sin ajustes es la opción más sencilla.