ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

气动力辨识的Observer法:从EKF状态空间建模到工程避坑

气动力辨识的Observer法:从EKF状态空间建模到工程避坑

简介:基于Observer(状态观测器)法实现的气动力辨识程序,聚焦航空航天与飞行控制领域,面向需要从飞行器动态行为中估计升力、阻力、侧向力等难以直接测量参数的研究者或工程师,解决气动模型建立与参数辨识难题。资源为ZIP格式压缩包,仅含1个.m源文件,大小约2KB,代码精简但覆盖完整流程:从线性化气动力模型与状态方程建立,到Observer增益矩阵设计、参数迭代更新及结果可视化展示,核心算法一目了然。目前已有171人学习,适合对现代控制理论中的状态估计、Lyapunov稳定性方法以及MATLAB数值计算有一定基础,希望复现或扩展气动力辨识算法的使用者。程序依据稳定性判据选取增益,可配合飞行实测数据(位置、速度、加速度)不断修正估计值,辨识结果可用于飞行控制系统反馈设计,同时包含去噪、滤波等预处理环节,为后续自适应辨识与气动性能分析提供了可运行、易修改的起点。

1. 为什么气动力辨识绕不开Observer法:从mine_observer这个程序标题说起

气动力辨识本质上是从飞行试验的时间序列里反算气动导数。传统做法是把整段数据交给最小二乘,一次拟合出俯仰力矩导数、升力斜率这些系数;而Observer法(标题里拼成observor,实际是observer的变体写法)走的是另一条路——把未知气动系数扩充为状态,让卡尔曼滤波在每一个采样点用模型预测和实测残差去修正它们,参数轨迹是随时间逐步收敛出来的,而不是一次性算出来的。mine_observer是一个作者自研的气动力辨识程序,这类自研程序最大的价值不是替代风洞实验,而是在面对动压变化剧烈、数据段短、非平稳的实测数据时,依然能给出带置信度的参数估计过程。

下文会从一个简化纵向短周期模型入手,把状态空间建模、EKF实现、参数整定和典型翻车场景一次说透。内容适合刚接触飞行力学辨识的工程师和学生,也适合对最小二乘结果不放心、想用第二种方法做交叉验证的熟手。你会看到Observer法并不神秘,但它的成败更多取决于模型结构和激励信号,而不是滤波器本身。

2. 先把模型写对:Observer法辨识气动导数的状态空间基础

2.1 从纵向短周期方程说起:哪些导数该进状态向量

要做Observer法辨识,第一步不是写代码,而是把飞机的运动方程改造成“状态-参数联合系统”。以固定翼纵向短周期近似为例,小扰动线性化后的力与力矩方程可以写成:

α̇ = q + Z_α / V · α + Z_δe / V · δe

q̇ = M_α · α + M_q · q + M_δe · δe

这里α是迎角,q是俯仰角速率,δe是升降舵偏角,V是飞行速度。Z_α、M_α这些量带量纲,分别是Z方向气动导数对质量和速度的合成,以及俯仰力矩导数对转动惯量的合成。实际换算关系是M_α = q̄Sc C_mα / I_y,Z_α = -q̄S C_Lα / m,符号约定以你手头教材为准。程序里不必每次都展开这些物理量,但必须保证单位统一:角度全部换弧度,速度用m/s,时间用秒。

Observer法的核心做法是把K个待辨识参数并入状态向量。例如取x = [α, q, Z_α, M_α, M_q, M_δe]ᵀ,前两个是运动状态,后四个是被辨识参数。参数在建模时被视为“随机游走”:当前步参数等于上一步参数加一个小扰动。扩维后的系统写成:

ẋ = f(x, δe)

其中参数分量对应的导数为0,运动状态分量则依赖参数本身。这个系统非线性程度不高,但有一个重要特征:参数对输出α、q的灵敏度随时间变化。滤波算法正是利用这种灵敏度,把预测残差投影到参数方向上去修正估计值。

