☰
【Deep-OC-SORT 03】多轨迹算法Deep-OC-SORT-卡尔曼滤波预测过程
2026/11/5 3:53:09 网站建设 项目流程

卡尔曼滤波预测过程(7 维与 8 维)

Deep-OC-SORT 的每条轨迹由一个卡尔曼滤波器驱动:预测(predict)外推目标运动,修正(update)用检测框拉回。滤波器有两种规格:

规格开关状态向量特点
8 维(默认)不传--new_kf_off[x,y,w,h,vx,vy,vw,vh]宽高直接建模,噪声随目标尺寸自适应
7 维传--new_kf_off[cx,cy,s,r,vcx,vcy,vs]面积+宽高比间接表示尺寸,固定噪声

两种规格共用同一个滤波器本体KalmanFilterNew(predict / update / freeze / unfreeze / apply_affine_correction),差异在状态定义、噪声参数与保护逻辑。本文先讲 7 维——结构简单,且带建档、预测、修正、冻结、重放的逐步数字实算,是理解卡尔曼部分的最短路径;再讲 8 维(默认规格);最后给两者速查对照。

代码位置:deep_ocsort/kalmanfilter.py(滤波器本体)、deep_ocsort/ocsort.py(KalmanBoxTracker 封装:矩阵定义、噪声函数、保护逻辑、坐标转换)。


一、7 维 KF(--new_kf_off时的规格)

1.1 状态与观测

状态 x = [cx, cy, s, r, vcx, vcy, vs]ᵀ (7x1 列向量) └─中心─┘ └尺寸┘ └── 速度 ──┘ 观测 z = [cx, cy, s, r]ᵀ (4x1)
分量含义
cx, cy检测框中心坐标
s面积(scale)= w × h——尺寸不直接记 w、h,记它们的乘积
r宽高比 = w / h——面积 + 宽高比可唯一还原宽高
vcx, vcy中心速度
vs面积变化速度——r 没有速度项,宽高比被认为帧间不变

坐标转换(convert_bbox_to_z/convert_x_to_bbox):

转观测:w = x2−x1, h = y2−y1, cx = x1+w/2, cy = y1+h/2, s = w·h, r = w/(h+1e-6) 转回框:w = √(s·r), h = s/w, x1 = cx−w/2, y1 = cy−h/2, x2 = cx+w/2, y2 = cy+h/2

转移矩阵 F 与观测映射 H

# 7x7:cx += vcx、cy += vcy、s += vs 各前进一步;r 无速度保持不变F=[[1,0,0,0,1,0,0],[0,1,0,0,0,1,0],[0,0,1,0,0,0,1],[0,0,0,1,0,0,0],[0,0,0,0,1,0,0],[0,0,0,0,0,1,0],[0,0,0,0,0,0,1]]# 4x7:只观测到中心与尺寸(s、r),速度隐含H=[[1,0,0,0,0,0,0],[0,1,0,0,0,0,0],[0,0,1,0,0,0,0],[0,0,0,1,0,0,0]]

1.2 初始化与建档实算

构造(KalmanBoxTracker.__init__的 7 维分支):F、H 如上,P、Q、R 从单位阵出发三步缩放:

self.kf.R[2:,2:]*=10.0# s、r 的观测噪声 ×10:面积/宽高比的检测抖动远比中心坐标大self.kf.P[4:,4:]*=1000.0# 初速度方差 ×1000self.kf.P*=10.0# 全体方差再 ×10self.kf.Q[-1,-1]*=0.01# vs 的过程噪声 ×0.01self.kf.Q[4:,4:]*=0.01# 三个速度的过程噪声 ×0.01(vs 被缩两次 → 最小)self.kf.x[:4]=convert_bbox_to_z(bbox)# 初速度全 0

得到的三个矩阵(全部是对角阵,全局常数,与目标大小无关):

