
做目标跟踪的朋友大概率都经历过这种尴尬传感器报文里明明一个目标占了七八个点算法却非得把它当成一个点来滤波。传统点目标滤波比如标准EKF只能给出位置和速度一旦目标在雷达、激光雷达或者摄像头画面里被多个量测同时覆盖点假设直接就崩了滤波结果要么来回跳要么干脆丢目标。扩展目标跟踪Extended Target Tracking就是专门处理这种“目标占据多个量测”工况的技术路线而MEM-EKF算法简单说就是“多椭圆模型 扩展卡尔曼滤波”的组合用椭圆去拟合目标的外形再通过EKF框架同时估计目标的质心运动状态和形状参数。这篇文章围绕一个基于Matlab的MEM-EKF椭圆扩展目标跟踪实现展开把算法怎么拆、状态怎么建、量测怎么处理、参数怎么调讲透适合正在做滤波跟踪、雷达数据处理、机器视觉目标检测后处理以及准备复现相关论文的读者参考。1. 项目概览与问题背景1.1 为什么非得做“扩展目标”而不能继续用点目标先还原一个很典型的场景。一辆车在交叉路口左转毫米波雷达一个扫描周期里从它身上返回了二十多个点这些点在距离-多普勒图上形成一条长长的拖影。传统点目标算法拿到这二十多个点以后通常的做法是“取个质心”或者“聚类后取一个代表点”再丢进卡尔曼滤波器。这么做的直接后果是车的长度、宽度、朝向这些信息全部丢失了而且聚类和取质心的过程本身就会引入额外误差尤其在两个目标挨得近的时候质心很容易被拽到两辆车中间的空地上。扩展目标跟踪的思路完全不同它承认目标不是一个点而是占有一定空间体积的实体。目标产生的每个量测都可以看作是目标表面上某个反射点的位置。这样一来滤波器的状态里除了位置、速度之外还必须包含描述目标轮廓的参数。对于大多数车辆、舰船、行人这类目标来说用一个椭圆去近似轮廓是一个性价比非常高的选择——既能描述尺寸和朝向又不会让状态维度过高导致计算爆炸。1.2 MEM-EKF到底做了什么先说明一点网上搜“MEM-EKF”不同论文和源码里的解释并不完全一致。有的文献把它写作Multiple Ellipse Model based Extended Kalman Filter多椭圆模型扩展卡尔曼滤波也有资料把它解释为Modified Extended Kalman Filter改进型扩展卡尔曼滤波。就这篇Matlab实现而言它的核心更贴合“多椭圆模型 扩展卡尔曼滤波”这条路线在单目标场景下用一个椭圆拟合目标的外形在目标形状复杂、单个椭圆拟合不住的时候就把目标拆成多个椭圆分别建模每个椭圆仍然用EKF递推。为什么用EKF而不是无迹卡尔曼滤波UKF或者粒子滤波PF原因也很实际。EKF对线性化精度要求高但计算量最小在形状参数变化平缓、量测噪声近似高斯的情况下完全够用。UKF精度更高但需要多生成一倍Sigma点粒子滤波在二维量测场景下倒是稳定可计算开销太大做实时处理的时候不划算。所以MEM-EKF的定位就是在精度和实时性之间取一个平衡工程上最容易落地。2. 算法原理解读与模块拆解2.1 状态向量与椭圆建模先定义状态向量。我这里用7维状态x [px, py, vx, vy, θ, L, S]^T其中px、py是椭圆中心也就是目标质心的位置vx、vy是质心速度θ是椭圆朝向角L是长半轴S是短半轴。有的实现会用“面积 纵横比”来代替L和S但我不推荐这么做因为长半轴和短半轴直接对应目标的几何尺寸给初始值、设边界约束、和真实世界做对比都特别直观调试的时候一眼就能看出估计值合不合理。椭圆上任意一个边界点可以用一个参数φ表示。假设椭圆中心在原点、朝向角为0边界点坐标是[L·cosφ, S·sinφ]^T。当椭圆旋转了角度θ之后边界点在全局坐标系下的位置就是质心坐标加旋转zx px L·cosφ·cosθ - S·sinφ·sinθ zy py L·cosφ·sinθ S·sinφ·cosθ这个式子就是量测模型的基础。量测方程的非线性就体现在这里φ和状态的乘积、θ的三角函数组合EKF需要对整条表达式求雅可比不能像线性系统那样直接套公式。提示状态向量里要不要加角速度ω如果你跟踪的是转弯机动目标可以加。但第一版实现建议保持常速模型CV先跑通直线场景再往里面加加速度或转弯率否则滤波发散的时候你根本分不清是模型没建对还是雅可比算错了。2.2 量测模型与雅可比矩阵每一帧目标会生成m个量测点每个量测点的模型是z_k^i h(x_k) w_k^iw_k^i是量测噪声通常假设为零均值高斯白噪声协方差为R。h(x_k)就是上面那个椭圆边界点公式。EKF的关键是雅可比矩阵H。对状态向量求偏导得到对px、py、vx、vy的偏导很简单前两列是[1,0]和[0,1]速度列全为0。对θ的偏导 ∂zx/∂θ -L·cosφ·sinθ - S·sinφ·cosθ ∂zy/∂θ L·cosφ·cosθ - S·sinφ·sinθ对L的偏导 ∂zx/∂L cosφ·cosθ ∂zy/∂L cosφ·sinθ对S的偏导 ∂zx/∂S -sinφ·sinθ ∂zy/∂S sinφ·cosθ把上面这些拼成一个2×7的矩阵就是完整的HH [1, 0, 0, 0, -L·cosφ·sinθ - S·sinφ·cosθ, cosφ·cosθ, -sinφ·sinθ; 0, 1, 0, 0, L·cosφ·cosθ - S·sinφ·sinθ, cosφ·sinθ, sinφ·cosθ]这个雅可比看起来繁琐但其实每次只改一个量测点计算量很小。这里的关键问题是φ在状态里没有显式建模它实际上是“未知的外形参数”代表量测点落在椭圆表面的哪个位置。工程上最常见的简化办法是用量测点相对预测质心的方位角α atan2(zy - py_pred, zx - px_pred)来近似φ。2.3 量测点方位的反解与迭代修正直接用量测方位角近似φ在短线轴比接近1的时候误差不大但如果目标是个长条形比如车身长10米宽2米边界点处的法向方向和质心连线方向并不一致这个近似误差就不能忽视了。我的处理方式是做迭代修正先用方位角α作为φ的初始值计算预测量测点和雅可比更新一次状态然后用更新后的状态重新计算质心位置再用量测点与质心的新的方位角作为新的φ再更新一次。一般迭代两轮就够再多容易过拟合量测噪声。% 迭代反解椭圆表面参数phi alpha atan2(z(2) - x_pred(2), z(1) - x_pred(1)); for iter 1:2 [z_hat, H] ellipse_measurement(x_pred, alpha); dx z(1) - z_hat(1); dy z(2) - z_hat(2); alpha atan2(dy, dx); [z_hat, H] ellipse_measurement(x_pred, alpha); end这里ellipse_measurement函数就干一件事输入状态向量和φ输出预测量测点和雅可比矩阵。这个迭代修正相当于变相的“量测方程线性化点修正”对长条形的目标形状估计有帮助实测能明显压低朝向角的稳态误差。3. Matlab代码实现与关键环节3.1 模拟场景生成先构造一个能复现的仿真环境。目标做匀速直线运动初始位置(0,0)速度(10, 2)米/秒采样周期T0.1秒一共跑20秒。目标的真实形状是长半轴6米、短半轴2米、朝向角30度的椭圆。每个采样周期随机生成15到25个量测点这些点落在椭圆边界上叠加少量高斯噪声T 0.1; N 200; pos_x 10 * (0:N-1) * T; pos_y 2 * (0:N-1) * T; theta_true deg2rad(30); L_true 6; S_true 2; for k 1:N m randi([15, 25]); % 每帧量测点数随机 phi 2 * pi * rand(1, m); % 椭圆参数角 R [cos(theta_true), -sin(theta_true); sin(theta_true), cos(theta_true)]; offset R * [L_true * cos(phi); S_true * sin(phi)]; z [pos_x(k); pos_y(k)] offset 0.1 * randn(2, m); meas{k} z; gt_center(:, k) [pos_x(k); pos_y(k)]; end量测噪声协方差R直接给0.1^2·I也就是每个轴的量测噪声标准差是0.1米。这个数值在真实雷达或者激光雷达里不算苛刻足够体现算法性能了。3.2 EKF递推主循环算法主体就是标准EKF的预测-更新结构但更新环节要做逐点处理。状态转移矩阵F是7维的只有位置速度部分有变化形状参数和朝向角假设不变F [1 0 T 0 0 0 0; 0 1 0 T 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 0 0 0 0 1 0; 0 0 0 0 0 0 1];过程噪声Q分两块位置速度部分支持目标机动形状参数部分只给很小的噪声因为车辆的长宽不会在0.1秒内突变。我常用的Q结构是Q blkdiag(0.5 * [T^3/3, T^2/2; T^2/2, T], ... 0.5 * [T^3/3, T^2/2; T^2/2, T], ... 0.001 * eye(3));这里的0.5是位置速度方向的加速度扰动强度0.001是形状参数和朝向角的随机游走强度。主循环里最关键的是逐点更新部分x_pred F * x_est(:, k-1); P_pred F * P_est * F Q; for i 1:size(meas{k}, 2) z meas{k}(:, i); [z_hat, H] iterate_ellipse_measurement(x_pred, P_pred, z); S H * P_pred * H R; K P_pred * H / S; x_pred x_pred K * (z - z_hat); P_pred (eye(7) - K * H) * P_pred; end x_est(:, k) x_pred; P_est P_pred;注意逐点更新时x_pred和P_pred是不断被更新的也就是说同一个量测帧里的第2个量测点用的是第1个量测点更新后的状态作为线性化点。这样做的好处是收敛速度快但也带来量测顺序敏感问题我的经验是每帧更新前先随机打乱量测点顺序不然某些场景下最后的估计值会偏向量测序列末尾点的分布。3.3 初始值与参数调试初始值决定EKF会不会在头几帧就炸掉。位置初值可以直接用第一帧量测点的均值速度初值可以给零或者根据前两帧质心位置差估计。朝向角初值强烈建议用PCA初始化取第一帧所有量测点计算样本协方差矩阵然后做特征值分解最大的特征值对应的特征向量方向就是初始朝向角。长短半轴初值则取特征值开方的适当缩放。这样做以后滤波器从第一帧开始就不会走太偏。P0的初始设定要覆盖真实状态的不确定性。位置和速度可以给大一点因为不知道目标从哪里来P0 blkdiag(100 * eye(4), deg2rad(5)^2, 0.5^2, 0.5^2); x0 [mean(meas{1}(1,:)); mean(meas{1}(2,:)); 0; 0; init_theta; 5; 2];形状参数的初值即使有一些偏差也没关系EKF会在帧间慢慢修正。但如果你第一帧就把L初值设为1米而真实目标是6米初始增益太大可能几帧就把协方差“锁死”到错误值上。所以调试顺序是先调位置速度再调朝向最后调形状。4. 常见问题与排错实录4.1 椭圆长短轴发散或变成负数这个是我被问得最多的一个问题现象就是跑了几十帧以后L或者S的估计值变成负的或者数值大到离谱。根源往往不是滤波公式本身而是状态更新没有物理约束。EKF不会管L和S必须为正这件事卡尔曼增益一旦给得偏大一个野值点就能把L推到负数。处理办法有两个层面。第一层是工程兜底每次更新结束后加一个投影操作把L和S钳位到合理区间x_pred(6) max(L_min, min(L_max, x_pred(6))); x_pred(7) max(S_min, min(S_max, x_pred(7))); if x_pred(7) x_pred(6) tmp x_pred(6); x_pred(6) x_pred(7); x_pred(7) tmp; end第二层是降低形状参数的过程噪声。Q里形状参数对应的方差设得越小滤波器对形状参数的更新就越保守L和S就越稳定。但没有过程噪声的话如果目标真实尺寸在变化比如车辆靠近传感器时点云变密形状参数又跟不上。这里需要一个折中跑一段数据之后观察形状参数的稳态波动再回头调整Q。4.2 位置估计发散、协方差异常收敛这类问题排查起来最费时间。典型症状是位置RMSE前几帧正常突然某帧开始误差急剧增大同时P矩阵对角线数值快速下降到一个极小值说明滤波器“自以为”非常确定但实际状态已经跑偏了。我通常会做两件事。第一打印新息序列innovation z - z_hat看均值是否接近0、标准差是否和理论值sqrt(H·P·H^T R)一致。如果新息均值长期为正或者波动剧烈八成是量测模型有偏差比如φ的迭代反解没收敛。第二检查量测野值。仿真数据里偶尔会有几个点不在椭圆边界附近这些点对EKF的伤害非常大因为它们的新息大、增益也大一次更新就能把状态拽走。建议加一个基于新息马氏距离的门限innov z - z_hat; dist innov / (H * P_pred * H R) * innov; if dist chi2inv(0.99, 2) continue; % 丢弃这个野值点 end门限取自由度为2的卡方分布的0.99分位点大约是9.21。这个操作的原理是如果量测点真的来自目标表面新息距应该服从卡方分布如果来自野值距离会明显偏大。实测这个简单门限能挡住大部分离群点。4.3 朝向角估计始终偏差表现是质心位置跟踪得很好椭圆中心紧贴真实中心但椭圆看起来“横着躺”——θ估计值比真实值偏了十几度甚至更多。这种情况常见于长条形目标因为当量测点均匀分布在椭圆边界上时目标前后两端反射点的曲率半径小点也就更密集对朝向角的约束更强而侧面点少约束弱估计就容易偏。我的处理经验有两条。第一初始化必须用PCA。直接拍脑袋给θ0的话前几十帧可能一直在纠正初始偏差而由于EKF线性化误差可能永远纠不回来。第二适当增大θ的过程噪声。θ的随机游走噪声给到0.01到0.02弧度级别可以让滤波器对朝向角保持一定的“追踪弹性”而不是被初始值锁定。如果目标真实朝向不变估计结果会有一点稳态波动但不会系统性偏。另一个实用技巧显示结果时不要把估计椭圆只画一个“实心填充”要画出椭圆边界和中间轴线。只需要一眼就能看出朝向角是否合理比看数字直观得多。4.4 常见问题速查表症状可能原因处理方案L/S变成负数或发散无物理约束增益过大钳位到合法区间降低形状过程噪声位置误差突然爆炸野值点未过滤协方差锁死新息马氏距离门限过滤检查Q是否过小朝向角始终偏初始化不合理线性化误差PCA初始化微调θ过程噪声迭代反解φ收敛太慢前几十帧大误差初始协方差P0过小过程噪声过小增大P0对应的位置速度块适当增大Q多目标场景下互相干扰量测关联错误先用数据关联最近邻或联合概率关联再滤波最后说点实际体会这套Matlab实现我前前后后调过好几轮最深的感受是扩展目标跟踪的难点不在EKF公式本身而在“量测点与目标形状之间的几何关系”建模。很多人把代码跑通以后发现位置跟得挺好但一画椭圆就歪问题几乎都出在φ的估计和雅可比的线性化点上。如果你也在复现这类项目建议先别急着上多目标或者复杂机动模型先把“单目标直线等速形状不变”这个基准场景调稳再逐步增加难度。另外一个小技巧仿真时记录真实椭圆参数和每次量测的真实φ和滤波器的估计值放一起画出来。一旦发现某个φ残差出现结构性偏移就说明你的量测模型需要修正这比对着协方差矩阵猜问题高效得多。这个项目后续如果要继续扩展可以往两个方向走一个是用交互多模型IMM替代单模型让滤波器能适应转弯目标另一个是把单个椭圆升级成多个椭圆拼接拟合更复杂的形状。框架不变改的只是状态向量和量测方程而已。