选哪些参数进状态向量,是这一步最容易出问题的地方。常见做法是只纳入当前模态下激励充分的导数:纵向短周期对C_Lα、C_mα、C_mq、C_mδe敏感,阻力系数、侧向静导数在这个模态下几乎不被激励。硬塞进状态向量,可观测性矩阵会出现近似的零奇异值,雅可比矩阵数值上能求逆,但滤波结果会表现为参数漂移,或者几个参数互相补偿。我一般会先跑一次最小二乘或伪逆,看每个参数的估计方差;方差数量级差超过两个数量级的参数就固定住,只留敏感项进Observer法。

2.2 三种Observer结构对比:龙伯格、EKF和滑模观测器

Observer法在气动力辨识里并不专指某一种滤波器。工程上常见的有三类,先看对比表:

结构模型要求噪声假设计算量辨识特点
龙伯格观测器线性定常不需要明确Q/R最小极点配置整定收敛速度,对量测噪声敏感
扩展卡尔曼滤波(EKF)可微分非线性需要过程噪声Q、量测噪声R中状态与参数联合估计,工程最常见
滑模观测器不确定非线性不需要中大对建模误差鲁棒,但抖振会污染参数轨迹

龙伯格观测器适合数据干净、模型已经线性化好的场景。把参数也当作状态时,系统矩阵变成时变非线性,此时EKF是更通用的选择。EKF每一步做两件事:用当前估计的模型做一步预测,然后用实测输出与预测输出的差值乘以卡尔曼增益修正所有状态。这里的残差(innovation)本身就是辨识质量的天然监控指标,后面第5章会用到这一点。

滑模观测器在气动力辨识中的案例不太多。它不需要精确的概率统计模型,但切换增益带来的抖振会直接进入参数估计值,最后画出来的参数轨迹像一条毛边很大的带子,难以收敛到干净数值。如果建模误差本身很小,不建议第一个版本上滑模;等EKF跑通、确认残差主要是噪声而非模型误差时,再考虑用滑模做鲁棒性对比。这是阶段性的取舍,不是一上来就把所有工具铺开。

2.3 可观测性与持久激励:为什么有的参数咋调都不收敛

把参数扩进状态后,第一个要回答的问题是:这套“状态+参数”系统可观测吗?线性时变系统可以构造Gramian矩阵来检查,但工程上更直接的手段是看参数对输出灵敏度的数值。以M_α为例,参数通过方程q̇ = … + M_α · α影响q,而q又被直接测量,M_α的可见性取决于α的时变程度:如果升降舵激励信号让α始终在平衡点附近小幅度波动,参数方向的灵敏度幅值会很低,滤波器几乎得不到关于M_α的信息。这不是算法问题,是信息不足。

所以激励信号设计,和Observer法辨识本身同等重要。常见信号有两种:3211多级阶跃和线性扫频。3211信号波形简单、易于复现,缺点是频带集中在特定频段;扫频信号能更均匀地覆盖频带,但幅度控制不好容易飞出线性区域。我一般建议先跑一段仿真闭环,用同一组信号对比EKF收敛效果,再决定实测方案。

一个很常见的坑:用自然扰动数据做辨识,输入功率谱密度太窄,最后EKF输出的M_α和M_q交替上升、结果完全失真。这不是Observer法玄学,而是持久激励(persistently exciting)条件未满足。判定方法很简单:把归一化信息矩阵的特征值算出来,最大与最小特征值比值超过几个数量级,就说明激励不够,需要重新设计信号。这一节没有代码,但它是后面所有程序参数能否有意义的前提。

3. 把程序跑起来:EKF型Observer法的代码实现与参数设置

3.1 输入数据长什么样:试飞时间序列的组织与预处理

mine_observer这类程序的输入,通常是一个四列文本:时间t、迎角α、俯仰角速率q、升降舵偏角δe。先看前几十行,确认没有缺失值和跳变。迎角传感器容易被气流扰动污染,尖峰要去掉,但不要平滑过头——过度平滑会压缩高频信息,而高频恰好包含了短周期气动力矩的辨识成分。我一般用3点中值滤波配合轻度低通,低通截止频率取激励信号最高频率的5倍以上。

