【ByteTrack 04】多轨迹算法ByteTrack改进点

ByteTrack 轨迹算法改进点

目标:只改进轨迹算法(bytetrack/ 核心代码),检测器不是目标。

测试视频与场景对应:dense.mp4(密集交叉)、turn.mp4(转弯减速)。

基线测试记录(源码,未加任何改进)

场景 视频源 基线输出目录 结果文件 人工标注
密集交叉 video/dense.mp4 test/out/bytetrk_yolov8s_dense_0915_114254/ dense.txt dense_anno.txt
转弯减速 video/turn.mp4 test/out/bytetrk_yolov8s_turn_0916_140556/ turn.txt turn_anno.txt

基线测试命令(检测器均为 yolov8s.pt,跟踪参数均为源码默认值;后续每个改进的对比结果与此基线对照):

bash 复制代码
# 密集交叉基线
python ./test/test_bytetrack.py --model model/yolov8s.pt --video video/dense.mp4 --conf 0.25 --classes 2 --track-thresh 0.3 --new-track-thresh 0.35

# 转弯减速基线
python ./test/test_bytetrack.py --model model/yolov8s.pt --video video/turn.mp4 --conf 0.25 --classes 2 --track-thresh 0.3 --new-track-thresh 0.35

人工标注记录规则

人工标注 = 人眼观察视频,为每个物理目标记录完整的 ID 生命时间线。辅助工具:test/frame_viewer.py(看片器:←→ 单帧、↑↓ 大步进、空格 播放、g 跳帧、e 设步长、q 退出)。

  1. 文件位置与命名 :与被标注的运行结果放在同一目录,命名 <场景>_anno.txt(如 dense_anno.txt)。看哪次运行就标哪次,不同运行的 ID 完全不同,不能混用
  2. 文件头 :首行注明视频 + 结果文件,如 dense.mp4 + dense.txt
  3. 格式 :一行一个目标,用 | 分列:
    • 第 1 列:目标编号(目标1、目标2...按出现顺序编,同一物理目标全程沿用同一编号)
    • 后续每列 :起帧-止帧:内容,内容四种
      • 数字 = 该段的轨迹 ID,如 46-51:27
      • 消失 = 无可见遮挡但轨迹断档
      • 遮挡 = 被其他目标挡住看不见
      • 出画 = 目标离开画面(一般作为时间线的结尾段)
    • 备注列 (可选,放最后):备注:xxx,如 备注:237-240同时出现53和58
  4. 记录范围:只记发生过 ID 变化的目标;全程一个 ID 的目标不用记
  5. 同一 ID 贴到不同目标(幽灵轨迹现象):两个目标各自如实记录,冲突由分析端识别,标注者不用合并
  6. 只记看得懂的:拿不准的事件不记、原因拿不准宁可不填

示例(真实记录节选):

复制代码
dense.mp4 + dense.txt

目标1|46-51:27|52-60:消失|61-64:31|65-394:出画|
目标2|222-236:53|237-241:58|242-270:消失|271-394:68|备注:237-240同时出现53和58
目标8|59-216:30|217-217:消失|218-227:30|228-235:消失|236-251:30|252-252:消失|253-253:30|254-256:消失|257-259:30|260-282:4|283-293:消失|294-308:4|309-394:出画|

改进一:双轨迹抑制(dense.mp4测试结果分析来源)

当前现象:同一目标同时存在两条轨迹------检测器对同一辆车输出两个框且都被建档,多出的轨迹只存活 1~5 帧。本改进目的:在跟踪端利用时序信息抑制这类双框轨迹,净化输出。

1. 源码问题

检测器(YOLO + NMS iou=0.7)偶尔对同一辆车输出两个框:两框 IoU 落在 0.15~0.7 区间时 NMS 不抑制(阈值 0.7 才抑),ByteTrack 的 remove_duplicate_stracks 也不管(只清 IoU > 0.85)。两个框都被采纳:一个框匹配老轨迹,另一个框(分数 ≥ new_track_thresh)在 Step 4 建档成新轨迹。

