项目地址:
yolo-object-detection-lab:基于 FastAPI 与 Ultralytics YOLO 的图像目标检测实验项目 - AtomGit
上一篇我已经把 YOLO 从图片检测扩展到了视频检测。
当时视频检测已经可以完成:
Video Upload → YOLO Detection → Video Progress → Detection Video → Video Summary → JSON Export但做完以后有一个很明显的问题。
比如一辆车连续出现在几十甚至几百帧中,普通 YOLO 会在每一帧都检测到它。
于是统计结果可能变成:
Car ×100但实际上,它可能从头到尾都是同一辆车。
所以这一次,我继续使用码道 Agent,在原来的 YOLO 视频检测项目上加入了:
YOLO11n + ByteTrack让模型不只是知道:
这一帧有什么?
还能够进一步判断:
这一帧里的这辆车,和上一帧的是不是同一个目标?
一、Detection 和 Tracking 到底差在哪?
普通目标检测的逻辑是:
Frame 1 → Car Frame 2 → Car Frame 3 → Car Frame 4 → CarYOLO 并不知道这四个 Car 是不是同一辆。
因此统计时可能得到:
Detection Events = 4而目标跟踪加入以后,会给目标一个持续存在的 ID。
例如:
Frame 1 → Car #7 Frame 2 → Car #7 Frame 3 → Car #7 Frame 4 → Car #7虽然产生了 4 次 Detection Event,但真正的唯一目标只有:
Unique Objects = 1这就是这一次加入 ByteTrack 最核心的原因。
二、加入第四种模式:Tracking
现在项目顶部已经有四种模式:
Single Image Batch Images Video TrackingTracking 模式继续复用之前的视频上传和 metadata 读取能力。
我这次测试的视频参数为:
Resolution: 1920 × 1080 FPS: 29.97 Total Frames: 962 Duration: 32.10 sTracking Settings 中增加了:
- Confidence Threshold
- Frame Interval
- Show Track Trails
- Trail Length
这次实际设置为:
Confidence = 0.50 Frame Interval = Every Frame Show Track Trails = ON Trail Length = 30三、ByteTrack 是怎么接进来的?
这次没有再引入 DeepSORT 或其他新的 Tracking 框架。
直接使用 Ultralytics 已经集成的:
ByteTrack核心思路类似:
model.track( frame, persist=True, tracker="bytetrack.yaml", conf=confidence, device=device)其中比较关键的是:
persist=True它让 tracker 在连续视频帧之间保留跟踪状态。
这样同一个目标在下一帧再次出现时,就有机会继续使用原来的:
track_id例如:
Car #4 Person #6 Bicycle #15而不是每一帧都当成新的目标。
四、Track ID 才是 Unique Count 的基础
这次后端会从 ByteTrack 返回结果中读取:
boxes.id再把它转换成真实的:
track_id然后维护一个集合:
unique_track_ids = set()每出现一个新的有效 ID:
unique_track_ids.add(track_id)最终:
Unique Objects = len(unique_track_ids)所以如果:
Car #7连续出现 200 帧:
Detection Events += 200 Unique Objects += 1这就和之前的视频检测统计产生了本质区别。
五、真实结果:3989 次检测,最终只有 37 个唯一目标
这次完整处理了:
962 / 962 Frames Progress: 100%页面最终统计:
Detection Events: 3989 Unique Objects: 37也就是说,YOLO 在视频帧中一共产生了:
3989 次检测事件但 ByteTrack 根据 Track ID 去重以后,真正跟踪到的唯一目标数量是:
37这就是 Detection 和 Tracking 最直观的区别。
六、37 个 Unique Objects 分别是什么?
Tracking Summary 会按照类别继续统计唯一目标。
这次真实结果为:
| Class | Unique Objects |
|---|---|
| Car | 18 |
| Person | 16 |
| Bicycle | 3 |
刚好:
18 + 16 + 3 = 37这比上一篇单纯看到大量 Detection Event 更容易解释实际场景。
以前只能说:
Car 被检测了很多次。
现在可以进一步说:
ByteTrack 最终关联出了 18 个不同的 Car Track。
七、为什么还要保留 Detection Events?
既然已经有了 Unique Objects,是不是 Detection Events 就没用了?
其实不是。
两者代表不同的信息。
Detection Events
表示:
所有检测帧中,一共出现了多少次目标框它更能反映目标在视频中的持续时间和检测频率。
Unique Objects
表示:
基于 ByteTrack Track ID 去重后的目标数量例如:
Car #7出现 150 帧。
那么:
Detection Events = 150 Unique Objects = 1所以这次页面同时保留:
Detection Event Summary Unique Object Summary两种统计,而不是用一个数字替代另一个。
八、给每个目标画上运动轨迹
这次另外加入了一个比较直观的功能:
Track Trails对于每个 Track ID,会根据 Bounding Box 中心点计算:
cx = (x1 + x2) / 2 cy = (y1 + y2) / 2然后保存最近若干个位置。
这次设置:
Trail Length = 30也就是每个目标最多保留最近 30 个中心点。
最后通过 OpenCV 把这些点连接起来。
因此在结果视频中,除了:
Bounding Box Class Track ID Confidence还可以看到黄色的运动轨迹。
这比普通 YOLO 的矩形框更容易观察目标到底从哪里移动到哪里。
九、Active Tracks 和 Unique Seen 不是一个概念
Tracking Progress 中还有两个容易混淆的值:
Active Tracks Unique Seen比如这次任务结束后页面显示:
Active Tracks: 0 Unique Seen: 37这并不代表跟踪失败。
因为:
Active Tracks代表的是当前帧仍然活跃的目标数量。
任务结束以后,最后一帧如果没有活跃目标,它自然可能变成 0。
而:
Unique Seen代表整个视频处理过程中累计见过多少个不同 Track ID。
所以这次真正要看的累计结果是:
Unique Seen = 37十、Track List 能看到每个目标的生命周期
Tracking 模式还增加了一个:
Track List每个唯一目标都会保存自己的生命周期信息:
Track ID Class First Frame Last Frame Frames Seen Avg Confidence Max Confidence例如:
#1 Car Frames: 0 → 79 Seen: 78 Avg Confidence: ... Max Confidence: ...这样就不只是知道“有 18 辆车”,还可以进一步查看:
某一辆车是什么时候第一次出现的?
持续了多少帧?
整个跟踪过程中置信度怎么样?
这已经开始有一点真正视频分析系统的感觉了。
十一、新任务必须重置 Tracker
做 Tracking 时还有一个需要特别注意的问题:
Tracker 本身是有状态的。
因为 ByteTrack 要记住:
上一帧有哪些 Track ID所以如果:
Video A处理完以后直接开始:
Video B但没有清空 tracker,那么第二个任务可能继承第一个视频中的状态。
因此这次专门增加了:
reset_tracker()每一个新的 Tracking Job 开始以前,都先把旧 tracker 状态清掉。
这样:
Tracking Job 1和:
Tracking Job 2就是完全独立的。
同时也不会影响原来的普通 Video Detection。
十二、导出 Tracking JSON 和 CSV
跟踪完成以后,现在可以导出两类结果。
Export Tracking JSON
JSON 会保存:
Detection Events Unique Objects Unique Class Summary Detection Class Summary Tracks每个 Track 里面还有:
track_id class_name first_frame last_frame frames_seen avg_confidence max_confidence结构类似:
{ "detection_events": 3989, "unique_objects": 37, "unique_class_summary": { "car": 18, "person": 16, "bicycle": 3 } }Export Tracks CSV
CSV 则是一条 Track 一行。
例如:
track_id,class_name,first_frame,last_frame,frames_seen,... 1,car,0,79,78,... 2,person,0,72,57,...相比把视频每一帧全部导出来,这种形式更适合分析:
一个独立目标的完整生命周期。
十三、测试也增加到了 126 个
在加入 Tracking 之前,整个项目已经有:
77 passed这次又增加了大量 ByteTrack 相关测试。
最终:
126 passed前端逻辑测试:
72 assertions passed新增测试主要覆盖:
- tracker reset
- Track ID 解析
boxes.id is None- Unique Track 去重
- 同一 ID 多帧只统计一次
- Detection Events
- Unique Class Summary
- Trail Length
- First / Last Frame
- Frames Seen
- Avg Confidence
- Max Confidence
- Active Tracks
- Tracking JSON
- Tracks CSV
- 新 Job 不继承旧 tracker
- 原 Video / Batch / Single Image 不回归
总结
这一篇之后,项目已经不只是:
YOLO Detection而是进一步进入了:
YOLO Detection ↓ ByteTrack ↓ Track ID ↓ Cross-frame Association ↓ Unique Object Count ↓ Trajectory Trail ↓ Track Lifecycle这次真实测试中:
962 Frames 3989 Detection Events 37 Unique Objects其中:
Car ×18 Person ×16 Bicycle ×3也就是说,之前那些成百上千次重复检测,现在终于可以通过 Track ID 关联成真正的独立目标。
从:
这一帧有一辆车
变成:
这是之前那辆 Car #7,它现在移动到了这里。
这就是从Object Detection到Multi-Object Tracking最直观的一步。
项目地址:
yolo-object-detection-lab:基于 FastAPI 与 Ultralytics YOLO 的图像目标检测实验项目 - AtomGit
下一篇就可以继续解决一个更实际的问题:
既然已经知道每辆车是谁,那能不能统计到底有多少辆车真正经过某一条线?
下一步:
YOLO + ByteTrack + Line Crossing做真正的行人 / 车流过线计数。