采样率方面,短周期模态频率通常在1~10 rad/s量级(飞机不同差异很大,以自己模型的短周期自然频率为准),建议采样率至少为该频率的20倍,100Hz是常见起步值。如果原始数据只有20Hz,EKF在参数更新上会显得粘滞,宁愿做升采样加滤波也不要直接跑,否则每一步预测误差被积分放大后,参数轨迹会出现锯齿。

注意:单位换算是数据预处理里最容易翻车的一环。迎角若存的是角度(deg),而运动方程用的是弧度,M_α估计值会整体偏差约57.3倍。老程序里这类单位混用几乎是最常见的“神秘bug”。

我的习惯是在代码开头显式声明所有物理量单位,用注释写清楚,并把转换后的数据画一张快速预览图,确认量级合理再进辨识。这一步做得越细,后面排错的时间越短。

3.2 一个可直接运行的EKF辨识核心:代码与逐段说明

先用一组已知真值生成模拟数据,用来验证Observer法程序本身没有逻辑错误。模拟系统就用2.1节的状态方程,叠加高斯量测噪声:

import numpy as np dt = 0.01 t = np.arange(0, 20.0, dt) N = len(t) # 升降舵激励:扫描频率从0.2Hz到2.0Hz,叠加一个小阶跃 de_cmd = (0.12 * np.sin(2 * np.pi * (0.2 + (2.0 - 0.2) * t / (2 * 20.0)) * t) + 0.03 * (t > 15.0)) # 模拟真实飞机:短周期状态方程,真值参数 V = 70.0 Za_true = -22.4 Ma_true = -4.5 Mq_true = -1.8 Mde_true = -12.0 alpha = np.zeros(N) q = np.zeros(N) for k in range(N - 1): dalpha = q[k] + (Za_true / V) * alpha[k] dq = Ma_true * alpha[k] + Mq_true * q[k] + Mde_true * de_cmd[k] alpha[k + 1] = alpha[k] + dalpha * dt q[k + 1] = q[k] + dq * dt # 加量测噪声:alpha约0.1deg,q约0.5deg/s alpha_meas = alpha + np.random.normal(0, 0.1 * np.pi / 180, N) q_meas = q + np.random.normal(0, 0.5 * np.pi / 180, N)

代码逻辑说明:模拟数据用欧拉法积分,积分步长与采样间隔相同。这里的Z_δe项被刻意省略,模拟系统和后面EKF使用的模型结构一致,避免“模型结构误差”干扰对程序本身的验证。如果你想验证算法对模型误差的鲁棒性,可以在这一步另加一项Z_δe·δe的影响,观察辨识结果如何偏移,那是4.x节讨论的问题。

下面是EKF核心实现。状态向量为x = [α, q, Z_α, M_α, M_q, M_δe]ᵀ,参数用随机游走建模:

# 连续时间状态方程,参数分量的导数为0 def f_dyn(x, de, V): alpha, q = x[0], x[1] Za, Ma, Mq, Mde = x[2], x[3], x[4], x[5] dalpha = q + (Za / V) * alpha dq = Ma * alpha + Mq * q + Mde * de return np.array([dalpha, dq, 0.0, 0.0, 0.0, 0.0]) # 欧拉离散后对x的雅可比,F = I + A * dt def jacobian_F(x, de, V, dt): alpha, q = x[0], x[1] Za = x[2] A = np.array([ [Za / V, 1.0, alpha / V, 0.0, 0.0, 0.0], [x[3], x[4], 0.0, alpha, q, de], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], ]) return np.eye(6) + A * dt # 观测矩阵:观测量是alpha和q def H_obs(): H = np.zeros((2, 6)) H[0, 0] = 1.0 H[1, 1] = 1.0 return H def ekf_step(x, P, de, y, dt, V, Q, R): F = jacobian_F(x, de, V, dt) x_pred = x + f_dyn(x, de, V) * dt P_pred = F @ P @ F.T + Q H = H_obs() S = H @ P_pred @ H.T + R K = P_pred @ H.T @ np.linalg.inv(S) y_pred = np.array([x_pred[0], x_pred[1]]) innov = y - y_pred x_new = x_pred + K @ innov P_new = (np.eye(6) - K @ H) @ P_pred return x_new, P_new, innov def run_ekf(t, alpha_meas, q_meas, de_cmd, dt, V, x0, P0, Q, R): x = np.array(x0, dtype=float) P = np.array(P0, dtype=float) traj = [] innovs = [] for k in range(len(t)): y = np.array([alpha_meas[k], q_meas[k]]) x, P, innov = ekf_step(x, P, de_cmd[k], y, dt, V, Q, R) traj.append(x.copy()) innovs.append(innov.copy()) return np.array(traj), np.array(innovs)

