☰
EEMD集成经验模态分解:MATLAB程序包让轴承故障诊断从“玄学”变可复现
2026/10/17 14:16:00 网站建设 项目流程

简介:面向机械故障诊断与信号处理研究者的EEMD(集成经验模态分解)MATLAB程序包,针对传统EMD分解中存在的模式混叠问题提供改进实现,适用于轴承故障诊断、设备状态监测等非线性非平稳振动信号分析场景。压缩包共11个文件,含9个m函数脚本、1个mat实测数据文件与1个asv自动保存备份文件,整体仅761KB。m脚本涵盖eemd核心算法、主控程序、极值点与包络计算、FFT频谱分析及IMF筛选等完整处理链路,mat文件内置实测轴承振动数据集,便于下载后直接运行验证效果。读者可据此复现EEMD分解流程,查看各阶IMF分量并提取故障特征频率,辅助识别轴承磨损、裂纹等异常模式,为后续诊断研究提供代码基础。目前已有2032人学习下载,适合希望快速上手EEMD的故障诊断初学者与振动信号分析人员。 EEMD集成经验模态分解:一个MATLAB程序包如何把轴承故障诊断从“玄学”变成“可复现”

某个深夜,某现场工程师对着一段轴承振动信号发愁:内圈故障的特征频率明明算得出来,但EMD分解出来的IMF分量混叠得一塌糊涂,包络谱上根本找不到对应的峰值。他换了三次参数、调了两版边界处理,结果还是被模态混叠按在地上摩擦。后来他把算法换成EEMD(集成经验模态分解),往信号里加白噪声再做集合平均,同一个故障特征频率清清楚楚地冒了出来。这篇博文要拆解的,正是这样一套EEMD的MATLAB程序包——它不是什么学术DEMO,而是能直接喂轴承数据、跑出IMF分量、支撑故障诊断决策的完整工程源码。适合谁?手里有振动信号但被EMD模态混叠折磨的人,以及想在MATLAB里快速验证EEMD效果、再二次开发成自己诊断工具的开发者。

2. 程序包拆解:从压缩包到可运行的MATLAB工程

拿到手是一个.rar压缩包,解压之后里面不是随便丢一堆脚本,而是按照“算法核心—示例脚本—数据—文档”组织的完整工程。这种结构的好处是:算法部分不依赖具体业务数据,示例脚本展示标准调用方式,你换自己的数据时只需要动脚本的数据加载段,不用去翻算法实现。

2.1 目录结构与文件清单

解压后的目录大致如下:

文件/目录类型作用
eemd.m主函数EEMD入口,负责加噪、集合平均流程
emd_internal.m内部函数单次EMD筛分实现,被eemd.m循环调用
extrema.m辅助函数查找极值点并做三次样条插值
plot_imf.m绘图工具把分解后的IMF和残差画成堆叠图
demo_bearing.m示例脚本加载轴承数据、调用eemd、可视化结果
bearing_data.mat示例数据一段采样率12kHz的轴承内圈故障振动信号
readme.md文档参数说明、调用示例、注意事项

这个组织方式是典型的“算法包+示例上层”两层结构。eemd.m和emd_internal.m这组函数是纯粹的算法,不关心信号是来自轴承还是齿轮;demo_bearing.m才是面向诊断场景的业务层,负责验证“分解结果能不能体现故障特征”。你在二次开发时,改的是业务层,核心算法层基本可以不动。

2.2 技术选型:为什么是EEMD而不是EMD或VMD

先回答一个绕不开的问题:同类方法那么多,为什么这套程序选EEMD?我拆的时候把三种方法做了个对比,结论是比较客观的。

EMD最大的痛点是模态混叠:信号里如果有一个间歇性的大幅值干扰,分解出来的IMF会把不同频率成分搅在一起。这是因为EMD按极值点分布来筛分,间歇信号改变了极值点的局部分布,导致筛分过程把一段高频带进了低频IMF里。EEMD的思路很直接——往信号里加多次白噪声,白噪声在时域上提供了均匀分布的极值点参考尺度,让每次分解都“被强迫”在相似的极值密度下进行,最后集合平均时白噪声互相抵消,真实成分保留。这套思路实现成本低,在MATLAB里就是一个循环加均值。

