云计算百科
云计算领域专业知识百科平台

【AI视觉软件 05】基于 Ultralytics + OC_SORT的多目标跟踪(支持YOLO全系列,重点车跟踪)

一、介绍

车流统计是智能交通分析的基础,流量、流速、行驶轨迹都建立在"每辆车被持续稳定跟住"之上,这一步就是多目标跟踪(MOT),业界通用方案是"检测器 + 跟踪器"两段式。本工程检测用 Ultralytics YOLO,跟踪用 OC-SORT——一个不依赖外观特征(ReID)、纯运动模型却能在密集/遮挡场景反超多数 ReID 方案的跟踪器(CVPR 2023),重点跟车。

OC-SORT 的三个核心机制都围绕"以观测为中心"(Observation-Centric):

机制全称解决什么
ORU Observation-centric Re-Update 目标漏检几帧后找回时,卡尔曼滤波参数已被盲推污染——冻结漏检前状态,找回后用插值虚拟观测重放,把滤波器"洗干净"
OCR Observation-Centric Recovery 找回时的关联参考位置不用漂移过的预测框,用轨迹最近一次真实观测框——目标回到"最后被看到的位置"就能认回
VDC Velocity Direction Consistency 匹配代价里加方向一致性项——密集交叉时防止轨迹"拐弯"到旁边对向车流的目标上

本工程目标:让车辆目标的轨迹 ID 保持稳定——不频繁切换,短暂丢失(漏检、遮挡)后能找回原 ID 继续跟踪。

OC-SORT 的三大机制与全部关联参数都是原生开关,不改代码即可实验。本文按以下顺序介绍:整体架构、检测器选型与参数、OC-SORT 轨迹算法流程与原理、use-byte 对照实验方法、结果对比数据、四个测试分析工具的完整用法(附实际命令)。


二、整体架构

技术栈

模块选型
检测器 Ultralytics YOLO(yolo26 / yolo11 / yolov8 / yolov5,库随项目内置)
跟踪器 OC-SORT(核心代码独立成 ocsort/ 包,原样拷贝自官方仓库,可单独阅读修改)
视频处理 OpenCV
语言 Python 3.x

数据流水线

视频帧 → Detector.detect() → Nx5 检测数组 [x1, y1, x2, y2, conf]
→ OcSortTracker.update() → OcTrack 轨迹列表(track_id / tlwh / xyxy)
→ draw_tracking() → 绘制边框 + ID + 帧信息
→ 写入输出视频 + 逐帧结果 txt

