【Deep-OC-SORT 01】多轨迹算法Deep-OC-SORT源码分析

一、代码解析

覆盖 deep_ocsort/ 核心包(移植自 Deep-OC-SORT-main 的 integrated_ocsort_embedding 变体)中本工程实际使用的类、方法与模块级函数------源码里未被调用的 filterpy 遗留方法与预留接口不列入。分析以本工程实际代码为准。


🎯 1. EmbeddingComputer类(外观特征提取器:裁剪预处理 → ckpt.t7 推理 → L2 归一化,支持整框/网格两模式)

文件: deep_ocsort/embedding.py(重写适配层)

类初始化 输入 默认值 作用
__init__ model_path: 权重路径 None(用包内 deep/checkpoint/ckpt.t7) 记录路径,模型惰性加载
grid_off: 网格开关 True True 整框单特征,False 上中下三条
max_batch: 推理批大小 256 分批前向上限
use_cuda: 允许用 GPU True 与 torch.cuda.is_available() 共同决定设备
类属性 类型 输入 作用
self.model Net/None None ReID 网络(首次用时 _initialize_model 加载)
self.grid_off bool grid_off 入参 网格模式开关
self.max_batch int max_batch 入参 推理批大小
self.crop_size 元组 (64, 128) 裁剪 resize 目标(宽64 高128,Market1501 约定)
self.device torch.device cuda 可用且 use_cuda 时 cuda,否则 cpu 推理设备(源码硬编码 cuda+half,此处自适应)
self.model_path 字符串 解析后的权重路径 ckpt.t7 位置
类定义方法 输入 输出 作用
_initialize_model 无 无 Net(reid=True) 加载 ckpt.t7 权重(兼容包 net_dict 键),to(device) 并 eval
_crop_to_tensor img: 原图(BGR) x1,y1,x2,y2: 框坐标 1xCxHxW 张量 裁剪→BGR 转 RGB→resize 64x128→/255→ImageNet 均值方差归一化→CHW 张量(空块黑图兜底)
_get_horizontal_split_patches img: 原图 box: 检测框 3xCxHxW 张量 框横切上中下三等分,各走一遍 _crop_to_tensor
compute_embedding img: 原图 bbox: Nx4 框 Nx512(grid_off)/ Nx3x512(网格)数组 ① 惰性加载模型 ② 逐框产 patch(整框 1 块或网格 3 块) ③ cat 后按 max_batch 分批前向 ④ L2 归一化 ⑤ 网格模式 reshape 回 (N,3,512) ⑥ 转 cpu numpy

🎯 2. CMCComputer类(相机运动补偿器:估计相邻帧仿射变换,供轨迹整体搬移)

文件: deep_ocsort/cmc.py(重写适配层)

类初始化 输入 默认值 作用
__init__ minimum_features: 最少匹配点数 10 低于它不改仿射(返回单位矩阵)
method: 估计方法 "sparse" sparse 稀疏光流 / sift 特征匹配
类属性 类型 输入 作用
self.minimum_features int minimum_features 入参 最少匹配点数门槛
self.prev_img 数组/None None 上一帧灰度图(光流法基准)
self.prev_desc 光流角点或 SIFT 描述子/None None 上一帧特征(光流法为角点数组,SIFT 法为 kp, desc)
self.sparse_flow_param dict 预设 goodFeaturesToTrack 参数(maxCorners=3000、qualityLevel=0.01、minDistance=1、blockSize=3)
self.comp_function 函数 按 method 选 仿射估计函数(_affine_sparse_flow / _affine_sift)
类定义方法 输入 输出 作用
compute_affine img: 当前帧(BGR) bbox: Nx4 本帧检测框 2x3 仿射矩阵 ① 转灰度 ② 检测框区域内掩码置 0(运动目标区域不取点) ③ 调 comp_function 估计仿射
_affine_sparse_flow frame: 灰度图 mask: 取点掩码 2x3 仿射矩阵 ① goodFeaturesToTrack 取角点 ② 首帧只记录返回单位矩阵 ③ calcOpticalFlowPyrLK 跟踪上帧角点 ④ 匹配点 > minimum_features 时 estimateAffinePartial2D(RANSAC) 估刚体变换,失败兜底单位矩阵 ⑤ 记录本帧角点
_affine_sift frame: 灰度图 mask: 取点掩码 2x3 仿射矩阵 ① SIFT 提取描述子 ② 暴力匹配 + Lowe 0.7 比值筛好点 ③ 好点足够时 RANSAC 估仿射,不足返回单位矩阵 ④ 记录本帧描述子

🎯 3. OCSort类(主跟踪器:每帧分拣 → 特征 → CMC → 两轮关联 → 建档/冻结 → 输出清理)

文件: deep_ocsort/ocsort.py

