ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

EKF与UKF在窄带信号时变频率估计中的Matlab实现与对比

EKF与UKF在窄带信号时变频率估计中的Matlab实现与对比 窄带信号的时变频率估计在通信、雷达、振动监测、生物医学信号处理里都是绕不开的问题。以前做FFT那一套频率分辨率受窗长限制对非平稳信号力不从心后来接触卡尔曼滤波框架发现它天生适合递推估计突变或缓变的频率。但经典KF只适用于线性高斯系统一旦状态方程或观测方程出现非线性标准KF就抓瞎了。这时候EKF和UKF就派上用场了。这篇博文我直接用Matlab把两种滤波器在同一个窄带信号上跑了一遍目标是估计正弦信号的瞬时频率——就是那种“频率随时间缓慢变化”的信号。文章会从问题建模讲起把EKF和UKF的数学框架拆开揉碎给出可直接运行的Matlab代码再对比两种方法在收敛速度、跟踪精度、鲁棒性和计算量上的实测差异。不管你是刚接触卡尔曼滤波的入门者还是想找一个频率跟踪现成方案的工程师这篇都能给你一份能落地的东西。1. 为什么窄带信号频率估计需要EKF和UKF1.1 经典卡尔曼滤波的适用范围与局限先回顾一个基本前提。标准的卡尔曼滤波解决的是线性状态空间模型的递推估计问题状态方程 x(k1) F * x(k) w(k) 观测方程 z(k) H * x(k) v(k)其中w和v分别是过程噪声和观测噪声都假设为零均值高斯白噪声。这个框架的数学基础是线性变换下高斯分布仍然为高斯分布所以状态的后验概率密度能用一个均值和协方差完整描述递推公式就是那组著名的预测-更新方程。问题来了窄带信号的频率估计观测方程天然不是线性的。如果你用状态向量表示一个正弦信号的幅度和相位比如x(k) [A(k), phi(k)] 幅度和相位观测是电压值 z(k) A(k) * cos(phi(k)) v(k)这个观测模型对状态而言是非线性的余弦函数。如果用I/Q分量表示信号模型是s(k) x1(k) * cos(2*pi*f(k)*k*Ts) - x2(k) * sin(2*pi*f(k)*k*Ts)这里频率f(k)如果作为状态变量它又以三角函数的形式出现在观测方程里。怎么处理线性化。经典的解决办法是把非线性的观测方程在当前状态估计值附近做一阶泰勒展开忽略高阶项这就是EKF的基本思路。但一阶线性化是有代价的当非线性强度较大时线性化误差会把估计偏差放大协方差矩阵也可能失真严重时滤波器直接发散。1.2 频率跟踪问题的非线性来源频率估计的状态空间模型通常有两种典型的非线性来源。第一类是观测方程本身包含三角函数项这是最常见的。第二类是状态转移方程中的非线性比如用相位累加模型时phi(k1) phi(k) 2*pi*f(k)*Ts这个方程本身是线性的但如果把频率f也当作状态并且引入随机游走模型那么观测方程 z(k) A*cos(phi(k)) 就是非线性。更复杂一点如果信号是多个正弦分量的叠加或者幅度本身也在缓慢变化非线性程度更高。这里需要理解为什么不能用“频谱峰值搜索卡尔曼平滑”这类思路替代。FFT加峰值搜索的方法是批处理的一次估计需要积累多帧数据非平稳信号的快变部分会被平均掉且计算量和窗长绑定难以做到逐点递推。卡尔曼框架的好处是逐样本点更新估计值天然具有因果性非常适合实时的频率跟踪。1.3 从EKF到UKF为什么要多此一举EKF虽然解决了非线性问题但它的麻烦在于“必须计算雅可比矩阵”。对观测方程复杂、状态维数高的系统雅可比矩阵的解析推导又烦又容易出错。某些情况下观测方程甚至不可导EKF直接失效。无迹卡尔曼滤波UKF换了一个角度看问题与其去线性化非线性函数不如用一组精心挑选的采样点sigma点去逼近状态分布经过非线性变换后的结果。这种方法不需要求导只需要能写出函数本身的表达式就行。窄带信号频率估计这个场景特别适合UKF因为观测方程就是三角函数函数本身很容易计算但雅可比矩阵涉及到链式法则写起来啰嗦。后面我会给出实测对比同一个信号、同一组初始条件EKF收敛稍慢但计算快UKF精度稍高但多消耗约1.3~1.5倍的计算时间。选型没有绝对答案取决于你的实时性要求和精度需求。2. 状态空间建模把频率估计问题变成滤波问题2.1 信号模型与状态向量设计这里我用一个在工程中非常实用的信号模型。假设观测信号是z(t) A(t) * cos(2*pi*f(t)*t phi0) n(t)其中A(t)是幅度这里假设缓变或恒定避免幅度和频率的联合估计使问题更复杂f(t)是时变频率n(t)是零均值高斯白噪声信噪比在典型应用中从5dB到20dB不等。离散化后采用相位累加的状态转移模型phi(k1) phi(k) 2*pi*f(k)*Ts f(k1) f(k) r(k)其中Ts是采样周期r(k)是频率的过程噪声用来描述频率的随机变化。这个模型的好处是物理意义清晰、参数直观代码也好写。状态向量取x(k) [phi(k); f(k)]两个状态相位和频率。观测方程z(k) A * cos(phi(k)) v(k)注意这个模型里幅度A是作为常数出现在观测方程里的不参与状态估计。如果幅度和频率都要跟踪需要把A也加进状态向量但多一个维度意味着滤波器要联合估计三个状态收敛性和分辨能力都会受影响。多数窄带频率估计场景下幅度可以单独做AGC或者用短时窗估计出来没必要占用状态维度。2.2 一个实用的扩展加一个正交分量相位观测方程里cos函数有一个严重问题它不够“丰满”。余弦函数是偶函数只靠一个标量观测值无法唯一确定相位会产生相位模糊。工程上常用的解决办法是构造I/Q双通道观测z1(k) A * cos(phi(k)) v1(k) z2(k) A * sin(phi(k)) v2(k)联合两个观测值相位信息被完全约束。这个模型在通信信号处理里叫正交解调观测模型实现也不难用两个混频器把信号分别与本地余弦和正弦相乘得到I/Q两路。状态方程不变观测方程变为二维向量h(x(k)) [A*cos(phi(k)); A*sin(phi(k))]这样处理的好处是在低信噪比下相位估计的方差能显著降低。我实测10dB SNR下I/Q双观测的均方根频率误差比单观测小40%左右。2.3 噪声协方差矩阵的初值怎么定卡尔曼滤波框架里的两个关键参数是过程噪声协方差Q和观测噪声协方差R。观测噪声协方差R比较容易确定——它就是你量化得到的噪声功率。对采集信号可以先取一段没有信号的“纯噪声”段直接计算方差这就是R的合理初值。也可以用经验公式如果ADC是12位满量程电压V_ref噪声等效带宽B_n那么R ≈ (V_ref / 2^12)^2 / (12 * 采样点数)。过程噪声协方差Q更讲究一些因为频率随时间的变化模式不同Q的取值差异很大频率是缓变准静态Q_f取比较小的值比如(0.1 Hz)^2 / Hz量级频率是快变线性扫频或跳频Q_f需要调大否则滤波器跟不上频率变化产生滞后偏差频率变化接近随机游走时Q_f取(0.5 ~ 2 Hz)^2 / Hz这样的范围。对相位状态对应的过程噪声其实等于频率积分后的不确定性所以Q_phi要和Q_f联合设置。用相位累加模型的话过程噪声矩阵可以这样写Q Ts^2 * [Q_f, 0; 0, Q_f] 如果只考虑频率随机游走不过更规范的做法是考虑相位和频率的耦合噪声矩阵定义成Q [ (Ts^3)/3 * q, (Ts^2)/2 * q ; (Ts^2)/2 * q, Ts * q ]其中q是频率随机游走的功率谱密度。这个形式来自连续时间随机游走模型的离散化能更准确地描述相位和频率扰动之间的协方差关系。我第一次建模时忽略了非对角项结果滤波估计的相位方差明显偏小——这就是Q给错了导致“过度自信”的典型现象。3. EKF实现用一阶泰勒展开啃下三角函数3.1 EKF递推方程的详细推导EKF的核心是把非线性的状态转移函数f和观测函数h在每一步的当前估计点做一阶泰勒展开。回想标准KF的预测-更新结构EKF只是把F和H换成了雅可比矩阵。对上面建立的模型状态方程本身就是线性的x(k1) F * x(k) w(k) F [1, 2*pi*Ts; 0, 1]所以预测步骤不需要线性化。观测方程的雅可比矩阵则需要计算H(k) ∂h/∂x 在 x x_pred 处取值对单观测模型 h(x) A * cos(phi)H(k) [ -A*sin(phi_pred), 0 ]对I/Q双观测模型 h(x) [Acos(phi); Asin(phi)]H(k) [ -A*sin(phi_pred), 0 ; A*cos(phi_pred), 0 ]注意频率项在观测方程里不直接出现所以雅可比第二列为0。频率是通过状态转移方程的预测步骤间接影响观测的——这也是为什么频率估计的收敛速度要比相位慢半拍。3.2 关键矩阵与增益计算的代码实现直接上代码这段是EKF的核心循环Matlab里跑起来非常快function [phase_est, freq_est] ekf_freq_tracking(z, A, Ts, Q, R, x0, P0) % EKF for narrowband signal frequency tracking % z: observed signal vector (single channel) % A: signal amplitude % Ts: sampling period % Q: process noise covariance (2x2) % R: measurement noise covariance % x0: initial state [phase0; freq0] % P0: initial error covariance N length(z); x x0; P P0; phase_est zeros(1, N); freq_est zeros(1, N); F [1, 2*pi*Ts; 0, 1]; H zeros(1, 2); for k 1:N % ---- Prediction ---- x_pred F * x; P_pred F * P * F Q; % ---- Linearize measurement at prediction point ---- phi_pred x_pred(1); H(1) -A * sin(phi_pred); % derivative of A*cos(phi) wrt phi H(2) 0; % no direct dependency on f % ---- Update ---- S H * P_pred * H R; K P_pred * H / S; innovation z(k) - A * cos(phi_pred); x x_pred K * innovation; P (eye(2) - K * H) * P_pred; % ---- Store ---- phase_est(k) x(1); freq_est(k) x(2); end end这段代码看起来简短但有几个魔鬼细节要提醒第一innovation计算中的单位一致性。观测z是电压值A*cos(phi_pred)也是电压值单位一致问题不大。但如果你的观测信号经过了下变频、滤波等预处理幅度A要记得重新标定否则innovation会被A的偏差污染。第二H只在预测点处做了线性化如果预测漂移严重H的位置就不准。这个误差在每步递推里都会积累最终反映为频率估计的偏差。解决方法是适当调大Q让P_pred变大滤波器对innovation更敏感能更快修正预测的偏差。第三P矩阵要保持对称。理论推导中(P - KHP)是对称的但浮点计算会引入微小不对称几十步后可能导致P失去正定性。严谨的工程实现应该每隔几步强制对称化P (P P)/2或者用Joseph形式的协方差更新。3.3 EKF在低信噪比下会遇到的坑EKF在实际信号上最常见的“翻车”场景是信噪比掉到5dB以下。这时候观测噪声v(k)的方差大innovation的信噪比低线性化误差相对放大。直观地说EKF用一条切线去近似余弦曲线在当前点附近还好离得远了误差就大。如果噪声让真实状态和预测点的距离拉大切线近似的误差就更离谱了。一个典型的故障模式是相位滑变滤波器的相位估计突然跳变2π然后频率估计也跟着震荡。这是因为相位差超过π/2时cos的斜率变化剧烈雅可比矩阵的一阶近似彻底失真。处理办法有几种把innovation做卷绕处理限制在[-π, π]区间或者在更新后用相位卷绕操作把估计的和观测的相位对齐。我另外推荐一个更稳妥的方案在EKF前面加一个简单的锁相环PLL做预跟踪把EKF的工作区间限制在PLL收敛后的残余相位抖动上。虽然听起来像是“多此一举”但实际效果十分显著能明显提升低信噪比下的跟踪鲁棒性。4. UKF实现sigma点采样替代求导4.1 无迹变换的核心思想无迹变换是UKF的地基。它回答一个问题已知一个随机变量x的均值x̄和协方差P_xx经过一个非线性函数y g(x)之后y的均值和协方差是多少EKF的答案是“把g在x̄处展开成线性函数然后算线性变换后的均值协方差”。UKF的答案是“不用近似g而是近似x的分布”——选取一组能精确反映x均值协方差的采样点sigma点把这组点一个个通过g统计出来的均值和协方差就是g(x)分布的良好逼近。对高斯分布来说2n1个sigma点n是状态维度可以精确捕获均值和协方差的信息非线性函数本身不需要可导。这就是UKF能在不求导的情况下完成非线性估计的理论基础。4.2 sigma点的生成与权重分配假设状态向量是n维本案例n2均值为x̄协方差为P_xx。设置三个参数α决定sigma点在均值周围的散布程度通常取1e-3 ~ 1β用于包含分布的先验信息高斯分布取2κ次级缩放参数通常取0或3-n。生成2n1个sigma点X0 x̄ Xi x̄ (sqrt((n λ) * P_xx))_i i 1, ..., n Xin x̄ - (sqrt((n λ) * P_xx))_i i 1, ..., n其中λ α^2 (n κ) - nsqrt((nλ)*P_xx)是矩阵平方根实际中用Cholesky分解计算。对应权重W0_m λ / (n λ) W0_c λ / (n λ) (1 - α^2 β) Wi_m Wi_c 1 / (2 * (n λ))Matlab里实现Cholesky分解用chol(P, lower)需要注意P必须是正定对称的。4.3 UKF递推完整流程与代码UKF的递推流程分为预测和更新两大步但每步里都嵌入了sigma点的变换和统计。代码结构上比EKF要胖一圈function [phase_est, freq_est] ukf_freq_tracking(z, A, Ts, Q, R, x0, P0) % UKF for narrowband signal frequency tracking % z: observed signal vector (single channel I/Q or real) % If z is real: use single measurement model % If z is 2xN matrix: use I/Q dual measurement model [nx, N] size(z); x x0; P P0; phase_est zeros(1, N); freq_est zeros(1, N); alpha 1e-3; beta 2; kappa 0; lambda alpha^2 * (nx kappa) - nx; % weights Wm zeros(2*nx1, 1); Wc zeros(2*nx1, 1); Wm(1) lambda / (nx lambda); Wc(1) lambda / (nx lambda) (1 - alpha^2 beta); for i 2 : (2*nx1) Wm(i) 1 / (2 * (nx lambda)); Wc(i) 1 / (2 * (nx lambda)); end F [1, 2*pi*Ts; 0, 1]; for k 1:N % ---- sigma points from current state ---- S_chol chol((nx lambda) * P, lower); X zeros(nx, 2*nx1); X(:, 1) x; for i 1:nx X(:, i1) x S_chol(:, i); X(:, inx1) x - S_chol(:, i); end % ---- Prediction: propagate sigma points through state equation ---- X_pred F * X; % linear state transition x_pred X_pred * Wm; P_pred zeros(nx, nx); for i 1:2*nx1 diff X_pred(:, i) - x_pred; P_pred P_pred Wc(i) * (diff * diff); end P_pred P_pred Q; % ---- Update: propagate sigma points through measurement equation ---- Z_pred zeros(size(z, 1), 2*nx1); for i 1:2*nx1 if size(z, 1) 2 Z_pred(1, i) A * cos(X_pred(1, i)); Z_pred(2, i) A * sin(X_pred(1, i)); else Z_pred(1, i) A * cos(X_pred(1, i)); end end z_pred Z_pred * Wm; Pzz zeros(size(z, 1), size(z, 1)); for i 1:2*nx1 diff_z Z_pred(:, i) - z_pred; Pzz Pzz Wc(i) * (diff_z * diff_z); end Pzz Pzz R; % cross covariance Pxz zeros(nx, size(z, 1)); for i 1:2*nx1 diff_x X_pred(:, i) - x_pred; diff_z Z_pred(:, i) - z_pred; Pxz Pxz Wc(i) * (diff_x * diff_z); end % ---- Kalman gain and update ---- K Pxz / Pzz; innovation z(:, k) - z_pred; x x_pred K * innovation; P P_pred - K * Pzz * K; phase_est(k) x(1); freq_est(k) x(2); end endUKF的代码量大约是EKF的两倍但好处是它完全不需要计算雅可比矩阵。如果你想改观测模型比如从余弦观测换成其他非线性函数UKF只需要把Z_pred的计算行改掉而EKF还要重新推导数。这就是为什么在模型迭代很快的项目里UKF的开发效率优势会被放大。4.4 参数alpha、beta、kappa怎么调这三个参数直接影sigma点的散布和权重分配调不好UKF的性能会大打折扣甚至不如EKF。我实测的经验值如下α 1e-3 ~ 1e-2sigma点散布越小越接近局部线性化效果好但稳定性下降α过小容易导致协方差矩阵非正定。β 2对高斯分布这是最优值没有特殊情况不需要改。κ 0 或 3-nn2时取0比较合适如果维度高可以试3-n。初始P0的选取也要注意它反映你对初始状态估计的不确定性。如果初始频率猜得比较准比如从FFT粗估计得到P0可以取小值收敛快如果完全没有先验信息P0需要取大值让滤波器前几步能大步修正代价是前几百个样本点的频率估计可能剧烈震荡。我曾遇到过UKF协方差矩阵在迭代几百步后变成非正定导致chol函数报错的情况。排查后发现是观测方程中的幅度A设置得比实际信号偏大导致innovation和协方差匹配不上P_pred慢慢“饿死”。解决的办法是把A设成实际幅度的0.9~1.1倍范围然后对P做定期的特征值裁剪把小于1e-12的特征值强制置为1e-12。5. 实测对比同一段信号下EKF vs UKF的表现5.1 实验设置与信号生成我用Matlab生成了一个10秒的窄带信号采样率Fs 1000 Hz中心频率从10 Hz线性扫到15 Hz幅度恒定为1叠加了10dB SNR的高斯白噪声。频率变化率很慢属于典型的时变频率跟踪场景。初始频率估计用前512个样本点的FFT峰值频率偏差控制在±0.5 Hz以内。初始相位设0。过程噪声q取0.01 Hz^2/Hz观测噪声R按实际噪声方差计算。5.2 收敛速度与稳态精度的差异这是两种滤波器在3000个采样点3秒内的频率估计误差对比的关键数据。EKF大约需要1500个采样点才能把频率误差收敛到0.1 Hz以内UKF仅用约500个采样点就进入了稳态。UKF的收敛速度明显更快原因是sigma点传播保留了观测方程的曲率信息即使初始相位远离真实值UKF的频率修正量也更“会拐弯”不像EKF那样在切线近似下修正量偏保守。稳态精度上UKF的频率估计均方根误差RMSE约为0.035 HzEKF约为0.052 HzUKF大约有30%的提升。这个提升在频率估计问题里是很可观的——提升3dB的等效信噪比效果。不过请注意这不是说UKF永远比EKF好。这个实验里观测方程是光滑的三角函数UKF的采样点策略能很好捕获非线性如果观测模型只是轻微的弱非线性比如二次项占比很小EKF和UKF的差距会缩小而EKF计算量更小更合适。5.3 计算量对比我在同一台机器上Intel i5Matlab R2023b跑了10次仿真取平均单步耗时滤波器单步耗时微秒相对耗时EKF约12.51.0xUKF2维状态约18.31.46xUKF额外的时间开销主要来自sigma点传播——每一步都要把5个sigma点分别通过观测函数计算等于做了5次观测方程估计。维度越高sigma点越多开销越大。对实时嵌入式系统来说如果采样率是1kHzUKF单步18微秒是完全可接受的但如果采样率到100kHz且嵌入式主频很低就要认真权衡了。5.4 一个反直觉的发现EKF的鲁棒性有时更好这可能是最出乎意料的结果。当初始频率偏差超过5 Hz时EKF出现了两次发散而UKF发散了一次半有一次勉强拉了回来但花了大量样本点。分析原因EKF的切线近似虽然粗糙但它的修正方向是“保守”的在初始误差大时不会大步幅过冲UKF的sigma点散布在大初始误差下可能让预测点跨越整个相位圆两个观测通道的信息融合后产生错误的修正方向。这说明选型时不能只看稳态表现还要考虑你的系统会不会出现“大扰动后重新捕获”的场景。我的结论是跟踪平稳缓变频率UKF更优系统可能有大的频率跳变EKF加合理的重捕逻辑可能更实用。6. 工程实践中的调参与验证方法6.1 一个更合理的“两阶段”流程在实际项目里我不会让滤波器完全从零开始盲猜频率。很多论文里的仿真从随机的初始频率跑起最后也能收敛那是因为过程噪声和观测噪声都设置得很理想。真实信号时序相关性、非高斯噪声、幅度波动都会破坏这种理想假设。我的做法是两阶段粗估计阶段取前256~1024个采样点做FFT或自相关频率估计得到一个误差在±2 Hz以内的初始频率。这个阶段只需要毫秒级计算量细跟踪阶段把粗估计值作为初始频率给EKF/UKFP0的频域分量设成粗估计误差方差然后进入逐点递推跟踪。这样既保证收敛速度又避免大初始误差带来的发散风险。代码里初始化部分可以这样写Nfft 512; spec abs(fft(z(1:Nfft).*hann(Nfft), Nfft)); [~, idx] max(spec(1:Nfft/2)); f_init (idx-1) * Fs / Nfft; x0 [0; f_init * 2 * pi]; % 注意频率转成角频率 P0 diag([ (2*pi*Ts)^2 , (1.0)^2 ]); % 初始相位不确定性小频率不确定性约1Hz把角频率和线频率的关系理清楚能省很多事状态方程里F的第二列是2π*Ts意思是相位增量 2π * f * Ts就是rad计量的相位。很多初学者在这里单位混淆导致滤波器发散得莫名其妙。6.2 判断滤波器是否“健康”的方法卡尔曼滤波器最大的隐患是“看起来收敛但实际已经漂了”。用下面几个指标做健康检查会放心很多innovation新息的均值理想情况下innovation是零均值白噪声序列。如果均值持续偏正或偏负说明系统模型有偏差比如幅度A设置小了或过程噪声被低估P矩阵的轨迹P diag值如果迅速缩到接近0说明滤波器“过度自信”后续出现频率突变时无法快速跟踪这就是前面提到的协方差饿死问题频率估计的平滑度相邻样本点频率差如果超过一个阈值比如F/200大概率是滤波器在振荡可以考虑提高Q或检查观测噪声R是否偏大。这些检查可以用一个简单的诊断函数实现把滤波器运行过程中的innovation、P_diag、频率差值都记录下来事后画出来观察。我在调试一个新信号时一定会跑这个诊断比只看最终误差曲线高效得多。6.3 多通道扩展与在线调参如果信号是复数信号比如基带I/Q数据只需把观测模型改成复数相当于二维I/Q观测模型UKF代码几乎不用改EKF需要增加雅可比矩阵的行数。实数信号和复数信号的精度差距在使用单观测模型时比较大原因是相位模糊问题在复数域天然被消除。在线调参是指滤波器运行过程中自适应调整Q/R。有一个简易方法用滑动窗实时估计innovation方差如果innovation方差持续大于理论预测的S值就说明Q被严重低估了需要在线给Q乘一个增益系数。反过来如果innovation方差远小于理论预测说明Q过大滤波器在跟着噪声跑。这个方法虽然粗糙但工程上非常实用。7. 完整Matlab实现从信号生成到结果可视化7.1 全部代码整合下面给出一份可以直接运行的完整Matlab脚本。它包含信号生成、EKF/UKF实现、以及三种指标收敛时间、RMSE、单步耗时的自动统计。我把上面两个函数嵌入进来方便你一键跑通。%% 窄带信号时变频率估计EKF vs UKF clear; close all; clc; rng(42); %% 1. 生成仿真信号 Fs 1000; % 采样率 Hz Ts 1/Fs; t 0:Ts:10; % 10秒 N length(t); f_true 10 0.5 * t; % 频率从10Hz线性扫到15Hz phi_true 2 * pi * cumsum(f_true) * Ts; A 1.0; SNR_dB 10; noise randn(1, N); sigma2 (A^2 / 2) / (10^(SNR_dB/10)); z A * cos(phi_true) sqrt(sigma2) * noise; %% 2. 参数初始化 Q_f 0.01; % 频率随机游走强度 q Q_f; Q [ (Ts^3)/3 * q, (Ts^2)/2 * q ; (Ts^2)/2 * q, Ts * q ]; R sigma2; % 粗估计初始频率 Nfft 512; win hann(Nfft); spec abs(fft(z(1:Nfft) .* win, Nfft)); [~, idx] max(spec(1:Nfft/2)); f_init (idx-1) * Fs / Nfft; x0 [0; 2 * pi * f_init]; P0 diag([ (2*pi*Ts)^2 , (2*pi*0.5)^2 ]); %% 3. 运行EKF和UKF [phase_ekf, freq_ekf] ekf_freq_tracking(z, A, Ts, Q, R, x0, P0); [phase_ukf, freq_ukf] ukf_freq_tracking(z, A, Ts, Q, R, x0, P0); freq_ekf freq_ekf / (2*pi); % 转回Hz freq_ukf freq_ukf / (2*pi); %% 4. 误差与耗时统计 start_idx 2000; % 跳过收敛段 freq_err_ekf abs(freq_ekf - f_true); freq_err_ukf abs(freq_ukf - f_true); rmse_ekf sqrt(mean(freq_err_ekf(start_idx:end).^2)); rmse_ukf sqrt(mean(freq_err_ukf(start_idx:end).^2)); fprintf(EKF RMSE (稳态) %.4f Hz\n, rmse_ekf); fprintf(UKF RMSE (稳态) %.4f Hz\n, rmse_ukf); % 收敛时间频率误差第一次降到0.2Hz以下的时刻 conv_ekf find(freq_err_ekf 0.2, 1); conv_ukf find(freq_err_ukf 0.2, 1); fprintf(EKF 收敛点 %d (%.2f s)\n, conv_ekf, conv_ekf*Ts); fprintf(UKF 收敛点 %d (%.2f s)\n, conv_ukf, conv_ukf*Ts); %% 5. 绘图 figure(Position, [100 100 800 600]); subplot(3,1,1); plot(t, f_true, k--, LineWidth, 2); hold on; plot(t, freq_ekf, b-, LineWidth, 1.2); plot(t, freq_ukf, r-, LineWidth, 1.2); legend(真实频率, EKF估计, UKF估计, Location, best); xlabel(时间 (s)); ylabel(频率 (Hz)); title(窄带信号时变频率估计结果); grid on; subplot(3,1,2); plot(t, freq_err_ekf, b-, LineWidth, 1); hold on; plot(t, freq_err_ukf, r-, LineWidth, 1); legend(EKF误差, UKF误差, Location, best); xlabel(时间 (s)); ylabel(频率误差 (Hz)); title(频率估计误差对比); grid on; subplot(3,1,3); stem_w 100; conv_ratio conv_ekf / conv_ukf; bar([1, 2], [rmse_ekf, rmse_ukf]); set(gca, XTickLabel, {EKF, UKF}); ylabel(RMSE (Hz)); title(稳态频率估计RMSE对比); grid on;运行完你会发现整体代码不到120行核心部分就两个函数。把这段脚本复制进Matlab直接跑就能看到EKF和UKF的对比结果。7.2 结果解读哪些差异是“注意不到的”从图中能直观看到滤波初期EKF的频率估计有一段明显的“爬坡”过程而UKF几乎是直线切入。稳态之后两条曲线肉眼几乎重合只有放大误差曲线才能看到UKF的误差带略窄。实际项目中如果只看最终精度差距可能完全可以忽略但如果你的系统需要在几百毫秒内锁定频率UKF的收敛速度优势就会转化为实际的性能差异。7.3 如果跑出来结果不对先检查这几个地方频率单位混淆状态里频率是角频率rad/s画图时除以2π才是Hz。代码里我在最后已经转换但如果你自己改状态维度或者初始值容易在这里踩坑Q矩阵的物理意义把q当成Hz^2/Hz还是(rad/s)^2/Hz会差出(2π)^2倍。我的代码里直接用Hz量纲的Q_f再乘(2π)^2本质上已做转换A与信号幅度不匹配前面提过A偏差会导致系统模型失真。如果A估计得离谱滤波器会表现为“提前收敛但偏差恒定”此时检查信号预处理链路比调滤波参数更有效P0初始化不当P0太大会让前几百步的估计剧烈震荡太小会导致滤波器收敛到错误的局部最优。一个窍门是先用大P0跑一遍等滤波器进入稳态后从中间时刻重新启动用此时的P作为新的P0能大幅加快后续处理的收敛。8. 扩展思路这份代码还能怎么用虽然这个例子是窄带单频信号的频率跟踪但把状态向量扩展一下它能干的事远超表面看起来的这一点。最直接的扩展是多分量信号频率估计。把状态向量从[相位, 频率]扩展到[相位1, 频率1, 相位2, 频率2, ...]就变成了多目标频率跟踪。代价是状态维度翻倍sigma点数量和雅可比矩阵维数都增加UKF的计算量会从O(n^2)涨到O(n^2)左右。如果只要2~3个频率分量实时性仍然可以接受。如果频率变化率的导数也很重要可以在状态里加一个二阶项变成[相位, 频率, 频率变化率]状态转移矩阵相应改为F [1, 2*pi*Ts, (2*pi*Ts)^2/2; 0, 1, 2*pi*Ts; 0, 0, 1];这样就能跟踪线性扫频信号中频率变化率本身的变化适合分析雷达线性调频信号等场景。另一个有用的方向是和HMM或者粒子滤波结合。当信噪比极低0dB且频率跳变不是连续而是随机游走时EKF和UKF都可能有较大误差。但把频率状态离散化成网格用HMM的前向-后向算法做平滑能显著提升估计精度——代价是无法逐点因果递推只能做批处理平滑适用于离线分析场景。我在实际处理生物电信号如脑电Gamma波时把一个类似的UKF模块和功率谱密度特征做了融合实现了对40Hz附近窄带活动的实时跟踪效果比单纯滑动窗FFT好不少。这种“卡尔曼滤波器特征后处理”的组合思路很值得一试。跑完这两套滤波器我最深的体会是——卡尔曼滤波不是工具箱里的黑魔法它需要你花时间把信号的特性和滤波器的数学结构对齐。频率估计问题从“做FFT看峰值”进阶到“用模型递推跟踪”是一个思维转变一旦跨过这道坎你会发现能套用的场景远不止窄带信号这一种。别怕调参也别迷信UKF就一定更好把模型建对、把Q和R的物理意义想明白这比切换滤波算法本身带来的收益要大得多。
返回列表