矩阵值含义
P₀(状态协方差)diag(10, 10, 10, 10, 10000, 10000, 10000)初速度高度不可信(×1000×10 = 万倍)
R(观测噪声)diag(1, 1, 10, 10)s、r 的观测噪声 ×10
Q(过程噪声)diag(1, 1, 1, 1, 0.01, 0.01, 0.0001)速度几乎不变,vs 最稳

建档实算——检测框 [x1,y1,x2,y2] = [100, 200, 180, 250]:

① 转观测:w = 180−100 = 80, h = 250−200 = 50 cx = 100+40 = 140, cy = 200+25 = 225 s = 80×50 = 4000, r = 80/50 = 1.6 z = [140, 225, 4000, 1.6]ᵀ ② 装状态:x = [140, 225, 4000, 1.6, 0, 0, 0]ᵀ ③ 装矩阵:P = P₀、R、Q、F、H(上表)

反算验证:w = √(s·r) = √6400 = 80,h = 4000/80 = 50 ✓——观测和状态能无损还原出宽高。

1.3 predict:盲推一步(实算)

x = F·x 位置外推:cx+=vcx、cy+=vcy、s+=vs(r 不动) P = F·P·Fᵀ + Q 协方差外推:不确定性随时间膨胀

实算——设跟踪若干帧后 x = [100, 200, 4000, 1.6, 20, 5, −10]ᵀ(cx 每帧 +20、cy +5、面积每帧 −10),P = diag(9, 9, 25, 1, 16, 16, 4):