类初始化 输入 默认值 作用
__init__ det_thresh: 检测置信度门槛 - ≤ 它的检测框直接丢弃,不设低分二次关联
max_age: 丢失判死帧数 30 time_since_update 超过它即删轨迹
min_hits: 输出资格线 3 连续命中达到才有输出
iou_threshold: 关联 IoU 下限 0.3 两轮关联的终审阈值
delta_t: VDC 回看帧数 3 传给 KalmanBoxTracker
asso_func: 关联相似度函数名 "iou" 从 ASSO_FUNCS 取 iou/giou/diou/ciou/ct_dist
inertia: VDC 权重 0.2 方向一致性项的缩放系数
w_association_emb: 外观权重 0.75 外观代价的基准权重(AW 在它附近浮动)
alpha_fixed_emb: EMA 基准系数 0.95 检测分越高越接近它,分低趋近 1
aw_param: AW 差异上限 0.5 compute_aw_new_metric 的 max_diff
embedding_off: 关外观 False True 时不算检测特征
cmc_off: 关 CMC False True 时不算仿射校正
aw_off: 关 AW False True 时外观权重固定为 w_association_emb
new_kf_off: 关 8 维 KF False True 时轨迹用 7 维 KF
grid_off: 关网格 False True 时整框单特征(Nx512),False 上中下三条(Nx3x512)
embedder: 外观特征提取器 None 源码无此参数(构造函数内部自建 FastReID 版);本工程新增,None 时自建适配版 EmbeddingComputer,也可由封装层传入实例
cmc: 相机运动补偿器 None 源码无此参数(构造函数内部自建);本工程新增,None 时自建适配版 CMCComputer,也可由封装层传入实例
类属性 类型 输入 作用
self.max_age / min_hits / iou_threshold 数值 同名构造参数 判死线 / 输出资格线 / 关联终审阈值
self.trackers list \[\] 存活轨迹(KalmanBoxTracker)列表
self.frame_count int 0 已处理帧数
self.det_thresh 数值 det_thresh 检测门槛
self.delta_t int delta_t VDC 回看帧数
self.asso_func 函数 ASSO_FUNCSasso_func 关联相似度函数
self.inertia 数值 inertia VDC 权重
self.w_association_emb / alpha_fixed_emb / aw_param 数值 同名构造参数 外观权重 / EMA 基准 / AW 上限
self.embedder EmbeddingComputer embedder 入参 外观特征提取器
self.cmc CMCComputer cmc 入参 相机运动补偿器
self.embedding_off / cmc_off / aw_off / new_kf_off / grid_off bool 同名构造参数 五个新增模块的开关
类定义方法 输入 输出 作用
update output_results: Nx5 检测数组 img_numpy: 当前帧原图(BGR) Kx5 数组 x1,y1,x2,y2,id 一帧执行步骤: ① 帧计数加 1,按第 5 列取置信度,只留 > det_thresh 的框 ② 未关外观且非空时 embedder.compute_embedding 提取检测特征 ③ 未关 CMC 时 cmc.compute_affine 估计仿射,全体轨迹 apply_affine_correction ④ 算每个检测的 EMA 系数 dets_alpha(分低趋 1 信历史) ⑤ 全体轨迹 predict 出预测框 trks 与特征 trk_embs,NaN 坏轨迹删除 ⑥ 备 velocities / last_boxes / k_observations ⑦ 第一轮 associate(IoU+VDC+外观AW) ⑧ 配上的 update(bbox) + update_emb(emb, dets_alpha) ⑨ 第二轮 OCR:落单检测 vs 落单轨迹 last_boxes 纯 IoU 重匹配,配上的同样 update + update_emb ⑩ 仍落单轨迹 update(None) 冻结 ⑪ 仍落单检测建新轨迹(带特征、按开关选 KF) ⑫ 倒序遍历输出(本帧配上且 hit_streak≥min_hits 或冷启动期)与判死(time_since_update>max_age)

🎯 4. KalmanBoxTracker类(轨迹类:一条轨迹的运动状态、观测历史与外观特征)

文件: deep_ocsort/ocsort.py

类初始化 输入 默认值 作用
__init__ bbox: 建档检测框 x1,y1,x2,y2,conf(5 元素数组) - 用检测框初始化卡尔曼状态,并登记计数器与观测记录
delta_t: VDC 速度方向估计回看帧数 3 存入 self.delta_t 供 update 取历史观测用
orig: True 用 filterpy 原版 KF False False 用本文件 KalmanFilterNew(含 ORU 机制)
emb: 该检测框的外观特征(512 维或 3x512 维数组) - 新轨迹的初始特征
alpha: 初始 EMA 系数 - 兼容入参,构造体内未使用(EMA 在 update_emb 时才生效)
new_kf: True 用 8 维 KF - False 退回 7 维 KF(中心+面积+宽高比建模)
类属性 类型 输入 作用
self.kf KalmanFilterNew 构造时按 new_kf 建 8 维或 7 维 卡尔曼滤波器(F/H/P/R/Q 按 KF 类型配置)
self.new_kf bool new_kf 入参 记住 KF 类型,决定 update/predict 走哪套坐标转换与噪声
self.bbox_to_z_func 函数 按 new_kf 选 convert_bbox_to_z_new / convert_bbox_to_z 检测框转观测向量的函数
self.x_to_bbox_func 函数 按 new_kf 选 convert_x_to_bbox_new / convert_x_to_bbox 状态向量转检测框的函数
self.time_since_update int 0 距上次配上检测的帧数(predict 加 1、配上归 0)
self.id int 类计数 count 自增后取值 轨迹全局编号(从 0 起,输出时 +1)
self.history list \[\] predict 过程的预测框历史(每次 update 清空)
self.hits int 0 累计配上检测次数
self.hit_streak int 0 连续配上检测次数(漏一帧即清 0,输出资格线)
self.age int 0 经历的 predict 次数(帧年龄,观测字典的键)
self.last_observation 数组 -1,-1,-1,-1,-1 占位 最近一次真实观测框(OCR 找回轮的锚点;全 -1 表示还没配上过)
self.history_observations list \[\] 全部真实观测框的时序列表
self.observations dict {} 帧年龄 → 观测框 的字典(VDC 取 Δt 前观测用)
self.velocity 数组/None None 由观测对估计的归一化速度方向 vy, vx(VDC 用)
self.delta_t int delta_t 入参 VDC 回看帧数
self.emb 数组 emb 入参 轨迹外观特征(EMA 维护,第一轮关联的外观锚点)
self.frozen bool False 漏检冻结标记(冻结时 predict 把宽高速度清零)
count 类属性 0(OCSort 构造时清零) 轨迹总数计数器,给新轨迹发号
类定义方法 输入 输出 作用
update bbox: 配上的检测框或 None 无 ① 框非空:置 frozen=False,从 observations 取 Δt 前观测与 last_observation 算速度方向,登记 last_observation / observations / history_observations,time_since_update 归 0、hits 与 hit_streak 加 1,卡尔曼修正(8 维按目标尺寸算 R) ② 框为 None:KF.update(None) 触发 ORU 冻结快照,置 frozen=True
update_emb emb: 检测特征 alpha: 本次 EMA 系数 无 轨迹特征 EMA 更新:emb = alpha*旧 + (1-alpha)*新,再 L2 归一化
get_emb 无 数组 返回当前轨迹特征
apply_affine_correction affine: 2x3 仿射矩阵 无 相机运动补偿:把本轨迹记住的框整体搬进新相机坐标系 ① 更新 self.last_observation:4------OCR 找回轮的锚点框,按角点仿射(sum>0 守门,全 -1 占位不搬) ② 更新 self.observationsage-Δt...age 中有记录帧的观测框------VDC 速度方向的历史依据 ③ 转发 self.kf.apply_affine_correction------KF 状态/协方差/冻结快照随之搬移(见 🎯5 同名方法) 总结:三份记忆搬到同一坐标系后,predict 与两轮关联比较的坐标才同帧,差值纯粹是目标自身运动
predict 无 1x4 预测框 ① 8 维时负宽高保护(w+vw≤0 则 vw=0,h+vh 同理) ② frozen 时把宽高速度清零 ③ 8 维按当前宽高算 Q,KF.predict 外推 ④ age 加 1,time_since_update>0 时 hit_streak 清 0、time_since_update 加 1 ⑤ 预测框入 history 并返回
get_state 无 1x4 框 状态向量转 x1,y1,x2,y2(新建轨迹未配上时的输出用)

