Strongsort改进点
StrongSORT(论文 StrongSORT: Make DeepSORT Great Again )在 DeepSORT 基础上提出的四项在线改进 + 一项相机运动补偿。本工程全部实现为可开关参数,五项统一默认开启 (测试脚本加对应 --no-nsa / --no-ema / --no-mc / --no-wo-c / --no-ecc 关闭)。全部改进都在线逐帧完成,无离线后处理(原版 StrongSORT 的 AFLink、GSI 未引入)。
一、NSA------卡尔曼观测噪声按置信度自适应
针对的问题:DeepSORT 的卡尔曼滤波对所有检测一视同仁,观测噪声 R 固定。但检测置信度有高有低------低置信度框位置往往更不准,用同样的 R 去修正状态,会把不可靠的观测"当真",污染预测。
改进做法:观测噪声随检测置信度缩放,置信度越低、观测噪声越大、这帧观测的话语权越小。
R N S A = ( 1 − c k ) 2 ⋅ R R_{NSA} = (1 - c_k)^2 \cdot R RNSA=(1−ck)2⋅R
其中 c k c_k ck 是当前帧检测置信度。置信度 0.9 的框噪声缩到 0.01 倍(修正力度大),置信度 0.3 的框噪声缩到 0.49 倍(修正力度小)。
源码位置 :strong_sort/sort/kalman_filter.py
| 函数 | 行为 |
|---|---|
project(mean, covariance, confidence, nsa) |
第 150-151 行:if nsa: std = [(1 - confidence) * x for x in std],噪声标准差逐项乘 (1−置信度),R = diag(std²) |
update(mean, covariance, measurement, confidence, nsa) |
第 184 行:把 confidence 和 nsa 透传给 project,新息协方差 S = HPHᵀ + R_NSA |
链路 :检测置信度从 Track.update()(track.py 第 169-171 行)透传给 kf.update,再传入 kf.project。置信度一路从检测走到滤波核,无额外存储。
开关与参数 :--no-nsa 关闭(默认开启)。
二、EMA------特征指数滑动平均替代特征库
针对的问题:DeepSORT 每条轨迹维护一个特征库(Gallery,最多 nn_budget 条),匹配时取与库内所有特征的最小距离。问题有二:① 库里积攒了目标早期(可能已被遮挡前)的外观,外观变化后老特征拖后腿;② 库存取和逐条比对有开销,且库容超限后"踢掉最老的"策略并不可靠。
改进做法:轨迹特征不再攒库,而是每帧与检测特征做指数滑动平均,只保留一条不断更新的平滑特征:
e f = α ⋅ e f − 1 + ( 1 − α ) ⋅ f k α = 0.9 e_f = \alpha \cdot e_{f-1} + (1 - \alpha) \cdot f_k \qquad \alpha = 0.9 ef=α⋅ef−1+(1−α)⋅fkα=0.9
新特征只占 10% 的权重------单帧错检、光照突变不会立刻带偏轨迹特征;而目标外观的真实渐变(转动、缩放)会随时间平滑跟进。平滑结果重新归一化。
源码位置 :strong_sort/sort/track.py Track.update() 第 173-179 行
python
feature = detection.feature / np.linalg.norm(detection.feature)
if self._ema:
smooth_feat = self._ema_alpha * self.features[-1] + (1 - self._ema_alpha) * feature
smooth_feat /= np.linalg.norm(smooth_feat)
self.features = [smooth_feat] # 库里永远只有这一条平滑特征
else:
self.features.append(feature) # 关闭时退回 DeepSORT 的 Gallery 攒库
配套改动 :EMA 模式下特征库容量自动置 1(strong_sort/__init__.py 工厂函数:nn_budget = 1 if args.ema else args.nn_budget)------库里本来就只有一条,容量无需更大;NearestNeighborDistanceMetric.distance 与库内单条特征做一次余弦即为代价。
开关与参数 :--no-ema 关闭(默认开启),--ema-alpha 调平滑系数(默认 0.9)。
三、MC------关联代价融合外观与运动
针对的问题:DeepSORT 的外观关联只看特征余弦距离,马氏距离只用来"一刀切"门控(超阈值直接禁止配对,阈值内不参与代价)。两个候选检测外观都很像、又都在门控内时,目标运动趋势的信息被浪费了。
改进做法:把马氏运动距离按权重揉进关联代价,两路信息加权融合:
c i j = λ ⋅ c a p p e a r a n c e + ( 1 − λ ) ⋅ d M a h a l a n o b i s λ = 0.98 c_{ij} = \lambda \cdot c_{appearance} + (1 - \lambda) \cdot d_{Mahalanobis} \qquad \lambda = 0.98 cij=λ⋅cappearance+(1−λ)⋅dMahalanobisλ=0.98
外观仍是主导(0.98),运动距离作为细微裁决项------外观打平时,预测位置更近的那个检测胜出。
源码位置 :strong_sort/sort/linear_assignment.py gate_cost_matrix() 第 220-227 行
python
gating_threshold = kalman_filter.chi2inv95[4]
...
cost_matrix[row, gating_distance > gating_threshold] = gated_cost # 先门控:物理上不可能的配对写死 1e+5
if mc: # 再融合:λ·外观 + (1−λ)·马氏
cost_matrix[row] = mc_lambda * cost_matrix[row] + (1 - mc_lambda) * gating_distance
注意顺序:先门控后融合------被门控的格子 1e+5 也会被融合拉低一点,但量级仍远超配对上限 max_distance,不会造成错误配对。
开关与参数 :--no-mc 关闭(默认开启),--mc-lambda 调融合系数(默认 0.98)。
四、woC------去掉匹配级联,单轮全局匹配
针对的问题:DeepSORT 的外观匹配是"级联"的:按轨迹丢失帧数分层(刚丢 1 帧的先匹配,丢 2 帧的下一层......),一层一轮匈牙利算法。设计初衷是让新检测优先分给最近见过的轨迹,但代价是:① 分层后每层只跟一部分轨迹比,全局最优可能被层序拆散;② 多层循环多轮匈牙利,开销更大。
改进做法:不分层,全部确认轨迹与全部检测一轮全局匈牙利匹配比完(级联深度等效为 1)。StrongSORT 论文的消融实验显示,检测质量足够好时级联的"优先级"并不必要,单轮全局匹配反而又快又准。
源码位置 :strong_sort/sort/linear_assignment.py matching_cascade() 第 149-158 行(wo_c 分支)
python
if wo_c:
track_indices_l = [k for k in track_indices] # 全部轨迹同一轮(不分层)
matches_l, _, unmatched_detections = \
min_cost_matching(distance_metric, max_distance, tracks, detections,
track_indices_l, unmatched_detections)
matches += matches_l
else: # 原版级联:按 time_since_update 分层逐层匹配
for level in range(cascade_depth):
...
关闭 woC 时走 else 分支,行为与 DeepSORT 原版级联完全一致(第 160-175 行)。
开关与参数 :--no-wo-c 关闭(默认开启)。
五、ECC------相机运动补偿(默认开启)
针对的问题:卡尔曼预测假设相机静止------上一帧在 (100, 200) 的目标,这一帧预测外推就画在附近。相机一动(云台转动、车载),全部轨迹的预测框整体偏移,马氏门控和 IoU 匹配双双失效,ID 跳变。
改进做法:逐帧在线估计相机运动矩阵,先把上一帧的轨迹框"搬"到当前帧坐标系,再做卡尔曼预测和匹配:
① _camera_motion(strong_sort/strong_sort.py):当前帧 vs 上一帧灰度图(缩到 0.1 倍加速),调 ECC 算法估计欧氏变换矩阵 (旋转+平移,2x3);变换畸变过大(矩阵范数异常)时视为无运动。ECC 参数:eps=1e-5、max_iter=100、scale=0.1。
② Tracker.camera_update → Track.camera_update(strong_sort/sort/track.py):每条轨迹的 tlbr 两角做仿射变换,重写卡尔曼状态均值前 4 维------轨迹框先跟上相机,再进入本帧预测-匹配流程。
源码位置 :ecc.py 的 ECC() 函数(工程适配版,仅保留在线逐帧计算所需的 ECC 函数,核心是 cv2.findTransformECC)。
开关与参数 :--no-ecc 关闭(默认开启)。ECC 在缩到 0.1 倍的小图上迭代,开销很小(720p 实测约 2-3 毫秒/帧);静止相机的小幅抖动会被匹配容错吸收,开着对跟踪结果没有可见改善,介意可关闭;运动相机场景(持续平移/旋转)才能体现价值。
六、改进项速查表
| 改进 | 全称 | 作用环节 | 源码位置 | 默认 | 开关参数 |
|---|---|---|---|---|---|
| NSA | Noise Scale Adaptive Kalman Filter | 卡尔曼修正(观测噪声) | strong_sort/sort/kalman_filter.py project/update |
开启 | --no-nsa 关 |
| EMA | Exponential Moving Average | 特征更新(轨迹外观记忆) | strong_sort/sort/track.py Track.update |
开启 | --no-ema 关 |
| MC | Motion-aware Cost | 外观匹配代价(融合马氏距离) | strong_sort/sort/linear_assignment.py gate_cost_matrix |
开启 | --no-mc 关 |
| woC | without Cascade | 外观匹配策略(去级联) | strong_sort/sort/linear_assignment.py matching_cascade |
开启 | --no-wo-c 关 |
| ECC | Enhanced Correlation Coefficient | 相机运动补偿(预测前校正) | ecc.py ECC + strong_sort/strong_sort.py _camera_motion |
开启 | --no-ecc 关 |
各项相互独立、可任意组合:如只测 NSA 单项贡献,跑 --no-ema --no-mc --no-wo-c --no-ecc(保留 NSA);基线(全关、等价 DeepSORT)跑 --no-nsa --no-ema --no-mc --no-wo-c --no-ecc。对比分析方法见 test/analyze_track_compare.py 。
| 描述 | 链接 |
|---|---|
| 项目 | 基于 Ultralytics + StrongSort 多目标跟踪与改进 |
| 往期回顾 | 链接 |
| 数据集 | 数据集汇总 |
| 软件开发 | 深度学习软件开发汇总 |