人工标注实证(dense_anno.txt,共 5 处):

  • 目标2:54[222-236] → 59[237-241] → ...------轨迹59 存活 5 帧后消失(备注:237-239 54和59同时存在)
  • 目标2:69[271-392] → 93[393] → 69[394]------轨迹93 只活 1 帧,69 继续(备注:393 69和93同时存在)
  • 目标9:40[135-173] → 45[174] → 40[175-302]------轨迹45 只活 1 帧,40 继续(备注:174 40和45同时存在)
  • 目标12:44[160-241] → 61[241] → 44[242-261]------轨迹61 只活 1 帧,44 继续(备注:241 44和61同时存在)
  • 目标13:48[193-293] → 73[294] → 48[295-342] → ...------轨迹73 只活 1 帧,48 继续(备注:294 48和73同时存在)

即:双框建档产生一条只活 1~5 帧的垃圾新轨迹,与老轨迹并存几帧后自行消失,老轨迹不受影响。危害是输出污染:假 ID、画面多框。

2. 源码位置

bytetrack/byte_tracker.py Step 4(274~281 行):

python 复制代码
        """ Step 4: Init new stracks"""
        for inew in u_detection:
            track = detections[inew]
            if track.score < self.det_thresh:
                continue
            track.activate(self.kalman_filter, self.frame_id)
            det_status[det_indices[inew]] = track.track_id  # 新增,赋值新建档的轨迹id
            activated_starcks.append(track)

检测端 NMS 只看单帧几何,无法区分"双框"和"紧贴的两辆车";跟踪端有时序信息------已稳定存活多帧的老轨迹旁边,突然出生的新轨迹大概率是双框。

3. 改进方案

Step 4 建档前加一道校验:新轨迹(尚未激活)与所有存活 ≥ dup_age 帧的 Tracked 老轨迹(含本帧已激活的新轨迹,防两个新框互为双框漏网)计算 IoU,大于 dup_iou 则判为双框,直接丢弃新轨迹(不激活、不进入任何池)。老轨迹不受影响。

  • 开关 use_dup_suppression(默认 False)/ dup_iou(默认 0.55)/ dup_age(建议 5)
  • 不传参数时行为与原版完全一致
  • 断档后重新出现的目标不受影响:老轨迹已 Lost,不在 Tracked 池,不参与比较

4. 改进代码

byte_track.py 的 ByteTrackArgs.__init__ 新增双轨迹抑制三个参数:use_dup_suppression、dup_iou、dup_age:

python 复制代码
        self.use_dup_suppression = getattr(args, 'use_dup_suppression', False)    # 是否启用双轨迹抑制
        self.dup_iou = getattr(args, 'dup_iou', 0.55)                              # 双框判定 IoU 阈值
        self.dup_age = getattr(args, 'dup_age', 5)                                # 老轨迹最小存活帧数

bytetrack/byte_tracker.py 的 BYTETracker 类新增方法(放在 update 方法之前):

python 复制代码
    def _is_dup_track(self, track, activated_now):
        """判断新轨迹是否为双框轨迹:与本帧新轨迹或存活足够久的老轨迹高度重叠即判为双框

        输入:
            track: 待建档的新轨迹(STrack,尚未激活)
            activated_now: 本帧已激活的新轨迹列表(list[STrack]),双框互斥直接比较不设年龄门槛
        返回:
            是双框轨迹: True
            不是双框轨迹: False
        """
        for t_new in activated_now:
            if matching.ious([track.tlbr], [t_new.tlbr])[0][0] > self.args.dup_iou:
                return True
        for t_old in self.tracked_stracks:
            if self.frame_id - t_old.start_frame < self.args.dup_age:
                continue
            if matching.ious([track.tlbr], [t_old.tlbr])[0][0] > self.args.dup_iou:
                return True
        return False

bytetrack/byte_tracker.py Step 4 改为:

python 复制代码
        """ Step 4: Init new stracks"""
        activated_now = []  # 新增:本帧已激活的新轨迹(双框互斥也要比)
        for inew in u_detection:
            track = detections[inew]
            if track.score < self.det_thresh:
                continue
            # 新增:双轨迹抑制------命中双框判定则丢弃新轨迹
            if self.args.use_dup_suppression and self._is_dup_track(track, activated_now):
                continue
            track.activate(self.kalman_filter, self.frame_id)
            det_status[det_indices[inew]] = track.track_id  # 新增,赋值新建档的轨迹id
            activated_starcks.append(track)
            activated_now.append(track)  # 新增

test/test_bytetrack.py 新增命令行参数:

python 复制代码
    parser.add_argument("--use-dup-suppression", action="store_true", help="是否启用双轨迹抑制(默认关闭)")
    parser.add_argument("--dup-iou", type=float, default=0.55, help="双框判定 IoU 阈值(默认 0.55)")
    parser.add_argument("--dup-age", type=int, default=5, help="老轨迹最小存活帧数(默认 5)")

5. 测试方式

bash 复制代码
python ./test/test_bytetrack.py --model model/yolov8s.pt --video video/dense.mp4 --conf 0.25 --classes 2 --track-thresh 0.3 --new-track-thresh 0.35 --use-dup-suppression

改进开关参数说明:

参数 作用 取值范围
--use-dup-suppression 双轨迹抑制总开关:不传则关闭,行为与原版完全一致 开关型:传了即开启
--dup-iou 0.55 重叠门槛:新出生的轨迹若和一条老轨迹的框重叠程度(0~1,1 表示完全重合)超过此值,就认为是同一辆车被检测出两个框,丢弃新轨迹。值越小越容易触发丢弃 0~1(小数),默认 0.55,建议 0.5~0.7
--dup-age 5 老轨迹的"资历"门槛:老轨迹至少已跟踪满这么多帧,新轨迹才拿它来比较(本帧同时新建的轨迹之间不受此限制,直接互斥) ≥ 1 的整数,建议 3~10

(基线 = 同命令去掉后三个参数,即 test/out/bytetrk_yolov8s_dense_0915_114254;可用 --dup-iou 0.5 / 0.7 做敏感性对比)

6. 对比结果

现象记录(每次测试追加一行):

参数 测试目录 现象
基线 test/out/bytetrk_yolov8s_dense_0915_114254 目标2:第237-239帧 54和59 同时存在 目标2:第393帧 69和93 同时存在 目标9:第174帧 40和45 同时存在 目标12:第241帧 44和61 同时存在 目标13:第294帧 48和73 同时存在
--use-dup-suppression --dup-iou 0.6 --dup-age 5 test/out/bytetrk_yolov8s_dense_0915_174340 目标2:第393帧 轨迹88 与轨迹66 并存
--use-dup-suppression --dup-iou 0.55 --dup-age 5 test/out/bytetrk_yolov8s_dense_0916_091132 无双轨迹并存

指标对比:

指标 基线 --dup-iou 0.6 --dup-iou 0.55
双轨迹并存(标注确认) 5 处 1 处 0 处
轨迹总条数 93 88 86
单帧轨迹条数 19 16 14
逐目标切换次数 23 20 14
目标2 切换次数 4 3 1
目标9 切换次数 3 2 1
目标12 切换次数 2 2 0
目标13 切换次数 2 1 0
其余 9 目标切换次数 持平 持平 持平
误杀(长轨迹消失) --- 无 无

7. 总结

建议开启,推荐直接使用 --use-dup-suppression(dup_iou 默认已为 0.55、dup_age 默认 5)。--dup-iou 往小调捕获能力更强:0.6 时漏掉 IoU 0.59 的 1 处双框,0.55 时全部捕获且无长轨迹误杀,不建议再低于 0.55(误杀风险上升,未实测)。--dup-age 调小(如 3)抑制更激进,刚进画面几帧的相邻新车易被误杀;调大(如 10)会漏掉出生 5~10 帧的双框,5 为实测无副作用值,一般不需调整。