🎯 5. KalmanFilterNew类(卡尔曼滤波器:标准 predict/update + ORU 冻结回滚 + CMC 仿射校正)

文件: deep_ocsort/kalmanfilter.py(自 filterpy KalmanFilter 扩展)

类初始化 输入 默认值 作用
__init__ dim_x: 状态维数(8 维 KF 为 8,7 维为 7) - 按维数建零初始化的 x/P/Q/F/H/R 等矩阵
dim_z: 观测维数(4) - 观测向量维数
dim_u: 控制输入维数 0 未使用
类属性 类型 输入 作用
self.x / self.P 数组 zeros / eye(dim_x) 状态向量与协方差(由 KalmanBoxTracker 构造后覆写 F/H/P/Q)
self.F / self.H / self.Q / self.R 数组 eye / zeros 状态转移 / 观测映射 / 过程噪声 / 观测噪声
self.K / self.y / self.S / self.SI 数组 zeros 卡尔曼增益 / 残差 / 系统不确定度及其逆(update 时算出)
self.x_prior / P_prior、x_post / P_post 数组 x/P 副本 预测后 / 修正后的状态与协方差快照
self.inv 函数 np.linalg.inv S 求逆函数(可换伪逆)
self.history_obs list \[\] 观测序列(None 表示漏检帧,ORU 用)
self.attr_saved dict/None None freeze() 存的整个属性字典快照
self.observed bool False 上一帧是否有观测(False→True 触发 unfreeze)
self.last_measurement 数组/None None 冻结前的最后一次观测(ORU 虚拟轨迹起点)
类定义方法 输入 输出 作用
predict u/B/F/Q 可选覆盖 无 标准预测:x=Fx,P=FPF'+Q(8 维 KF 每帧传入按当前宽高算的 Q)
update z: 观测向量或 None R: 观测噪声覆盖 H: 观测映射覆盖 new_kf: KF 类型 无 ① z=None 且刚有观测:记 last_measurement、freeze() 存快照,置 observed=False ② z 非 None 且 observed 为 False:unfreeze(new_kf) 做在线平滑找回 ③ 标准修正:y=z-Hx、S=HPH'+R、K=PH'S⁻¹、x=x+Ky、P=(I-KH)P(I-KH)'+KRK' ④ z 入 history_obs
freeze 无 无 deepcopy 整个 dict 存 attr_saved(ORU 快照)
unfreeze new_kf: KF 类型 无 ORU 找回重放:① 恢复 freeze 时的属性字典(history_obs 保留并去掉最后一个 None) ② 取冻结前最后观测 box1 与找回观测 box2(7 维先换算 w/h) ③ 按帧距线性插值出虚拟轨迹(匀速假设,每步 x/y/w/h 等差) ④ 逐点 update(+predict) 重放洗参数
apply_affine_correction m: 2x2 线性部分 t: 2x1 平移 new_kf: KF 类型 无 相机运动补偿:KF 内部状态整体搬进新相机坐标系 8 维:① 更新 self.x------big_m=kron(I4,m) 全量变换(4 对 (值,速度) 各按 m 旋转缩放),t 只加在中心 x,y ② 更新 self.P------协方差共轭变换 big_m·P·big_mᵀ ③ 冻结中(observed=False 且有快照)更新 self.attr_saved------快照 x/P 同①②,快照 last_measurement 前 2 位(中心)仿射、后 2 位(宽高)只旋转缩放 7 维:① 更新 self.x:2(+t 旋转)与 x4:6(vx,vy 旋转),s、r 不动(缩放尝试已注释) ② 更新 self.P 的 :2,:2 与 4:6,4:6 两个子块 ③ 冻结中同样只搬快照的 x:2/x4:6、P 两子块、last_measurement 前 2 位 总结:状态、不确定度、ORU 冻结快照三者同步搬移,保证找回后 unfreeze 回放也在新坐标系展开

🎯 6. BasicBlock类(残差基础块:ReID 骨干的组成单元)

文件: deep_ocsort/deep/model.py

类初始化 输入 默认值 作用
__init__ c_in: 输入通道数 - 建两层 3x3 卷积(下采样块首层步长 2)+ BN + ReLU
c_out: 输出通道数 - 通道变换目标
is_downsample: 空间下采样 False True 时旁路 1x1 步长 2 投影;通道数不等时自动加 1x1 投影并置 True
类属性 类型 输入 作用
self.is_downsample bool 入参或通道不等时自动置 True forward 是否走旁路投影
self.conv1 / bn1 / conv2 / bn2 层 构造时建 主路两层卷积各带 BN
self.relu 层 ReLU(True) 激活
self.downsample Sequential/无 需要时建 1x1 卷积 + BN 的旁路投影
类定义方法 输入 输出 作用
forward x: 输入张量 残差输出张量 主路 conv1→bn1→relu→conv2→bn2,需要时 x 走 downsample 投影,最后 relu(x+y)

🎯 7. Net类(ReID 网络:resnet18 风格骨干,reid=True 输出 512 维 L2 归一化特征)

文件: deep_ocsort/deep/model.py

类初始化 输入 默认值 作用
__init__ num_classes: 分类头类别数 751 分类分支输出维(reid 模式不用)
reid: 是否取特征 False True 输出 512 维归一化特征,False 走分类头
类属性 类型 输入 作用
self.conv Sequential 预设 首层 3→64 卷积 + BN + ReLU + 3x3 最大池化
self.layer1 Sequential make_layers(64,64,2,False) 64 通道不降采样残差组
self.layer2 Sequential make_layers(64,128,2,True) 128 通道降采样残差组
self.layer3 Sequential make_layers(128,256,2,True) 256 通道降采样残差组
self.layer4 Sequential make_layers(256,512,2,True) 512 通道降采样残差组
self.avgpool 层 AdaptiveAvgPool2d(1) 全局平均池化 → 512 维
self.reid bool reid 入参 前向分支选择
self.classifier Sequential 512→256→num_classes 分类头(reid 模式不用)
类定义方法 输入 输出 作用
forward x: Bx3x128x64 输入张量 reid=True: Bx512 归一化特征 reid=False: Bxnum_classes 得分 conv→layer1~4→avgpool→展平→reid 时 L2 归一化直接返回,否则过分类头