代码逻辑说明:f_dyn返回的是连续时间导数,欧拉预测用x + f_dyn·dt一步完成,参数分量导数写0,它们只靠Q中对应元素提供随机游走能力。雅可比矩阵中的第二行∂q̇/∂M_α = α、∂q̇/∂M_q = q、∂q̇/∂M_δe = δe,这三项是参数方向灵敏度在雅可比矩阵里的实体。如果手写雅可比与数值差分对不上,P矩阵很快会发散成NaN,第4章会专门讨论。

run_ekf里每个采样点调用一次ekf_step,内部顺序是预测-算增益-更新。返回的traj是N行六列的数组,每一行是当前时刻的状态加参数估计,可以直接画出来看参数随时间的收敛过程。innovs数组务必保留,后面做残差白噪声检验要靠它。

3.3 三个必须人工干预的参数:Q、R与P0

EKF不是完全自动的工具,有三个参数基本靠人工调节,这也是大家常说的“玄学区”:

参数作用起始设定调参方向
Q中状态部分(α, q)容忍模型误差与状态突变1e-4 ~ 1e-3残差低频偏大则增大
Q中参数部分(Za, Ma, ...)决定参数跟踪速度1e-8 ~ 1e-6参数收敛太慢增大,抖动过大减小
R(量测噪声)对应传感器噪声方差α: (0.1°→rad)²,q: (0.5°/s→rad/s)²按传感器手册取,一般不乱动
P0(初始协方差)初始估计的不确定度状态部分1e-4,参数部分取初值平方的10倍参数初值不确定就再放大10倍

Q参数部分的设计核心是一个矛盾:给得太大,参数会被每条残差拽着走,轨迹高频抖动;给得太小,参数慢吞吞,20秒数据可能只走了一半。快速起步办法是先给中等量级1e-7,看轨迹:如果前5秒就猛烈震荡,降两个数量级;如果20秒还没平,升一个数量级。相比Q,R尽量信任传感器手册,不要为了视觉美观去压R,一旦R给得远小于真实噪声,滤波器会对残差过度反应,参数轨迹一样会抖动。

初值P0与x0的关系也容易踩坑。x0里的参数若给一个偏离真值较多的初值,比如M_α初值给0,P0对应位置必须设置足够大,让滤波器自信地修改它;若P0给得太小,滤波器认为初值很可信,参数只会缓慢移动。常见做法是让P0参数对角线取初值平方的10倍,这就给了滤波器充分的“后悔药”空间。

4. 避坑与排查:Observer法辨识结果“看起来对”但靠不住的时刻

4.1 现象:参数收敛但数值和已知数据系统性偏大

这是最麻痹人的情况——参数轨迹都平了,曲线光滑,你以为成功了,但对比气动数据发现M_α普遍偏大20%。原因多半不是滤波器错了,而是配平点没找准。气动力辨识用的小扰动方程是在某个平衡状态附近线性化的,如果数据段实际上带一个稳态舵偏或非零的俯仰角加速度,模型里没体现这个偏置,M_α就要吸收这部分效应。解决:先用数据末段做平均,估计稳态α、q、δe,把状态和输入都减去配平值再跑EKF;或者在模型里加一个常数偏置项一起辨识,看它是否显著不为零。偏置项显著时,优先修正配平。

4.2 现象:某个参数长时间漂移,永远不给稳定值

