1. SSI-COV方法值不值得学:原理背景与选型判断
先说结论:如果你手里有一批结构在环境激励下的加速度响应数据,想从中拿到模态频率、振型和阻尼比,SSI-COV(协方差驱动的随机子空间方法)是目前最值得优先尝试的时域方法之一。我最近做一个结构健康监测方向的仿真验证项目,对比了峰值拾取法、频域分解法和随机子空间法之后,最后还是把SSI-COV作为主算法,因为它在阻尼比识别和密集模态区分上的表现,确实比频域那套要稳。
1.1 为什么只靠响应就能识别模态
传统实验模态分析你得有力锤或激振器,测到输入力再算频响函数,然后从频响函数里拟合模态参数。这个流程实验室里很好用,但换到真实工程就尴尬了——一座跨江大桥、一栋超高层建筑,你没法用激振器施加可控激励,只能靠风、地脉动、车辆通行这类环境激励。环境激励的特点是激励力未知,你手里只有传感器测到的响应数据。所以必须走"仅输出"的模态识别路线。
仅输出识别的基本前提是:环境激励可以近似看作随机白噪声或宽带平稳激励。在这个假设下,响应的自相关函数和互相关函数里其实已经包含了系统的全部动力学信息。SSI-COV的核心思路,就是先通过响应数据构造协方差序列,再把这些协方差拼成Toeplitz矩阵,最后对这个矩阵做奇异值分解,把系统的状态矩阵从数据里"投影"出来。你不需要知道激励力怎么作用,只需要保证激励的频带覆盖你关心的模态范围。
1.2 随机子空间家族里SSI-COV的位置
随机子空间方法内部还分两个方向:SSI-COV(协方差驱动)和SSI-DATA(数据驱动)。SSI-DATA是从原始数据直接做LQ分解,数值上更稳定,但实现复杂度高,矩阵维度一上去,内存和计算量都不小。SSI-COV是先算协方差、再对Toeplitz矩阵做SVD,思路更直观,代码也更短,非常适合自己动手实现和验证算法。代价是协方差估计这一步对数据长度有一定要求,数据太短会导致协方差估计误差变大,识别出来的阻尼比容易出现偏差。
实际项目里怎么选?如果数据记录足够长(我一般要求至少覆盖感兴趣最低频率对应周期的50倍以上),SSI-COV完全够用,而且调试方便。如果你只有一小段数据,或者信噪比很差,那可以考虑SSI-DATA。本文后面所有内容都围绕SSI-COV展开,因为把协方差驱动的原理吃透了,再去看SSI-DATA就很轻松。
提示:模态参数识别里的"阻尼比"是最难识别准的参数,不管哪种方法都逃不过数据长度和噪声水平的制约。后面你会看到,识别频率误差能做到0.5%以内,阻尼比误差却经常到5%~10%,这是方法的固有特性,不是代码写错了。
2. 仿真算例:三自由度系统的响应数据怎么来
做算法验证的第一件事就是构造一个"答案已知"的系统。实际测量数据永远没有标准答案,你无法判断算法识别出来的是对是错。所以我先用一个三自由度弹簧-质量-阻尼系统做仿真,把理论模态参数先算出来,再生成模拟响应数据,最后用SSI-COV去识别,看识别结果和理论值差多少。
2.1 系统模型与状态空间表达
我选的模型是一个串联形式的剪切型结构,类似三层框架的简化模型。三个质量块都取1000kg,层间刚度取4e6N/m,阻尼采用瑞利阻尼:
[ \mathbf{M}=m\mathbf{I},\quad \mathbf{K}=k\begin{bmatrix}2 & -1 & 0\ -1 & 2 & -1\ 0 & -1 & 1\end{bmatrix},\quad \mathbf{C}=0.5\mathbf{M}+0.0005\mathbf{K} ]
质量、刚度矩阵都是典型的三自由度形式。阻尼用瑞利阻尼是为了方便计算理论阻尼比,因为比例阻尼条件下模态阻尼比有解析表达式:
[ \zeta_i=\frac{a}{2\omega_i}+\frac{b\omega_i}{2} ]
这里 (a=0.5)、(b=0.0005),可以让三个模态的阻尼比落在1.5%~3%这个比较真实的范围内,不至于太小导致数值仿真难以稳定,也不至于太大偏离实际结构。
把运动方程改写成状态空间形式,状态向量取位移和速度:
m = 1000; k = 4e6; M = m * eye(3); K = k * [2 -1 0; -1 2 -1; 0 -1 1]; C_damp = 0.5 * M + 0.0005 * K; % 状态空间连续时间矩阵 Ac = [zeros(3), eye(3); -M\K, -M\C_damp]; Bc = [zeros(3); inv(M)]; Cc = [-M\K, -M\C_damp]; % 输出加速度 Dc = inv(M); fs = 200; dt = 1/fs; t = 0:dt:600; N = length(t); f_ext = randn(3, N); % 三个自由度上独立的随机激励 sys_c = ss(Ac, Bc, Cc, Dc); y = lsim(sys_c, f_ext', t)'; % 3 x N 的加速度响应采用加速度作为输出,是因为工程现场绝大多数传感器是加速度计。注意这里Dc不是零,当激励是作用于质量块上的力时,加速度输出会直接包含力项。很多教程为了方便把D设成零,那其实是假设激励通过某种方式不直接影响测量,和实际情况有偏差。
2.2 理论模态参数:用于后续验证的标准答案
三自由度系统的理论模态参数可以直接求解广义特征值问题:
[Phi_th, Om2] = eig(K, M); [omega_th, idx] = sort(sqrt(diag(Om2))); Phi_th = Phi_th(:, idx); f_th = omega_th / (2*pi); zeta_th = 0.5 ./ (2*omega_th) + 0.0005 * omega_th / 2;这套代码算出来,三个模态的理论频率大约是4.48Hz、12.55Hz、18.13Hz,理论阻尼比分别是1.59%、2.29%、3.07%。振型是三维向量,后面跟识别结果做MAC对比时用。
有一点要提醒:这里的振型是位移振型,而后面SSI-COV识别用的是加速度响应。加速度振型和位移振型之间差一个 (-\omega^2) 的缩放因子,但因为每个模态都有各自的系数,归一化之后两者是一致的,做MAC相关性计算时不受影响。
2.3 生成模拟加速度响应并加入噪声
仿真激励用的是高斯白噪声,目的是模拟环境脉动这类宽带随机激励。采样频率设200Hz,数据长度600秒。加噪声时没有用固定的绝对噪声幅值,而是按每个通道响应RMS的百分比加,这样信噪比更可控:
noise_ratio = 0.05; y = y + noise_ratio * std(y, 0, 2) .* randn(size(y));std(y,0,2)是求每个输出通道的标准差,noise_ratio=0.05表示噪声RMS是信号RMS的5%,这个水平在仿真里已经不算干净了,可以用来检验算法的抗噪能力。实际工程项目里,现场数据的噪声水平经常比这更差,所以下面还会单独分析不同噪声水平的影响。
3. SSI-COV的Matlab实现:算法拆解与代码逐段剖析
这一章是整篇文章的核心。我会把SSI-COV算法的每个关键步骤拆开讲,配上可以直接运行的Matlab代码。理解了每个矩阵在做什么,你的算法调试能力会上一个台阶。
3.1 Hankel矩阵、协方差矩阵与SVD截断
SSI-COV虽然叫"协方差驱动",但工程实现时通常用Hankel矩阵的投影来计算,效果等价、代码更简洁。先把响应数据排成Hankel矩阵,分成"过去"和"未来"两块:
l = size(y, 1); % 输出通道数,这里是3 i_block = 30; % 块行数 Ndata = size(y, 2); ncol = Ndata - 2*i_block + 1; H = zeros(2*i_block*l, ncol); for r = 1:2*i_block H((r-1)*l+1:r*l, :) = y(:, r:r+ncol-1); end Yp = H(1:i_block*l, :); % 过去块 Yf = H(i_block*l+1:2*i_block*l, :); % 未来块 T = Yf * Yp' / ncol; % l*i_block x l*i_block这个T矩阵就是算法要分解的对象。它的物理含义是:把"未来"的响应数据往"过去"的响应数据方向上投影,提取出"由过去状态可预测"的那部分结构信息,剩下的部分属于新进入系统的随机激励,被这一步过滤掉。
接下来对该矩阵做奇异值分解:
[U, S, V] = svd(T); n_order = 8; % 先按略大于2倍物理模态数选取,后面再讲稳定图定阶 O = U(:, 1:n_order) * sqrt(S(1:n_order, 1:n_order));S矩阵的奇异值大小反映了各阶子空间在数据里的能量占比。系统只有3阶,对应6个共轭极点,理论上取n_order=6就够。但实际数据有噪声、有随机误差,SVD的奇异值不会干净地"截断",所以通常取大一点,后面通过稳定图筛选真实模态。我这里的n_order=8只是为了先把算法跑通。
3.2 提取系统矩阵并换算频率、阻尼比、振型
从截断后的可观测矩阵O可以提取输出矩阵C和系统矩阵A_d。关键关系是:如果 (O=[C;CA;CA^2;\cdots;CA^{i-1}]),那么去掉前块和去掉后块的两部分满足 (\text{O_down}=O_{up}\cdot A_d)。所以:
C_est = O(1:l, :); A_d = pinv(O(1:end-l, :)) * O(l+1:end, :);A_d是离散时间状态矩阵,要先做特征值分解:
[Psi, Lambda] = eig(A_d); lambda_c = log(diag(Lambda)) / dt;lambda_c就是连续时间系统的特征值,是一对一对共轭复数。每一个共轭对对应一阶物理模态,再进行参数换算:
fn_est = abs(lambda_c) / (2*pi); zeta_est = -real(lambda_c) ./ abs(lambda_c); Phi_est = C_est * Psi;这里的物理逻辑是:连续时间特征值 (\lambda=-\zeta\omega+j\omega\sqrt{1-\zeta^2}),它的模就是无阻尼固有圆频率,实部的负值除以模就是阻尼比。振型向量是输出矩阵和特征向量的乘积 (C\Psi) 的各列,按任意通道归一化后就是实际意义的模态振型。
识别完成后,把频率排序、剔除虚部为负的共轭重复项、只保留阻尼比在合理范围内的极点,就得到最终结果。
3.3 离散特征值到连续特征值的坑
这一节必须单独拿出来说,因为新手经常在这里翻车。离散状态矩阵的特征值 (\lambda_d) 和连续特征值 (\lambda_c) 的关系是:
[ \lambda_d=e^{\lambda_c\Delta t} ]
所以反求连续特征值不能直接对矩阵开方,而是要用矩阵对数:
lambda_c = log(diag(Lambda)) / dt;Matlab里如果直接写logm(A_d)/dt也能算出结果,但对角化情况下用log(diag(Lambda))更直观,而且便于逐阶处理。麻烦的点在于复对数有分支问题:如果采样频率不够高,某些模态的离散特征值会落在单位圆的其它分支上,导致换算出来的频率产生虚假偏移。我的经验是:采样频率至少要覆盖感兴趣最高模态频率的5~10倍,再低就容易出问题。
此外,识别结果里会出现成对的共轭极点,频率完全相同,这是正常现象。筛选取值时只保留其中一个即可,千万别把共轭对当成两阶模态。
提示:阻尼比计算时,如果
lambda_c的实部为正,说明识别出的极点不稳定,对应的可能是噪声造成的伪模态,可以直接丢弃。真实结构的阻尼比是正的,但环境激励数据里偶尔会识别出负阻尼极点,这在随机子空间方法里很常见。
4. 结果验证:频率、阻尼、振型到底准不准
算法跑通了,下一步就是看识别结果靠不靠谱。我用的是5%噪声、600秒数据的仿真算例,随机种子固定后得到一组具体结果。下面从频率、阻尼比、振型三个维度逐一验证。
4.1 模态参数对比
三阶模态的识别结果和理论值对比如下:
| 模态 | 理论频率 (Hz) | 识别频率 (Hz) | 频率误差 | 理论阻尼比 (%) | 识别阻尼比 (%) | 阻尼误差 |
|---|---|---|---|---|---|---|
| 1阶 | 4.48 | 4.49 | 0.22% | 1.59 | 1.52 | 4.4% |
| 2阶 | 12.55 | 12.53 | 0.16% | 2.29 | 2.39 | 4.4% |
| 3阶 | 18.13 | 18.20 | 0.39% | 3.07 | 3.22 | 4.9% |
频率识别精度非常高,三阶都控制在0.5%以内,这符合SSI-COV的一贯表现。阻尼比的误差明显更大,在4%~5%左右,这也是所有仅输出法的通病。阻尼比的本质是能量耗散参数,它隐含在响应衰减速度里,对噪声、数据长度、频率分辨率都非常敏感。
4.2 振型相关性MAC
振型对比通常用模态置信准则MAC值。MAC的定义是在两个振型向量之间做一个相关性归一化:
[ MAC=\frac{|\phi_1^H\phi_2|^2}{(\phi_1^H\phi_1)(\phi_2^H\phi_2)} ]
数值越接近1,说明两个振型越一致。Matlab里实现很简单:
function mac = MAC(phi1, phi2) mac = abs(phi1' * phi2)^2 / ((phi1' * phi1) * (phi2' * phi2)); end我用识别振型和理论振型逐阶做了MAC计算。三阶的MAC分别为0.9997、0.9991、0.9980,说明振型识别得相当准。唯一要注意的是,识别出的振型符号可能与理论振型相差180度,这完全不影响MAC值,因为模的平方会把符号消掉。
提示:做MAC时要注意向量数据类型。加速度响应的识别振量本质上跟位移振量差一个缩放,但归一化后就不影响MAC了。实际工程中如果做多工况合并,每个工况的振型也要先归一化再比较,否则MAC计算就是白搭。
4.3 噪声水平和数据长度的影响
同一个算例,我把噪声比从2%调高到10%,再缩短数据长度观察表现。噪声在2%水平时,频率误差小于0.1%,阻尼误差约2%;噪声升到10%时,频率误差仍在1%以内,但阻尼误差会扩大到10%~15%,而且高阶模态可能出现伪极点,需要靠稳定图手动筛选。
数据长度的影响更值得注意。同样的5%噪声,600秒数据识别出来的阻尼比误差约5%;压缩到120秒后,频率误差还在0.5%以内,但阻尼比误差会飙到10%~20%。这说明如果目标是识别准确的阻尼比,数据记录一定要足够长,建议至少保证最低频率对应周期的50倍以上,我个人习惯按100倍来取。
5. 实战中绕不开的细节:稳定图、预处理和定阶
仿真环境里一切都干净利落,真正做实测数据时各种问题才会暴露出来。这一章聊聊那些我在实际项目中反复踩过、花了不少时间才摸清楚的细节。
5.1 模型阶次不是拍脑袋定的,靠稳定图
前面代码里我直接给了n_order=8,那是预设条件。实际数据里系统阶次完全未知,而且你也不可能恰好知道结构有几阶模态在频带里。我的做法是:把阶次从2循环到40,对每个阶次都做一遍SSI-COV识别,把所有识别出的极点画到一张"频率-阶次"图上,这就是稳定图。
稳定点的判断标准一般是相邻阶次之间:频率变化小于1%、阻尼变化小于5%~10%、MAC大于0.95。满足这些条件的极点会在图上形成一条垂直的"稳定线",那条线对应的就是真实模态。手写稳定图代码不复杂,核心逻辑是:
orderRange = 2:2:40; stableFreq = []; for n_order = orderRange % 执行SSI-COV,得到fn_est, zeta_est, Phi_est % 对每个候选极点,和前面已确认的稳定极点比较 % 如果频率差<1% 且 阻尼差<5% 且 MAC>0.95,标记为稳定点 scatter(fn_est, n_order * ones(size(fn_est)), 20, 'fill'); end千万不要直接选一个过高的阶次然后拿结果去讲物理故事,那样会把噪声拟合得特别好,但识别出来的"模态"毫无意义。
5.2 数据预处理的正确姿势
很多同学把原始数据拿过来就直接丢进SSI-COV,结果识别出来一堆莫名其妙的小峰值。我踩过几次坑之后总结出一条基本流程:
第一,去均值。加速度计现场记录数据经常有直流偏置,不去均值的话,协方差矩阵的第一行会有明显的趋势项污染,识别结果在低频段会出现假模态。
第二,去趋势。数据本身如果有缓慢漂移,先做多项式去趋势。我一般用一次或二次多项式,趋势项对协方差估计的影响比均值更大。
第三,滤波。只在感兴趣的频带内保留信号,比如你关心2~25Hz的模态,就做2Hz高通和25Hz低通滤波。滤波能显著压低频带外噪声,但要注意滤波器本身可能引入相位失真,最好用零相位滤波函数如filtfilt,不然识别出的阻尼比会被虚假地放大。
第四,重采样。原始数据采样率过高会让Hankel矩阵维度膨胀、计算量暴增。先低通再降采样到最高关注频率的5~10倍,计算效率可以提升一个数量级。
5.3 从仿真走向实测的三点提醒
最后说三个很容易被忽略、却直接影响实测效果的点。
传感器数量。SSI-COV是哪个通道都参与计算的。通道数太少,你只能识别出测点能够分辨的振型,高阶模态或者空间上相邻的模态会糊在一起。如果条件允许,测点数量至少应该是你关心的模态阶数的一倍以上。
激励的充分性。SSI-COV的白噪声激励假设在实际中永远是近似满足的。如果有风致涡激振动、设备转速引起的周期信号,会在响应谱里叠加窄带峰值,SSI会把这些也识别成模态。遇到这种情况,要么换一段数据,要么对这个频带做专门处理,别硬套算法。
振型归一化约定。做完振型识别,在报告里说明你用的是哪种归一化:是按第一个传感器位置归一,还是按质量归一。不同归一化方式会直接影响后续损伤识别、模型修正的计算结果。我平时习惯统一按最大幅值归一,跨工况对比时省很多纠结。
我个人经验是,SSI-COV这个算法调试一旦过了"稳定图"这道门槛,后面就顺畅了。下一步如果想进阶,可以试试在协方差的基础上加入加权矩阵,或者把SSI-COV识别的状态矩阵拿去和有限元模型做相关性分析,那又是另一个很有意思的方向了。