📍 8. k_previous_obs函数(取 Δt 帧前的历史观测)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
k_previous_obs observations: 帧年龄→观测字典 cur_age: 当前帧年龄 k: 回看帧数 观测框(5 元素数组),空历史返回全 -1 占位 找当前帧 age 往前第 k 帧内最近的有效观测(VDC 的参考点) ① 历史为空返回 -1,-1,-1,-1,-1 ② 从 dt=k 往下试到 1,第一个命中 cur_age-dt 的观测返回 ③ 都没有返回最老一条

📍 9. convert_bbox_to_z函数(7 维 KF:检测框转观测向量)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
convert_bbox_to_z bbox: x1,y1,x2,y2 检测框 4x1 数组 x, y, s, r 检测框转 7 维 KF 的观测向量(中心+面积+宽高比) ① 算宽 w、高 h ② 中心 x=x1+w/2、y=y1+h/2 ③ s=w*h、r=w/(h+1e-6) ④ reshape 成 4x1

📍 10. convert_bbox_to_z_new函数(8 维 KF:检测框转观测向量)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
convert_bbox_to_z_new bbox: x1,y1,x2,y2 检测框 4x1 数组 x, y, w, h 检测框转 8 维 KF 的观测向量(中心+宽高,直接建模尺寸) ① 算宽高 ② 中心坐标 ③ reshape 成 4x1

📍 11. convert_x_to_bbox_new函数(8 维 KF:状态向量转检测框)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
convert_x_to_bbox_new x: 8 维状态列向量 1x4 数组 8 维状态 x,y,w,h,... 转回 x1,y1,x2,y2 ① 取前 4 位 x/y/w/h ② 中心加减半宽半高

📍 12. convert_x_to_bbox函数(7 维 KF:状态向量转检测框)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
convert_x_to_bbox x: 7 维状态列向量 score: 置信度(可选) 1x4(或 1x5)数组 7 维状态 x,y,s,r,... 转回 x1,y1,x2,y2(可附 score) ① w=sqrt(s*r)、h=s/w 由面积与宽高比反解宽高 ② 中心加减半宽半高得角点 ③ 带 score 时拼成 5 元

📍 13. speed_direction函数(单轨迹速度方向估计)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
speed_direction bbox1: 参考观测框 bbox2: 当前观测框 2 元数组 vy, vx(归一化) 两观测框中心连线方向(KalmanBoxTracker.update 维护 VDC 惯性方向用) ① 算两框中心 ② 中心差向量归一化

📍 14. new_kf_process_noise函数(8 维 KF 过程噪声)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
new_kf_process_noise w, h: 当前目标宽高 p: 位置噪声系数 1/20 v: 速度噪声系数 1/160 8x8 对角阵 按目标实际尺寸生成 Q(大目标噪声绝对值大,相对比例一致) ① 位置 4 项方差 (pw)² / (p h)² ② 速度 4 项方差 (vw)² / (v h)² ③ np.diag 拼对角阵

📍 15. new_kf_measurement_noise函数(8 维 KF 观测噪声)

文件: deep_ocsort/ocsort.py

函数 输入 输出 作用 执行步骤
new_kf_measurement_noise w, h: 当前目标宽高 m: 观测噪声系数 1/20 4x4 对角阵 按目标尺寸生成 R(每次 update 重算) ① w 方差 (mw)²、h 方差 (m h)² ② x,y,w,h 对角排列

📍 16. iou_batch函数(IoU 矩阵)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
iou_batch bboxes1: Nx4 bboxes2: Mx4 NxM 矩阵 两组框两两 IoU(默认关联相似度) ① 广播算交集角点与宽高 ② 交面积 / 并面积

📍 17. giou_batch函数(GIoU 矩阵)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
giou_batch bboxes1: Nx4 bboxes2: Mx4 NxM 矩阵(0~1) 广义 IoU:不相交时按外接框差距给负分(先算 IoU 再折到 0~1) ① 算 IoU ② 算最小外接框面积 ③ giou = iou - (外接-交)/外接,再 (g+1)/2 折到 0~1

📍 18. diou_batch函数(DIoU 矩阵)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
diou_batch bboxes1: Nx4 bboxes2: Mx4 NxM 矩阵(0~1) 距离 IoU:中心距惩罚版 ① 算 IoU 与两框中心距平方 ② 算外接框对角线平方 ③ diou = iou - 中心距/外接对角线,折到 0~1

📍 19. ciou_batch函数(CIoU 矩阵)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
ciou_batch bboxes1: Nx4 bboxes2: Mx4 NxM 矩阵(0~1) 完整 IoU:DIoU 再加宽高比一致性惩罚 ① 算 DIoU 部分 ② arctan 宽高比差算 v 与 alpha ③ ciou = diou - alpha*v,折到 0~1

📍 20. ct_dist函数(中心距相似度)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
ct_dist bboxes1: Nx4 bboxes2: Mx4 NxM 矩阵(0~1) 中心欧氏距离转相似度(源码注明:粗糙实现,对帧率与速度敏感,不推荐单独用) ① 算中心距 ② 按全矩阵 max 归一 ③ 取 max-距离 折成越大越像

📍 21. speed_direction_batch函数(VDC 方向矩阵)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
speed_direction_batch dets: Nx5 检测 tracks: Mx5 参考框(last_observation) (Y, X) 两个 MxN 矩阵 参考框中心指向各检测中心的方向(归一化 dy、dx),VDC 与惯性方向求夹角用 ① 算两组中心 ② 差向量归一化 ③ 返回 (dy, dx)(注意行列顺序为 轨迹x检测)

📍 22. linear_assignment函数(全局最优分配)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
linear_assignment cost_matrix: 代价矩阵(越小越好) Kx2 匹配对数组 匈牙利算法求全局最优指派 ① 优先 lap.lapjv(extend_cost=True) ② 无 lap 库时回退 scipy.linear_sum_assignment