改进二:未确认轨迹宽限期(turn.mp4测试结果分析来源)

当前现象:刚建档的轨迹只要断档一帧就被立即删除,检测框随后回来也无法配回,只能以新 ID 重新建档------目标反复换 ID。本改进目的:给转正失败的未确认轨迹短暂宽限期,检测恢复时配回原轨迹,减少这类切换。

1. 源码问题

目标检测短暂波动(漏检一帧或置信度短暂下降)时,已转正轨迹 会进入丢失池,受 track_buffer(默认 30 帧)保护,检测恢复后可配回原 ID;但未确认轨迹 (刚建档、还没通过转正考试)一帧没配上就被 mark_removed 立即删除,不进丢失池、无任何保护 。同一目标只因断档发生在转正前后,命运完全不同:转正后断档能等 30 帧,转正前断档一帧即死。刚建档的轨迹死后,检测框即使原地回来(IoU 再高),池中已无该轨迹可配,只能等置信度重新过建档线后以新 ID 重生------这就是目标在 track_buffer 帧内连续换 ID 的根因。

人工标注实证(turn_anno.txt,共 11 处,涉及 7 个目标):

  • 目标3(右转弯):33[1-232] → 133[281] → 134[285-300]------轨迹133281:第281帧建档,第282帧一帧漏检即被删除;第283/284帧检测框原地回来(conf 0.28/0.34)已无轨迹可配;第285帧以新 ID 134 重生
  • 目标7:41[8] → 47[11] → 80[100-266]------轨迹418:建档次帧(第9帧)检测框在原位(conf 0.31)但轨迹已删;轨迹4711:建档次帧(第12帧)框在原位(conf 0.30)同样已删;第12~99帧目标长期无稳定轨迹,直到 100 帧以新 ID 80 重生
  • 目标8:35[6] → 42[8] → 45[10-15] → 87[121-269]------轨迹356:次帧(第7帧)框在原位(conf 0.29)即删;轨迹428:次帧(第9帧)框在原位(conf 0.32)即删;直到轨迹4510 转正成功才暂时稳定,第121帧又以新 ID 87 重生
  • 目标13:77[94] → 93[129] → 105[185] → 115[219] → 118[223] → 121[225] → 125[231-300]------第219~231帧内连续三个单帧 ID 接力:115 建档后次帧框在(conf 0.31)即删,220~222 无框,223 建 118 又即删(次帧框 conf 0.29),225 建 121 再即删,直到 231 帧 125 才存活
  • 目标14:98[152] → 99[154-162] → 107[193] → 108[201] → 109[204-300]------轨迹98152:次帧(第153帧)框在原位(conf 0.32)即删,第154帧以新 ID 99 重生;轨迹107193:次帧框在(conf 0.32)即删;轨迹108201:次帧框在(conf 0.31)即删,204 帧以 109 重生
  • 目标15:27[1-5] → 71[78] → 72[79-205]------轨迹7178:第78帧建档,次帧(第79帧)检测框就在原位(conf 0.36),若轨迹还在即可配回,实际已被删,该帧以新 ID 72 重生
  • 目标18:11[1-26] → 66[65] → 67[66]------连续两帧各换一个 ID:第65帧 66 建档即死(次帧框 conf 0.38 在原位),第66帧又建 67,67 次帧无框再删

未确认轨迹死于"一帧没配上"的两种情况:一是建档次帧漏检 (无检测框,转正考试无人应考);二是次帧有框但转正考试挂科 ------第三轮判分公式 cost = 1 − IoU×conf 要求 ≤ 0.7,conf 被直接乘进分数,临界置信度的框容错极小:例如 conf 0.36 的框,即使与预测框 IoU 达 0.80(几乎重合),cost = 1 − 0.80×0.36 = 0.71 > 0.7,转正失败;而 conf 0.90 的框只需 IoU ≥ 0.78。即临界置信度的框必须与预测框几乎完全重合才能转正,框稍有抖动即死。track_thresh(0.3)只是参与配对的入场券,不保证配得上。