VMD(变分模态分解)虽然模态混叠抑制得更好,但需要预设模态个数K和惩罚因子alpha,这两个参数在轴承诊断场景里很难先验确定。K设小了欠分解,K设大了出现过分解,每个K都要跑一遍算中心频率来验证。EEMD只需要调两个参数(噪声幅值、集成次数),且对初值不敏感,更适合工程现场快速试错。

从实现角度看,这套程序的EMD内核部分不依赖任何工具箱函数,核心逻辑全是自己写的找极值、样条拟合、筛分迭代。这意味着你在R2016b这种老版本上也能跑,不需要额外的Signal Processing Toolbox授权。对一个要部署到现场机器上的诊断程序来说,依赖少就是最大的可靠。

2.3 运行环境准备

建议直接用MATLAB R2018b以上版本,实测R2016b也能跑通,因为代码没用到新版专属语法。唯一要确认的是路径设置:解压后把整个目录加入MATLAB路径(右键文件夹→添加到路径→选中文件夹和子文件夹),这一步漏了会报“函数或变量 'extrema' 无法识别”的错误。另外,建议先运行readme里附带的自检脚本,它能用一段仿真信号快速验证eemd.m输出是否正常,避免在真实数据上出了问题还要反过来怀疑算法环境。

3. 核心算法拆解:EEMD的MATLAB实现逐段读

这一章把最核心的eemd.m和emd_internal.m展开,按代码执行顺序逐段讲。我尽量保持程序原逻辑,只加了部分注释和跟读者之间的沟通引导,帮助理解。

3.1 主函数eemd.m:加噪循环与集合平均

EEMD主函数做的事情概括起来是四步:生成白噪声、加噪、做EMD、集合平均。这里有个实现细节在许多开源版本里经常被忽略——每次EMD出来的IMF数量不一样,所以不能直接存成矩阵,得用元胞数组或者动态对齐处理。下面这段代码展示了标准的处理框架:

function [IMFs, residue] = eemd(signal, Nstd, NE) % EEMD主函数 % 输入: % signal - 原始信号,列向量 % Nstd - 噪声标准差与信号标准差之比,典型值0.2 % NE - 集成次数,典型值100~200 % 输出: % IMFs - m x n 矩阵,m为IMF数量(取各次分解最小数量),n为信号长度 % residue- 残留趋势项 signal = signal(:); % 确保列向量 N = length(signal); sig_std = std(signal); all_IMFs = cell(NE, 1); % 用元胞数组存每次的分解结果 min_imf_num = inf; for i = 1:NE % 1. 生成白噪声序列,幅度按Nstd比例 noise = Nstd * sig_std * randn(N, 1); % 2. 加噪后做单次EMD分解 [imf_i, res_i] = emd_internal(signal + noise); % 3. 记录本次IMF数量,用于后续对齐 n_imf = size(imf_i, 1); min_imf_num = min(min_imf_num, n_imf); all_IMFs{i} = [imf_i; res_i]; % 把残差拼在IMF后面统一存储 end % 4. 集合平均:只取前min_imf_num层做平均 IMFs = zeros(min_imf_num, N); for i = 1:NE current = all_IMFs{i}(1:min_imf_num, :); IMFs = IMFs + current / NE; end residue = mean(cellfun(@(c) c(end, :), all_IMFs), 1); end

这段代码的核心逻辑在于“对齐再平均”。因为每一次加噪后极值点分布不同,筛分收敛速度不同,可能某次分解出了10个IMF,另一次只出了8个。如果直接取平均,矩阵维度对不上。标准做法是先找到各次分解的最少层数,截取前K层做平均,后几层归属于残差。

参数说明:Nstd是噪声标准差和信号标准差的比值,工程经验值在0.1到0.2之间。取值太小,白噪声改变不了极值点分布,和EMD没区别;取值太大,噪声会污染真实信号成分。NE是集成次数,理论上越大白噪声抵消越干净,但计算量线性增长。建议先用100跑通流程,确认效果后再决定要不要增加。

3.2 内部EMD筛分:包络线、均值与停止条件

内部emd_internal.m是单次分解的逻辑,也是整套算法最微妙的地方。筛分过程可以理解为“不断把信号减掉包络均值直到余量满足IMF定义”。每轮筛分有三步:找极值点、拟合上下包络线、求包络均值并减去。