📍 23. compute_aw_new_metric函数(AW 自适应加权矩阵)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
compute_aw_new_metric emb_cost: 外观相似度矩阵 w_association_emb: 基准权重 0.75 max_diff: 差异上限 0.5 同形状权重矩阵 按行列的"最像与第二像差距"给外观权重发奖金:差距大(区分度高)话语权加重 ① 全阵初始化为基准权重、奖金阵置 0 ② 每行(检测视角):top1 与 top2 相似度差(封顶 max_diff)的一半加到该行奖金 ③ 每列(轨迹视角)同样发一半奖金 ④ 返回 基准 + 奖金

📍 24. split_cosine_dist函数(网格特征分条余弦相似度)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
split_cosine_dist dets: Nx3x512 检测网格特征 trks: Mx3x512 轨迹网格特征 affinity_thresh: 0.55 pair_diff_thresh: 0.6(未用) hard_thresh: True NxM 相似度矩阵 网格模式外观比对:检测与轨迹的上中下三条特征分别比,任何一条不像即判 0(硬阈值) ① 逐对算 3x3 余弦相似度阵 ② 检测每条取与轨迹三条的最大值(patch_affinity) ③ 硬阈值:三条全过 affinity_thresh 才取 max,否则 0

📍 25. associate函数(第一轮关联:IoU + VDC + 外观 AW 总代价)

文件: deep_ocsort/association.py

函数 输入 输出 作用 执行步骤
associate detections: Nx5 检测 trackers: Mx5 预测框 det_embs: 检测特征 trk_embs: 轨迹特征 iou_threshold: 0.3 velocities: 惯性方向 previous_obs: k_observations 参考点 vdc_weight: 0.2 w_assoc_emb: 0.75 aw_off / aw_param / emb_off / grid_off: 开关与参数 (matches, unmatched_dets, unmatched_trks) 三元组 Deep-OC-SORT 核心关联:运动 + 方向 + 外观三路代价融合后全局最优分配 ① speed_direction_batch 算参考点→检测方向,与惯性方向点积得夹角余弦,折算 diff_angle 成本(π/2 内为正奖励) ② previous_obs 第 5 位 <0(无有效观测)的轨迹方向项置 0(valid_mask) ③ iou_batch 算 IoU 矩阵 ④ angle_diff_cost = valid_mask×diff_angle×vdc_weight,转置后乘检测置信度 ⑤ 未关外观:整框模式算 det_embs @ trk_embs.T 点积相似度,网格模式走 split_cosine_dist ⑥ IoU 超阈值且无竞争走快路径直接配 ⑦ 否则 AW 开时 emb_cost × compute_aw_new_metric 权重、关时 × 固定 w_assoc_emb ⑧ final_cost = -(iou + angle_diff_cost + emb_cost),匈牙利分配 ⑨ 低 IoU 匹配对拆回落单

📍 26. make_layers函数(残差组搭建)

文件: deep_ocsort/deep/model.py

函数 输入 输出 作用 执行步骤
make_layers c_in: 输入通道 c_out: 输出通道 repeat_times: 重复块数 is_downsample: 首块是否降采样 nn.Sequential 给 Net 拼一组 BasicBlock ① 首块 c_in→c_out(可选降采样) ② 其余块 c_out→c_out ③ Sequential 串接

二、轨迹跟踪步骤

OCSort.update() 每帧执行一次,加上开头的数据准备与收尾的输出清理,共可分七步:数据准备与分拣 → 外观特征与相机运动补偿 → 批量预测备料 → 第一轮关联 → OCR 找回轮 → 冻结与建档 → 输出与清理。一条轨迹在跟、丢失还是死亡,全部由 time_since_update(漏检计数)和 hit_streak(连续命中计数)两个计数器表达,收尾一个倒序循环同时完成输出与判死。

2.1 第一步:数据准备与分拣

任务 变量 实例说明
① 帧计数与判空 self.frame_count self.frame_count += 1:帧计数每帧加 1;output_results 为 None 时直接返回 0×5 空数组 例:上一帧 3 → 本帧 4(收尾的冷启动期判断 frame_count ≤ min_hits 用它)
② 拆分数组 scores、bboxes 输入是 N×5 时直接拆列:scores 取第 5 列、bboxes 取前 4 列(6 列输入走 obj×cls 联合置信度路线,本工程封装层传 5 列,不走) 例:3 个检测框 → scores = [0.85, 0.35, 0.05],bboxes = [[100,200,150,300], [400,300,450,380], [600,100,650,180]]
③ 拼回 N×5 dets np.concatenate((bboxes, scores)) 拼成 x1,y1,x2,y2,置信度(封装层传入的已是原图坐标,无需换算) 例:dets = [[100,200,150,300,0.85], [400,300,450,380,0.35], [600,100,650,180,0.05]]
④ 单路分拣 dets 只有 scores > det_thresh(默认 0.3) 一条门槛:0.3 以上的留主池,≤0.3 的全部直接丢弃------本算法不做低分二次关联:门槛以下的框不进任何一轮比对 例:dets = [[100,200,150,300,0.85], [400,300,450,380,0.35]](两框都 > 0.3 全留),0.05 那框直接丢弃

这一步的产出 :主池高分框 dets------后续特征提取、两轮关联、建档全部只用它;0.3 以下的框一步淘汰,没有备用通道,保证进入关联的每个框都带着可信的外观特征。

2.2 第二步:外观特征提取与相机运动补偿

任务 变量 实例说明
① 检测特征提取 dets_embs 先给占位 np.ones((N,1))(关外观时保住后续数组运算的形状);embedding_off=False 且主池非空时 embedder.compute_embedding(img, dets[:,:4]) 批量提取 ReID 特征 例:默认网格模式,2 个框 → dets_embs 形状 (2, 3, 512)(每框上中下三条各 512 维);--emb_off 时占位不提取
② CMC 仿射估计 transform cmc.compute_affine(img, dets[:,:4]):检测框区域遮掉后,从背景角点的光流估 2×3 仿射(首帧或特征不足给单位阵,等于不动) 例:相机右移 3 像素 → transform 平移列 ≈ -3, 0
③ 全轨迹记忆搬家 trk.apply_affine_correction(transform) 逐条轨迹把三份记忆一起搬进新相机坐标系:last_observation(OCR 锚)、observations 回看窗(VDC 依据)、KF 状态/协方差/冻结快照 例:下轮预测与关联比较时坐标已同帧,差值只剩目标自身运动
④ EMA 系数备料 dets_alpha trust = (conf−0.3)/0.7 把置信度折成 0~1 信任度,dets_alpha = 0.95 + 0.05×(1−trust) 例:conf=0.85 → alpha≈0.961(新特征占约 3.9%);conf=0.35 → alpha≈0.996(几乎全信历史,防低质框污染轨迹特征)