最常见的是M_q这类时间尺度与运动模态相近、又容易被其他参数补偿的量。原因是状态扩维后,两个参数对同一条输出轨迹有近似相同的贡献,信息矩阵近似奇异,滤波器无法独立分离它们。解决分三步:先把参数轨迹和运动状态轨迹画在同一张图,观察参数漂移是否与α或q的长期趋势相关;再做敏感性分析,逐一固定某个参数、让其余参数辨识,比较残差平方和,找出那个“被掩盖”的参数;最后补充包含更丰富频带的激励数据。这里要记住:Observer法和最小二乘一样受制于信息量,不要指望算法能创造数据里不存在的信息。

4.3 现象:协方差矩阵在某一刻变成NaN

这个现象几乎总能追溯到雅可比矩阵写错。我遇到过的一种典型错误:状态方程里q̇ = M_α·α + M_q·q + M_δe·δe被写成q̇ = M_α·α + M_q·q + M_δe,忘了乘δe,于是∂q̇/∂M_δe变成了0,而状态里又有M_δe这一维,滤波器认为这个参数毫无可见性,P中对应行会在几百步内被数值误差打崩。解决:写一个脚本对jacobian_F做中心差分校验,比较解析F与数值F的每个元素,容差取1e-6。另一个常见诱因是Q或R非正定,比如对角线出现负数或者零加上浮点精度问题,进主循环前检查所有特征值大于零。

4.4 现象:仿真数据能收敛,实测数据一上来就发散

仿真的输出是“干净”的,因为模型和滤波器里的模型完全一致;实测数据携带结构误差——气动滞后、弹性模态、传感器安装角偏差,全落在模型外面。Observer法里这些误差都会表现为innovation。发散前一般有先兆:innovation不再围绕零均值波动,而是出现缓慢变化的长周期分量,或方差随时间增大。解决路径:先看一段残差,残差低频成分大说明建模阶次或配平有问题,回去补模型结构;残差是高频尖峰,则检查传感器延迟与数据对齐,特别是升降舵指令与响应对齐差一两个采样周期就可能导致发散;最后才考虑调Q。不要一上来就乱调Q/R,先把数据质量和模型结构理顺。

5. 验证与进阶:交叉验证、残差检验和UKF扩展的方向

跑通EKF不是终点,没有验证的辨识结果没有说服力。最有效的验证是数据分段交叉验证:把数据切成两段,前60%用Observer法辨识出参数,后40%用辨识出的模型做开环仿真,从后段的初始状态开始递推,比较仿真与实测的α、q误差。如果后段预测误差随时间快速增长,说明模型在其他数据段上不可靠,多半是过拟合了前段数据的噪声。这个验证把“参数看起来收敛”升级为“模型真的能预测新数据”,这才是气动力辨识的目的。

第二个检验是残差白噪声检验。把innov序列画出来并做自相关:理想情况下,除零阶滞后外,自相关系数会落在±1.96/√N的置信带内。如果自相关系数在某个滞后处显著越界,说明残差里还有模型结构成分,比如被忽略的弹性模态或输入延迟。这一步在代码里只有几行,却能帮你区分“噪声大”和“模型错了”——前者可以接受,后者必须回头改模型。

如果模型非线性较强,比如大迎角下的非定常气动力,EKF的线性化误差会累积。可以把预测步换成无迹变换,写成UKF,参数部分的随机游走建模不变,只改状态分布的传播方式。另一个方向是平方根滤波,用协方差的Cholesky因子替换P,避免P矩阵非对称或负定,对长数据段的数值稳定性提升明显。还可以对比递推最小二乘与Observer法的结果,两者在数据充分时应接近;若差异大,多半是模型结构或激励的问题,而非算法本身的问题。

我自己用Observer法最深的体会是:不要一开始就追求复杂算法,先用EKF把数据和模型的故事讲通。曾经我为一组高频抖动数据忙了一周,最后发现是迎角传感器安装角偏差了1.2°,数据对齐处理后的结果好得惊人。辨识程序里,代码只是骨架,真正值钱的是你对气动、测量和概率模型的层层校准。把第4章的四个坑记在心里,再用交叉验证堵住最后一道怀疑,这条路会顺很多。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进