【DeepSort 02】多轨迹算法DeepSort-卡尔曼滤波轨迹预测过程
卡尔曼滤波轨迹预测过程
源码:deep_sort/sort/kalman_filter.py。
本文按代码顺序、用实际数字逐步推算predict(预测)、project(投影)、update(修正)、initiate(建档)四个函数,
最后是 DeepSORT 级联匹配的核心——gating_distance(马氏距离门控)。所有数字均用本工程源码实际运行验证过。
调用链(谁在调这些函数):
DeepSort.update() # deep_sort.py 每帧入口 ├─ self.tracker.predict() # 预测:先猜这一帧目标在哪 │ └─ for track in self.tracks: # Tracker 逐条轨迹循环(没有批量版) │ track.predict(self.kf) │ └─ kf.predict(mean, covariance) # ← 本文一 ├─ self.tracker.update(detections) # 匹配 + 修正 │ ├─ linear_assignment.gate_cost_matrix # ← 本文五(内部调 kf.gating_distance) │ ├─ track.update(kf, detection) │ │ └─ kf.update(mean, cov, detection.to_xyah()) # ← 本文三(经 project) │ └─ self.kf.initiate(detection.to_xyah()) # 未匹配检测建新轨迹 ← 本文四公式与符号总表
| 符号 | 代码里的真身 | 是什么 | 从哪来 |
|---|---|---|---|
| mean | 参数mean | 状态均值(8 维) | 轨迹上一帧的 mean(initiate 建档时初始化) |
| cov(P) | 参数covariance | 状态协方差(8×8),对角线是各分量的不确定度 | 轨迹上一帧的 covariance |
| F | self._motion_mat | 状态转移矩阵(8×8),匀速模型 | __init__构造:单位阵,右上 4×4 填 dt=1 |
| Q | 局部变量motion_cov | 过程噪声协方差(8×8 对角阵),“匀速假设本身的误差预算” | predict 内部用两个噪声权重 × mean[3] 现算 |
| h | mean[3] | 这条轨迹的框高(像素),不是固定值,每条轨迹各自取 | 状态向量第 4 个分量 |
_std_weight_position | 类属性 | 位置噪声权重 = 1/20 | __init__写死 |
_std_weight_velocity | 类属性 | 速度噪声权重 = 1/160 | __init__写死 |
| H | self._update_mat | 观测矩阵(4×8),只取前 4 维 | __init__构造 |
| R | innovation_cov(project 内局部变量) | 观测噪声(4×4 对角阵),检测框自身的不确定性 | project 内部用噪声权重 × mean[3] 现算 |
| mean_new | 返回的mean | 外推后的状态均值(8 维) | F · mean 算出 |
| cov_new(P’) | 返回的covariance | 外推后的协方差(不确定性变大) | F · cov · Fᵀ + Q 算出 |
本文三个核心公式:
predict: mean_new = F · mean (位置 = 位置 + 速度×1 帧,速度不变) cov_new = F · cov · Fᵀ + Q (不确定性传播:变大) project: z_mean = H · mean (8 维状态 → 4 维观测空间) S = H · cov · Hᵀ + R (观测空间的协方差) update: mean_new = mean + K · (measurement − H · mean) cov_new = cov − K · H · cov (本工程写法,与 cov − K·S·Kᵀ 恒等)一、predict(单条轨迹)实际推算
| 方法 | 作用 | 输入 | 输出 |
|---|---|---|---|
predict | 预测步:按匀速模型把状态外推到下一帧 | mean: 上一帧状态均值(8 维 ndarray);covariance: 上一帧协方差(8×8 ndarray) | (mean, covariance):外推后的均值与协方差(不确定性变大) |
输入(举例)
mean = [100, 50, 1.5, 40, 8, 0, 0, 0] ← x=100, y=50, a=1.5, h=40, vx=8, vy=0, va=0, vh=0 cov = [[16, 0, 0, 0, 0, 0, 0, 0], [0, 18, 0, 0, 0, 0, 0, 0], [0, 0, 0.5, 0, 0, 0, 0, 0], [0, 0, 0, 14, 0, 0, 0, 0], [0, 0, 0, 0, 6, 0, 0, 0], [0, 0, 0, 0, 0, 5, 0, 0], [0, 0, 0, 0, 0, 0, 0.3, 0], [0, 0, 0, 0, 0, 0, 0, 7]] ← 8×8 对角阵(演示值),各分量方差不同、交叉项 0第 1 步:构造 std_pos(位置类 4 个分量的噪声标准差)
std_pos=[self._std_weight_position*mean[3],# x 的噪声 = h/20self._std_weight_position*mean[3],# y 的噪声 = h/201e-2,# a 的噪声 = 固定 0.01(比例无量纲,不随 h 缩放)self._std_weight_position*mean[3]]# h 的噪声 = h/20代入 h = mean[3] = 40:
std_pos = [40/20, 40/20, 0.01, 40/20] = [2, 2, 0.01, 2]第 2 步:构造 std_vel(速度类 4 个分量的噪声标准差)
std_vel=[self._std_weight_velocity*mean[3],# vx 的噪声 = h/160self._std_weight_velocity*mean[3],# vy 的噪声 = h/1601e-5,# va 的噪声 = 固定 0.00001self._std_weight_velocity*mean[3]]# vh 的噪声 = h/160代入 h = 40:
std_vel = [40/160, 40/160, 0.00001, 40/160] = [0.25, 0.25, 0.00001, 0.25]第 3 步:拼成 Q 矩阵(motion_cov)
motion_cov=np.diag(np.square(np.r_[std_pos,std_vel]))# np.r_ :把两个 4 元素列表拼成 8 个(顺序 = 状态分量顺序 x,y,a,h,vx,vy,va,vh)# np.square :标准差 → 方差(平方)# np.diag :放到 8×8 对角线上Q = diag(2², 2², 0.01², 2², 0.25², 0.25², 0.00001², 0.25²) = diag(4, 4, 0.0001, 4, 0.0625, 0.0625, 0.0000000001, 0.0625)第 4 步:外推 mean
mean=np.dot(self._motion_mat,mean)# F · mean(本工程写法)F(=_motion_mat,__init__构造)右上角的 1 使"位置 + 速度×dt":
x' = 100 + 8 × 1 = 108 (dt = 1,__init__ 里 ndim, dt = 4, 1. 写死) y' = 50 + 0 × 1 = 50 a' = 1.5 + 0 × 1 = 1.5 h' = 40 + 0 × 1 = 40 vx' = 8, vy' = 0, va' = 0, vh' = 0 (速度不变) mean_new = [108, 50, 1.5, 40, 8, 0, 0, 0]第 5 步:外推 cov
covariance=np.linalg.multi_dot((self._motion_mat,covariance,self._motion_mat.T))+motion_cov# = F · cov · Fᵀ + Qcov 是对角阵时,结果规律简单:新对角线 = 旧对角线(自己) + 旧对角线(对应速度分量) + Q(自己),位置分量与自己的速度分量之间产生交叉项 = 旧速度方差:
cov_new(x 行): 16 + 6 + 4 = 26, 交叉项 [x, vx] = 6 cov_new(y 行): 18 + 5 + 4 = 27, 交叉项 [y, vy] = 5 cov_new(a 行): 0.5 + 0.3 + 0.0001 ≈ 0.8001,交叉项 [a, va] = 0.3 cov_new(h 行): 14 + 7 + 4 = 25, 交叉项 [h, vh] = 7 cov_new(速度行): vx 6+0.0625=6.0625,vy 5.0625,va 0.3,vh 7.0625输出(返回)
mean_new = [108, 50, 1.5, 40, 8, 0, 0, 0] cov_new = [[26, 0, 0, 0, 6, 0, 0, 0 ], [0, 27, 0, 0, 0, 5, 0, 0 ], [0, 0, 0.8001,0, 0, 0, 0.3, 0 ], [0, 0, 0, 25, 0, 0, 0, 7 ], [6, 0, 0, 0, 6.0625, 0, 0, 0 ], [0, 5, 0, 0, 0, 5.0625,0, 0 ], [0, 0, 0.3, 0, 0, 0, 0.3, 0 ], [0, 0, 0, 7, 0, 0, 0, 7.0625]]位置方差变大(16→26、18→27、14→25,外推后更不确定),速度方差基本不变,x-vx、y-vy、a-va、h-vh 之间出现交叉项(位置的不确定性里掺进了速度的不确定性)。
本工程落点:Tracker.predict()用 for 循环对每条轨迹调track.predict(self.kf)(tracker.py:54-60),轨迹内部再调本文的kf.predict,同时age += 1、time_since_update += 1(track.py:116-128)。
二、project(投影到观测空间)
| 方法 | 作用 | 输入 | 输出 |
|---|---|---|---|
project | 把 8 维预测状态投影到 4 维观测空间(检测器只能看到 x、y、a、h 这 4 维),并叠加观测噪声 | mean: 预测均值(8 维 ndarray);covariance: 预测协方差(8×8 ndarray) | (new_mean, new_cov):投影后的均值(4 维)与协方差(4×4) |
project 是内部辅助函数,不对外直接调用——update和gating_distance都先用它把状态转到观测空间再算。
公式
new_mean = H · mean new_cov(S) = H · cov · Hᵀ + R| 字母 | 是什么 | 从哪来 |
|---|---|---|
| H | 观测矩阵(4×8),只取前 4 维 | __init__的_update_mat |
| mean | 8 维预测均值 | predict 的输出 |
| cov | 8×8 预测协方差 | predict 的输出 |
| R | 观测噪声(4×4 对角阵),检测框自身的不确定性 | 函数内现算:diag((h/20)², (h/20)², 0.1², (h/20)²) |
| h | 目标框高(像素) | mean[3] |
计算过程(实际数字)
输入(predict 输出的那条轨迹):
mean = [108, 50, 1.5, 40, 8, 0, 0, 0] cov = [[26, 0, 0, 0, 6, 0, 0, 0 ], [0, 27, 0, 0, 0, 5, 0, 0 ], [0, 0, 0.8001,0, 0, 0, 0.3, 0 ], [0, 0, 0, 25, 0, 0, 0, 7 ], [6, 0, 0, 0, 6.0625, 0, 0, 0 ], [0, 5, 0, 0, 0, 5.0625,0, 0 ], [0, 0, 0.3, 0, 0, 0, 0.3, 0 ], [0, 0, 0, 7, 0, 0, 0, 7.0625]]算 R(h = 40):
std = [40/20, 40/20, 0.1, 40/20] = [2, 2, 0.1, 2] ← 注意 a 的噪声这里是 1e-1(predict 里是 1e-2) R = diag(2², 2², 0.1², 2²) = diag(4, 4, 0.01, 4)投影(H 取前 4 维 = 取 cov 左上 4×4):
new_mean = H · mean = [108, 50, 1.5, 40] new_cov = H · cov · Hᵀ + R = [[26+4, 0, 0, 0 ], [0, 27+4, 0, 0 ], [0, 0, 0.8001+0.01, 0], [0, 0, 0, 25+4]] = [[30, 0, 0, 0 ], [0, 31, 0, 0 ], [0, 0, 0.8101,0 ], [0, 0, 0, 29 ]]输出:
new_mean = [108, 50, 1.5, 40] ← 4 维 new_cov(S) = [[30, 0, 0, 0 ], ← 4×4 [0, 31, 0, 0 ], [0, 0, 0.8101,0 ], [0, 0, 0, 29 ]]三、update(修正步)
| 方法 | 作用 | 输入 | 输出 |
|---|---|---|---|
update | 修正步:用配对成功的检测框修正预测状态——算卡尔曼增益 K,加权融合预测和检测,输出更准的状态 | mean: 预测均值(8 维 ndarray);covariance: 预测协方差(8×8 ndarray);measurement: 检测框[x, y, a, h](4 维 ndarray) | (mean, covariance):修正后的均值与协方差(不确定性变小) |
公式
K = cov · Hᵀ · S⁻¹ (卡尔曼增益,8×4,cholesky 求解) innovation = measurement − H · mean (残差:检测框比预测框偏了多少) mean_new = mean + K · innovation cov_new = cov − K · H · cov (本工程代码的写法)
cov − K·H·cov与cov − K·S·Kᵀ两种写法代数恒等(由 K 的定义推出),本工程 kalman_filter.py:186-187 用前者。
| 字母 | 是什么 | 从哪来 |
|---|---|---|
| K | 卡尔曼增益(8×4 矩阵),每个状态分量的修正权重 | 公式算出 |
| S | 投影后的协方差(4×4) | project 的输出 |
| innovation | 残差(4 维),检测框比预测框偏了多少 | 检测框 − 投影预测 |
| measurement | 检测框[x, y, a, h] | 配对成功的检测(Detection.to_xyah() 转换) |
| mean / cov | 8 维预测状态 | predict 的输出 |
计算过程(实际数字)
输入:
mean = [108, 50, 1.5, 40, 8, 0, 0, 0] ← predict 的输出 cov = [[26, 0, 0, 0, 6, 0, 0, 0 ], [0, 27, 0, 0, 0, 5, 0, 0 ], [0, 0, 0.8001,0, 0, 0, 0.3, 0 ], [0, 0, 0, 25, 0, 0, 0, 7 ], [6, 0, 0, 0, 6.0625, 0, 0, 0 ], [0, 5, 0, 0, 0, 5.0625,0, 0 ], [0, 0, 0.3, 0, 0, 0, 0.3, 0 ], [0, 0, 0, 7, 0, 0, 0, 7.0625]] ← predict 的输出 measurement = [108.5, 50.5, 1.5, 41] ← 检测框(检测器说目标在 108.5)project 的输出(上一步算好的):
H·mean = [108, 50, 1.5, 40] S = [[30, 0, 0, 0 ], [0, 31, 0, 0 ], [0, 0, 0.8101,0 ], [0, 0, 0, 29 ]]算 K(公式带实际数据):
K = cov · Hᵀ · S⁻¹ = [[26, 0, 0, 0 ] [1/30, 0, 0, 0 ] [0, 27, 0, 0 ] [0, 1/31, 0, 0 ] [0, 0, 0.8001,0 ] · [0, 0, 1/0.8101, 0 ] [0, 0, 0, 25 ] [0, 0, 0, 1/29] [6, 0, 0, 0 ] ← 左边 8×4 = cov·Hᵀ(cov 的前 4 列),右边 4×4 = S⁻¹ [0, 5, 0, 0 ] [0, 0, 0.3, 0 ] [0, 0, 0, 7 ]] = [[26/30, 0, 0, 0 ] [0, 27/31, 0, 0 ] [0, 0, 0.8001/0.8101,0 ] [0, 0, 0, 25/29 ] [6/30, 0, 0, 0 ] [0, 5/31, 0, 0 ] [0, 0, 0.3/0.8101, 0 ] [0, 0, 0, 7/29 ]] = [[0.8667, 0, 0, 0 ] ← 8 行(8 个状态分量)× 4 列(4 个观测分量) [0, 0.8710, 0, 0 ] ← 每行的非零值 = 该分量的修正权重 [0, 0, 0.9877, 0 ] ← a 的权重最大(观测比预测可信) [0, 0, 0, 0.8621] [0.2, 0, 0, 0 ] ← 速度行的小权重:位置残差通过它传导到速度 [0, 0.1613, 0, 0 ] [0, 0, 0.3704, 0 ] [0, 0, 0, 0.2414]]算残差:
innovation = measurement − H·mean = [108.5−108, 50.5−50, 1.5−1.5, 41−40] = [0.5, 0.5, 0, 1]修正 mean(公式带实际数据):
mean_new = mean + K · innovation = [108, 50, 1.5, 40, 8, 0, 0, 0] + [0.8667×0.5, 0.8710×0.5, 0.9877×0, 0.8621×1, 0.2×0.5, 0.1613×0.5, 0.3704×0, 0.2414×1] = [108, 50, 1.5, 40, 8, 0, 0, 0 ] + [0.4333, 0.4355, 0, 0.8621, 0.1, 0.0806, 0, 0.2414] = [108.4333, 50.4355, 1.5, 40.8621, 8.1, 0.0806, 0, 0.2414]修正 cov(公式带实际数据):
cov_new = cov − K · H · cov K·H(先算,8×4 · 4×8 → 8×8,每行把自己的权重放回对应的观测列): = [[0.8667, 0, 0, 0, 0, 0, 0, 0] [0, 0.8710, 0, 0, 0, 0, 0, 0] [0, 0, 0.9877, 0, 0, 0, 0, 0] [0, 0, 0, 0.8621, 0, 0, 0, 0] [0.2, 0, 0, 0, 0, 0, 0, 0] [0, 0.1613, 0, 0, 0, 0, 0, 0] [0, 0, 0.3704, 0, 0, 0, 0, 0] [0, 0, 0, 0.2414, 0, 0, 0, 0]] K·H·cov(再乘 cov,8×8 · 8×8,每行 = 该行的权重 × cov 对应位置那一行的取值): = [[0.8667×26, 0, 0, 0, 0.8667×6, 0, 0, 0 ] [0, 0.8710×27, 0, 0, 0, 0.8710×5, 0, 0 ] [0, 0, 0.9877×0.8001,0, 0, 0, 0.9877×0.3, 0 ] [0, 0, 0, 0.8621×25, 0, 0, 0, 0.8621×7] [0.2×26, 0, 0, 0, 0.2×6, 0, 0, 0 ] [0, 0.1613×27, 0, 0, 0, 0.1613×5, 0, 0 ] [0, 0, 0.3704×0.8001,0, 0, 0, 0.3704×0.3, 0 ] [0, 0, 0, 0.2414×25, 0, 0, 0, 0.2414×7]] = [[22.5333, 0, 0, 0, 5.2, 0, 0, 0 ] [0, 23.5161,0, 0, 0, 4.3548, 0, 0 ] [0, 0, 0.7902, 0, 0, 0, 0.2963, 0 ] [0, 0, 0, 21.5517,0, 0, 0, 6.0345] [5.2, 0, 0, 0, 1.2, 0, 0, 0 ] [0, 4.3548, 0, 0, 0, 0.8065, 0, 0 ] [0, 0, 0.2963, 0, 0, 0, 0.1111, 0 ] [0, 0, 0, 6.0345, 0, 0, 0, 1.6897]] cov_new = cov − 上面的矩阵 = [[26−22.5333, 0, 0, 0, 6−5.2, 0, 0, 0 ] [0, 27−23.5161, 0, 0, 0, 5−4.3548, 0, 0 ] [0, 0, 0.8001−0.7902, 0, 0, 0, 0.3−0.2963, 0 ] [0, 0, 0, 25−21.5517, 0, 0, 0, 7−6.0345] [6−5.2, 0, 0, 0, 6.0625−1.2, 0, 0, 0 ] [0, 5−4.3548, 0, 0, 0, 5.0625−0.8065,0, 0 ] [0, 0, 0.3−0.2963, 0, 0, 0, 0.3−0.1111, 0 ] [0, 0, 0, 7−6.0345, 0, 0, 0, 7.0625−1.6897]] = [[3.4667, 0, 0, 0, 0.8, 0, 0, 0 ] [0, 3.4839, 0, 0, 0, 0.6452, 0, 0 ] [0, 0, 0.0099, 0, 0, 0, 0.0037, 0 ] [0, 0, 0, 3.4483, 0, 0, 0, 0.9655 ] [0.8, 0, 0, 0, 4.8625, 0, 0, 0 ] [0, 0.6452, 0, 0, 0, 4.2560, 0, 0 ] [0, 0, 0.0037, 0, 0, 0, 0.1889, 0 ] [0, 0, 0, 0.9655, 0, 0, 0, 5.3728 ]]输出:
mean_new = [108.4333, 50.4355, 1.5, 40.8621, 8.1, 0.0806, 0, 0.2414] cov_new = [[3.4667, 0, 0, 0, 0.8, 0, 0, 0 ], [0, 3.4839, 0, 0, 0, 0.6452, 0, 0 ], [0, 0, 0.0099, 0, 0, 0, 0.0037, 0 ], [0, 0, 0, 3.4483, 0, 0, 0, 0.9655], [0.8, 0, 0, 0, 4.8625, 0, 0, 0 ], [0, 0.6452, 0, 0, 0, 4.2560, 0, 0 ], [0, 0, 0.0037, 0, 0, 0, 0.1889, 0 ], [0, 0, 0, 0.9655, 0, 0, 0, 5.3728]]修正后的框中心 (108.4333, 50.4355) 落在预测 108 和检测 108.5 之间(按 0.8667 的权重偏向预测);方差全面变小(26→3.47、25→3.45,观测吸收了不确定性)。这两个值存回轨迹,下一帧 predict 从它们出发——predict 变大 / update 变小,循环闭环。
本工程落点:Track.update(kf, detection)内部调kf.update(self.mean, self.covariance, detection.to_xyah())(track.py:130-151),同时追加特征、hits+1、time_since_update 清零、hits≥n_init 时转 Confirmed。
四、initiate(建档初始化)
| 方法 | 作用 | 输入 | 输出 |
|---|---|---|---|
initiate | 建档初始化:用建档检测框生成轨迹的初始状态——位置来自检测框,速度置 0(没观测过速度),并给一个初始不确定性(速度方差给得大,位置方差给得小) | measurement: 建档检测框[x, y, a, h](4 维 ndarray,Detection.to_xyah() 转换来的中心坐标、宽高比、高) | (mean, covariance):初始均值(8 维 ndarray,速度 4 维全 0)与初始协方差(8×8 对角阵) |
计算过程(实际数字)
输入(检测框 tlwh[30, 40, 50, 100]→ 转 xyah):
measurement = [55, 90, 0.5, 100] ← x=55, y=90(框中心),a=0.5(宽高比),h=100(框高)第 1 步:拼初始 mean(位置 + 速度)
mean_pos = [55, 90, 0.5, 100] ← 位置 4 维直接用检测框 mean_vel = [0, 0, 0, 0] ← 速度 4 维:没观测过,全置 0 mean = np.r_[mean_pos, mean_vel] ← 拼成 8 维 = [55, 90, 0.5, 100, 0, 0, 0, 0]第 2 步:构造初始 std(8 个分量的噪声标准差,h=100)
std = [ 2 × (1/20) × 100, ← x:2 × h/20 = 10 2 × (1/20) × 100, ← y:10 1e-2, ← a:固定 0.01 2 × (1/20) × 100, ← h:10 10 × (1/160) × 100, ← vx:10 × h/160 = 6.25 10 × (1/160) × 100, ← vy:6.25 1e-5, ← va:固定 0.00001 10 × (1/160) × 100] ← vh:6.25 = [10, 10, 0.01, 10, 6.25, 6.25, 0.00001, 6.25]位置噪声用 2 倍权重、速度噪声用 10 倍权重(比 predict 的 1 倍大)——刚建档时啥都不确定,尤其速度完全没观测过,方差给大,让 update 阶段能快速修正它。
第 3 步:拼初始 covariance(std 平方上对角线)
covariance = diag(10², 10², 0.01², 10², 6.25², 6.25², 0.00001², 6.25²) = diag(100, 100, 0.0001, 100, 39.0625, 39.0625, ~0, 39.0625)输出:
mean = [55, 90, 0.5, 100, 0, 0, 0, 0] covariance = [[100, 0, 0, 0, 0, 0, 0, 0 ], [0, 100, 0, 0, 0, 0, 0, 0 ], [0, 0, 0.0001, 0, 0, 0, 0, 0 ], [0, 0, 0, 100,0, 0, 0, 0 ], [0, 0, 0, 0, 39.0625,0, 0, 0 ], [0, 0, 0, 0, 0, 39.0625,0, 0 ], [0, 0, 0, 0, 0, 0, ~0, 0 ], [0, 0, 0, 0, 0, 0, 0, 39.0625]]本工程落点:Tracker._initiate_track对未匹配检测调self.kf.initiate(detection.to_xyah())(tracker.py:139),返回的 mean/covariance 存进新建的 Track 对象,下一帧 predict 从它们出发。
五、gating_distance(马氏距离门控)
| 方法 | 作用 | 输入 | 输出 |
|---|---|---|---|
gating_distance | 算一条轨迹的预测分布与N 个检测框各自的平方马氏距离,用于级联匹配的运动门控:距离超过卡方阈值的配对判为不可行 | mean: 这条轨迹的 8 维状态均值;covariance: 这条轨迹的 8×8 协方差;measurements: Nx4 检测框矩阵(每行 x, y, a, h);only_position: 只按中心位置算(默认 False) | 长度 N 的数组,第 i 个元素 = 这条轨迹与第 i 个检测框的平方马氏距离 |
公式
d² = dᵀ · S⁻¹ · d (d = 残差 = 检测框 − 投影后的预测均值;S = H·cov·Hᵀ + R)| 字母 | 是什么 | 从哪来 |
|---|---|---|
| d | 残差(4 维),检测框比预测框偏了多少 | measurements − H·mean |
| S | 投影后的协方差(4×4) | project 的输出(本文二,检测框噪声 R 已叠在里面) |
| d² | 平方马氏距离(标量),一个检测框一个 | 公式算出 |
| measurements | Nx4 检测框矩阵,每行 x, y, a, h | 调用方把 Detection 转 to_xyah() 拼成 |
代码实现(kalman_filter.py:223-230)不直接求 S⁻¹,用 Cholesky 分解代替,推导:
S = L·Lᵀ → S⁻¹ = (L·Lᵀ)⁻¹ = L⁻ᵀ·L⁻¹ d² = dᵀ·S⁻¹·d = dᵀ·L⁻ᵀ·L⁻¹·d = (L⁻¹·d)ᵀ·(L⁻¹·d) = ‖L⁻¹·d‖²mean,covariance=self.project(mean,covariance)# 第 1 步:投影到 4 维观测空间(本文二)cholesky_factor=np.linalg.cholesky(covariance)# L,使 S = L·Lᵀd=measurements-mean# 第 2 步:残差(Nx4)z=scipy.linalg.solve_triangular(# 第 3 步:解方程 L·z = d,得 z = L⁻¹·dcholesky_factor,d.T,lower=True,check_finite=False,overwrite_b=True)squared_maha=np.sum(z*z,axis=0)# 第 4 步:d² = ‖z‖²,每个检测框一个# (only_position=True 时在 cholesky 前把 mean/cov/measurements 都截成前 2 维)S 是对角阵时 L = diag(√S),z_i = d_i/√S_ii,于是d² = Σ d_i²/S_ii(残差平方除以方差再求和)——下面的手算就用这个形式。
判定阈值(模块顶部的 chi2inv95 表)
卡方分布 0.95 分位数表(kalman_filter.py:11-20):
| 自由度 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 阈值 | 3.8415 | 5.9915 | 7.8147 | 9.4877 | 11.070 | 12.592 | 14.067 | 15.507 | 16.919 |
自由度 = 参与计算的维数:only_position=False(默认)用 4 维(x, y, a, h)→ 阈值9.4877;only_position=True只用 2 维(x, y)→ 阈值 5.9915。d² 小于阈值 ≈ 检测框落在轨迹 95% 的预测范围内。
计算过程(实际数字)
输入(轨迹 = 本文一 predict 之后的那个例子;两个候选检测框):
mean = [108, 50, 1.5, 40, 8, 0, 0, 0] ← predict 后的状态 cov = [[26, 0, 0, 0, 6, 0, 0, 0 ], [0, 27, 0, 0, 0, 5, 0, 0 ], [0, 0, 0.8001,0, 0, 0, 0.3, 0 ], [0, 0, 0, 25, 0, 0, 0, 7 ], [6, 0, 0, 0, 6.0625, 0, 0, 0 ], [0, 5, 0, 0, 0, 5.0625,0, 0 ], [0, 0, 0.3, 0, 0, 0, 0.3, 0 ], [0, 0, 0, 7, 0, 0, 0, 7.0625]] measurements = [[108.5, 50.5, 1.5, 41], ← 检测1:就在预测位置附近 [130, 60, 1.5, 42]] ← 检测2:中心偏了 22 像素第 1 步:project 投影到观测空间(过程与本文二完全相同,同一组数):
H·mean = [108, 50, 1.5, 40] S = [[30, 0, 0, 0 ], [0, 31, 0, 0 ], [0, 0, 0.8101,0 ], [0, 0, 0, 29 ]]第 2 步:算两个检测框的残差 d = 检测框 − H·mean:
检测1: d = [108.5−108, 50.5−50, 1.5−1.5, 41−40] = [0.5, 0.5, 0, 1] 检测2: d = [130−108, 60−50, 1.5−1.5, 42−40] = [22, 10, 0, 2]第 3 步:d² = Σ 残差² / 方差(S 对角线):
检测1: d² = 0.5²/30 + 0.5²/31 + 0²/0.8101 + 1²/29 = 0.0083 + 0.0081 + 0 + 0.0345 = 0.0509 检测2: d² = 22²/30 + 10²/31 + 0²/0.8101 + 2²/29 = 16.1333 + 3.2258 + 0 + 0.1379 = 19.4971第 4 步:与阈值 9.4877 比较:
d² = [0.0509, 19.4971] [0.0509 < 9.4877, 19.4971 > 9.4877] [进门, 拒绝] ← 检测2 不可能是这条轨迹的输出:[0.0509, 19.4971](长度 2 的数组,第 i 个元素 = 这条轨迹与第 i 个检测框的平方马氏距离;本例检测1 进门、检测2 被拒)。
| 描述 | 链接 |
|---|---|
| 项目 | 基于 Ultralytics + DeepSORT 多目标跟踪与改进 |
| 往期回顾 | 链接 |
| 数据集 | 数据集汇总 |
| 软件开发 | 深度学习软件开发汇总 |