这一步的产出 :dets_embs(第一轮外观比对的检测端)、坐标系统一的全部轨迹、逐检测的 EMA 系数 dets_alpha。

2.3 第三步:批量预测与备料

任务 变量 实例说明
① 逐轨迹外推 trks 全零 M×5 数组,逐条调 KalmanBoxTracker.predict():卡尔曼 x=Fx 外推(8 维 KF 先做负宽高保护、冻结轨迹把宽高速度清零、按当前宽高算 Q),预测框填入对应行(第 5 列恒 0 占位) 例:某轨迹预测框 [104,206,154,306] 填进 trks
② 坏轨迹清理 to_del、trks、trk_embs 预测框含 NaN(如面积项变负后开根号)的轨迹:trks 压缩掉坏行、trackers 弹出本体(倒序删防下标位移);轨迹特征 trk_embs 只收好轨迹,与压缩后的 trks 行行对齐 例:轨迹 7 的预测框含 NaN → trks 删第 7 行,trackers 弹出第 7 条,trk_embs 不收它
③ 备三组轨迹数据 velocities 每条轨迹的惯性方向 (dy,dx)(KalmanBoxTracker.update 算好存的),从没配上过观测的轨迹用 (0,0) 例:velocities = [[-0.707, 0.707], [0, 0]](轨迹0 往右上走,轨迹1 还没观测过)
last_boxes 每条轨迹最近一次真实观测框 x1,y1,x2,y2,置信度,没观测过的是 -1×5 占位(OCR 找回轮的比对锚) 例:last_boxes = [[102,198,152,298,0.82], [-1,-1,-1,-1,-1]]
k_observations 每条轨迹约 delta_t(3) 帧前的观测框(k_previous_obs 从 observations 字典回查),作 VDC 的方向参考点;没观测过的是 -1×5 例:k_observations = [[96,204,146,304,0.88], [-1,-1,-1,-1,-1]]

这一步的产出:trks 预测框与 trk_embs 轨迹特征(第一轮关联比对的两端)、三组轨迹数据,坏轨迹已移除。

2.4 第四步:第一轮关联(高分框 vs 预测框,IoU + VDC + 外观)

用谁配谁:主池高分检测框 vs 全部存活轨迹的卡尔曼预测框,一对一全局分配;配对代价由 IoU、VDC 方向分、外观分三路合成,三路都高的对才配得上。

任务 变量 实例说明
① 进 associate matched、unmatched_dets、unmatched_trks associate(dets, trks, dets_embs, trk_embs, iou_threshold=0.3, velocities, k_observations, inertia=0.2, ...) 返回三个下标数组:K×2 配对表(每行=检测下标, 轨迹下标)、落单检测下标、落单轨迹下标
② 算三路相似度 iou_matrix、angle_diff_cost、emb_cost iou_batch 算 N×M IoU 矩阵;VDC 链:轨迹参考点看检测的方向与惯性方向点积转方向分(同向 +0.5 / 垂直 0 / 反向 −0.5),×inertia(0.2)、转置、×检测置信度;外观链:整框模式 dets_embs @ trk_embs.T 余弦点积,网格模式 split_cosine_dist 上中下三条全过 0.55 才认 例(1 检测 × 2 轨迹):iou = [0.72, 0.05],外观相似度 = 0.83, 0.31
③ 外观加权 emb_cost AW 开(默认):emb_cost × compute_aw_new_metric,权重按"最像与第二像的差距"在基准 0.75 附近上浮(奖金封顶 0.5)------外观相似度 0.83, 0.31 区分度高,轨迹0 那格的话语权加码;AW 关(--aw_off):×固定 0.75
④ 分配 matched_indices 候选图(IoU 超阈记 1)每行每列至多 1 个候选时直接取格子(快路径,跳过求解器);否则匈牙利分配 −(IoU + 方向分 + 外观分) 求全局最优 例:匈牙利配出 [[0,0]]
⑤ 逐对终审 matched、unmatched_dets、unmatched_trks 配好的每一对按 IoU 阈值 0.3 复核,不够的拆散、双方并入落单 例:0.72 那对通过 → matched = [[0,0]]
⑥ 兑现配对 self.trackers 遍历 matched:trackers[m[1]].update(dets[m[0], :]) 卡尔曼修正、登记观测;紧跟 update_emb(dets_embs[m[0]], alpha=dets_alpha[m[0]]) 按本检测的 EMA 系数更新轨迹特征------运动与外观双修正 例:轨迹0 以检测0 的框执行 update,轨迹特征按约 96:4 混入检测0 的新特征再归一化

这一步的产出 :配上轨迹完成修正、延续跟踪;落单检测 unmatched_dets 与落单轨迹 unmatched_trks 带去第五步找回轮。

2.5 第五步:OCR 找回轮(纯 IoU)

用谁配谁 :落单高分框 vs 落单轨迹的 last_boxes(最近真实观测框,不是预测框)------轨迹丢失期间预测框会漂走,目标重现的位置更接近它上次被看见的地方。

任务 变量 实例说明
① 进门条件 unmatched_dets、unmatched_trks 双方都有落单才进
② 纯 IoU iou_left dets[unmatched_dets] vs last_boxes[unmatched_trks],asso_func 纯 IoU(无 VDC、无外观------代码里切了落单双方的特征但没用上,TODO 注明 maybe use embeddings here) 例:检测1 vs 轨迹1 的 last_boxes=-1,-1,-1,-1,-1(轨迹1 刚建档没观测过)→ IoU 全是垃圾值
③ 门卫与分配 rematched_indices iou_left.max() > 0.3 才继续;匈牙利分配 −IoU;逐对终审,低于 0.3 的拆散
④ 兑现配对 self.trackers 配上的轨迹 update(高分框) + update_emb(双下标回译 unmatched_dets[m[0]]、unmatched_trks[m[1]]),检测、轨迹两侧名单都划掉 例:轨迹1 是占位框,找回失败 → 检测1、轨迹1 仍落单;若轨迹1 曾有过真实观测、检测1 恰好出现在附近,IoU 达标即找回------OCR 轮的价值就在这

这一步的产出:重现目标在"消失处附近"被找回,ID 不断;仍落单的双方带去第六步。

2.6 第六步:冻结(漏检)与建档(新目标)

