GM-PHD滤波器扩展目标跟踪MATLAB源码解析与调参实践
简介这是一套面向多扩展目标跟踪场景的GM-PHD滤波器MATLAB实现适合雷达、计算机视觉等领域的研究者与工程师。代码完整覆盖高斯混合概率假设密度滤波器的初始化、预测、更新、剪枝与目标状态估计流程并集成扩展卡尔曼滤波处理非线性模型、匈牙利算法解决量测与航迹关联还提供OSPA距离指标量化评估跟踪精度。资源共32个文件以30个m语言源文件为主包含核心滤波器主程序、EKF相关函数、仿真测量与绘图模块辅以2个txt说明文件整体体积仅32KB简洁易用。已有351人学习下载。借助本套源码读者可快速搭建仿真环境深入理解GM-PHD滤波器的数学推导与工程实现细节为扩展目标跟踪算法研究提供实用参考。1. 雷达多目标跟踪里的扩展目标为什么需要 GM-PHD做雷达多目标跟踪的同行应该都遇到过这种场景一个行人走在雷达面前一帧回波能打出十几个散射点一辆车在弯道里行驶点迹宽度甚至超过车长。传统“一个目标至多一个量测”的点目标假设在这里完全失效直接做最近邻或 JPDA 数据关联计算量先不说关联错了状态就跟着发散。GM-PHD 滤波器把多目标后验密度的一阶矩PHD用高斯混合近似天然接受一帧里来自同一个目标的多个量测不需要显式做航迹-量测组合枚举扩展目标跟踪问题就被压缩成了一个“预测-更新-剪枝”的高斯分量管理问题。这套 MATLAB 源码正是围绕这条链路写的里面有 GM-PHD 的主循环、EKF 扩展、匈牙利量测分配、OSPA 性能度量适合正在做雷达或视觉多目标跟踪、想从点目标切到扩展目标模型的工程师。2. GM-PHD 滤波器数学近似与 MATLAB 文件地图2.1 从多目标后验密度到强度函数多目标跟踪的完整解是后验多目标密度但它在数学上是一个有限集分布直接递推在工程上是算不动的。PHD 滤波器退而求其次只递推强度函数 (D_{k|k}(x))即后验密度的一阶统计矩。对任意区域 (S)( \int_S D(x) dx ) 就是区域内目标数量的期望。这个近似把“多目标滤波”变成了“单目标空间上的密度滤波”代价是丢失了目标间的关联信息但换来的是维度不随目标数爆炸。GM-PHD 进一步假设每个目标的强度函数都可以写成若干个高斯分量之和[ D(x) \sum_{i1}^{J} w^{(i)} \mathcal{N}(x; m^{(i)}, P^{(i)}) ] 其中 (w) 是权重(m) 是均值(P) 是协方差。这样 PHD 的预测和更新公式就退化成对高斯分量的权重和参数做代数运算滤波过程变成了一堆高斯分量的“出生、存活、更新、剪枝、合并、提取”。这个源码包里的GM_PHD_Predict_Existing.m、GM_PHD_Update.m、GM_PHD_Prune.m对应的正是这几步。2.2 源码包文件结构与主函数入口打开压缩包先不要急着跑先看文件命名规律。前缀GM_是普通线性高斯实现GM_EKF_是用扩展卡尔曼处理非线性量测的实现。两者共享主循环接口只是预测和更新里把状态转移矩阵换成了雅可比矩阵。我在工程上一般建议先跑通GM_PHD_Filter.m主循环再换到 EKF 版本做非线性的雷达/声纳场景。文件作用GM_PHD_Filter.m主循环初始化、逐帧预测/更新、剪枝、提取GM_PHD_Initialisation.m滤波器初始参数检测概率、存活概率、剪枝门限GM_PHD_Predict_Existing.m/GM_PHD_Predict_Birth.m存活目标与新出生目标的预测GM_PHD_Update.m/GM_PHD_Construct_Update_Components.m用当前量测更新每个高斯分量GM_PHD_Prune.m合并临近分量、丢弃低于权重门限的分量GM_EKF_PHD_Simulate_Measurements.m模拟扩展目标量测簇Hungarian.m匈牙利算法用于量测-分量分配CalculateOSPAMetric.m/ospa_dist.mOSPA 性能指标计算主循环的逻辑可以用下面的示意代码表达实际调用时注意传参方式以源码为准% 主循环核心步骤示意 phd GM_PHD_Initialisation(); % 初始化滤波器参数 for k 1 : K phd GM_PHD_Predict_Existing(phd); % 存活目标预测 phd GM_PHD_Predict_Birth(phd); % 新生目标预测 Z{k} GM_EKF_PHD_Simulate_Measurements(track_true, phd); % 模拟量测 [phd, ~] GM_PHD_Update(phd, Z{k}); phd GM_PHD_Prune(phd); est GM_PHD_Estimate(phd); ospa(k) CalculateOSPAMetric(est, track_true, p, c); end这段代码不是一个可以直接跑的完整程序而是把源码包里各函数的调用关系串起来。实际工程里GM_PHD_Initialisation返回的结构体通常包含w,m,P三个字段分别表示权重、均值向量、协方差矩阵预测步会先按存活概率衰减权重再叠加出生分量更新步则对每个量测生成一个“量测驱动”的高斯分量所以量测一多分量数量会膨胀这就是最后必须剪枝的原因。OSPA 参数里的p是阶数c是基数误差惩罚系数后面第 5 章再展开。3. 从初始化到更新GM-PHD 核心循环的 MATLAB 实现3.1 初始化状态分量权重不都是 1很多第一次读源码的人会误以为所有高斯分量的初始权重都是 1。实际上在多目标框架里权重代表的是“该位置存在一个目标的概率强度”单位不是概率所以可以大于 1也可以小于 1。初始化时通常设置一个存活目标分量和多个出生位置分量出生分量的权重一般设得比存活分量小一个数量级。下面这段代码是GM_PHD_Initialisation.m的典型写法基于二维匀速模型状态向量为 ( [x, \dot{x}, y, \dot{y}]^T )function phd GM_PHD_Initialisation() % 状态空间维数 state_dim 4; % 存活目标权重 0.9位置由先验给定 phd.w(1) 0.9; phd.m(:,1) [100; 0; 100; 0]; phd.P(:,:,1) diag([10, 5, 10, 5]); % 出生目标在三个可能入口设置低权重分量 birth_centers [0, 200; 0, 200; 0, 200]; for i 1 : 3 phd.w(end1) 0.1; phd.m(:,end1) [birth_centers(i,1); 0; birth_centers(i,2); 0]; phd.P(:,:,end1) diag([50, 10, 50, 10]); end end初始化里真正要调的参数是出生分量的位置和权重。出生位置对应你允许新目标出现的区域权重决定了系统对“突然冒出来的目标”的敏感度。如果设太小新生目标要连续几帧量测才能累积起足够权重设太大虚警会被当成目标跟踪OSPA 的基数误差立刻变大。这个过程在后面用 OSPA 评估时最容易看出来。3.2 预测与更新birth 模型和量测驱动预测分两段。第一段是存活目标的预测把每个高斯分量的均值按状态转移矩阵 (F) 推进协方差加上过程噪声 (Q)第二段是出生目标预测直接把出生分量的权重乘以出生概率然后原样带入本帧。下面是GM_PHD_Predict_Existing.m的关键片段function phd_pred GM_PHD_Predict_Existing(phd) F [1, dt, 0, 0; % 匀速模型状态转移 0, 1, 0, 0; 0, 0, 1, dt; 0, 0, 0, 1]; Q 0.5 * [dt^4/4, dt^3/2, 0, 0; ... dt^3/2, dt^2, 0, 0; ... 0, 0, dt^4/4, dt^3/2; ... 0, 0, dt^3/2, dt^2]; for i 1 : length(phd.w) phd_pred.w(i) p_s * phd.w(i); % 存活概率衰减 phd_pred.m(:,i) F * phd.m(:,i); phd_pred.P(:,:,i) F * phd.P(:,:,i) * F. Q; end endp_s是目标存活概率一般取 0.950.99。它不单是个乘法因子还控制着目标消失后需要多少帧才能让残留分量的权重低于剪枝门限。如果调太小目标短暂遮挡后状态直接丢失调太大目标已经离开探测区域很久位置分量还拖着不散导致输出一堆“幽灵航迹”。更新步是整个滤波器最耗时间的地方。标准 GM-PHD 更新对每个量测构造一个“似然比”分量因此分量数会乘以量测数。GM_PHD_Construct_Update_Components.m里做的就是把每个分量与每个量测配对计算卡尔曼增益和权重系数。这里的 Kalman 增益用的是线性观测量测矩阵 (H [1,0,0,0; 0,0,1,0])对应直接观测 (x) 和 (y) 坐标。3.3 剪枝与合并保住计算量也保住精度量测一多分量膨胀是必然的。剪枝合并直接决定实时性源码里对应的就是GM_PHD_Prune.m。标准做法分三步先丢弃权重小于阈值的分量再把距离近的分量合并成一个最后限制最多保留的分量数。常见实现如下function phd GM_PHD_Prune(phd) % 1) 权重阈值裁剪 keep phd.w truncthresh; phd.w phd.w(keep); phd.m phd.m(:, keep); phd.P phd.P(:, :, keep); % 2) 按权重降序排列逐个合并马氏距离小于 U 的分量 [~, idx] sort(phd.w, descend); merged struct(w, [], m, [], P, []); while ~isempty(idx) i idx(1); d mahal(phd.m(:, idx), phd.m(:, i)); close_idx idx(d U); merged.w(end1) sum(phd.w(close_idx)); merged.m(:,end1) sum(phd.w(close_idx) .* phd.m(:, close_idx), 2) / merged.w(end); idx setdiff(idx, close_idx); end % 3) 超出最大分量数时保留权重最大的前 max_c 个 end这里的truncthresh典型值是 1e-5U是合并门限常见取 410。U太小临近分量不合并分量数很快顶到上限U太大会把相距较近的两个真实目标合并成一个OSPA 的定位误差会显著上升。一个稳妥做法是把U设成量测噪声标准差的 23 倍让合并发生在噪声尺度之内而不是目标尺度之上。剪枝参数是和下文 OSPA 评估联动调的核心对象。4. 扩展目标建模与 EKF、匈牙利算法的落地细节4.1 模拟量测一个目标生成一簇散射点扩展目标和点目标最大的区别在于一个目标在一帧里会产生多个量测且量测数量服从泊松分布。GM_EKF_PHD_Simulate_Measurements.m就是用这种方式生成模拟数据的。它的逻辑是先按目标的扩展形状生成若干散射点再对每个散射点叠加高斯噪声最后把落在传感器视场内的点作为量测输出。function Z GM_EKF_PHD_Simulate_Measurements(X, lambda, R) Z cell(length(X), 1); for i 1 : length(X) n poissrnd(lambda); % 目标 i 产生 n 个量测 center X{i}(1:2); % 目标位置 scatter center randn(2, n) * sqrt(extent_cov); Z{i} scatter sqrtm(R) * randn(2, n); % 加量测噪声 end endlambda是每个目标每帧平均量测数典型取值 520。这个参数会直接传导给更新步lambda 越大量测越多更新后分量数越多剪枝压力越大。模拟时注意量测噪声R必须和滤波器内部假设的R_ekf一致否则更新步的似然比算出来是错的滤波器会把真实量测当成杂波。一个常见坑是在模拟时用了较小的R而滤波时用了较大的R结果导致量测更新权重偏低目标强度不断衰减。4.2 EKF 雅可比矩阵从解析求导到数值验证非线性传感器场景比如雷达的极坐标量测不能直接用线性 (H)需要用 EKF 把量测函数对状态求导。源码里Calculate_Jacobian_H.m负责解析计算雅可比矩阵Test_Jacobian_Calculation.m用数值差分校验它的正确性。这个校验在调试时非常重要雅可比写错一个符号滤波结果不会立即发散而是先出现几帧的偏差然后权重错误累积最终航迹偏移。以雷达量测 (z [r, \theta]^T)、状态 ([x, \dot{x}, y, \dot{y}]^T) 为例雅可比为function H Calculate_Jacobian_H(x) % x [px; vx; py; vy] r sqrt(x(1)^2 x(3)^2); H [ x(1)/r, 0, x(3)/r, 0; % dr/dx -x(3)/r^2, 0, x(1)/r^2, 0 ]; % dtheta/dx end注意r很小时雅可比会变得很大导致更新步增益异常。我一般会在r小于某个阈值比如 0.1 m时截断强制让 H 的方位角行乘以一个保护系数。Test_Jacobian_Calculation.m的做法是对状态逐维做扰动用中心差分的结果和解析 H 对比如果相对误差大于 1e-5 就要查公式了。这个文件是整套源码里最容易被忽略但最有学习价值的部分。4.3 匈牙利算法做量测划分GM-PHD 的更新公式理论上需要对量测集合的所有子集做划分这是组合爆炸的。工程实现一般用匈牙利算法做近似先把量测按与预测分量的似然距离构建代价矩阵再求解最优一对一分配最后把分配到同一预测分量的量测合并为一个“量测簇”参与更新。源码里的Hungarian.m就是标准匈牙利求解器。% 代价矩阵 C(i,j) -log( p_Z(i | j) ) cost zeros(num_meas, num_comp); for i 1 : num_meas for j 1 : num_comp cost(i,j) -log( mvnpdf(Z(:,i), S(:,j), R, ...) ); end end assignment Hungarian(cost); % assignment(i) j 表示第 i 个量测分配给第 j 个分量用匈牙利算法肯定不是最优的目标划分但胜在多项式时间可解。实际中要注意代价矩阵里mvnpdf的协方差用的是新息协方差 (S HPH^T R)不是单纯量测噪声。如果忘了算S分配结果会偏向噪声小的量测导致目标覆盖位置的量测被错误切分。运行前先检查Hungarian返回的分配是行索引到列索引还是反过来错了会把量测簇分到别的目标上OSPA 指标会给出一个突然的尖峰。5. 用 OSPA 指标验证滤波精度并反推参数瓶颈5.1 OSPA 距离定位误差与基数误差的解耦OSPA 距离把多目标误差拆成两部分目标位置偏差和数量偏差。给定阈值 (c) 和阶数 (p)(d^{(c)}_p(X,Y)) 越小表示跟踪越好。源码里的ospa_dist.m返回的是定位误差和基数误差两个分量CalculateOSPAMetric.m负责对整段仿真的每一帧计算并汇总。调用方式很简单d_ospa ospa_dist(X_est, X_true, c, p); % 例如 c 50, p 1 d_ospa CalculateOSPAMetric(X_est, X_true, 50, 1);c是截断阈值决定了单个目标最大允许的距离惩罚。c设置太小系统会过度惩罚“多跟踪了一个目标”这种基数错太大又会把严重偏离的目标当作不可接受但又不罚得够狠。实际中我倾向于用c等于量测误差的 510 倍来观察整体趋势再用不同c对比同一组数据确认结论不依赖阈值选择。5.2 一个调参经验剪枝门限和出生强度的联动我拿这套源码做仿真时发现最隐蔽的组合问题出生强度调大、剪枝门限也调大两者会互相掩盖症状。出生强度大意味着每帧注入很多低权重分量剪枝门限大会把它们一次性裁掉表面上分量数可控但新出现的真实目标需要几帧才能累积到可检测权重造成漏检。反之出生强度小、剪枝门限也小分量数会缓慢膨胀内存和 CPU 都不好看。一个具体可复现的调参顺序是先用固定的 (c50, p1) 跑一段标准场景画出 OSPA 随帧的曲线如果曲线中段出现持续的平台偏高看基数误差分量如果基数误差确实占主导先把出生强度放大 0.1 倍观察 OSPA 是否下降如果定位误差主导则优先减小合并门限 (U)让两个靠近目标保留为两个分量。每次只动一个参数记录分量平均数和 OSPA 指标这样源码包就能变成一个参数标定工具。把出生强度调高 0.1 个数量级OSPA 的基数误差会立刻反映出来这是排查漏警最快的一条路径。本文还有配套的精品资源点击获取