x' = F·x = [100+20, 200+5, 4000−10, 1.6, 20, 5, −10]ᵀ = [120, 205, 3990, 1.6, 20, 5, −10]ᵀ P' 的非零项(P 是对角阵,P' 只在「量与同轴速度」之间长出交叉项): P'cx,cx = Pcx + 2P(cx,vcx) + Pvcx + Qcx = 9 + 0 + 16 + 1 = 26 ← 位置不确定性被速度不确定性撑大 P'cx,vcx = P(cx,vcx) + Pvcx = 0 + 16 = 16 ← 交叉项:见位置误差可反推速度误差 P'cy,cy / P'cy,vcy = 26 / 16 (与 cx 同构) P's,s = 25 + 0 + 4 + 1 = 30 P's,vs = 0 + 4 = 4 P'r,r = 1 + Qr = 2 ← r 没有速度,只加过程噪声 P'vcx,vcx = 16 + 0.01 = 16.01 (vcy 同构) P'vs,vs = 4 + 0.0001 = 4.0001

交叉项 P(cx,vcx) = 16 是下一步 update 能"看到位置误差就修速度"的关键。

KalmanBoxTracker.predict 的保护与状态维护(7 维分支):

  1. 面积保护:if s + vs ≤ 0: vs = 0——外推面积不能是负的;
  2. Q = None:不传新 Q,KF 用建好的固定 Q;
  3. 状态维护:age += 1(帧年龄,观测字典的键);上帧漏检(time_since_update > 0)则hit_streak = 0;time_since_update += 1。

1.4 update:观测修正(实算)

y = z − H·x 残差:观测与预测的差 S = H·P·Hᵀ + R 系统不确定度 K = P·Hᵀ·S⁻¹ 卡尔曼增益:在 0~1 之间调配"信预测还是信观测" x = x + K·y 状态修正:按增益分配残差 P = (I−K·H)·P·(I−K·H)ᵀ + K·R·Kᵀ 协方差收缩(Joseph 形式,数值稳定)

实算——设预测后 x = [150, 250, 5000, 0.5, 25, 0, 0]ᵀ,P(含上一轮长出的位置-速度交叉项):

cx cy s r vcx vcy vs P = [ 12 0 0 0 11 0 0 ] [ 0 12 0 0 0 11 0 ] [ 0 0 21 0 0 0 11 ] [ 0 0 0 6 0 0 0 ] [ 11 0 0 0 11 0 0 ] [ 0 11 0 0 0 11 0 ] [ 0 0 11 0 0 0 11 ]

检测观测 z = [152, 250, 5100, 0.48]ᵀ,R = diag(1, 1, 10, 10)。五步:

① y = z − H·x = [152−150, 250−250, 5100−5000, 0.48−0.5]ᵀ = [2, 0, 100, −0.02]ᵀ ② S = H·P·Hᵀ + R = diag(12+1, 12+1, 21+10, 6+10) = diag(13, 13, 31, 16) ③ K = P·Hᵀ·S⁻¹(7×4,交叉项让速度行也非零): cx 行 [0.923, 0, 0, 0 ] ← 12/13 cy 行 [0, 0.923, 0, 0 ] s 行 [0, 0, 0.677, 0] ← 21/31(s 的 R=10,增益低一截) r 行 [0, 0, 0, 0.375] ← 6/16 vcx 行 [0.846, 0, 0, 0 ] ← 11/13:位置残差直接驱动速度修正 vcy 行 [0, 0.846, 0, 0 ] vs 行 [0, 0, 0.355, 0] ← 11/31 ④ x' = x + K·y = [150+1.85, 250+0, 5000+67.7, 0.5−0.008, 25+1.69, 0, 0+35.48]ᵀ = [151.85, 250, 5067.7, 0.492, 26.69, 0, 35.48]ᵀ ⑤ P' 收缩(Joseph 形式): P'cx,cx = 0.923 ← 12 → 0.923:一次观测把位置方差压掉 92% P'cx,vcx = 0.846 P'vcx,vcx = 1.692 ← 11 → 1.69:速度方差大幅收缩 P's,vs = 3.548 P'vs,vs = 7.097 P'r,r = 3.75

注意 ④ 的 vs:单次面积残差(+100)经由交叉项直接推出"面积正以 ~35/帧 增长";而 r 无速度建模,宽高比残差(−0.02)只能修 r 自己——这正是 7 维对"目标侧转/宽高渐变"响应慢的根源(8 维的对策见第二章)。

KalmanBoxTracker.update 的观测登记与状态维护(两种规格一致):

1. frozen = False(解除漏检态) 2. 从 observations 找 Δt(3) 帧前的观测,与本次观测算 speed_direction → self.velocity(VDC 惯性方向) 3. last_observation = 本次检测框(OCR 找回轮的锚点) 4. observations[age] = 本次观测;history_observations 追加 5. time_since_update = 0;hits += 1;hit_streak += 1

若本帧是从漏检找回(此前收到过 None),KF.update 内部会先 unfreeze 重放再做本次修正——见 1.5。

1.5 ORU:漏检冻结与找回重放

问题:漏检期间每帧 predict,P 持续膨胀、速度被外推放大——找回时滤波器参数已被"盲推"污染,状态拉回容易,协方差与速度的信任关系已经乱了。ORU(Observation-centric Re-Update)的做法:漏检前冻结全部参数,找回后用两帧真实观测线性插值出"虚拟轨迹"重放 predict-update,把滤波器洗干净。

冻结(update 收到 None,即本帧这条轨迹没配上任何检测):

KF.update(None): history_obs.append(None) # 漏检帧占位 last_measurement = history_obs[-2] # 冻结前最后一次真实观测(z 空间) attr_saved = deepcopy(整个 __dict__) # 快照全部参数 observed = False # 进入漏检态 KalmanBoxTracker 侧:frozen = True (predict 时 7 维照常外推;8 维额外清零 vw/vh,见 2.4)

找回重放(unfreeze:漏检后第一次 update 收到真实观测时触发):

1. __dict__ = attr_saved 整体回滚到冻结时刻(参数、协方差全回去) history_obs 去掉末尾 None;用完整历史定位最后两个真实观测的下标 2. box1 = last_measurement(冻结前最后观测,z 空间 [cx,cy,s,r]) box2 = 本次找回的观测 7 维先换算:w = √(s·r),h = s/r——插值要在直尺寸空间做 (源码 r = w/float(h) 已适配为 w/h 数组除法,数值等价——本文件唯一适配改动处) 3. 帧距 time_gap = 两观测的下标差 4. 匀速假设线性插值虚拟轨迹: dx = (x2−x1)/gap,dy、dw、dh 同式;第 i 步 = box1 + (i+1)×步长,逐点转回 [cx,cy,s,r] 5. 逐虚拟框 update(new_box),步间 predict —— 等效于"漏检期间目标匀速走且每帧都有观测" 6. 重放完的 P、K、速度信任关系是干净的,再对本次真实观测做正常 update

重放实算——漏检 2 帧后找回,完整观测历史 = [z1, None, None, z2]:

z1 = [300, 400, 5120, 0.8](冻结前最后观测) z2 = [324, 406, 5120, 0.8](找回观测) 换算:w1 = √(5120×0.8) = 64, h1 = √(5120/0.8) = 80;z2 同宽高 下标:两个真实观测在下标 0 和 3 → time_gap = 3 步长:dx = (324−300)/3 = 8, dy = (406−400)/3 = 2, dw = dh = 0 虚拟框(每帧 update,步间 predict): i=0: [308, 402, 5120, 0.8] i=1: [316, 404, 5120, 0.8] i=2: [324, 406, 5120, 0.8] ← 最后一个只 update,不再 predict

一句话:漏检期间不积累错误,找回时当无事发生、匀速补课。

1.6 CMC:相机运动的仿射搬移(7 维分支)

相机自身在动(抖动/平移/缩放)时,目标"看起来"整体位移,运动模型全部失准——CMC 每帧先估计相机运动的仿射变换(线性部分 m 2×2 + 平移 t 2×1),把轨迹搬到新相机坐标系再谈预测。

KalmanBoxTracker.apply_affine_correction先搬观测记录(两种规格一致):last_observation 与 observations 字典里 Δt 窗口内的每个观测框,4 个角点坐标 m·p + t。

KF 状态部分走KalmanFilterNew.apply_affine_correction的 7 维分支:

self.x[:2]=m @ self.x[:2]+t# 中心:旋转缩放 + 平移self.x[4:6]=m @ self.x[4:6]# 中心速度:跟着旋转缩放(方向语义保持)self.P[:2,:2]=m @ self.P[:2,:2]@ m.T# 中心块共轭变换(保持正定)self.P[4:6,4:6]=m @ self.P[4:6,4:6]@ m.T# 速度块共轭变换# s、r(x[2]、x[6])原地不动——非线性量无法直接仿射(源码的缩放补偿尝试被注释掉)

冻结快照同步:若轨迹正处于漏检态(observed=False 且有快照),attr_saved 里的 x[:2]、x[4:6]、P 两个子块与 last_measurement[:2] 同样搬移——找回重放的起点也在新坐标系里。

代价:s、r 吃不到相机缩放补偿——相机变焦时 7 维的尺寸估计会失真,这是 8 维(见 2.6)与 CMC 配合更优的一处。


二、8 维 KF(默认,new_kf=True)

2.1 状态与观测

状态 x = [x, y, w, h, vx, vy, vw, vh]ᵀ (8x1 列向量) └中心┘ └宽高┘ └─ 速度 ─┘ 观测 z = [x, y, w, h]ᵀ (检测框转换而来,4x1)
  • 中心 +宽高直接建模:w、h 是线性量,各自有速度项 vw、vh——侧转、驶近镜头等尺寸变化有直接的动态模型;
  • 坐标转换convert_bbox_to_z_new/convert_x_to_bbox_new:中心换算与 7 维同式,尺寸直接取 w、h(无 s/r 换算)。
# 8x8:位置 += 速度 × 1 帧F=[[1,0,0,0,1,0,0,0],[0,1,0,0,0,1,0,0],[0,0,1,0,0,0,1,0],[0,0,0,1,0,0,0,1],[0,0,0,0,1,0,0,0],[0,0,0,0,0,1,0,0],[0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0,1]]# 4x8:只观测到位置与宽高,速度隐含H=[[1,0,0,0,0,0,0,0],[0,1,0,0,0,0,0,0],[0,0,1,0,0,0,0,0],[0,0,0,1,0,0,0,0]]

匀速模型:下一帧位置 = 当前位置 + 速度 × 1 帧,速度保持不变。

2.2 初始化

x = [检测框中心x, y, w, h, 0, 0, 0, 0]ᵀ 初速度为 0 Q = new_kf_process_noise(w, h) 尺寸自适应(见 2.3) P = Q 的结构放大:位置块(前 4 维)×4、速度块(后 4 维)×100

P 的放大含义:协方差 = “我对这个估计有多不确定”。速度块 ×100(方差放大万倍)表示"初速度瞎猜的",前几帧 update 会以极快的速度把速度修正到观测差分出来的真实值;位置块 ×4 表示"检测框大体准但留余地"。(7 维是全局常数 ×10/×1000,见 1.2——8 维的 P 直接复用噪声结构,尺度随目标大小。)

2.3 噪声设计:随尺寸自适应(8 维核心改进)

过程噪声 Q —— 每帧 predict 前重算

defnew_kf_process_noise(w,h,p=1/20,v=1/160):Q=diag((p·w)²,(p·h)²,(p·w)²,(p·h)²,# x, y, w, h(v·w)²,(v·h)²,(v·w)²,(v·h)²)# vx, vy, vw, vh
  • 位置噪声 ∝ 目标尺寸的 1/20:w=10px 的远处小目标位置噪声 ~0.5px,w=200px 的近处大目标 ~10px——同一相对不确定度,不同绝对尺度;
  • 速度噪声 ∝ 1/160:速度比位置稳得多(同上两目标分别0.06px、1.25px);
  • 用当前估计的 w、h 每帧重算(predict 里Q = new_kf_process_noise(self.kf.x[2,0], self.kf.x[3,0]))——目标变大变小,噪声尺度跟着变。

观测噪声 R —— 每次 update 前重算

defnew_kf_measurement_noise(w,h,m=1/20):R=diag((m·w)²,(m·h)²,(m·w)²,(m·h)²)

观测(检测框)的可信度也随目标大小缩放:小目标 1px 的检测抖动和大目标 20px 的抖动不是一回事。

为什么这样设计

卡尔曼增益K = P·Hᵀ·S⁻¹(S = H·P·Hᵀ + R)由 P 与 R 的相对大小决定:R 大 → 信预测不信观测,R 小 → 信观测。全局常数 R 意味着"小目标的 2px 抖动和大目标的 2px 抖动被同等对待"——对小目标,2px 已经是 20% 的宽,滤波器却以为观测很准,轨迹跟着抖;尺寸自适应后两类目标获得一致的平滑度。

2.4 predict:盲推一步

x=F · x# 位置外推:x+=vx, y+=vy, w+=vw, h+=vhP=F · P · Fᵀ+Q# 协方差外推:不确定性随时间膨胀

KalmanBoxTracker.predict 在调 KF.predict 之前做两件保护:

  1. 负尺寸保护:if w + vw ≤ 0: vw = 0(h 同理)——外推出的框宽高不能是负的,速度项越界就钳回 0;
  2. 冻结保护:if frozen: vw = vh = 0——漏检中的轨迹不外推尺寸变化(目标被挡住时尺寸不变是更合理的先验),位置速度照常外推等找回。

随后按当前宽高算 Q 传入(Q = new_kf_process_noise(w, h))。状态维护与 7 维一致:age += 1;上帧漏检则hit_streak = 0;time_since_update += 1。

2.5 update:观测修正

五步公式与 1.4 完全一致(含 Joseph 形式与 1.4 的全部状态维护),差异只在 R 的来源:每次 update 前R = new_kf_measurement_noise(当前 w, h)传入 KF.update——观测噪声跟目标尺寸走(7 维是建好的固定 R)。直觉同 1.4:P 大(刚建档、长时间漏检后)K 趋大,观测把状态大步拉回;P 小(长期稳定跟踪)K 趋小,检测抖动被滤掉。

2.6 ORU 与 CMC:8 维的不同表现

ORU:冻结/重放机制与 1.5 完全一致,唯一差异在第 2 步——z 空间本来就是 [x,y,w,h],免去 s/r 换算,直接线性插值(unfreeze的 new_kf=True 分支)。

CMC:8 维走 kron 全量搬移:

big_m=np.kron(np.eye(4),m)# m 是 2x2 仿射线性部分 → 8x8 块对角x=big_m · x;x[:2]+=t# 每个 (量, 其速度) 对按 m 变换,平移只加中心P=big_m · P · big_mᵀ# 协方差共轭变换(保持正定)
  • [x,vx]、[y,vy]、[w,vw]、[h,vh] 四对各自被 m 旋转缩放——宽高的速度向量也跟着相机变换,方向语义保持;平移 t 只加在中心 x、y 上(相机平移不改变尺寸与速度方向);
  • 冻结快照全量同步:attr_saved 的 x、P 整体变换,last_measurement 前 2 位做 m·p + t、后 2 位做 m·p——找回重放的起点也在新坐标系里;
  • w、h、vw、vh 都能吃到相机的缩放/旋转补偿——没有 7 维 s/r 原地不动的失真问题。

三、7 维 / 8 维速查对照

环节7 维(OC-SORT 规格,--new_kf_off)8 维(默认)
状态[cx,cy,s,r,vcx,vcy,vs],r 无速度[x,y,w,h,vx,vy,vw,vh]
尺寸语义s=面积、r=宽高比(非线性,需换算)w、h 直接线性
建档 P₀diag(10,10,10,10,10⁴,10⁴,10⁴)(全局常数)Q 结构:位置 ×4、速度 ×100(随尺寸)
Q固定 diag(1,1,1,1,0.01,0.01,0.0001),构造一次∝ 当前宽高(p=1/20, v=1/160),每帧重算
R固定 diag(1,1,10,10)∝ 当前宽高(m=1/20),每次 update 重算
predict 保护s+vs≤0 钳 vs=0w/h 越界钳速度 + 冻结时清 vw/vh
update五步公式(1.4 实算),固定 R同左,仅 R 按尺寸重算
ORU 重放s/r 先换算 w/h 插值(1.5 实算)直接 w/h 插值
CMC 搬移只搬中心与速度两块,s/r 不动kron(I4,m) 全状态搬移
转换函数convert_bbox_to_z / convert_x_to_bboxconvert_bbox_to_z_new / convert_x_to_bbox_new

四、一帧里的完整时序

轨迹视角一帧(8 维默认): [CMC] apply_affine_correction(仿射) ← 相机动了先搬坐标(观测记录 + KF 状态 + 冻结快照) [预测] 负尺寸保护 → 冻结时清 vw/vh → 按当前宽高算 Q → x=Fx, P=FPFᵀ+Q → age+=1 [关联] 预测框进第一轮(IoU+VDC+外观)→ 配上或进 OCR 找回轮 [配上] update(bbox):状态维护 + 按宽高算 R + KF 修正(若从漏检找回,先 unfreeze 重放) update_emb(emb, alpha):特征 EMA [漏检] update(None):freeze 快照 → 下帧 predict 继续外推等找回 [输出] 本帧配上且 hit_streak≥min_hits → 输出 [x1,y1,x2,y2,id] [判死] time_since_update > max_age → 删除 7 维差异:CMC 只搬中心与速度两块;predict 无尺寸保护与冻结清零、Q 用固定矩阵; update 用固定 R;ORU 重放前先 s/r→w/h 换算。

描述链接
项目基于 Ultralytics +Deep-OC-SORT 多目标跟踪与改进
往期回顾链接
数据集数据集汇总
软件开发深度学习软件开发汇总

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询