任务 变量 实例说明
① 落单轨迹冻结 self.trackers[m].update(None) 仍落单的轨迹逐条 update(None):KF 收到 None 观测,第一帧漏检触发 freeze 把滤波参数整套快照进 attr_saved(ORU 起点)、frozen=True;此后每帧只 predict 不修正(惯性外推),冻结期 predict 把宽高速度清零防尺寸漂移 例:轨迹1 → attr_saved 存下漏检前的干净参数,等找回后 unfreeze 回放
② 落单高分框建档 trk、self.trackers 仍落单的高分框逐个 KalmanBoxTracker(dets[i,:], delta_t=3, emb=dets_embs[i], alpha=dets_alpha[i], new_kf=True) 建新轨迹:建 8 维卡尔曼、首框填初始状态、检测特征建档为初始外观、领全局 id,append 进轨迹名单 例:检测1 100,200,150,300,0.85 → 新轨迹 id=12(带初始特征),下一帧起参加关联

新轨迹当帧不输出(hit_streak=0,冷启动期 frame_count ≤ 3 除外),要连续配满 min_hits(3) 帧才有输出资格。

2.7 第七步:输出与清理(收尾)

倒序遍历全部轨迹,一趟同时完成输出与判死(倒序是因为边遍历边 pop,从尾往头删不挪还没遍历到的下标)。

任务 变量 实例说明
① 选输出坐标 d last_observation.sum() < 0(从没真实观测过)用 get_state() 卡尔曼估计框;否则用 last_observation[:4] 最近真实观测框 例:轨迹0 本帧刚配上 → 输出 102,198,152,298(就是本帧检测框本身)
② 输出资格门 ret time_since_update < 1(本帧刚配上)且(hit_streak ≥ 3 或 frame_count ≤ 3 冷启动)→ 拼 x1,y1,x2,y2,id+1 挂进 ret(id+1:MOT 规范要求 ID 从 1 起) 例:轨迹0 streak=5、id=6 → 输出 [102,198,152,298,7];漏检中的轨迹(time_since_update ≥ 1)不满足输出资格,本帧不输出
③ 判死 self.trackers time_since_update > max_age(30) 的轨迹 pop(i) 删除,编号不回收 例:连丢 31 帧的轨迹删除(输出条件 <1 与判死条件 >30 互斥,不会删到本帧刚输出的)

这一步的产出 :concatenate(ret) 成 K×5 返回 x1,y1,x2,y2,id,就是本帧跟踪结果。至此一帧闭环:分拣(2.1)→ 特征与补偿(2.2)→ 预测备料(2.3)→ 第一轮关联(2.4)→ OCR 找回(2.5)→ 冻结与建档(2.6)→ 输出与清理(2.7)。

三、轨迹算法整体流程图(每帧 update())

#mermaid-svg-fQrJKiFoG7qP2a0q{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-fQrJKiFoG7qP2a0q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fQrJKiFoG7qP2a0q .error-icon{fill:#552222;}#mermaid-svg-fQrJKiFoG7qP2a0q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fQrJKiFoG7qP2a0q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fQrJKiFoG7qP2a0q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fQrJKiFoG7qP2a0q .marker.cross{stroke:#333333;}#mermaid-svg-fQrJKiFoG7qP2a0q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fQrJKiFoG7qP2a0q p{margin:0;}#mermaid-svg-fQrJKiFoG7qP2a0q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q .cluster-label text{fill:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q .cluster-label span{color:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q .cluster-label span p{background-color:transparent;}#mermaid-svg-fQrJKiFoG7qP2a0q .label text,#mermaid-svg-fQrJKiFoG7qP2a0q span{fill:#333;color:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q .node rect,#mermaid-svg-fQrJKiFoG7qP2a0q .node circle,#mermaid-svg-fQrJKiFoG7qP2a0q .node ellipse,#mermaid-svg-fQrJKiFoG7qP2a0q .node polygon,#mermaid-svg-fQrJKiFoG7qP2a0q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fQrJKiFoG7qP2a0q .rough-node .label text,#mermaid-svg-fQrJKiFoG7qP2a0q .node .label text,#mermaid-svg-fQrJKiFoG7qP2a0q .image-shape .label,#mermaid-svg-fQrJKiFoG7qP2a0q .icon-shape .label{text-anchor:middle;}#mermaid-svg-fQrJKiFoG7qP2a0q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fQrJKiFoG7qP2a0q .rough-node .label,#mermaid-svg-fQrJKiFoG7qP2a0q .node .label,#mermaid-svg-fQrJKiFoG7qP2a0q .image-shape .label,#mermaid-svg-fQrJKiFoG7qP2a0q .icon-shape .label{text-align:center;}#mermaid-svg-fQrJKiFoG7qP2a0q .node.clickable{cursor:pointer;}#mermaid-svg-fQrJKiFoG7qP2a0q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fQrJKiFoG7qP2a0q .arrowheadPath{fill:#333333;}#mermaid-svg-fQrJKiFoG7qP2a0q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fQrJKiFoG7qP2a0q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fQrJKiFoG7qP2a0q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fQrJKiFoG7qP2a0q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fQrJKiFoG7qP2a0q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fQrJKiFoG7qP2a0q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fQrJKiFoG7qP2a0q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fQrJKiFoG7qP2a0q .cluster text{fill:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q .cluster span{color:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q 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-fQrJKiFoG7qP2a0q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fQrJKiFoG7qP2a0q rect.text{fill:none;stroke-width:0;}#mermaid-svg-fQrJKiFoG7qP2a0q .icon-shape,#mermaid-svg-fQrJKiFoG7qP2a0q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fQrJKiFoG7qP2a0q .icon-shape p,#mermaid-svg-fQrJKiFoG7qP2a0q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fQrJKiFoG7qP2a0q .icon-shape .label rect,#mermaid-svg-fQrJKiFoG7qP2a0q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fQrJKiFoG7qP2a0q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fQrJKiFoG7qP2a0q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fQrJKiFoG7qP2a0q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 置信度 > 0.3
置信度 ≤ 0.3
配上的
落单的高分框
落单的轨迹
配上的
轨迹仍落单
惯性外推超过 max_age 30 帧
高分框仍落单
本帧配上 且 连续命中 ≥ 3 或冷启动期
漏检中或资格不够
检测器输出本帧全部检测框
第一步:按置信度分拣

只留 > 0.3 的框
主池 dets
直接丢弃

不参与任何关联与建档
第二步:外观特征与相机补偿