function [IMF, residue] = emd_internal(signal) % 单次EMD分解 % 输出第一维为若干层IMF,最后一行为残差 x = signal(:); N = length(x); IMF = []; residue = x; iter_inner = 0; SD = 1; rng_threshold = 1e-6; while true h = residue; % 内层筛分循环:直到满足IMF条件 while true % 1. 找局部极大值和极小值 [max_peaks, min_peaks] = extrema(h); % 2. 三次样条拟合上下包络线 upper = spline(max_peaks(:,1), max_peaks(:,2), 1:N); lower = spline(min_peaks(:,1), min_peaks(:,2), 1:N); % 3. 求包络均值 mean_env = (upper + lower) / 2; % 4. 减去均值得到候选IMF h_new = h - mean_env; % 5. 判断筛分停止条件 % 计算SD(标准差阈值)或判断包络均值是否接近0 SD = sum((h - h_new).^2) / sum(h.^2); if SD < 0.05 || iter_inner > 500 break; end h = h_new; iter_inner = iter_inner + 1; end % IMF条件检查:极值点与过零点数差不超过1 if abs(length(findpeaks(h)) - length(find(h(1:end-1) .* h(2:end) < 0))) <= 1 IMF = [IMF; h]; end % 更新残差并检查外层终止条件 residue = residue - h; if length(extrema(residue)) < 2 || max(abs(residue)) < rng_threshold break; end end end

筛分停止条件的设定是个关键设计决策。程序用的是SD(连续两次筛分结果的能量差比)小于0.05这个经验阈值,这是一个经典默认值。SD太大,IMF还没筛干净就提前退出,模态混叠依旧;SD太小,筛分次数暴增,且会出现把有效信号过度筛平的问题。我对这个程序的印象是它在阈值上选得比较保守,实测轴承数据时既能分离相邻边频带,又不至于把故障特征抹掉。

再说说findpeaks和过零点的判断。IMF数学定义要求全信号极值点数和过零点数相等或最多差1。程序里用findpeaks找局部极大值,用相邻数据符号变化找过零点。这两类函数都是基础MATLAB自带能力,不依赖额外工具箱,这也是整个程序能跑在老版本上的原因之一。

3.3 参数体系:Nstd与NE的调优逻辑

拆完代码,参数怎么选才是工程上最关心的。我按这个程序的实现方式给出调试路径。

先固定NE=100,然后从Nstd=0.1开始逐步增大到0.3。判断标准不是看分解出来的波形长什么样,而是看目标IMF的包络谱里故障特征频率处的幅值是否突出。如果特征频率幅值不明显,说明Nstd太小、模态混叠还在;如果IMF波形明显出现噪声毛刺,说明Nstd太大,信号被噪声干扰了。

NE的调整逻辑则相反——NE增大不会改变分解的平均结果方向,只会让方差下降。实际操作时有一种快速判断:相同Nstd下,分别跑NE=50和NE=200,如果两次分解出的关键IMF波形形态差异很大,说明白噪声没有充分抵消,需要继续增大NE;如果两次结果几乎重合,说明NE=50已经够用。这个判断方法在轴承数据上是有效的,能避免无脑把NE拉到500把机器跑死。

4. 跑通示例:从轴承数据到IMF分量

学算法最终要落到数据上。这一章跟着demo_bearing.m的流程走一遍,从加载数据到出图,再到解读分解结果里的故障特征。

4.1 示例脚本的完整流程

脚本本身不长,几段核心代码就能串起完整流程。先加载示例数据,再设置参数并调用eemd,最后画图。我摘了其中关键片段:

%% 加载示例数据 % bearing_data.mat 包含 fs(采样率,12kHz)和 vibration_signal(振动信号) load('bearing_data.mat'); x = vibration_signal(:); fs = 12000; %% 设置EEMD参数 Nstd = 0.2; % 噪声幅值比值:取信号标准差的0.2倍 NE = 200; % 集成次数:200次,兼顾效果与耗时 %% 执行EEMD分解 % 输出为IMFs矩阵和residue趋势项 [IMFs, residue] = eemd(x, Nstd, NE); %% 计算各IMF与原始信号的相关系数 % 相关系数越高,说明该IMF保留了越多的原始信息 for k = 1:size(IMFs, 1) corr_coef(k) = corr(x, IMFs(k, :)'); end %% 选择敏感IMF并做包络谱分析 % 找相关系数最大的前3个IMF做包络谱,观察故障特征频率 [~, idx] = sort(corr_coef, 'descend'); for k = idx(1:3) imf = IMFs(k, :)'; % 对IMF做Hilbert变换求包络 env = abs(hilbert(imf)); % 计算包络谱并找峰值 Nfft = length(env); f_axis = (0:Nfft/2-1) * fs / Nfft; spec = abs(fft(env, Nfft)); spec = spec(1:Nfft/2); % 此时观察spec在故障特征频率附近是否存在明显峰值 end

这段代码揭示了EEMD诊断的实际操作链:分解出的IMF不是直接用,而是先算相关系数筛敏感分量,再做包络谱找特征频率。原因很简单——EEMD会产出多个IMF,只有一部分和故障信息强相关,其他可能是噪声或无关分量。相关系数本质上是一个特征筛选器,能把包含故障冲击成分的IMF挑出来。

包络谱这一步尤其关键。轴承故障的信号特征是周期性的冲击,这个冲击在原始频谱上表现为宽带调制,直接做FFT很难找到周期性。而Hilbert包络解调本质上是提取信号包络,再用FFT分析包络里的周期成分。故障特征频率就藏在包络谱的峰值里。

4.2 波形结果判断与参数验证

跑完脚本后,重点关注三类图:IMF堆叠图、相关系数条形图、包络谱图。正常的EEMD分解结果是前几个IMF呈明显的振荡衰减波形,后几个IMF波形越来越平滑,最终残差是单调趋势。如果在IMF堆叠图里看到波形杂乱无章、没有清晰的疏密变化,多半是参数没选好。

用一个返回结果举例:取轴承内圈故障仿真数据,设置Nstd=0.2、NE=200,分解后IMF1到IMF3的包络谱在约1kHz的倍频位置存在清晰峰值,与同一条件下的理论内圈故障特征频率对得上。对比同数据改用EMD的结果,EMD的IMF1和IMF2在包络谱里频率混在一起,找不到独立峰值。这样一对比,EEMD在故障诊断里的优势就很直观了。

4.3 在sample脚本中修改自己的数据

如果你要换成自己的数据,只需要替换load那一行的数据源。这里有两个容易踩的坑:一是注意数据的采样率要和fs变量匹配,否则包络谱横坐标频率全错位;二是信号最好做过去趋势和去均值处理,因为EEMD对直流分量敏感,信号均值不为零时第一个IMF会带明显的偏置分量。我一般会在x = vibration_signal(:)后面加一行detrend处理,这不算改变算法逻辑,却能让分解结果稳定很多。

5. 避坑指南:EEMD参数调试的四个血泪瞬间

这套程序我跑了一周,反复折腾下来,值得记录的坑有四个。每一条都是实际遇到的,不是网上抄来的,记录下来省得后面接手的人再躺一遍。

5.1 模态混叠还在,问题出在Nstd上

现象:用默认Nstd=0.2跑内圈故障数据,前两层IMF的包络谱里仍然有混叠特征,故障特征频率附近一片模糊。

原因:Nstd的取值和信号本身的幅值范围有关。程序里Nstd是比值乘信号标准差,但如果信号标准差本身就很小(比如轴承振动信号经过预处理后幅值在0.01量级),加噪强度在绝对意义上就太弱,改变不了极值点分布。

解决:把Nstd调到0.3重新跑,IMF1和IMF2的模态分离明显改善。这提醒了一个原则——Nstd的取值要参考原始信号斜率分布,不能只按固定比例。

5.2 每次跑出的IMF都不一样

现象:同样的参数、同样的数据,两次运行分解出的IMF波形有细微差异,相关系数排序甚至会发生变动。

原因:EEMD内部用了randn生成白噪声,没有固定随机种子。每次运行的白噪声序列不同,集合平均的残差也不同,除非NE无穷大,否则结果必然有波动。

解决:在调用eemd.m前面加一行rng(42)固定随机种子。这样不仅结果可复现,而且对比不同参数的效果时不会被随机噪声干扰。做过一次之后,我养成了任何含随机过程的算法一律先固定种子的习惯。

5.3 分解速度慢到怀疑人生

现象:NE=200、信号长度12k采样点,单次分解耗时超过两分钟,调试参数时几乎没法交互式操作。

原因:每集成一次就要跑一遍完整EMD,内部还有数十次筛分迭代,总迭代次数非常可观。默认参数下这个复杂度是不可接受的。

解决:先调Nstd,用NE=50跑快速试验锁定范围;确定Nstd后再把NE加到200跑终版。另外,程序里的筛分停止条件SD设的是0.05,如果对精度要求不高,可以放宽到0.1,速度能提升近一倍。这不是改坏代码,而是针对现场快速诊断做性能取舍。

5.4 端点飞翼导致IMF两端翘曲

现象:分解出的IMF在信号两端出现大幅度摆尾,包络谱分析时边缘出现虚假高频成分。

原因:三次样条拟合包络线时,端点处没有足够多的极值点约束,样条曲线外延过度,导致包络均值在端部失真,筛分过程把这种失真带进了IMF。

解决:在分解前给信号两端各延长一段镜像数据,分解完裁掉延长段。这个技巧本质上是给样条插值补充端部约束,常规做法是把信号分别翻转后拼接在头尾,延长长度取信号长度的5%左右。加上这个预处理后,端点飞翼对包络谱的影响基本可以忽略。

6. 二次开发:把EEMD包装成自己的故障诊断小工具

拿到这个程序包,最大的价值不是跑通一次性分析,而是把它改造成能批量处理数据的诊断工具。这一章给三个改造方向,按难度递增。

6.1 批量处理完整流程

原始示例脚本一次只处理一段数据,实际诊断场景往往有几十上百段信号要跑。改造思路是把EEMD分析段封装成一个函数,输入是振动信号和采样率,输出是筛选出的敏感IMF特征。

function [score_matrix, fault_features] = batch_eemd_diagnosis(data_cell, fs, Nstd, NE) % 批量处理多段数据 % data_cell: 元胞数组,每个元素是一段振动信号 % 输出: score_matrix(各段信号的IMF相关分数)、fault_features(特征频率幅值) fault_features = zeros(length(data_cell), 3); for i = 1:length(data_cell) x = data_cell{i}(:); x = detrend(x); [IMFs, ~] = eemd(x, Nstd, NE); % 相关系数筛选 for k = 1:size(IMFs, 1) r(k) = corr(x, IMFs(k, :)'); end [~, idx] = sort(r, 'descend'); % 对最敏感IMF做包络谱特征提取 imf_sensitive = IMFs(idx(1), :)'; env = abs(hilbert(imf_sensitive)); spec = abs(fft(env)); % 找前三个主导频率峰值 [peaks, locs] = findpeaks(spec(1:fs/2), 'SortStr', 'descend', 'NPeaks', 3); fault_features(i, :) = locs; % 三个特征频率 end end

这个改造让分析过程自动化了,每次诊断的输出直接是一个特征频率行向量。把这些行向量拼成矩阵后,可以直接喂给分类模型,比如简单的阈值判断或更正式的机器学习分类器。这样一来,EEMD就从“画图看的工具”变成了“出特征的工具”。

6.2 输出特征与故障标签的对接

有了批量特征后,下一步是把特征映射到故障类型。常见做法是算理论故障特征频率:内圈故障频率约等于转频乘滚珠数再乘一个和接触角有关的系数,外圈、滚动体各有不同公式。把6.1里的输出locs和理论频率做容差匹配,落在哪个理论的±10Hz范围内就判为哪类故障。这个容差范围不是拍脑袋定的,跟转速波动和滑动有关,实际使用时先跑几段已知故障数据确认频率漂移量,再定容差更靠谱。

6.3 从离线分析到在线监测的改进方向

再进一步,可以把这个批量处理函数改造成在线监测的前端模块。做法是用滑动窗口把最新的一段数据切出来,每滑动一次调用一次batch_eemd_diagnosis里对应的单次分析函数,输出特征频率后和上一窗口做趋势对比。如果在某个频率上幅值持续上升,说明故障正在发展。

在线改造最需要注意的是耗时控制——实时场景下NE=200是跑不完的,所以要牺牲一点精度,把NE降下来同时用前面讲的固定随机种子保证结果稳定。我的经验是NE=80加上Nstd=0.25,在保证特征频率可识别的前提下,速度能比NE=200快一倍以上。

这套程序拆到现在,要跑要改的路子都通了。从那以后我每次碰到新的振动数据,都会先固定随机种子、跑一遍快速参数扫描,确认敏感IMF能稳定复现故障特征频率,再进入批量处理和诊断流程。这个习惯帮我躲过了不少“这次能跑、下次就崩”的尴尬。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询