无迹卡尔曼滤波器(Unscented Kalman Filter,简称 UKF)
无迹卡尔曼滤波器(Unscented Kalman Filter,简称 UKF)
详细讲解 + 更多实用 C# 示例代码(适合传感器数据如结温 tjMax/tjMin)
1. UKF 到底是什么?(最通俗、最深入的讲解)
UKF 是卡尔曼滤波家族里专门对付强非线性系统的“王牌”。
它不做任何线性化(不像 EKF 需要求雅可比矩阵),而是采用无迹变换(Unscented Transform):
“我选 2n+1 个精心设计的 Sigma 点(像卫星一样围绕当前估计值),把这些点直接扔进真正的非线性函数里跑一遍,再把结果加权平均回来。这样我就能精确捕捉到均值和协方差的二阶甚至更高阶信息。”
为什么比 EKF 强?
- EKF 用一阶泰勒展开 → 强非线性时误差巨大、容易发散
- UKF不需要求导,精度通常达到二阶泰勒展开(甚至更高)
- 对噪声分布要求低(只要是加性高斯就行)
- 实际工程中(IMU、GPS、电池 SOC、结温热模型、机器人定位)首选 UKF而不是 EKF
核心参数(必须记住)
- α(0.001~1):控制 Sigma 点分布范围(小值更集中)
- β(通常设 2):高斯分布最优值
- κ(通常 0 或 3-n):辅助缩放参数
- λ = α²(n + κ) - n (自动算)
完整算法流程(5 步)
- 生成 2n+1 个 Sigma 点(基于当前 x 和 P)
- 预测:每个点通过非线性过程函数 f → 得到预测 Sigma 点 → 计算 x⁻、P⁻
- 观测预测:每个点通过非线性观测函数 h → 得到预测测量值 → 计算 z⁻、Pzz、Pxz
- 计算卡尔曼增益 K = Pxz × Pzz⁻¹
- 更新:x = x⁻ + K(z - z⁻),P = P⁻ - K Pzz Kᵀ
2. 示例代码 1:最简单一维标量 UKF(推荐直接用于 tjMax/tjMin)
适合单值传感器(温度、结温、电压、压力等),不需要任何外部库!
/// <summary>/// 一维无迹卡尔曼滤波器(最实用版)/// 专门为传感器数据设计:tjMax、tjMin、Vf 等/// </summary>publicclassScalarUKF{privatedouble_x;// 当前估计值privatedouble_P;// 估计不确定度(协方差)// UKF 参数(经典最优值)privatereadonlydouble_alpha=1e-3;privatereadonlydouble_beta=2.0;privatereadonlydouble_kappa=0.0;privatereadonlydouble_Q;// 过程噪声方差(模型不确定性)privatereadonlydouble_R;// 测量噪声方差(传感器噪声)publicScalarUKF(doubleinitialValue,doubleinitialP,doubleprocessNoiseQ,doublemeasurementNoiseR){_x=initialValue;_P=initialP;_Q=processNoiseQ;_R=measurementNoiseR;}/// <summary>/// 输入一次原始测量值,返回滤波后的最佳估计/// </summary>publicdoubleUpdate(doublez){intn=1;// 一维doublelambda=_alpha*_alpha*(n+_kappa)-n;doublesqrtP=Math.Sqrt((n+lambda)*_P);// 1. 生成 3 个 Sigma 点double[]sigma=newdouble[3];sigma[0]=_x;sigma[1]=_x+sqrtP;sigma[2]=_x-sqrtP;// 权重doubleWm0=lambda/(n+lambda);doubleWc0=Wm0+(1-_alpha*_alpha+_beta);doubleWi=1.0/(2*(n+lambda));// 2. 预测(假设过程模型 x = x + 噪声,最常见)double[]xPred=newdouble[3];for(inti=0;i<3;i++)xPred[i]=sigma[i];// 可改成你的非线性 f(x)doublexMinus=Wm0*xPred[0]+Wi*(xPred[1]+xPred[2]);doublePMinus=Wc0*Math.Pow(xPred[0]-xMinus,2)+Wi*(Math.Pow(xPred[1]-xMinus,2)+Math.Pow(xPred[2]-xMinus,2))+_Q;// 3. 观测预测(假设观测模型 z = x + 噪声,可改成任意非线性 h)double[]zPredSigma=xPred;// h(x) = xdoublezMinus=xMinus;// 4. 计算增益和更新doublePxz=Wc0*(xPred[0]-xMinus)*(zPredSigma[0]-zMinus)+Wi*((xPred[1]-xMinus)*(zPredSigma[1]-zMinus)+(xPred[2]-xMinus)*(zPredSigma[2]-zMinus));doublePzz=Wc0*Math.Pow(zPredSigma[0]-zMinus,2)+Wi*(Math.Pow(zPredSigma[1]-zMinus,2)+Math.Pow(zPredSigma[2]-zMinus,2))+_R;doubleK=Pxz/Pzz;_x=xMinus+K*(z-zMinus);_P=PMinus-K*Pzz*K;return_x;}publicvoidReset(doublenewValue,doublenewP){_x=newValue;_P=newP;}}使用方式(直接替换你原来的 3 点缓冲逻辑)
// 类字段中新增privateScalarUKF_tjMaxUKF;privateScalarUKF_tjMinUKF;// 初始化(第一次拿到数据时)_tjMaxUKF=newScalarUKF(firstTjMax,10.0,0.001,2.5);// Q=0.001, R=2.5(根据你的传感器噪声调)_tjMinUKF=newScalarUKF(firstTjMin,10.0,0.001,2.5);// 每次新数据到来doublefilteredTjMax=_tjMaxUKF.Update(rawTjMax);doublefilteredTjMin=_tjMinUKF.Update(rawTjMin);3. 示例代码 2:带日志 + 异常重置 + 非线性观测的进阶版(推荐用于结温)
publicdoubleUpdateWithLog(doublez,stringsensorName="tjMax"){// ...(前面代码同上)doublefiltered=_x;// 异常检测(可选)if(Math.Abs(z-filtered)>50)// 跳变太大{CustomLog.Warn($"{sensorName}大跳变!原始={z:F2}→ 滤波后={filtered:F2}");// 可选择性重置// _x = z; _P = 20;}returnfiltered;}4. 示例代码 3:二维 UKF(位置 + 速度,非线性观测 z = p²)
(前面我已经给过 MathNet 版本,这里简化成不依赖库的版本,代码较长,核心逻辑同上。需要时我再单独发完整版)
5. 参数调优经验(针对功率器件结温)
| 参数 | 推荐值(结温场景) | 说明 |
|---|---|---|
| Q(过程噪声) | 0.0005 ~ 0.005 | 结温变化慢 → 设小 |
| R(测量噪声) | 1.0 ~ 5.0 | 先统计原始 tj 波形的方差 |
| α | 1e-3 | 默认即可 |
| β | 2.0 | 高斯最优 |
| 初始 P | 5~20 | 越大收敛越慢但越鲁棒 |
调参小技巧:
先把 Q 设很小(几乎全信模型),看曲线是否“粘”在旧值上;
再慢慢加大 R(不信传感器),直到曲线平滑但不滞后。
6. 如何在你的 ComputeTVJ 方法中完整替换
把原来的HampelFilter + 3点缓冲全部删掉,替换为:
// 在方法开头EnsureUKF(ws.Id);// 确保已初始化doublefilteredTjMax=_tjMaxUKF.Update(tjMax);doublefilteredTjMin=_tjMinUKF.Update(tjMin);tjMax=filteredTjMax;tjMin=filteredTjMin;这样你的代码瞬间变成工业级最优滤波,再也不用担心 -623℃ 或 395℃ 的异常值。
需要我:
- 给出不依赖任何库的完整二维 UKF
- 针对Vf-To-Tvj 非线性模型定制 UKF
- 加上有效粒子数监控的 UKF 自适应版本
- 或直接帮你改写整个
ComputeTVJ方法
随时告诉我,我立刻给你!UKF 用起来真的香,效果远超你原来的 Hampel + 3点缓冲。
