25/2/18 <算法笔记> ByteTrack
ByteTrack(发表在 2021 年)是一种高效且精确的 **多目标跟踪(Multi-Object Tracking, MOT)**算法。它属于目标跟踪领域中基于检测的类别(tracking by detection),核心思想是利用目标检测器的高置信度和低置信度检测结果,通过简单的后处理策略实现高效和准确的目标跟踪。
多目标跟踪 (MOT) 的主要目的是对视频或帧序列中的多个对象进行检测和跟踪。在 MOT 方法中通常有两种主要阶段:
- 检测阶段:识别目标(人、车、动物等)。
- 跟踪阶段:将连续帧中的目标进行关联,生成轨迹。
核心难点
传统 MOT 的核心挑战包括:
- 检测不够可靠: 检测器可能漏检或置信度过低,导致目标轨迹中断。
- 遮挡问题: 多个目标之间可能发生遮挡,导致轨迹消失或错误关联。
- 实时性: 跟踪需要在高精度和高效率之间取得平衡。
为了应对以上问题,ByteTrack 引入了一种简单、高效的方法,通过同时利用高置信和低置信检测结果提高了跟踪性能。
核心难点
传统 MOT 的核心挑战包括:
- 检测不够可靠: 检测器可能漏检或置信度过低,导致目标轨迹中断。
- 遮挡问题: 多个目标之间可能发生遮挡,导致轨迹消失或错误关联。
- 实时性: 跟踪需要在高精度和高效率之间取得平衡。
为了应对以上问题,ByteTrack 引入了一种简单、高效的方法,通过同时利用高置信和低置信检测结果提高了跟踪性能。
-
设置 τ 作为置信度阈值:
- HighConfDets={di∣Conf(di)>τ}
- LowConfDets={dj∣Conf(dj)≤τ}
-
轨迹初始化:
已经生成的轨迹存储为一个集合 Tracks={t1,t2,...,tn},其中包含跟踪对象的历史状态与信息(如位置、形状、速度等)。 -
数据关联 (Data Association):
- 第一阶段匹配(高置信匹配):
- 通过匈牙利算法或 IOU 匹配,将高置信检测结果与现有轨迹进行关联。
- 使用两个常见度量:
- 外观特征: 比如目标的 ReID 特征。
- 运动相似性: 预测轨迹框位置与当前检测框的 IOU。
- 完成后将更新轨迹集合,任何未匹配的轨迹标记为
未匹配轨迹
。
- 第二阶段匹配(低置信检测参与匹配):
- 针对未匹配的轨迹,尝试将低置信检测结果进行二次匹配。
- 通过同样的匹配策略(如 IOU)进行匹配,扩大追踪范围。
- 第一阶段匹配(高置信匹配):
轨迹状态更新:
- 已匹配的轨迹更新历史状态(例如位置、大小等)。
- 未匹配的轨迹(包括从低置信检测中也未匹配上的)经过多个连续帧未激活后,将被删除。
- 新的检测(高或低置信)初始化为新轨迹
ByteTrack 的知识点解析
数据关联 (Data Association)
这是 MOT 的核心技术,用于在帧间将目标匹配起来,主要方法包括:
匈牙利算法 (Hungarian Algorithm):
- 一种用于解决二分图匹配问题的经典方法,用于寻找最优匹配关系。
- ByteTrack 使用匈牙利算法完成轨迹和检测框的匹配。
IOU 匹配:
- IOU(交并比)是目标检测中的重要度量,表示预测框和真实框的重叠程度。
- IOU 越高,匹配关系越强。
成本矩阵:
- 在数据关联时,会基于 IOU(或结合其他特征)生成成本矩阵,用于衡量轨迹和检测框之间的匹配代价
匈牙利算法流程
匈牙利算法的核心思想是通过动态调整权重,寻找代价矩阵中的最优匹配,具体分为以下步骤:
输入:
- 代价矩阵 CC(即轨迹和检测框之间的权重关系)。
- 初始化一个矩形矩阵(轨迹行 ×× 检测列),未匹配的元素填充一个大的无穷值。
步骤:
- 对矩阵的每一行减去该行的最小值。
- 保证矩阵中每一行都有至少一个零。
- 对矩阵的每一列减去该列的最小值。
- 保证矩阵中每一列也至少有一个零。
- 在矩阵中找到覆盖所有零元素的最小直线条数:
- 如果直线条的数量等于矩阵的维度,表明已经找到了最优匹配,停止。
- 否则,调整矩阵中的值,重复之前过程:
- 找出未被覆盖的最小值。
- 将其减去未覆盖元素,增加到两条覆盖线条的交点处。
- 重复步骤 3,直到覆盖线条数等于矩阵维度。
高置信与低置信检测的分裂
传统 MOT 主要依赖高置信度的检测框,而忽略低置信检测可能导致轨迹中断。ByteTrack 基于检测的置信度做了以下改进:
- 高置信检测:直接更新轨迹。
- 低置信检测:尝试与未匹配轨迹进一步关联。
轨迹管理
轨迹管理包含对目标状态的跟踪和更新,主要包括:
- 初始化: 新检测目标生成新的轨迹。
- 更新: 匹配成功后更新轨迹位置与状态。
- 删除: 连续未被匹配的轨迹删除。
# 核心逻辑:ByteTrack 简化版实现
def byte_track(detections, tracks, track_id_count, conf_threshold=0.5, iou_threshold=0.3, max_age=30):
"""
ByteTrack 核心逻辑
detections: 检测框及置信度 [(bbox, conf), ...],bbox 是 [x1, y1, x2, y2]
tracks: 当前已有轨迹 Track 对象列表
track_id_count: 唯一的轨迹 ID 计数器
conf_threshold: high_conf 的置信度分割阈值
"""
# Step 1: 根据置信度划分高置信检测和低置信检测
high_conf_dets = [det for det in detections if det[1] >= conf_threshold]
low_conf_dets = [det for det in detections if det[1] < conf_threshold]
# Step 2: 高置信检测进行第一阶段匹配
matches, unmatched_tracks, unmatched_detections = associate_detections_to_tracks(
[det[0] for det in high_conf_dets], tracks, iou_threshold
)
# 更新轨迹(匹配成功的部分)
for track_idx, det_idx in matches:
tracks[track_idx].update(high_conf_dets[det_idx][0]) # 更新轨迹位置
# 标记未匹配轨迹
for track_idx in unmatched_tracks:
tracks[track_idx].mark_missed()
# 未匹配的高置信检测框则初始化为新轨迹
for det_idx in unmatched_detections:
new_track = Track(high_conf_dets[det_idx][0], track_id_count, high_conf_dets[det_idx][1])
tracks.append(new_track)
track_id_count += 1
# Step 3: 第二阶段:低置信检测参与匹配(补充漏检)
if len(unmatched_tracks) > 0:
unmatched_tracks = [tracks[i] for i in unmatched_tracks]
low_conf_bbox = [det[0] for det in low_conf_dets]
_, unmatched_tracks, unmatched_detections = associate_detections_to_tracks(
low_conf_bbox, unmatched_tracks, iou_threshold
)
# 更新未匹配轨迹
for track_idx, det_idx in matches:
unmatched_tracks[track_idx].update(low_conf_dets[det_idx][0])
# 删除长期未被匹配的轨迹
tracks = [t for t in tracks if t.time_since_update <= max_age]
return tracks, track_id_count