turn.mp4 换 ID 的构成与本改进的覆盖范围:

换 ID 类型 机制 宽限期能否解决
建档次帧漏检 次帧无检测框,转正考试无人应考,轨迹一帧即删;检测恢复时池中无轨迹可配 能:进宽限池,检测框回来时经第一轮匹配配回
次帧有框但转正考试挂科 临界 conf 的框 cost = 1 − IoU×conf > 0.7,转正失败即删,同帧以新 ID 重建档 能:进宽限池且 state 保持 Tracked,下一帧第一轮(阈值 0.8 比转正考试 0.7 宽)或第二轮低分框(纯 IoU 配对)均可配回

2. 源码位置

bytetrack/byte_tracker.py 第三轮关联的未确认轨迹处理(286~290 行):

python 复制代码
        for it in u_unconfirmed:
            track = unconfirmed[it]
            track.mark_removed()
            removed_stracks.append(track)

3. 改进方案

转正失败的未确认轨迹不立即删除:转入独立的宽限池(不进丢失池、不 mark_lost,state 保持 Tracked),打上宽限截止帧标记(当前帧 + grace)。宽限轨迹参与第一轮(高分框)与第二轮(低分框,纯 IoU 配对)匹配,配上即转正回归在跟池;超过截止帧仍无匹配才真正移除。丢失池与已转正轨迹的超时逻辑完全不变。同时抑制同帧重建档:导致挂科的检测框若与宽限中的轨迹高度重叠,本帧不再用它新建档,把框留给宽限轨迹下一帧配回,避免闪现 1 帧的新 ID。

  • 开关 use_unconfirmed_grace(默认 False)/ grace(建议 3)/ grace_iou(默认 0.7)
  • 不传参数时行为与原版完全一致
  • 误配风险控制:宽限期极短(3 帧,远小于 buffer),且仍受 IoU 匹配门槛约束
  • 与双轨迹抑制无冲突(宽限池轨迹不参与双框判定)

4. 改进代码

byte_track.py 的 ByteTrackArgs.__init__ 新增未确认宽限三个参数:use_unconfirmed_grace、grace、grace_iou:

python 复制代码
        self.use_unconfirmed_grace = getattr(args, 'use_unconfirmed_grace', False)  # 是否启用未确认轨迹宽限期
        self.grace = getattr(args, 'grace', 3)                                      # 未确认轨迹宽限帧数
        self.grace_iou = getattr(args, 'grace_iou', 0.7)                             # 同帧重建档抑制的重叠门槛

bytetrack/byte_tracker.py 的 STrack.__init__ 新增宽限标记:

python 复制代码
        self.grace_deadline = None  # 未确认宽限截止帧(None 表示非宽限状态)

bytetrack/byte_tracker.py 的 BYTETracker.__init__ 新增宽限池:

python 复制代码
        self.grace_tracks = []  # 新增:宽限池(第三轮挂科的未确认轨迹,独立于丢失池管理)

bytetrack/byte_tracker.py 第一轮匹配池纳入宽限轨迹:

python 复制代码
        strack_pool = joint_stracks(tracked_stracks, self.lost_stracks)
        # 新增:宽限池轨迹参与第一轮(开关关闭时跳过,不白跑合并)
        if self.args.use_unconfirmed_grace:
            strack_pool = joint_stracks(strack_pool, self.grace_tracks)

bytetrack/byte_tracker.py 第二轮落单处理跳过宽限轨迹(不判丢、不进丢失池,留在宽限池等下一帧):

python 复制代码
        for it in u_track:
            track = r_tracked_stracks[it]
            if self.grace_tracks and track in self.grace_tracks:  # 新增:宽限轨迹不判丢,留在宽限池等下一帧
                continue
            if not track.state == TrackState.Lost:
                track.mark_lost()
                lost_stracks.append(track)

