Трекинг объектов с ByteTrack, BoT-SORT и TrackTrack
Назначай каждому объекту стабильный идентификатор на всех кадрах, выбирай трекер и подбирай его пороги под свой детектор.
Каждый подсчёт, время пребывания и оценка скорости зависят от одного вопроса: является ли ID 17 на этом кадре тем же объектом, что и ID 17 на предыдущем? На одном и том же ролике из 62 кадров примерно с 30 пешеходами и с одним и тем же детектором ByteTrack нашёл 32 ID, а TrackTrack с настройками по умолчанию — 9. Выбор и настройка трекера — часть модели, а ошибка в них незаметно искажает все последующие показатели.
Практические занятия
Что добавляет трекер#

Метки и траектории на этом кадре — результат работы трекера. Каждый ID — это трек, гипотеза об одном реальном объекте, а траектория показывает его недавнюю историю. На каждом кадре трекер предсказывает положение каждого трека с помощью фильтра Калмана, а затем сопоставляет новые обнаружения с этими предсказаниями по перекрытию боксов (и, при необходимости, по внешнему виду):
Треки, которые не сопоставляются с обнаружениями в течение track_buffer кадров, удаляются. Таков весь механизм, и его пороги определяют всё, что происходит дальше.
Выбери трекер#
В режиме Track доступны шесть трекеров: BoT-SORT, ByteTrack, OC-SORT, Deep OC-SORT, FastTracker и TrackTrack (используется по умолчанию, если не передать tracker). Для большинства задач подойдут три:
| Трекер | Признаки сопоставления (конфигурация по умолчанию) | Используй, когда |
|---|---|---|
| ByteTrack | Движение и перекрытие боксов, а также второй проход для восстановления боксов с низкой уверенностью | Неподвижные камеры, небольшая или неуверенная модель детектора, важна пропускная способность |
| BoT-SORT | Сопоставление по принципу ByteTrack и компенсация движения камеры (gmc_method: sparseOptFlow) | Движущиеся или дрожащие камеры; задай with_reid: True для скоплений объектов и длительных перекрытий |
| TrackTrack | Сопоставление по нескольким признакам (форма бокса, уверенность, угол), инициализация с учётом треков, компенсация движения камеры | Уверенные обнаружения от сильной, хорошо обученной модели |
Сопоставление по внешнему виду в BoT-SORT (повторная идентификация) по умолчанию отключено; если задать model: auto, оно повторно использует признаки самого детектора, поэтому его включение почти не увеличивает накладные расходы.
Сравни трекеры на одном ролике#
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На ролике сверху видно, как примерно 30 человек пересекают площадь, поэтому ByteTrack и BoT-SORT дают результаты, близкие к реальности. TrackTrack не сломан: он начинает трек только при значении выше new_track_thresh: 0.7 и впервые сопоставляет объекты при значении выше track_high_thresh: 0.6, тогда как медианная уверенность YOLO26n для людей на этом ролике — 0,53, и только 17% обнаружений достигают 0,7. Большинство людей так и не получают ID. Правило принятия решения: прежде чем доверять любому подсчёту, сравни пороги трекера с распределением уверенности детектора.
Используй ID в конвейере#
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Множество для каждого класса учитывает каждый ID один раз, сколько бы кадров он ни появлялся, поэтому результат показывает количество объектов, а не обнаружений. stream=True выдаёт результаты по одному кадру, не загружая всё видео в память. Подсчёт в регионах, тепловые карты и расчёт расстояния — это тонкие надстройки над этим циклом.
Отслеживай кадры, которые считываешь самостоятельно#
При передаче пути или URL состояние трекера обрабатывается автоматически. Если кадры поступают из собственного декодера, веб-камеры или RTSP-камеры, передай persist=True, чтобы каждый вызов продолжал одну и ту же последовательность (в документации по режиму Predict перечислены все типы источников):
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Результат такой же, как при передаче пути, потому что persist=True сохранял один трекер активным на протяжении всех 62 вызовов.
Настрой трекер под свой детектор#
Скопируй готовую конфигурацию, снизь пороги, которые не подходят твоему детектору, и передай новый файл:
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Установка порогов чуть ниже медианной уверенности детектора увеличила количество ID в TrackTrack с 9 до 27 — почти до результата ручного подсчёта. Параметры, общие для всех трекеров:
| Параметр | Значение по умолчанию для ByteTrack | Повышай, чтобы | Понижай, чтобы |
|---|---|---|---|
track_high_thresh | 0.25 | Игнорировать слабые обнаружения при первом сопоставлении | Отслеживать результаты детектора с низкой уверенностью |
new_track_thresh | 0.25 | Удалять кратковременные ложные треки | Начинать треки для слабозаметных объектов |
track_buffer | 30 кадров | Переживать более длительные перекрытия | Быстрее освобождать ID ушедших объектов |
match_thresh | 0.8 | Сопоставлять менее строго (быстрое движение, скачущие боксы) | Сопоставлять строже (скопления объектов) |
Подводные камни#
Симптом: ID гораздо меньше, чем объектов, особенно при использовании небольшой модели. Решение: выведи медианную уверенность детекций (см. решение) и установи new_track_thresh и track_high_thresh чуть ниже неё.
Симптом: ID сбрасываются на каждом кадре, когда ты сам вызываешь model.track(frame), или перескакивают между объектами, когда две камеры передают данные одной модели. Решение: передавай persist=True для последовательных кадров одного потока и создай отдельный экземпляр модели для каждой камеры (или используй источник .streams, который хранит отдельный трекер для каждого потока).
Симптом: ID припаркованного автомобиля меняется каждые несколько кадров. Решение: трекер создаёт новые треки вместо сопоставления с существующими. Сначала проверь уверенность детектора для этого объекта, затем увеличь match_thresh и track_buffer.
Показать решение
Сначала измерь уверенность детектора, затем установи пороги чуть ниже этих значений:
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%Для ролика с площадью в таблице получаются такие значения: ByteTrack — 32, BoT-SORT — 33, TrackTrack — 9, настроенный TrackTrack — 27, ручной подсчёт — около 30. Только 17% обнаружений превышают new_track_thresh: 0.7 TrackTrack, поэтому правильное решение — снизить его до 0.35 (с track_high_thresh: 0.3); для этой неподвижной камеры с видом сверху проще использовать обычный ByteTrack.