设计要点两条:

  • 检测与跟踪彻底解耦:Detector.detect() 输出标准 Nx5 数组,OcSortTracker.update() 只认这个格式——检测端不限 YOLO、不限框架。OC-SORT 源码侧零修改,所有适配(坐标契约、det_status 还原、参数换算)都收在 oc_sort.py 封装层。
  • 每次运行全量落盘:输出 txt 逐帧记录每个检测框的去向(配上了哪条轨迹 / 被过滤 / 没配上),后续所有分析、人工核查、对照实验都基于这份 txt,实验完全可追溯。
  • 目录结构

    AiMotOcSort/
    ├── detector.py # YOLO 检测器封装类(加载模型、设备选择、帧推理、解析结果)
    ├── oc_sort.py # OC-Sort 跟踪器封装类(OcSortArgs 参数配置 + OcSortTracker 统一接口 + OcTrack 轨迹对象 + det_status 检测去向还原)
    ├── README.md # 工程说明(环境安装、快速开始、文档导航)
    ├── requirements.txt # 依赖清单(pip install -r 用)
    │
    ├── 📁 ocsort/ # OC-SORT 核心实现(原样拷贝自 OC_SORT-master/trackers/ocsort_tracker/,禁止修改)
    │ ├── ocsort.py # OCSort 主跟踪器 + KalmanBoxTracker 轨迹类
    │ ├── kalmanfilter.py # 卡尔曼滤波器 KalmanFilterNew(含 ORU 冻结/回滚机制 freeze/unfreeze)
    │ └── association.py # 关联算法(iou/giou/diou/ciou/ct_dist 代价 + speed_direction_batch VDC + linear_assignment 匈牙利分配)
    │
    ├── 📁 ultralytics/ # 内置 Ultralytics YOLO 库(仅用于检测,免安装)
    │
    ├── 📁 utils/ # 工具函数
    │ ├── __init__.py
    │ ├── draw.py # draw_tracking 轨迹绘制(边框、ID、帧信息)
    │ ├── logger.py # 日志封装
    │ ├── paths.py # 路径工具
    │ └── config.py # 配置读取(读 configs/system_config.json)
    │
    ├── 📁 configs/ # 配置文件目录
    │ └── system_config.json # 系统配置(日志级别等)
    │
    ├── 📁 logs/ # 运行日志目录
    │ └── app.log # 日志输出(utils/logger.py 写入)
    │
    ├── 📁 test/ # 测试与分析脚本
    │ ├── test_ocsort.py # 跟踪主测试脚本(检测 → 跟踪 → 可视化 → 输出)
    │ ├── frame_viewer.py # 逐帧看片器(结果 txt 叠加原视频逐帧查看)
    │ ├── analyze_track_results.py # 单次运行分析(7 张图 + markdown 报告)
    │ ├── analyze_track_compare.py # 两次运行对比(参数对比表 + 8 张对比图 + 报告)
    │ └── 📁 out/ # 运行输出目录(ocsort_<模型>_<视频>_<时间戳>,含视频和 txt)
    │
    ├── 📁 model/ # 模型文件目录
    │ ├── yolov8s.pt # YOLOv8s 检测模型
    │ ├── yolo11s.pt # YOLO11s 检测模型
    │ ├── yolov5su.pt # YOLOv5su 检测模型
    │ └── yolo26s.pt # YOLO26s 检测模型
    │
    ├── 📁 video/ # 视频文件目录
    │ ├── dense.mp4 # 密集交叉场景测试视频
    │ └── turn.mp4 # 转弯减速场景测试视频
    │
    └── 📁 doc/ # 项目文档目录
    ├── 项目介绍.md # 工程详解(架构、参数、算法流程、测试方法、工具用法)
    ├── 项目结构.md # 本文件
    ├── OCSort源码分析.md # 主跟踪器逐段源码解读
    ├── 卡尔曼滤波预测过程.md # 卡尔曼运动预测原理(含 ORU 冻结/回滚)
    ├── IoU代价与VDC方向一致性融合过程.md # 代价计算与全局最优分配
    └── *.md # 其余文档


    三、检测器(ultralytics)

    3.1 检测器选型

    检测端走 ultralytics 框架,凡是符合 ultralytics 框架的 YOLO 检测器都能使用,不限于下面四个系列,也不限于 s 档。下表是本工程实际内置的四个:

    检测器模型文件说明
    YOLO26 model/yolo26s.pt 2026 年 1 月发布,端到端无 NMS 头(默认 one-to-one,最多输出 300 框)。注意:无 NMS 模式下 iou 参数不生效,要传统 NMS 行为需显式 end2end=False
    YOLO11 model/yolo11s.pt 实测对跟踪最友好——框时序一致性最好,建档噪音最低
    YOLOv8 model/yolov8s.pt 经典款,本工程全部测试数据基于它
    YOLOv5 model/yolov5su.pt v5 的 anchor-free 升级版(u 版),检出框最多(没有原版 yolov5s.pt:原版权重属于老 yolov5 仓库,新框架只收录 u 版——写 yolov5s.pt 也会被自动替换并下载 yolov5su.pt;u 版只是把 anchor-based 检测头换成 anchor-free,骨干与训练数据不变)

    规模档位:每个系列都有 n / s / m / l / x 五档——n 最小最快,x 最大最准。本工程统一用 s 档:速度比较快,检测精度也适合车流跟踪,是速度与精度的折中。换档只改文件名(如 yolov8n.pt、yolov8m.pt),命令不变。

    3.2 检测器参数

    跟检测器有关的参数都挂在命令行上,完整示例:

    python ./test/test_ocsort.py –model model/yolov8s.pt –video video/dense.mp4 –conf 0.25 –classes 2 –imgsz 1280

    参数默认说明
    –model model/yolov8s.pt 检测模型路径。任何 ultralytics 框架的 YOLO 权重都能填,换系列、换规模只改文件名
    –conf 0.25 检测置信度阈值,低于它的检测框直接丢弃。车流场景实测 0.25 合适。注意与跟踪端 –track-thresh 的关系:OC-SORT 的低分池是 0.1 ~ track_thresh(0.6),–conf 低于 0.6 才有框能进低分池——想用 –use-byte 低分二次关联,就保持 0.25 让低分框有量可救
    –iou 0.7 检测 NMS 的 IoU 阈值,影响同帧重复框的抑制强度(YOLO26 无 NMS 模式下不生效,见 3.1)
    –classes 2 类别过滤,填 COCO 类别 ID:2 = car。跟踪器不区分类别,同类目标可合并多个 ID,跟全部车辆用 –classes 1 2 3 5 7(bicycle / car / motorcycle / bus / truck,即 COCO 80 类里的全部车型);不传则不过滤
    –imgsz 640 推理输入尺寸(边长像素):填数字用该档位,填 0 自动用视频原始宽高(圆整到 32 的倍数)。实测:720p 用 640 够用(本工程全部测试即此);1080p 用 640 检出目标变少、置信度也变低;用原始分辨率检测最完整,但算力不足时 FPS 掉得很低——GPU 可以支撑原分辨率检测,只有 CPU 建议用默认 640

    本工程所有测试用的都是官方预训练好的通用模型(COCO 80 类),没有针对车辆检测专门训练,因此通用模型检车会有比较多的漏检;要提高检车能力,可以专门训练车辆检测模型——这是本工程后续优化的方向。

    imgsz 对比效果(dense.mp4 同一帧,yolov8s,两次运行只差 –imgsz,CPU 下的测试):

    640×640(默认): 在这里插入图片描述

    1280×720(视频原分辨率):

    在这里插入图片描述

    对比可见原分辨率档把 640 档漏掉的远处小目标明显补齐(左上角信息条的 num 为本帧目标数,原分辨率档明显更高),代价是推理计算量大、FPS 相应下降。直观印证上表结论:原分辨率检出最完整但算力开销大,GPU 可以支撑,CPU 建议用默认 640。


    四、轨迹算法(OC-SORT)

    4.1 每帧 update() 流程

    #mermaid-svg-xxuxpqIWaHK7EpPP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xxuxpqIWaHK7EpPP .error-icon{fill:#552222;}#mermaid-svg-xxuxpqIWaHK7EpPP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xxuxpqIWaHK7EpPP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xxuxpqIWaHK7EpPP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xxuxpqIWaHK7EpPP .marker.cross{stroke:#333333;}#mermaid-svg-xxuxpqIWaHK7EpPP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xxuxpqIWaHK7EpPP p{margin:0;}#mermaid-svg-xxuxpqIWaHK7EpPP .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP .cluster-label text{fill:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP .cluster-label span{color:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP .cluster-label span p{background-color:transparent;}#mermaid-svg-xxuxpqIWaHK7EpPP .label text,#mermaid-svg-xxuxpqIWaHK7EpPP span{fill:#333;color:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP .node rect,#mermaid-svg-xxuxpqIWaHK7EpPP .node circle,#mermaid-svg-xxuxpqIWaHK7EpPP .node ellipse,#mermaid-svg-xxuxpqIWaHK7EpPP .node polygon,#mermaid-svg-xxuxpqIWaHK7EpPP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xxuxpqIWaHK7EpPP .rough-node .label text,#mermaid-svg-xxuxpqIWaHK7EpPP .node .label text,#mermaid-svg-xxuxpqIWaHK7EpPP .image-shape .label,#mermaid-svg-xxuxpqIWaHK7EpPP .icon-shape .label{text-anchor:middle;}#mermaid-svg-xxuxpqIWaHK7EpPP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xxuxpqIWaHK7EpPP .rough-node .label,#mermaid-svg-xxuxpqIWaHK7EpPP .node .label,#mermaid-svg-xxuxpqIWaHK7EpPP .image-shape .label,#mermaid-svg-xxuxpqIWaHK7EpPP .icon-shape .label{text-align:center;}#mermaid-svg-xxuxpqIWaHK7EpPP .node.clickable{cursor:pointer;}#mermaid-svg-xxuxpqIWaHK7EpPP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xxuxpqIWaHK7EpPP .arrowheadPath{fill:#333333;}#mermaid-svg-xxuxpqIWaHK7EpPP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xxuxpqIWaHK7EpPP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xxuxpqIWaHK7EpPP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xxuxpqIWaHK7EpPP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xxuxpqIWaHK7EpPP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xxuxpqIWaHK7EpPP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xxuxpqIWaHK7EpPP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xxuxpqIWaHK7EpPP .cluster text{fill:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP .cluster span{color:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xxuxpqIWaHK7EpPP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xxuxpqIWaHK7EpPP rect.text{fill:none;stroke-width:0;}#mermaid-svg-xxuxpqIWaHK7EpPP .icon-shape,#mermaid-svg-xxuxpqIWaHK7EpPP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xxuxpqIWaHK7EpPP .icon-shape p,#mermaid-svg-xxuxpqIWaHK7EpPP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xxuxpqIWaHK7EpPP .icon-shape .label rect,#mermaid-svg-xxuxpqIWaHK7EpPP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xxuxpqIWaHK7EpPP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xxuxpqIWaHK7EpPP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xxuxpqIWaHK7EpPP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    置信度 ≥ 0.6 → 高分框

    0.1 < 置信度 < 0.6 → 低分框

    置信度 ≤ 0.1

    配上的

    落单的轨迹

    否,默认

    否:低分框直接丢弃

    是

    配上的,恢复轨迹跟踪

    轨迹仍落单

    落单的低分框

    落单的高分框

    配上的

    轨迹仍落单

    惯性外推超过 max_age 30 帧

    高分框仍落单

    本帧配上 且 连续命中 ≥ 3 或冷启动期

    漏检中或资格不够

    检测器输出本帧全部检测框

    第一步:按置信度分拣

    主池 dets

    低分池 dets_second

    直接丢弃

    第二步:全部轨迹批量预测逐条 predict 外推预测框含 NaN 的坏轨迹直接删除

    第三步 第一轮关联:高分框 vs 预测框IoU + VDC 方向分,阈值 0.3

    轨迹以检测框执行 update登记观测、刷新速度方向、卡尔曼修正

    BYTE 轮:use_byte 开启?默认关闭

    第四步 OCR 找回轮:落单高分框 vs 落单轨迹最近真实观测框 last_boxes,纯 IoU,阈值 0.3

    直接丢弃低分框没有建档资格

    BYTE 轮匹配:落单轨迹 vs 低分框纯 IoU,阈值 0.3

    第五步 冻结:update 传入 Nonehistory_obs 追加 None,ORU 快照,惯性外推等找回

    第六步 判死 pop 删除编号不回收

    第五步 建档:建新轨迹,领新 ID连续配满 min_hits 3 帧才有输出资格

    第六步:输出与清理,倒序遍历全部轨迹

    输出 x1,y1,x2,y2,idid 加 1 从 1 起

    不输出,继续等下帧

    流程图文字说明:

    第一步:按置信度分拣。检测框按置信度分三路:≥0.6 进主池参与关联;0.1~0.6 进低分池(BYTE 轮备用,默认关闭);≤0.1 直接丢弃。进关联前坐标已按 img_info/img_size 换算回原图坐标系。

    第二步:批量预测。全部存活轨迹逐条卡尔曼外推(x=Fx,位置各加一次速度),预测框凑成 trks;预测出 NaN 的坏轨迹连行带本体一起删除。同时备好三组数据:惯性方向 velocities、最近真实观测 last_boxes、k 帧前观测 k_observations。

    第三步 第一轮关联。高分框 vs 预测框,IoU + VDC 方向分(惯性方向与"参考点看检测"方向的一致性:同向加分、反向减分,×0.2 权重、×检测置信度),匈牙利求全局最优(无竞争时走快路径跳过求解器),逐对按 IoU ≥ 0.3 终审。配上的轨迹用检测框执行 update 完成修正。

    第四步 找回轮。BYTE 轮(默认关,进门条件:use_byte 开启 且 低分池非空 且 有落单轨迹):落单轨迹 vs 低分框纯 IoU,阈值 0.3;配上的低分框恢复轨迹跟踪,落单的低分框直接丢弃(低分框没有建档资格)。OCR 找回轮:落单高分框 vs 落单轨迹的 last_boxes(最近真实观测,不是漂走的预测框),纯 IoU、阈值 0.3,配上的恢复跟踪——这是 OC-SORT 观测中心思想的找回通道。

    第五步 冻结与建档。仍落单的轨迹 update(None):history_obs 追加 None、第一帧漏检 ORU 快照冻结,进入惯性外推。仍落单的高分框建新轨迹领新 ID,当帧不输出,连续配满 3 帧才有资格。

    第六步 输出与清理。倒序遍历:本帧配上且(连续命中 ≥ 3 或冷启动期)的轨迹输出 [x1,y1,x2,y2,id](ID 从 1 起,坐标优先用最近真实观测框);惯性外推超过 30 帧的判死删除。输出条件(<1)与判死条件(>30)互斥,不会删到刚输出的轨迹。

    六步的逐任务分解表、每步实例说明与三轮关联对比详情见 OCSort源码分析.md 第二、三章;VDC 方向分的计算过程详见 IoU代价与VDC方向一致性融合过程.md。

    4.2 原理简述

    一句话:检测器的漏检不可怕,可怕的是漏检期间把滤波器状态和找回参考点都搞丢。三个机制各堵一个洞:

    • VDC 管"别配错"——密集交叉时方向一致性压制轨迹拐到旁车;
    • OCR 管"找得回"——用真实观测框当找回锚点;
    • ORU 管"找回之后还是干净的"——冻结/插值重放洗掉漏检期的参数污染(详见 卡尔曼滤波预测过程.md 3.3 / 3.4 节)。

    与 ByteTrack 的结构性差异对照见 OCSort源码分析.md。两个最影响行为的区别:置信度不进匹配代价(只做分拣 + 轻微缩放 VDC 项,低分真目标不会被系统性压制);无未确认轨迹转正考试(用 hit_streak ≥ 3 静默把关,前 3 帧不出结果)。


    五、对照实验(–use-byte 低分二次关联)

    OC-SORT 的机制与关联参数全部原生开关化。本章以 –use-byte(BYTE 低分二次关联,默认关)为例做单开关对照实验——同样的方法适用于 –asso、–inertia、–track-thresh 等任何参数。

    验证铁律:同视频、同参数,基线与开关两次运行只差一个开关——换检测器等于换基线,跨模型结果不可直接比。

    5.1 跑基线(不开 use-byte)

    # dense.mp4 密集交叉场景基线(全部参数取默认)
    python ./test/test_ocsort.py –model model/yolov8s.pt –video video/dense.mp4 –conf 0.25 –classes 2
    # → test/out/ocsort_yolov8s_dense_1028_141014/

    5.2 跑对照(同一命令加开关)

    # use-byte 验证:同一命令只加 –use-byte
    python ./test/test_ocsort.py –model model/yolov8s.pt –video video/dense.mp4 –conf 0.25 –classes 2 –use-byte
    # → test/out/ocsort_yolov8s_dense_1028_140912/

    5.3 核查与对比

    基线和开关各跑一次后:

  • 人工核查痛点:用 frame_viewer.py 逐帧看基线结果——–conf 0.25 放进来的 0.25~0.6 低分框(遮挡中的车居多)在不开 use-byte 时整批被跟踪器过滤,目标时隐时现、断跟换 ID;
  • 逐帧核查开关效果:同样用 frame_viewer 打开 use-byte 结果对照核查,确认低分框被落单轨迹吃进、断跟目标以原 ID 续命;
  • 自动对比:analyze_track_compare.py 把两次运行的 txt 全套指标画成对比图(实际命令见 7.4)。
  • 三个工具的具体用法见第七章。


    六、结果对比

    6.1 use-byte 对照实测(dense.mp4,两次运行只差一个开关)

    指标基线(use-byte 关)use-byte 开
    检测框总数 9181 9181
    被跟踪器过滤 3588 0
    匹配框数 5593 7401
    参与关联但未配上 0 1780
    检测框利用率(匹配 ÷ 检测总数) 60.9% 80.6%
    轨迹总条数 55 52
    短命轨迹(≤5 帧) 4 0

    两次运行的全部参数在对比报告的表1(参数配置对比)里并排列出、差异行自动标红——除"低分池二次关联"一行外全部相同,验证铁律自证(表1 实测图见 7.4)。

    数字解读:

    • 基线里 0.25~0.6 的低分框共 3588 个(占检测总数 39%)整批进不了关联——这些多是遮挡中的车。use-byte 开后它们进入第二轮关联,其中 1808 个(约一半)被落单轨迹吃进:检测框利用率 60.9% → 80.6%,轨迹条数 55 → 52、短命轨迹 4 → 0——遮挡目标不再靠"删了重建"续命。
    • 未匹配数从 0 变 1780:这些是参加了第二轮但没配上的低分框。低分池永不建档(见 4.1 第五步),它们只是"给了机会没救活",不产生假 ID。

    七、测试分析工具

    test/ 目录四个脚本构成完整工具链:跑测试 → 逐帧看片 → 单次分析 → 两次对比。

    7.1 跟踪主测试脚本(test_ocsort.py)

    检测 → 跟踪 → 可视化 → 输出,一条命令跑完整流水线(第五章所有命令都是它)。输出目录名 = ocsort_<模型>_<视频>_<时间戳>,内含可视化视频 + 逐帧结果 txt(开头记录本次全部配置参数,保证可复现)。

    全部参数:

    参数默认说明
    –video video/dense.mp4 输入视频
    –camera -1 摄像头索引(-1 用视频文件,0/1/2 用对应摄像头)
    –frame-interval 1 帧间隔,每隔 N 帧处理一次,跳过的帧不解码
    –display 关 弹出实时跟踪窗口
    –model model/yolov8s.pt 检测模型路径(选型与参数详见第三章)
    –conf 0.25 检测置信度阈值
    –iou 0.7 检测 NMS IoU 阈值
    –classes 2 类别过滤,2 = car(只跟车)
    –imgsz 640 推理尺寸,0 = 视频原始分辨率
    –track-thresh 0.6 高低分分拣线 det_thresh(低分池 = 0.1 ~ 它)
    –iou-thresh 0.3 关联匹配有效 IoU 下限
    –track-buffer 30 丢失轨迹保留帧数(按 30fps 基准换算)
    –min-hits 3 轨迹输出资格线(连续配上帧数)
    –deltat 3 VDC 速度方向估计的时间间隔(帧)
    –inertia 0.2 VDC 方向一致性项权重
    –asso iou 关联相似度:iou / giou / diou / ciou / ct_dist
    –use-byte 关 低分池二次关联(开启需 –conf 低于 track-thresh)

    结果 txt 格式(后续分析和标注都基于它):

    frame:237, detect:22, trackfilter:0, match:20, unmatch:2 ← 帧汇总
    detect:1 , trackid:54 , conf:0.62, match:true , box:[235.3,504.3,333.8,566.5] ← 框1配上了轨迹54
    detect:2 , trackid:N , conf:0.28, match:false, box:[…] ← 框2谁都没配上

    7.2 逐帧看片器(frame_viewer.py)

    把结果 txt 的检测框和 ID 叠加到原视频上逐帧查看,人工核查轨迹问题。

    实际例子(核查第五章 dense.mp4 基线运行):

    python test/frame_viewer.py –video video/dense.mp4 –txt test/out/ocsort_yolov8s_dense_1028_141014/dense.txt
    # 打开窗口 → ←/→ 单帧翻页、g 跳帧,肉眼核查遮挡目标的断跟与换 ID

    按键功能
    ← / → 上一帧 / 下一帧
    ↑ / ↓ 大步进(步长默认 10 帧)
    空格 播放 / 暂停
    g 跳帧模式:输帧号回车跳转
    e 设置步长
    q 退出

    看片效果(dense.mp4 基线运行):

    在这里插入图片描述

    打开后停在第 1 帧处于暂停态。窗口左上角信息条实时显示 frame:1/394(当前处理帧 / 总处理帧数)、步长、播放状态和按键提示;画面上每辆车叠加检测框,框上的数字就是轨迹 ID。看片时重点盯两件事:同一辆车上出现两个数字 = 双轨迹,翻帧时数字突然变了 = 换 ID——配合 g 跳到问题帧、← / → 单帧前后翻,即可逐处核查第五章的对照结果。

    7.3 单次运行分析(analyze_track_results.py)

    对一次运行的 txt 做整体画像。全部输出保存在 txt 同目录:7 张 png 图 + 1 份 markdown 分析报告(dense_report.md),报告内嵌全部图表,每张图都附解读——绘制内容、从图反馈的信息、现象调节建议。

    报告图表清单:

    图表文件看什么
    图1 逐帧指标数量 dense_frame_overview.png 每帧检测数 / 被过滤数 / 匹配成功数 / 匹配失败数四条曲线——检测稳不稳、框有没有被用上
    图2 性能指标逐帧变化 dense_performance_indicators.png 被过滤率 / 匹配率 / 未匹配率三条比例曲线
    图3 轨迹路径图 dense_track_paths.png 每条轨迹中心点的移动路径(绿圆 = 起点,红方 = 终点)——轨迹抖动、断裂一眼看出
    图4 轨迹生命周期 dense_track_lifecycle.png 每条轨迹 ID 的存活时间段横条,红色间隙 = 断裂——短命轨迹直接暴露
    图5 轨迹 ID 出现频率 dense_track_id_frequency.png 每个轨迹 ID 出现的帧数柱状图,从未出现的 ID 标 0
    图6 轨迹置信度散点 dense_track_confidence_scatter.png 每条轨迹的置信度分布——哪条轨迹长期低置信度
    图7 置信度分布 dense_confidence_distribution.png 匹配 / 未匹配框按置信度区间并排统计

    实际例子:

    python test/analyze_track_results.py test\\out\\ocsort_yolov8s_dense_1028_141014\\dense.txt
    # → test/out/ocsort_yolov8s_dense_1028_141014/ 下生成 dense_*.png + dense_report.md

    报告图表实测效果(dense.mp4 基线运行):

    图1 逐帧指标数量:

    在这里插入图片描述

    图2 性能指标逐帧变化:

    在这里插入图片描述

    图3 轨迹路径图:

    在这里插入图片描述

    图4 轨迹生命周期: 在这里插入图片描述

    图5 轨迹 ID 出现频率:

    在这里插入图片描述

    图6 轨迹置信度散点:

    在这里插入图片描述

    图7 置信度分布:

    在这里插入图片描述

    适合场景:拿到一次新结果(比如第一次换 yolo26s 跑),先整体看画像——检测数是否稳定、置信度分布是否健康、未配上框多不多——再决定要不要深入逐帧排查。

    7.4 两次运行对比(analyze_track_compare.py)

    把任意两次运行的 txt 放一起成对对比(开关对开关、参数对参数、模型对模型)。输出保存在 test/out/ 下以两次运行名命名的目录:1 张参数配置对比表 + 8 张对比图(compare_*.png)+ 1 份对比报告(compare_report.md)。

    报告图表清单:

    图表文件看什么
    表1 参数配置对比 compare_config_compare.png 两次运行的检测器与跟踪全部参数并排列出,不同的行自动标红——两次运行到底差在哪一眼看出
    图1 逐帧指标数量对比 compare_frame_overview_compare.png 检测数 / 过滤数 / 匹配数 / 未匹配数逐帧并排
    图2 性能指标对比 compare_performance_indicators_compare.png 匹配率等比例曲线逐帧并排
    图3 轨迹路径对比 compare_track_paths_compare.png 两次运行的轨迹路径并排
    图4 轨迹生命周期对比 compare_track_lifecycle_compare.png 轨迹存活时间段并排——短命轨迹有没有被压掉,这张最直观
    图5 ID 出现频率对比 compare_track_id_frequency_compare.png 每个轨迹 ID 的出现帧数并排
    图6 置信度散点对比 compare_track_confidence_scatter_compare.png 每条轨迹的置信度分布并排
    图7 置信度分布对比 compare_confidence_distribution_compare.png 匹配 / 未匹配框的置信度区间分布并排
    图8 关键性能指标对比 compare_key_indicators_compare.png 两次运行的关键指标汇总对比——开关有没有效先看这张

    实际例子(第五章 use-byte 对照实验):

    python test/analyze_track_compare.py –txt_path_a=test\\out\\ocsort_yolov8s_dense_1028_140912\\dense.txt –name_a=BYTE –txt_path_b=test\\out\\ocsort_yolov8s_dense_1028_141014\\dense.txt –name_b=OCR
    # → test/out/ 下生成 dense_BYTE_vs_dense_OCR/ 目录,内含 compare_*.png + compare_report.md

    –name_a/–name_b 不只用在图表显示名,输出目录名也用它拼(目录 = dense_BYTE_vs_dense_OCR,生成在 test/out/ 下),建议用英文避免路径出现中文。

    报告图表实测效果(dense.mp4 基线 vs use-byte 开):

    表1 参数配置对比:

    在这里插入图片描述

    图1 逐帧指标数量对比:

    在这里插入图片描述

    图2 性能指标对比:

    在这里插入图片描述

    图3 轨迹路径对比:

    在这里插入图片描述

    图4 轨迹生命周期对比:

    在这里插入图片描述

    图5 ID 出现频率对比:

    在这里插入图片描述

    图6 置信度散点对比:

    在这里插入图片描述

    图7 置信度分布对比:

    在这里插入图片描述

    图8 关键性能指标对比:

    在这里插入图片描述

    适合场景:任何两次运行对比——基线对开关、模型对模型、参数调前对调后。


    八、总结

    本工程围绕车流场景搭建了一套检测与跟踪解耦的多目标跟踪系统:检测端基于 Ultralytics 框架,任意 YOLO 系列模型即插即换;跟踪端将 OC-SORT 核心独立成包,坐标契约、det_status 还原、参数换算等适配全部收在封装层;配套从运行测试、逐帧核查、单次分析到两次运行对比的完整工具链,每次运行全量落盘、实验完全可追溯。

    OC-SORT 的三大机制(VDC 防配错、OCR 找得回、ORU 洗参数)与全部关联参数原生开关化,不改代码即可实验。use-byte 对照实验实测有效:低分二次关联把检测框利用率从 60.9% 提到 80.6%、短命轨迹清零——被遮挡目标的续命通道在密集车流场景确实生效。

    项目仍有局限:检测端沿用官方通用预训练模型,未针对车辆专门训练,检车漏检偏多;OC-SORT 无外观特征,目标离开视野很久后重现无法重识别(ID 必换);宽高比 r 无速度建模,车辆大幅侧转时面积/形状预测有误差。后续优化方向:一是专门训练车辆检测模型,提高检车能力、减少漏检;二是在本工程继续做原生参数对照实验(asso 关联函数横评、inertia/VDC 权重、track_thresh 分拣线)。

    九、 链接

    描述链接
    代码 基于 Ultralytics + OC-SORT的多目标跟踪与改进
    往期回顾 链接
    数据集 数据集汇总
    软件开发 深度学习软件开发汇总
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【AI视觉软件 05】基于 Ultralytics + OC_SORT的多目标跟踪(支持YOLO全系列,重点车跟踪)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!