简介:面向非线性系统状态估计问题的无迹卡尔曼滤波(UKF)MATLAB实现,压缩包内共1个m文件,体积仅2KB,是一份轻量级的状态估计算法参考代码。文件中完成UKF核心迭代闭环,包括利用无迹变换生成sigma点、通过非线性状态转移函数进行预测并加权合成预测均值与协方差、再经观测方程计算卡尔曼增益并完成状态更新;同时提供初始化、迭代计算与结果输出的基本结构。程序预留了状态转移函数、观测函数以及过程噪声和观测噪声协方差等关键参数,读者可按需修改α、β、κ等UKF参数以适配不同非线性强度场景。该实现可迁移至导航、目标跟踪、电力系统动态估计、生物医学信号处理等领域,对理解UKF从数学原理到工程代码的映射很有帮助。资源已有345人学习下载,虽然仅有一个脚本,但算法步骤完整、模块划分清晰,适合作为MATLAB下快速验证UKF算法或二次开发的基础。
1. 拿到 ukf.zip 后,先搞懂它解决的是哪一类问题
非线性系统的状态估计是工程里绕不开的坎。EKF 把非线性函数做泰勒展开取一阶近似,遇到强非线性场景,线性化误差会直接写进协方差,滤波结果发散只是时间问题。ukf.zip 里的 ukf.m 走的是另一条路:不做线性化,而是用一组精心挑选的 sigma 点直接穿过非线性函数,用加权统计量还原输出分布。这个思路避开了雅可比矩阵的推导和计算,也突破了 EKF 对可导性的依赖。对于做导航、目标跟踪、无人车状态融合的工程师,这份代码能直接作为滤波器核心模块嵌进自己的框架里。下面我从 sigma 点的生成原理讲起,再逐段拆解 ukf.m 的实现逻辑,最后给出仿真对比和排错经验。
2. 无迹变换与 sigma 点生成:UKF 的数学基石
2.1 sigma 点为什么能代替线性化
UKF 的理论基础是无迹变换(Unscented Transformation, UT)。核心思想很直接:与其用一个线性函数去逼近非线性函数,不如用一组确定性的样本点去逼近随机变量的分布。这组样本点就是 sigma 点,它们被构造为具有与原状态分布相同的均值和协方差。
给定 n 维状态向量 x,均值 x_mean,协方差 P,sigma 点的生成规则如下:
- 第 0 个点:x_0 = x_mean
- 第 i 个点(i=1,...,n):x_i = x_mean + (sqrt((n+lambda) * P))_i
- 第 i+n 个点:x_{i+n} = x_mean - (sqrt((n+lambda) * P))_i
这里 lambda = alpha^2 * (n + kappa) - n,其中 sqrt 表示矩阵平方根,通常用 Cholesky 分解计算。这组点通过非线性函数 f 传播后,对输出做加权平均,就能还原出传播后分布的均值和协方差。相比 EKF 只保留一阶泰勒项,UT 变换在计算量相近的前提下,精度能匹配到三阶矩。
2.1.1 三个缩放参数的物理意义
alpha 控制 sigma 点离均值的距离,通常取 1e-3 到 1 之间的数值。alpha 越小,sigma 点越靠近均值,对非线性函数的局部曲率越敏感,但也越容易在协方差计算中出现数值误差。
kappa 是次级缩放参数,默认取 0 即可。当状态维度较高时可以设置为 3 - n,用于调节高阶矩的影响。beta 与先验分布有关,高斯分布时最优值为 2。这三个参数不是随意拍的,它们的取值直接影响滤波收敛速度和数值稳定性。
2.1.2 权重计算规则
均值权重 W_m 和协方差权重 W_c 分别定义为:
- W_m_0 = lambda / (n + lambda)
- W_c_0 = lambda / (n + lambda) + (1 - alpha^2 + beta)
- W_m_i = W_c_i = 1 / (2 * (n + lambda)),i = 1, ..., 2n
注意 W_c_0 里多了一项 (1 - alpha^2 + beta),这是为了在高斯假设下补偿四阶矩信息。写代码时这里最容易漏,漏掉之后滤波均值和协方差的更新会缓慢偏置。
2.2 sigma 点生成的 MATLAB 实现
在 ukf.m 中,sigma 点生成通常封装为一个独立函数,便于在预测和更新阶段复用。下面这段代码是常见的实现方式,直接基于状态维度和协方差矩阵计算:
function [X, Wm, Wc] = ut_transform(x, P, alpha, beta, kappa) n = numel(x); % 状态维度 lambda = alpha^2 * (n + kappa) - n; % Cholesky 分解求矩阵平方根 S = chol((n + lambda) * P, 'lower'); % 生成 2n+1 个 sigma 点 X = zeros(n, 2*n+1); X(:, 1) = x; for i = 1:n X(:, i+1) = x + S(:, i); X(:, i+n+1) = x - S(:, i); end % 计算权重 Wm = zeros(1, 2*n+1); Wc = zeros(1, 2*n+1); Wm(1) = lambda / (n + lambda); Wc(1) = Wm(1) + (1 - alpha^2 + beta); for i = 2:2*n+1 Wm(i) = 1 / (2 * (n + lambda)); Wc(i) = Wm(i); end end这段代码的输入参数依次是当前状态均值 x、协方差 P、缩放参数 alpha、beta、kappa。输出 X 的每一列是一个 sigma 点,Wm 和 Wc 是对应的均值与协方差权重。在调用时建议优先用chol函数而不是sqrtm,因为 Cholesky 分解更快,且对正定矩阵天然保证三角矩阵形式,后续加权计算更稳定。
注意:如果协方差矩阵 P 接近半正定,
chol会直接报错。遇到这种情况,先用P = (P + P') / 2强制对称化,再叠加一个很小的单位阵,比如P = P + 1e-9 * eye(n),避免分解失败。
3. ukf.m 核心实现:预测与更新两个阶段的完整拆解
3.1 系统模型的定义方式
ukf.m 的关键设计在于把系统模型以函数句柄的形式传入,这样滤波核心不依赖具体物理场景。通常需要四个要素:
- 状态转移函数 f:描述状态从 k 时刻到 k+1 时刻的演化,输入是状态向量和控制量,输出是预测状态
- 观测函数 h:描述状态到观测空间的映射,输入是状态向量,输出是预测观测值
- 过程噪声协方差 Q:刻画模型误差的不确定性
- 观测噪声协方差 R:刻画传感器测量噪声的统计特性
在代码里,f 和 h 需要用@定义匿名函数或单独写成 function 文件。以下是一个简化的调用示例:
f = @(x) [x(1) + x(2)*dt; x(2)]; % 匀速运动模型 h = @(x) [sqrt(x(1)^2 + x(2)^2); atan2(x(2), x(1))]; % 距离和方位角观测 Q = diag([0.1, 0.01]); % 过程噪声 R = diag([0.5, 0.01]); % 观测噪声这里 f 假设目标做匀速直线运动,状态为位置和速度;h 模拟雷达返回的距离和方位角。dt 是采样间隔,在脚本外层定义。这种通过函数句柄注入模型的方式,让 ukf.m 可以无缝切换到不同的应用场景,不需要改动滤波核心代码。
3.2 预测阶段:sigma 点穿过状态转移函数
预测阶段的核心是把上一时刻的 sigma 点逐列通过状态转移函数 f 传播,再加权计算预测均值和协方差。伪代码逻辑如下:
[X_prev, Wm, Wc] = ut_transform(x_est, P_est, alpha, beta, kappa); X_pred = zeros(n, 2*n+1); for i = 1:2*n+1 X_pred(:, i) = f(X_prev(:, i)); % 每个 sigma 点独立通过非线性模型 end x_pred = X_pred * Wm'; % 加权平均得到预测均值 P_pred = Q; for i = 1:2*n+1 dx = X_pred(:, i) - x_pred; P_pred = P_pred + Wc(i) * (dx * dx'); % 加权外积累加协方差 end这段代码有两点值得注意。第一,sigma 点必须一列一列穿过 f,不能用矩阵运算一次性替代,因为 f 通常包含三角函数、指数等逐点运算。第二,协方差初始化为 Q 再累加,是因为过程噪声本身是加性的,直接在预测协方差中叠加即可。
3.2.1 非加性噪声的处理
上面的代码默认过程噪声是加性噪声,即 x_{k+1} = f(x_k) + w_k。如果噪声通过非线性方式进入状态,比如乘法噪声,就需要把噪声变量扩维到状态向量中,生成 sigma 点时连带噪声的均值和协方差一起处理。扩维后的状态维度变成 n + q,其中 q 是噪声维度,计算量会相应增加,但精度更高。
3.3 更新阶段:观测投影、交叉协方差与卡尔曼增益
更新阶段把预测的 sigma 点通过观测函数 h 映射到观测空间,然后计算实际观测与预测观测的差值(创新项),最后用卡尔曼增益修正状态。核心代码:
Z_pred = zeros(m, 2*n+1); for i = 1:2*n+1 Z_pred(:, i) = h(X_pred(:, i)); % 预测 sigma 点映射到观测空间 end z_pred = Z_pred * Wm'; % 预测观测均值 P_zz = R; for i = 1:2*n+1 dz = Z_pred(:, i) - z_pred; P_zz = P_zz + Wc(i) * (dz * dz'); % 观测协方差 end P_xz = zeros(n, m); for i = 1:2*n+1 dx = X_pred(:, i) - x_pred; dz = Z_pred(:, i) - z_pred; P_xz = P_xz + Wc(i) * (dx * dz'); % 状态-观测交叉协方差 end K = P_xz / P_zz; % 卡尔曼增益 x_est = x_pred + K * (z_actual - z_pred); % 状态修正 P_est = P_pred - K * P_zz * K'; % 协方差修正这里P_zz对应创新协方差,P_xz是状态与观测的交叉协方差。卡尔曼增益通过P_xz / P_zz计算,在 MATLAB 中等价于P_xz * inv(P_zz),但用右除运算符避免了显式求逆,数值上更稳定。更新后的x_est和P_est会作为下一时刻的输入,形成递归滤波闭环。
提示:观测矩阵 H 在线性卡尔曼滤波中是常数矩阵,而在 UKF 中完全由 h 函数替代。这意味着 h 的编写质量直接决定滤波性能,务必在接入真实系统前用仿真数据验证 h 的映射是否正确。
下表总结了 ukf.m 中主要变量的含义,方便在阅读和修改代码时对照:
| 变量名 | 维度 | 含义 |
|---|---|---|
| x_est | n x 1 | 当前时刻状态估计值 |
| P_est | n x n | 当前时刻状态协方差矩阵 |
| X_pred | n x (2n+1) | 预测阶段的 sigma 点矩阵 |
| z_pred | m x 1 | 预测观测向量 |
| P_zz | m x m | 创新协方差矩阵 |
| P_xz | n x m | 状态与观测交叉协方差矩阵 |
| K | n x m | 卡尔曼增益矩阵 |
3.4 ukf.m 的完整主循环框架
把上面两个阶段串起来,ukf.m 的主循环通常长这样:
function [x_hist, P_hist] = ukf(f, h, Q, R, x0, P0, z_hist, alpha, beta, kappa) n = numel(x0); x_est = x0; P_est = P0; N = size(z_hist, 2); x_hist = zeros(n, N); P_hist = zeros(n, n, N); for k = 1:N % 预测 [X_prev, Wm, Wc] = ut_transform(x_est, P_est, alpha, beta, kappa); X_pred = zeros(n, 2*n+1); for i = 1:2*n+1 X_pred(:, i) = f(X_prev(:, i)); end x_pred = X_pred * Wm'; P_pred = Q; for i = 1:2*n+1 dx = X_pred(:, i) - x_pred; P_pred = P_pred + Wc(i) * (dx * dx'); end % 更新 Z_pred = zeros(size(z_hist, 1), 2*n+1); for i = 1:2*n+1 Z_pred(:, i) = h(X_pred(:, i)); end z_pred = Z_pred * Wm'; P_zz = R; P_xz = zeros(n, size(z_hist, 1)); for i = 1:2*n+1 dz = Z_pred(:, i) - z_pred; P_zz = P_zz + Wc(i) * (dz * dz'); dx = X_pred(:, i) - x_pred; P_xz = P_xz + Wc(i) * (dx * dz'); end K = P_xz / P_zz; x_est = x_pred + K * (z_hist(:, k) - z_pred); P_est = P_pred - K * P_zz * K'; x_hist(:, k) = x_est; P_hist(:, :, k) = P_est; end end这个函数的输入输出设计比较通用:f 和 h 是模型函数句柄,Q 和 R 是噪声协方差,x0 和 P0 是初始状态及协方差,z_hist 是观测序列,输出 x_hist 和 P_hist 保存每一时刻的估计结果,便于后续画图和误差分析。
4. 仿真验证:用经典非线性场景实测 ukf.m 的估计精度
4.1 搭建一个带强非线性的仿真环境
为了检验 ukf.m 的估计效果,这里用一个典型的目标跟踪场景:雷达观测目标的距离 r 和方位角 theta,状态为笛卡尔坐标系下的位置和速度。状态转移是线性的(匀速运动),但观测方程是强非线性的,因为 r = sqrt(px^2 + py^2),theta = atan2(py, px)。这种场景下 EKF 的线性化误差在目标靠近雷达或方位角快速变化时会被放大,而 UKF 不需要任何近似。
仿真脚本大致如下:
dt = 0.1; t = 0:dt:20; % 真实轨迹:匀速直线运动 x_true = [100; 5; 50; 2]; % px, vx, py, vy A = [1 dt 0 0; 0 1 0 0; 0 0 1 dt; 0 0 0 1]; % 生成观测数据 z_hist = zeros(2, numel(t)); for i = 1:numel(t) px = x_true(1); py = x_true(3); z_hist(1, i) = sqrt(px^2 + py^2) + sqrt(R(1,1)) * randn; z_hist(2, i) = atan2(py, px) + sqrt(R(2,2)) * randn; x_true = A * x_true; end这里用了一个简单的匀速模型生成仿真观测,添加了高斯噪声。R 的值可以根据传感器精度设定,比如距离噪声标准差 5 米,方位角噪声标准差 0.01 弧度。真实轨迹和观测生成完毕后,调用 ukf.m 进行状态估计,再把估计轨迹与真实轨迹对比。
4.2 EKF 与 UKF 的估计误差对比
在同样的仿真数据和初始条件下,分别运行 EKF 和 UKF,比较两者的位置估计误差。EKF 需要手动计算雅可比矩阵,观测方程的雅可比在极坐标到笛卡尔坐标的转换中推导比较繁琐,而且当目标距离较近时,方位角的线性化误差会显著增大。
实际运行中常见的现象是:EKF 在目标远离雷达时表现尚可,但一旦目标进入近距离区域,位置误差出现明显抖动甚至发散。UKF 由于直接使用 sigma 点映射,不需要线性化,距离和方位角的强耦合关系被完整保留在样本传播中,误差稳定在噪声水平附近。建议对比时用均方根误差(RMSE)作为量化指标:
pos_err_ukf = sqrt((x_hist(1,:) - x_true(1,:)).^2 + (x_hist(3,:) - x_true(3,:)).^2); rmse_ukf = sqrt(mean(pos_err_ukf.^2));这个指标能直观反映整体估计精度。理论上,UKF 的估计精度在非线性强度较高时优于 EKF,在弱非线性场景下两者差距不大,但 UKF 不需要推导雅可比矩阵,开发效率更高。
注意:仿真时观测序列的生成和滤波器的调用要使用相同的 R 和 Q,否则对比结果没有参考意义。过程噪声 Q 的设置通常比真实模型略大,以吸收未建模的动态误差。
4.3 参数敏感性与收敛性调试
alpha 的取值对 UKF 的收敛速度影响很大。取 alpha = 1 时 sigma 点分布范围较大,对非线性函数的探索更充分,但协方差可能扩张过快;取 alpha = 1e-3 时 sigma 点集中在均值附近,局部精度高,但如果初始协方差 P0 设置过大,前几步可能出现协方差收缩过慢的问题。
我一般这样调试:先固定 beta = 2 和 kappa = 0,然后以 10 倍步长扫描 alpha 从 1e-3 到 1,观察状态估计的 RMSE 变化曲线。如果 alpha 过小导致滤波发散,优先增大 alpha 或在 P0 中叠加微小的对角扰动。kappa 的调整优先级最低,只有高维状态(n > 10)才需要主动优化。
5. 数值稳定性排错与从仿真到实际应用的三个关键点
5.1 协方差矩阵失去正定性时的处理策略
UKF 在长时间运行时,协方差矩阵可能因浮点舍入误差逐渐失去对称正定性,触发chol报错。一个快速修复方案是每次迭代开始前对 P_est 做对称化处理:
P_est = (P_est + P_est') / 2; [V, D] = eig(P_est); D(D < 0) = 1e-6; % 将负特征值钳位到极小正值 P_est = V * D * V';这个操作的代价是多次特征分解,对实时性要求高的场景可以改为每隔 N 步执行一次。另一个更轻量的做法是直接把chol换成sqrtm,但 sqrtm 对非正定矩阵同样会警告,且计算速度更慢,建议优先修正协方差而不是换分解函数。
5.2 从 ukf.m 到嵌入式环境的定点化注意点
ukf.m 在 MATLAB 里运行依靠双精度浮点,迁移到嵌入式平台时需要关注两个问题:一是 Cholesky 分解在单精度下可能频繁触发奇异警告,需要把主对角元的最小值钳位到 1e-6 量级;二是 sigma 点传播过程中三角函数的计算开销,如果实时性紧张,可以考虑用查找表近似观测函数 h 的输出。
实际接传感器数据时,观测向量 z 的维度 m 如果小于状态维度 n,滤波依然正常工作,因为更新阶段通过 P_xz 和 P_zz 的维度自然匹配。但如果 m 大于 n,需要检查观测之间是否存在冗余,否则 P_zz 接近奇异会导致增益矩阵异常。
5.3 用残差分析判断滤波器是否发散
一个实用的验证手段不只是看估计轨迹,还要分析创新序列(innovation)的统计特性。在滤波正常时,创新序列应近似为零均值白噪声,协方差与 P_zz 匹配。用 MATLAB 计算:
innov = z_hist - z_pred_all; mean_innov = mean(innov, 2); std_innov = std(innov, 0, 2);如果均值显著偏离零或标准差远大于 sqrt(diag(R)),说明系统模型存在偏差,需要重新校准 Q 和 R,而不是继续调 alpha。还有一种常见情况:滤波器很快收敛但之后缓慢漂移,这与过程噪声 Q 设置过小相关,适度增大 Q 可以让滤波器保持对状态变化的响应能力。这几个检查点能帮助快速定位模型失配、噪声参数不合理和数值异常三类问题。
本文还有配套的精品资源,点击获取