embedder 提取检测特征 dets_embs

cmc 估仿射,全部轨迹记忆搬家

按置信度算每个检测的 EMA 系数 dets_alpha
第三步:全部轨迹批量预测

逐条 predict 外推预测框

含 NaN 的坏轨迹直接删除
第四步 第一轮关联:高分框 vs 预测框

IoU + VDC 方向分 + 外观分,阈值 0.3
轨迹执行 update + update_emb

卡尔曼修正 + 特征 EMA 更新
第五步 OCR 找回轮:落单高分框 vs 落单轨迹

最近真实观测框 last_boxes,纯 IoU,阈值 0.3
第六步 冻结:update 传 None

ORU 快照 freeze,惯性外推等找回
第七步 判死 pop 删除

编号不回收
第六步 建档:建新轨迹

8 维卡尔曼 + 初始外观特征,领新 ID

连配 min_hits 3 帧才有输出资格
第七步:输出与清理,倒序遍历全部轨迹
输出 x1,y1,x2,y2,id

id 加 1 从 1 起
不输出,继续等下帧

流程图文字说明:

第一步:按置信度分拣。检测框按置信度分两路:>0.3 进主池参与关联;≤0.3 直接淘汰,没有备用通道------门槛以下的框置信度不足、外观特征不可靠,直接舍弃以保证进关联的每个框都可信。坐标已是原图坐标系,无需换算。

第二步:外观特征与相机补偿。主池检测框批量提取 ReID 特征(默认网格模式:上中下三条各 512 维);CMC 从背景估 2×3 仿射,全部轨迹把 last_observation、observations 回看窗、KF 状态/协方差/冻结快照一起搬进新相机坐标系;再按检测置信度算每个检测的 EMA 系数 dets_alpha(分高信新特征、分低信历史)。

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

第四步 第一轮关联。高分框 vs 预测框,三路代价融合:IoU + VDC 方向分(同向加分、反向减分,×0.2 权重、×检测置信度)+ 外观分(余弦相似度 ×0.75 基准权重,AW 开时按区分度上浮、关时固定),匈牙利求全局最优(无竞争时走快路径跳过求解器),逐对按 IoU ≥ 0.3 终审。配上的轨迹 update 修正运动、update_emb 按 EMA 系数混入新特征------运动与外观双修正。

第五步 OCR 找回轮。落单高分框 vs 落单轨迹的 last_boxes(最近真实观测,不是漂走的预测框),纯 IoU、阈值 0.3(配前 max 预检、配后逐对复核),配上的恢复跟踪并同样做特征 EMA------这是观测中心思想的找回通道,目标重现于消失处附近时 ID 不断。

第六步 冻结与建档。仍落单的轨迹 update(None):第一帧漏检 ORU 快照冻结,进入惯性外推。仍落单的高分框建新轨迹:8 维卡尔曼 + 检测特征作初始外观、领新 ID,当帧不输出,连续配满 3 帧才有资格。

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

两轮关联对比详情:

对比项 第一轮(主关联轮) 第二轮(OCR 找回轮)
场景 每帧常态:目标按预期运动,预测框贴合检测框 目标漏检若干帧后重现,重现位置靠近消失位置(预测框已惯性漂走)
谁 vs 谁 全部高分检测框(>0.3)vs 全部存活轨迹的卡尔曼预测框(trks) 落单高分检测框 vs 落单轨迹的最近真实观测框(last_boxes)
方法 IoU + VDC 方向分(×0.2 权重、再 ×检测置信度)+ 外观分(余弦 ×0.75,AW 按区分度上浮)合成代价,匈牙利求全局最优(无竞争走快路径),逐对按 IoU ≥ 0.3 终审 纯 IoU + 匈牙利,阈值 0.3(配前 max 预检、配后逐对复核)
作用 常态配对、运动与外观双修正;VDC 防交叉运动换 ID,外观分防同貌目标换 ID 用真实观测锚找回被漂移预测框甩掉的配对,保持 ID 不断
触发条件 主池检测框与存活轨迹都非空才执行(轨迹为空时全部检测直接建档) 落单检测框与落单轨迹两侧都非空才执行
比对锚 卡尔曼预测框:补偿帧间运动,快速目标不掉队 最近真实观测框:不用漂走的预测框,承载"目标在消失处重现"假设
配上后轨迹的动作 update 修正滤波参数、刷新 last_observation、time_since_update 归 0;update_emb 按 dets_alpha EMA 更新轨迹特征 同主轮:update + update_emb 双修正
落单去向 检测框:转入 OCR 轮;轨迹:先争取 OCR 找回,仍落单则 update(None) 冻结外推 检测框:建档领新 ID(带初始特征,当帧不输出,连配 3 帧才有输出资格);轨迹:update(None) 惯性外推等待,超 30 帧判死
描述 链接
项目 基于 Ultralytics +Deep-OC-SORT多目标跟踪
往期回顾 链接
数据集 数据集汇总
软件开发 深度学习软件开发汇总
相关推荐
tellmewhoisi1 小时前
深度学习39:RNN基础介绍,api参数解惑和代码api
人工智能·rnn·深度学习
武天旭1 小时前
AI安全--AI大模型的毒性偏见与安全对齐实践
人工智能·python·深度学习
电科_银尘3 小时前
【深度学习项目实战·11】损失函数与评价指标:让模型输出、标签与结果真正对应
人工智能·深度学习
AI日报派送佬4 小时前
高效目标检测利器:X-AnyLabeling 加载自定义 YOLO 模型自动标注实操指南
人工智能·yolo·目标检测
陈嘿萌4 小时前
ECCV 2026 | CausalDrive:澳门大学×小米汽车,面向自动驾驶的实时因果世界模型
图像处理·人工智能·深度学习·计算机视觉·自动驾驶·学术研究与理论基础
七元权6 小时前
FoundationPose 推理全流程解读:从一帧 RGB-D 到 6D 位姿
论文阅读·深度学习·6d姿态估计
Neurocoda7 小时前
基于 NumPy 从零实现 Tiny-AlexNet 在 MNIST 数据集上的验证
人工智能·深度学习
AOI小白新手上路7 小时前
深度学习算力购置方案分析:本地 RTX 4090 服务器 vs 云端租赁
服务器·人工智能·深度学习
坐吃山猪8 小时前
果蝇AI06-Mac部署实操
人工智能·深度学习·机器学习·果蝇ai