bytetrack/byte_tracker.py 第三轮未确认轨迹处理改为:

python 复制代码
        grace_now = []  # 新增:本帧挂科进宽限的轨迹(同帧建档抑制用)
        for it in u_unconfirmed:
            track = unconfirmed[it]
            # 新增:未确认轨迹宽限期------转正失败不立即删除,进宽限池等检测回来
            if self.args.use_unconfirmed_grace:
                track.grace_deadline = self.frame_id + self.args.grace
                if track in self.tracked_stracks:
                    self.tracked_stracks.remove(track)  # 从在跟池移出,统一由宽限池管理
                self.grace_tracks.append(track)
                grace_now.append(track)
            else:
                track.mark_removed()
                removed_stracks.append(track)

bytetrack/byte_tracker.py 的 BYTETracker 类新增方法(放在 update 方法之前):

python 复制代码
    def _overlap_grace(self, track, grace_tracks):
        """判断待建档轨迹与挂科进宽限的轨迹是否高度重叠(同帧重建抑制用)

        输入:
            track: 待建档的新轨迹(STrack,尚未激活)
            grace_tracks: 本帧挂科进宽限的轨迹列表(list[STrack])
        返回:
            高重叠: True
            不重叠: False
        """
        for tg in grace_tracks:
            if matching.ious([track.tlbr], [tg.tlbr])[0][0] > self.args.grace_iou:
                return True
        return False

bytetrack/byte_tracker.py Step 4 建档循环内新增同帧重建档抑制:

python 复制代码
            # 新增:双轨迹抑制------命中双框判定则丢弃新轨迹
            if self.args.use_dup_suppression and self._is_dup_track(track, activated_now):
                continue
            # 新增:宽限同帧建档抑制------挂科进宽限的轨迹与该框高度重叠时不建档,
            # 把框留给宽限轨迹下一帧第一轮配回,消除 1 帧闪现新 ID
            if self.args.use_unconfirmed_grace and grace_now and self._overlap_grace(track, grace_now):
                continue

bytetrack/byte_tracker.py Step 5 超时判断改为(宽限轨迹用截止帧,其余走原逻辑):

python 复制代码
        """ Step 5: Update state"""
        # 新增:宽限池维护------超期者移除,转正/移除者移出(宽限轨迹不进丢失池,不走下方超时逻辑)
        # 开关关闭时池恒为空,跳过不白跑
        if self.grace_tracks:
            for track in self.grace_tracks:
                if self.frame_id > track.grace_deadline:
                    track.mark_removed()
                    removed_stracks.append(track)
            self.grace_tracks = [t for t in self.grace_tracks
                                 if t.state == TrackState.Tracked and not t.is_activated]
        for track in self.lost_stracks:
            if self.frame_id - track.end_frame > self.max_time_lost:
                track.mark_removed()
                removed_stracks.append(track)

test/test_bytetrack.py 新增命令行参数:

python 复制代码
    parser.add_argument("--use-unconfirmed-grace", action="store_true", help="是否启用未确认轨迹宽限期(默认关闭)")
    parser.add_argument("--grace", type=int, default=3, help="未确认轨迹宽限帧数(默认 3)")
    parser.add_argument("--grace-iou", type=float, default=0.7, help="同帧重建档抑制的重叠门槛(默认 0.7)")

5. 测试方式

bash 复制代码
python ./test/test_bytetrack.py --model model/yolov8s.pt --video video/turn.mp4 --conf 0.25 --classes 2 --track-thresh 0.3 --new-track-thresh 0.35 --use-unconfirmed-grace --grace 3

(基线 = 同命令去掉 --use-unconfirmed-grace --grace 3,即 test/out/bytetrk_yolov8s_turn_0916_140556)

改进开关参数说明:

参数 作用 取值范围
--use-unconfirmed-grace 未确认轨迹宽限期总开关:不传则关闭,行为与原版完全一致 开关型:传了即开启
--grace 3 宽限帧数:转正失败的未确认轨迹在宽限池保留这么多帧,期间检测框回来(第一轮高分框或第二轮低分框)可配回转正,超期仍未配上才删除 ≥ 1 的整数,建议 2~5
--grace-iou 0.7 同帧重建档抑制的重叠门槛:挂科进宽限的同一帧,若待建档框与宽限轨迹重叠超过此值则不建档,把框留给宽限轨迹下一帧配回。值越小越容易抑制(密集车流中易误抑制相邻新目标),越大越保守 0~1(小数),默认 0.7。依据:覆盖边界------挂科时 IoU×conf ≤ 0.3,中速目标(帧间位移 ≤ 框宽 15%,30fps)挂科框 IoU 下限 ≈ 0.70;误杀边界------dense.mp4 全片 10 万个同帧框对实测,不同目标的框 IoU 最高 0.65。0.70 覆盖中速目标且与误杀上限保持 0.05 边距。15fps 视频帧间位移加倍,需降至约 0.6

6. 对比结果

现象记录(每次测试追加一行):

参数 测试目录 现象
基线 test/out/bytetrk_yolov8s_turn_0916_140556 目标3:第281帧建档轨迹133,第282帧检测未出现轨迹丢弃,第285帧重新建档换轨迹134 目标7:第8帧建档轨迹41,第9帧有检测未通过转正考试轨迹丢弃;第11帧重新建档轨迹47,第12帧有检测仍未通过轨迹丢弃,第100帧重新建档换轨迹80 目标8:第6帧建档轨迹35,第7帧有检测未通过轨迹丢弃;第8帧重新建档轨迹42,第9帧有检测未通过轨迹丢弃,第10帧重新建档轨迹45 存活 目标13:第219帧建档轨迹115,第220帧有检测未通过轨迹丢弃;第223帧重新建档轨迹118,第224帧有检测未通过轨迹丢弃;第225帧重新建档轨迹121,第226帧检测未出现轨迹丢弃,第231帧重新建档换轨迹125 目标14:第152帧建档轨迹98,第153帧有检测未通过轨迹丢弃,第154帧重新建档换轨迹99;第193帧建档轨迹107,第194帧有检测未通过轨迹丢弃;第201帧建档轨迹108,第202帧有检测未通过轨迹丢弃,第204帧重新建档换轨迹109 目标15:第78帧建档轨迹71,第79帧有检测未通过轨迹丢弃,同帧重新建档换轨迹72 目标18:第65帧建档轨迹66,第66帧有检测未通过轨迹丢弃,同帧重新建档换轨迹67,第67帧检测未出现轨迹丢弃
--use-unconfirmed-grace --grace 3 --grace-iou 0.7 test/out/bytetrk_yolov8s_turn_0917_184227 目标3 已解决:第281→285帧轨迹变换;未解决:第232→281帧轨迹变换,断档 48 帧已超过 30 帧范围,不属于宽限期改进的目标 目标7 已解决:第8→11帧轨迹变换;未解决:第13→100帧轨迹变换,断档 86 帧已超过 30 帧范围 目标8 已解决:第6→8帧、第8→10帧轨迹变换;未解决:第15→121帧轨迹变换,断档 105 帧已超过 30 帧范围 目标13 已解决:第219→231帧三个单帧 ID 接力;未解决:第94→129帧(断 34 帧)、第134→185帧(断 50 帧)轨迹变换,已超过 30 帧范围;第192→219帧轨迹变换(断 26 帧,30 帧内未接回:断档期第208/210帧低分框本可配上但丢失池收不到;第219帧重现 cost 0.817 超阈值 0.8 仅 0.017 贴线卡死) 目标14 已解决:第152→154帧、第193→201帧轨迹变换(轨迹94 经宽限期低分框配回连续保持至第300帧)、第201→204帧轨迹变换;未解决:第171→193帧轨迹变换(断 21 帧,30 帧内未接回:断档期车移动 40px 且检测框漂移,高分框与外推框 IoU 仅 0~0.29,第193帧重现 cost 0.85 以上) 目标15 已解决:第78→79帧轨迹变换;未解决:第13→78帧轨迹变换,断档 64 帧已超过 30 帧范围 目标18 已解决:第65→67帧轨迹闪现变换;未解决:第39→65帧轨迹变换(断 25 帧,30 帧内未接回:断档期检测漏检;第65帧重现外推框 IoU 0.544 × conf 0.37,cost 0.799 差 0.001 贴线卡死)

