Ultralytics YOLO27: 加入候补名单。
Get Started
用于生产环境的 Ultralytics YOLO·跟踪并构建流水线·课程 5/9
课程中级

使用 ByteTrack、BoT-SORT 和 TrackTrack 跟踪对象

为每个对象在不同帧中分配稳定的 ID,选择跟踪器,并根据检测器调整其阈值。

每个计数、停留时间和速度估算都取决于一个问题:这一帧中的 ID 17,是否与上一帧中的 ID 17 是同一个对象?对于同一段包含约 30 名行人的 62 帧视频片段,使用同一个检测器时,ByteTrack 找到了 32 个 ID,而默认的 TrackTrack 找到了 9 个。选择和调整跟踪器是模型工作的一部分;如果选错,后续的每个数字都会在不知不觉中出错。

学习成果

在同一段视频上运行三个跟踪器,根据检测器的置信度解释它们的 ID 数量,在流水线中使用 box.id,并通过调整 YAML 修复不匹配的跟踪器。

快速通道
如果你已经熟悉相关内容,以下是简要版。
  1. model.track(source, stream=True, tracker="bytetrack.yaml");每个框都会获得一个稳定的 id。

  2. 自己输入帧?传入 persist=True,这样不同调用之间就能延续 ID;每个摄像头使用一个跟踪器。

  3. 默认值为 tracktrack.yaml;ByteTrack 快速且容错性强;BoT-SORT 加入了摄像头运动补偿,ReID 则为可选功能。

  4. ID 太少:测量检测器的置信度,并在复制的 YAML 中调低 new_track_thresh 和 track_high_thresh。

实践操作

跟踪器的作用#

Ultralytics YOLO 跟踪高速公路交通:每辆汽车、公交车和卡车都有编号 ID(汽车 #12、公交车 #21、卡车 #16),并沿最近的行驶路径留下彩色轨迹线

这一帧中的标签和轨迹线就是跟踪器生成的结果。每个 ID 都是一个跟踪目标,表示对某个真实对象的假设;轨迹线则记录了该目标最近的运动历史。每一帧中,跟踪器都会通过卡尔曼滤波器预测每个跟踪目标的位置,然后根据框的重叠程度(以及可选的外观特征)将新检测结果与这些预测相匹配:

未匹配的跟踪目标在 track_buffer 帧后会被删除。整个机制就是如此简单,而阈值决定了之后的一切。

选择跟踪器#

跟踪模式内置六种跟踪器: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,让每次调用都延续同一序列(预测模式文档列出了所有来源类型):

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

将阈值设置在略低于检测器置信度中位数的位置后,TrackTrack 的 ID 数量从 9 个升至 27 个,接近人工计数结果。所有跟踪器共有的可调参数:

设置项ByteTrack 默认值调高后的效果调低后的效果
track_high_thresh0.25在首次匹配中忽略较弱的检测结果跟踪低置信度检测器的结果
new_track_thresh0.25减少短暂出现的误检跟踪目标为模糊对象启动跟踪
track_buffer30 帧应对更长时间的遮挡更快释放已离开对象的 ID
match_thresh0.8放宽匹配条件(快速运动、框位置跳动)收紧匹配条件(人群密集)

常见问题#

阈值高于检测器的置信度

症状:ID 数量远少于目标数量,小模型上尤其明显。解决方法:打印检测置信度的中位数(见解决方案),并将 new_track_thresh 和 track_high_thresh 设在该值略低的位置。

忘记设置 persist,或在多个流之间共享它

症状:你自己调用 model.track(frame) 时,ID 每帧都会重置;或者两个摄像头输入同一个模型时,ID 会在目标之间跳转。解决方法:为同一视频流的连续帧传入 persist=True,并为每个摄像头创建独立的模型实例(或使用 .streams 数据源,它会为每个流保留一个跟踪器)。

缓慢移动的目标出现 ID 闪烁

症状:停着的汽车每隔几帧就会更换 ID。解决方法:跟踪器正在创建新轨迹,而不是匹配旧轨迹。先检查检测器对该目标的置信度,然后提高 match_thresh 和 track_buffer。

试试看

从你自己的领域中截取一段 30–60 秒的视频,并人工统计其中某个类别的对象数量。在这段视频上运行 compare_trackers.py,使用解决方案脚本测量检测器的置信度,并调整一个跟踪器 YAML,使其 ID 数量朝人工计数结果靠拢。最终提交一张表格,列出:跟踪器、唯一 ID 数量、人工计数。

完成条件
满足以下所有条件,即表示你已完成本课程。
  • 你已在同一段视频上获得 ByteTrack、BoT-SORT 和 TrackTrack 的唯一 ID 数量,并将其与人工计数结果进行对照。

  • 你可以根据跟踪器阈值和检测器置信度中位数,解释数量最低的结果。

  • 调整跟踪器 YAML 后,计数结果朝你运行前预测的方向变化。

显示解答

先测量检测器的置信度,再将阈值设在略低于该置信度的位置:

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% 的检测结果达到 TrackTrack 的 new_track_thresh: 0.7 阈值,因此将其降至 0.35(并设置 track_high_thresh: 0.3)是正确的修复方案;对于这台固定的俯视摄像头,直接使用 ByteTrack 更简单。

接下来做什么

有了稳定的 ID 后,下一课将在此基础上构建最常见的生产流水线:计数、区域、热力图和速度。

登录以保存学习进度