首页 / 资讯中心 / 文章详情

集合经验模态分解EEMD的MATLAB实现与工程应用

集合经验模态分解EEMD的MATLAB实现与工程应用 ★ FEATURED ARTICLE
简介EEMD集合经验模态分解的MATLAB完整实现代码面向从事非平稳信号分析的研究者与工程师尤其适用于机械故障诊断、振动监测、生物医学信号处理等场景旨在解决传统EMD方法中模态混叠导致分解失效的常见问题。包内以主程序eemd.m为核心配套极值点提取、局部均值包络计算、希尔伯特变换、瞬时幅值与频率估计等子函数并加入白噪声辅助机制及集合平均逻辑可直接调用或二次开发。资源共46个文件42个m脚本覆盖完整分解流程、绘图验证与显著性检验2个csv文件提供示例数据另有1个txt数据文件和1个p加密函数压缩包仅189KB轻量且便于快速熟悉算法结构。目前已有5188人学习下载。通过这份代码可系统理解EEMD从添加白噪声、筛分IMF到最终集合平均的全流程掌握参数设置对分解效果的影响代码模块划分明确方便读者将算法迁移到自己的振动、语音或金融数据上完成时频分析、特征提取等后续研究。1. EEMD集合经验模态分解一套能直接改写的MATLAB信号拆解程序从传感器采回来的振动信号里藏着设备故障特征一堆股票收盘价里藏着长期趋势。直接拿FFT看频谱非平稳信号会让频率信息糊成一团。EEMD集合经验模态分解的MATLAB程序代码就是用一个“加白噪声、多次分解、取平均”的办法把非线性非平稳信号按时间尺度拆成一组固有模态函数IMF噪声被平均抵消模态混叠被明显压制。这套资源适合两类人做故障诊断和噪声分析的工程师想快速拿到一个能改成自己工具的算法研究者。我拆完这套程序之后发现它没有魔法核心就是几百行MATLAB函数完全可以自己改。2. 原理与结构从模态混叠到集成平均再看程序目录2.1 EMD为什么不够用模态混叠的一次直观体验经验模态分解EMD做的事情很直接把原始信号拆成若干条IMF和一条残差。它的逻辑很工程化找到信号局部极大值和局部极小值用三次样条把上下极值点分别连成包络取上下包络的均值用原信号减去这个均值得到筛分分量如果筛出来的分量满足“上下包络均值处处为零”这种条件就认为它是一个IMF否则继续筛直到满足停止阈值。这个思想来自一个朴素的观察一个复杂波形总是由快速振荡叠加在慢速趋势上组成的筛分就是一层层把快速成分剥下来。EMD的问题在于模态混叠。当一个IMF里既有高频振荡又有低频振荡或者同一个频率成分被分散到多条IMF中时分解就失去了物理意义。实际信号里最常见的场景是间歇性的高频振荡夹杂在连续低频信号中。比如机器正常运转时是一段稳定振动突然撞了一下产生一个高频冲击EMD很容易把这个冲击和高频成分叠进低频包络里分解结果看起来就像是低频波形上长了一排毛刺没法区分谁是谁。原因是极值点分布被局部异常波干扰包络被带偏筛分过程就很难把单一尺度干净地分离出来。2.2 集合平均的改法EEMD的噪声加持集合经验模态分解Ensemble EMDEEMD解决模态混叠的办法非常直白多次给原始信号加上正态分布白噪声每次加噪声的幅值随机然后对加噪后的信号做一遍完整的EMD得到一组IMF最后把所有组的IMF按对应位置做算术平均。白噪声的频谱是均匀铺开的它会自动填充信号里缺失的尺度带让极值点在不同尺度上均匀分布从而把不同尺度的成分赶到各自的IMF里。平均的时候白噪声因为互不相关而相互抵消所以平均结果里噪声残留很小。这种“扰动再平均”的思路比算法本身更像一个实验技巧但效果很稳。代价是计算量翻了N倍N通常是100到500。所以看一个EEMD程序好不好用一半看分解效果一半看有没有做速度优化。一个值得注意的细节是EEMD不是简单地对同一个信号做多次EMD每次都要重新生成随机噪声所以在同一台机器上重复运行结果会有一点点随机差异。这是白噪声机制带来的正常现象不是程序出错了。2.3 资源包目录结构每个文件干什么这套MATLAB程序解压后是典型的“函数加脚本”组合目录大致长这样EEMD_Toolbox/ ├── eemd.m % EEMD主函数负责加噪声循环和平均 ├── emd.m % EMD核心分解函数负责包络和筛分 ├── local_extrema.m % 查找局部极大值和极小值点 ├── envelope_spline.m % 用三次样条构造上下包络 ├── find_imf.m % 单次筛分迭代输出一个候选IMF ├── plot_imfs.m % 绘制原始信号与全部IMF的对照图 ├── test_eemd.m % 可直接运行的测试脚本 ├── data/ │ ├── vibration_signal.mat │ └── synthetic_signal.mat └── README.mdeemd.m是入口它不负责筛分只负责对原信号反复加噪声、调用emd.m、最后对累计结果取平均。emd.m才是真正的核心调用local_extrema.m找极值点调envelope_spline.m生成包络用find_imf.m迭代筛出单条IMF。plot_imfs.m是后处理绘图工具把分解结果可视化方便肉眼检查有没有混叠。data目录里放的是测试信号synthetic_signal.mat是仿真组合信号vibration_signal.mat是设备振动样本用来验证故障特征提取。搞清楚这个调用链后面改代码就有方向想动分解精度改envelope_spline.m想加批量处理在test_eemd.m外面再包一层循环想提速在eemd.m里把串行循环换成parfor。2.4 调用接口与输出约定程序包的入口调用非常简单最常见的调用方式是这样[imfs, residue] eemd(x, 200, 0.2);输入参数的含义如下参数类型说明xdouble列向量待分解信号建议长度不小于100点N整数集成次数默认200科研场景可取300kdouble白噪声幅值系数相对原始信号标准差的比重输出有两个imfs是n行m列的矩阵每一列是一条IMF按频率从高到低排列residue是n行1列的向量表示分解完所有IMF后剩下的趋势项。这个输出约定决定了后续绘图和特征提取代码的写法所有依赖IMF的后续处理都建立在“列是IMF”这个约定上。3. 核心代码拆解噪声循环与包络插值的MATLAB实现3.1 eemd主函数加噪声、调emd、累计平均打开eemd.m第一眼看到的是一段很工整的MATLAB函数定义。一个合格的实现应该是这样的function [imf_set, residue] eemd(x, N, k) % EEMD: 集成经验模态分解 % 输入: % x: 一维列向量长度100 % N: 集成次数默认200 % k: 白噪声幅值系数默认0.2 % 输出: % imf_set: 各IMF按列排列的矩阵行数与x一致 % residue: 最终残差与x同长 if ~isvector(x) error(x必须是向量); end x x(:); n length(x); acc zeros(n, 10); % 累加器最多支持10条IMF for i 1:N noise k * std(x) * randn(n, 1); x_noisy x noise; [imfs_cur, res_cur] emd(x_noisy); cur_n size(imfs_cur, 2); acc(:, 1:cur_n) acc(:, 1:cur_n) imfs_cur; if i N residue res_cur; end end imf_set acc / N; % 去掉没有被累加过的全零列 imf_set imf_set(:, any(imf_set)); end这个版本把每条IMF按列存储预分配一个n行10列的零矩阵。如果某次分解出的IMF超过10条后面的会被截断但对大多数工程信号来说10条以内足够覆盖有效频段。这里藏着一个关键细节每次加噪分解出的IMF数量可能不一样所以累加时只取当前IMF的列数只累加重合的部分避免矩阵维度不匹配导致报错。参数k直接控制了噪声强度。k乘以原始信号标准差得到实际添加的白噪声幅值k太小时噪声没法充分填充信号里缺失的尺度带模态混叠依然存在k太大时白噪声会主导极值点分布分解结果虽然稳定但真实分量的波形会被拉变形。常见建议是k取0.1到0.4工程上从0.2开始试。N的选择则看需求N从100起步论文级应用用到300以上每增加一倍运行时间也差不多翻倍。3.2 emd子函数极值点、包络和筛分条件eemd.m调用的emd.m是真正的体力活。它要做的事是function [imfs, residue] emd(x) imfs []; residue x(:); n length(x); for imf_idx 1:10 h residue; sd 1; for iter 1:100 [max_pos, max_val] local_extrema(h, max); [min_pos, min_val] local_extrema(h, min); upper envelope_spline(max_pos, max_val, n); lower envelope_spline(min_pos, min_val, n); m (upper lower) / 2; h_new h - m; sd sum((h_new - h).^2) / sum(h.^2); h h_new; if sd 0.2 break; end end imfs [imfs, h]; residue residue - h; % 如果残差极值点少于2个说明只剩单调趋势停止 max_count size(local_extrema(residue, max), 1); min_count size(local_extrema(residue, min), 1); if max_count min_count 2 break; end end end筛分过程里的sd阈值取0.2这是业界流传很广的经验值。sd太小会把噪声当成有效IMF筛出一堆等幅振荡sd太大则包络均值没有充分归零分解结果不够纯粹。每个IMF最多迭代100次这是防止极端信号下死循环的安全阀。内部循环中h每次减去包络均值这个过程叫“筛分”做十几次甚至几十次之后h的上下包络才会基本对称成为一个合格的IMF。包络插值用的是三次样条。这里有一个关键点样条插值在信号端点处容易发生“过冲”导致包络在端点偏离真实值分解出的IMF两端出现明显涨落。资源包里的envelope_spline.m自带端点延拓逻辑在左右两端各补两个虚拟极值点让样条在端点外也有约束所以分解结果的头尾会比裸调interp1稳定很多。如果你把这个文件换成普通插值端点问题会立刻冒出来。3.3 性能优化预分配矩阵和parforEEMD慢在循环层数多N次集成乘上每条IMF的筛分迭代计算量不小。MATLAB里最直接的提速办法是把eemd.m里的普通for循环改成parforacc zeros(n, 10); parfor i 1:N noise k * std(x) * randn(n, 1); x_noisy x noise; [imfs_cur, ~] emd(x_noisy); cur_n size(imfs_cur, 2); acc(:, 1:cur_n) acc(:, 1:cur_n) imfs_cur; end注意parfor循环里不能依赖循环顺序每次加噪分解都是独立的天然适合并行。前提是安装了Parallel Computing Toolbox否则parfor会按普通for运行没有任何加速。还有一个习惯值得保留使用预分配矩阵而不是在循环里不断拼接因为MATLAB里矩阵动态增长每轮都要重新分配内存N达到几百后这种开销会被放大很多倍。资源包里没有默认启用parfor可能是为了兼容没有并行工具箱的用户自己改的时候加一个判断即可。4. 跑通示例用测试脚本分解一段非平稳信号4.1 测试脚本test_eemd.m怎么把信号喂进去资源包里的test_eemd.m做三件事生成测试信号、调用eemd、把结果画出来。典型的测试信号是把低频正弦、间歇高频振荡和噪声叠加模拟设备振动里常见的“连续低频趋势突发高频冲击”场景%% test_eemd.m clear; clc; fs 500; t (0:999) / fs; % 低频连续分量 low_freq 0.8 * sin(2 * pi * 5 * t); % 高频间歇分量只出现在0.4~0.6秒内 high_freq cos(2 * pi * 80 * t) .* (t 0.4 t 0.6); % 白噪声 noise 0.15 * randn(size(t)); x low_freq high_freq noise; N 200; k 0.2; [imfs, residue] eemd(x, N, k); plot_imfs(x, imfs, residue, fs);这段合成信号里5Hz连续正弦和80Hz突发余弦故意组合在一起考验的就是EEMD能不能把突发段的高频成分从低频里剥出来。如果直接用EMD分解80Hz成分容易和5Hz趋势搅在一起换成EEMD集成次数设200得到的IMF里应该有一条明显对应80Hz的振荡分量另一条对应5Hz的低频分量。运行脚本只需要把文件夹加入MATLAB路径然后在命令窗口执行test_eemd即可。第一次跑建议先把N改成50跑一遍流程确认程序没报错再恢复200否则等待时间会有点煎熬。跑完之后plot_imfs会弹出一张多子图对照图。4.2 结果解读哪条IMF对应哪个分量plot_imfs.m绘制的是每层IMF按顺序排列的子图。一个典型的输出如下子图内容第1行原始信号x第2行IMF1最高频成分包含噪声和80Hz突发第3行IMF2约5Hz的低频连续分量第4行residue接近0的残余趋势判断分解是否成功重点看IMF1在0.4到0.6秒之间振幅是否明显变大其他时间段接近噪声水平IMF2是否是一条平滑的低频正弦没有出现明显的振幅断裂。如果IMF2在0.4秒处也跟着抖一下说明模态混叠没有完全消除这时候应该把k调大一点或者增加N再跑一次。一个容易被忽略的细节是EEMD给出的IMF个数不固定取决于信号长度、成分数量和噪声幅值。不要指望IMF1一定对应某个物理量正确做法是把每条IMF做FFT看峰值频率再对应设备转速、轴承故障频率等已知特征频率。这也是为什么工具包里要保留plot_imfs肉眼先看形态再用频谱确认频率是效率最高的步骤。4.3 速度问题为什么第一次跑等了半天EEMD慢是先天决定的。N200表示要执行200次完整的EMD每次EMD要筛约10个IMF每个IMF迭代几十次每次迭代做两次样条插值。三层循环全串行处理几千点信号等几分钟很正常。如果你想提速最省事的办法就是把eemd.m里的for循环改成parfor前提是机器装了Parallel Computing Toolbox。如果不想改代码那就把信号降采样后再分解。降采样有风险关心频率是80Hz时原始采样率500Hz降到200Hz仍然够用如果目标特征在100Hz以上就不要随便降。另外一个经验是不要在主脚本里频繁用plot和disp输出进度那也会让整体运行时间明显变长。4.4 配合希尔伯特变换看时频图EEMD分解出的IMF最常用的后续处理是希尔伯特变换用来求瞬时频率和时频谱。在MATLAB里可以用hilbert函数方便地算analytic hilbert(imfs(:,1)); inst_phase unwrap(angle(analytic)); inst_freq diff(inst_phase) * fs / (2 * pi); t_mid t(1:end-1); plot(t_mid, inst_freq);这个代码块等于是给EEMD补上了时频分析能力。IMF1的瞬时频率曲线在0.4到0.6秒之间应该稳定在80Hz附近其他时间因为噪声影响会跳得厉害这是正常的。如果想看整体时频图可以把所有IMF的瞬时频率画在一张图上颜色用对应IMF的幅值表示。这一步是故障诊断项目里很常见的下游需求测试脚本里没写但依赖关系很清楚值得补上。5. 踩坑记录与排查思路噪声参数、边界效应与运行卡死5.1 集成次数设成500运行五分钟没结果现象命令窗口一直在跳CPU单核跑满但五分钟还没出图任务管理器看MATLAB只占了一个核。原因N设置得太大且代码是串行循环三层嵌套下运行时间随N线性增长。解决先用N50跑通整个流程确认信号和参数没问题后再加大N要提速就把eemd.m里的for改成parfor。还有一个习惯值得养成不要在循环体里用disp打印每次加噪的进度这会让输出窗口变成瓶颈批量跑的时候尤其明显。% 错误示范每次循环都打印 for i 1:N disp([第 num2str(i) 次分解]); % ... end % 正确做法关闭输出或用百分比控制台刷新5.2 分解出的IMF和预期信号对不上现象已知信号包含5Hz和80Hz两个主要成分但分解结果找不到80Hz这条IMF。原因k取值过大白噪声把80Hz的真实极值点全冲掉了筛分时高频成分被当成噪声处理。解决把k从0.2降到0.05或0.1再跑一次。反过来如果分解结果里IMF1仍然混叠明显说明k太小没起到集成平均的效果应该把k调到0.3。这是EEMD里最玄学的参数我一般按0.05、0.1、0.2、0.3做一组对比然后选振荡条带最清晰的那次结果。5.3 IMF数量不一致导致矩阵拼接报错现象程序单次运行正常连续跑第二次、第三次时返回的imfs矩阵列数变了后续用size(imfs,2)做循环时越界。原因每次加噪分解出的IMF数量不同如果代码里用固定矩阵存结果就会出现尺寸对不上。解决用元胞数组存每次分解结果最后再按公共列数对齐。简单的写法acc_cell cell(N, 1); parfor i 1:N [imfs_cur, ~] emd(x k * std(x) * randn(size(x))); acc_cell{i} imfs_cur; end min_col min(cellfun((c) size(c, 2), acc_cell)); acc zeros(length(x), min_col); for i 1:N acc acc acc_cell{i}(:, 1:min_col); end imf_set acc / N;这样做虽然多了一轮循环但不会再因为列数不一致而报错代价是内存占用变大。5.4 端点出现飞翼波形两端突然翘起现象分解出的第一条IMF在信号两端振幅突然增大波形像多了一截翅膀。原因样条插值在端点没有极值点约束包络在端点之外自由延伸导致端点附近包络严重偏离真实信号。解决对原始信号做镜像延拓把前后各一段信号翻转接上等分解完再切掉延拓段L round(length(x) * 0.05); x_ext [flipud(x(1:L)); x; flipud(x(end-L1:end))]; % 对x_ext做EEMD然后删除前L条和后L条这个预处理是解决端点效应的通用手段。资源包里的envelope_spline.m已经带了端点约束但如果你替换了插值函数这一步就很容易丢分解结果的头尾质量会立刻下降。5.5 输入信号太短直接报错现象输入信号长度只有30个点调用eemd后报错提示找不到足够的极值点。原因EMD筛分依赖极值点位置的样条插值信号太短时局部极值点数量不够包络误差大到无法接受程序直接拒绝继续。解决不要在信号只有几十个点时直接上EEMD先用padarray做边界填充x_pad padarray(x, 50, replicate); % 分解完后取中间原来的长度即可如果信号本身是短片段且无法延长常见做法是把多个片段拼起来做一次分解再从结果里切回原长度。这种方法会引入拼接处的误差但至少能让算法跑起来。6. 二次开发玩法让EEMD支持自适应参数6.1 自适应噪声幅值减少手调参数的次数每次手填k很麻烦实际项目里信号幅值差异很大同一个k在不同信号上效果完全不同。我习惯把k的计算封装进函数里让调用者只关心“噪声相对信号的标准差比例”function [imfs, residue] eemd_auto(x, N, target_ratio) if nargin 3 target_ratio 0.2; end k target_ratio * std(x) / std(diff(x)); [imfs, residue] eemd(x, N, k); end这里用信号一阶差分的标准差来归一化对突变信号更友好。验证方法还是拿test_eemd里的合成信号跑一遍对比一下IMF1的瞬时频率在0.4到0.6秒内是不是稳定在80Hz附近。从那以后我每次拿到新的EEMD程序都先跑一遍小N测试再去看包络函数和端点处理而不是直接上全量数据。环境变量、边界延拓、累加矩阵这三个地方几乎决定了这个程序能不能直接用于生产。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站