7 个目标逐个对比(基线链 → 宽限开启链,格式为 轨迹ID帧段):

目标 基线链 宽限开启链 切换次数
目标3 331-232 → 133281 → 134285-300 331-232 → 113281-300 2→1
目标7 418 → 4711 → 80100-266 418-13 → 72100-266 2→1
目标8 356 → 428 → 4510-15 → 87121-269 356-15 → 77121-269 3→1
目标13 7794 → 93129 → 105185 → 115219 → 118223 → 121225 → 125231-300 6994 → 82129-134 → 92185-192 → 100219-300 6→3
目标14 98152 → 99154-162 → 107193 → 108201 → 109204-300 86152-171 → 94193-300 4→1
目标15 271-5 → 7178 → 7279-205 271-13 → 6678-205 2→1
目标18 111-26 → 6665 → 6766 111-39 → 6265-67 2→1

整体指标:

指标 基线 宽限开启
7 目标切换次数 21 9
轨迹总条数 141 120

7. 总结

建议开启,推荐 --grace 3(grace_iou 默认 0.7 无需显式传)。建档即死类切换基本解决,剩余换 ID 的直接原因均为断档后配对失败、以新 ID 重建,按失败环节分两类:

墙 卡住的案例 原因
断档超 30 帧,轨迹已移除 目标3(第232→281帧)、目标7(第13→100帧)、目标8(第15→121帧)、目标15(第13→78帧)、目标13(第94→129帧、第134→185帧) 框回来时轨迹已超时移除,无配对资格,直接以新 ID 建档
匹配分数(IoU×conf)不够过线 贴线档:目标18 的轨迹62(cost 0.799,差 0.001)、目标13 的轨迹92(断档期第208/210帧低分框可配但丢失池收不到,重现帧 cost 0.817,超 0.017)------放宽 match_thresh 到 0.85 即可覆盖 远离档:目标14 的轨迹86(断档期车移 40px 且检测框漂移 20~60px,cost 0.85 以上,需放宽到 0.9,误配风险需评估) 断档期低 conf(0.3~0.4)与外推漂移叠加,fuse_score 把 cost 压在 0.8~0.9 贴线带

未解决换 ID 中 6 处轨迹已移除(无配对资格)、3 处匹配分数差距过大(放宽阈值将致误配失控)。

描述 链接
项目 基于 Ultralytics + ByteTrack 的多目标跟踪与改进
往期回顾 链接
数据集 数据集汇总
软件开发 深度学习软件开发汇总
相关推荐
小蒋观天下5 小时前
雷击与浪涌防护:前端SPD选型、接地网误区,接地不当反而引入干扰
深度学习·算法·机器学习
WBluuue6 小时前
数据结构与算法:分块(一):块状数组、块状链表
数据结构·c++·算法·链表
澳鹏Appen8 小时前
澳鹏重磅发布ExamOCRBench:面向端到端试卷判分的多任务基准
算法·benchmark·基准评测
benjormin8 小时前
Java数据结构 二叉搜索树
数据结构·算法
人工智能培训8 小时前
知识图谱:结构化表达实体与关系的知识组织技术
大数据·人工智能·算法·重构·生活·知识图谱
地平线开发者8 小时前
QAT 实战:从 PyTorch 模型到 J6E 实板精度与延时验证
pytorch·算法·自动驾驶
IT星宿8 小时前
github-gravity:用 Rapier2D 把 GitHub 贡献图做成 SVG 物理动画
算法·github
♡すぎ♡9 小时前
SWE 的基本条件
算法·swe·浅水模拟