☰
CEEMDAN与EMD算法MATLAB实现:信号分解与参数调试指南
2026/11/3 17:00:47 网站建设 项目流程

简介:针对信号自适应分解中的模态混叠、端点效应与噪声干扰问题,这份Matlab代码包覆盖EMD、EEMD、CEEMD与CEEMDAN四种代表性算法,适合本科、硕士阶段学习信号处理、故障诊断与振动分析的学生及科研人员快速上手。包内共7个文件,4个m脚本为核心算法实现,可直接运行或调整参数;2张png图片展示仿真结果与分解效果,便于逐段对照代码验证;1个txt运行说明涵盖环境配置、执行顺序与常见报错处理。整个压缩包仅8KB,轻量便携,目录结构清晰。已有582人浏览学习,通过对比四种算法的分解结果,可直观理解从EMD到CEEMDAN的改进脉络,包括噪声辅助分解、模态混叠抑制等机制;也可为智能优化算法、神经网络预测等方向提供信号预处理参考,是教研学习与项目初期的实用工具。运行前按说明调整Matlab版本或路径即可复现,运行结果可进一步用于后续特征提取与论文实验。

1. CEEMDAN 与 EMD 算法包:为什么我还在用 MATLAB 做信号分解

做机械设备故障诊断的朋友大概都有过这种经历:拿到一段振动加速度信号,先做 FFT 看频谱,结果转频和故障特征频率混在一起,边频带糊成一片,根本分不清是轴承外圈故障还是齿轮啮合问题。这时候把信号交给 EMD(经验模态分解),让它按频率从高到低逐层剥离出固有模态函数(IMF),往往是比盲源分离更省事的出路。这套资源把 EMD、EEMD、CEEMD 和 CEEMDAN 四种分解算法连同 MATLAB 实现代码、仿真结果和运行方法打包在一起,装完就能跑,适合正在做信号处理课设、毕业论文或者刚接触故障诊断的从业者。它的价值不只是给你一套能出图的代码,而是帮你把四种算法的差异、参数含义和坑的位置一次看清。

2. 四种分解算法的来龙去脉:从模态混叠到自适应白噪声

2.1 EMD 的先天不足:模态混叠和端点效应

EMD 的核心思想很简单:把信号 x(t) 分解成若干个 IMF 分量和一个残差 r(t),每个 IMF 要满足两个条件——极值点数和零点交叉数相等或最多差一,以及上下包络均值始终为零。算法内部通过反复筛选(sifting)完成分解,每轮筛选都做三次样条插值包络、求均值、做差,直到满足停止条件。

但 EMD 有个知名问题叫模态混叠。当一个 IMF 里同时含有不同时间尺度的成分时,这个分量就失去了物理意义。比如分析一段含有间歇性冲击的振动信号,冲击会把自己的能量“溅”到相邻的 IMF 上,导致分解结果不稳定。我在实际处理风机齿轮箱数据时遇到过:同一段信号多跑几次,IMF 数量和形状都有差异,这就是筛选过程对噪声和端点敏感的体现。

端点效应同样头疼。信号两端只有单侧极值点可用,三次样条插值包络在端点附近经常飞出去,导致分解结果在首尾段失真。常见处理手法是信号延拓——镜像延拓、多项式拟合延拓或者自回归预测延拓,但每种方法在信号变化剧烈时都会引入新的误差。EEMD 的出现就是为了对付模态混叠,它利用白噪声的统计特性,通过多次加噪取平均来稳定分解结果。

2.2 EEMD 和 CEEMD:加噪声的思路与代价

EEMD(集合经验模态分解)的做法是给原始信号加上高斯白噪声,再进行 EMD 分解,重复 N 次后对相同序号的 IMF 取平均。因为白噪声在不同尺度上均匀分布,信号中的真实成分会被“钉”在对应的 IMF 里,而噪声成分在多次平均后相互抵消。

EEMD 用得顺手,但计算量成倍增长。更麻烦的是,每次加噪后分解出的 IMF 数量不一定相同,对 IMF 取平均时需要对缺失的 IMF 做补零或丢弃处理。而且噪声残留在重构信号里无法完全消除,分解结果对噪声幅值和迭代次数比较敏感。

CEEMD(互补集合经验模态分解)针对 EEMD 的残留噪声做了改进。它的做法是成对添加正负白噪声——给原始信号加 +n(t) 得到分解结果,再加 -n(t) 得到另一组结果,然后两两平均,残余噪声因此大幅削减。我个人的经验是 CEEMD 的分解结果比 EEMD 干净不少,但它的代价是计算量再翻一倍,因为每次都要跑两遍分解。

2.3 CEEMDAN 的自适应噪声:这才是最终形态

CEEMDAN(完全自适应噪声集合经验模态分解)跟前两种思路有本质区别。EEMD 和 CEEMD 是在信号域加噪声,CEEMDAN 则在每一层分解时对上一轮的残差添加自适应白噪声,而且噪声系数是每层实时计算的。它的流程大致是:

  • 对 x(t) + ε0 * n(t) 做 EMD,得到第一个 IMF1,注意这里的 ε0 是噪声幅值系数
  • 对残差 r1(t) 再做 EMD,但在包络均值计算时利用噪声的 IMF 分量进行自适应修正
  • 重复迭代,直到残差不满足分解条件

直接给一个概念对比表:

算法加噪方式计算量残留噪声模态混叠抑制适用场景
EMD不加噪低无弱平稳信号、快速预览
EEMD信号域加噪高较多中等含强间歇成分、对实时性要求低
CEEMD正负配对加噪很高较少中等追求重构精度、计算资源充足
CEEMDAN残差自适应加噪高很低强非平稳、非线性的复杂实测信号

需要提醒的是,CEEMDAN 也不是没有短板。它的计算耗时明显高于 CEEMD,而且对于高频噪声较多的信号,前几层 IMF 往往被白噪声主导,这时候需要控制迭代次数或调整噪声幅值。

2.4 代码包结构:拿到手先看清四个入口

解压后你会看到按算法名分目录的组织方式,每个目录下都有对应的主脚本和配套函数,其中EMD.m、EEMD.m、CEEMD.m、CEEMDAN.m是四个核心入口,test_*.m是演示脚本。还有一份运行说明文档,里面标注了测试信号类型和参数建议。先跑test_CEEMDAN.m看看完整出图流程,再回头逐个替换核心函数,这样最快建立直观印象。

3. 仿真复现与参数调优:从跑通到跑出可用结果

3.1 先复现 demo:看懂每一行在干什么

我习惯拿到代码先跑一遍默认参数,再对照结果图理解每个输出项的含义。以test_CEEMDAN.m为例,典型内容如下:

% 生成测试信号:低频正弦 + 间歇冲击 + 白噪声 fs = 1000; % 采样率 1000 Hz t = (0:1999) / fs; % 2 秒数据 x = sin(2*pi*20*t) + 0.4*sin(2*pi*60*t); x(500:520) = x(500:520) + 2; % 模拟瞬态冲击 % 调用 CEEMDAN 分解 imfs = ceemdan(x, 0.02, 100, 0.2, 0, 100); % 输入参数依次为: % x - 待分解信号 % 0.02 - 噪声标准差 Nstd % 100 - 噪声添加次数 NE % 0.2 - 噪声幅值系数 % 0 - 最大筛分迭代次数(0 表示自动) % 100 - 最大 IMF 数量上限 % 绘制前 6 个 IMF figure; for k = 1:6 subplot(6,1,k); plot(t, imfs(k,:)); ylabel(['IMF', num2str(k)]); end

这段代码里,采样率fs决定了信号的频率分辨率,20 Hz 和 60 Hz 的正弦叠加构成低频主导成分,中间插入的冲击模拟了设备故障中常见的瞬态特征。ceemdan函数返回的imfs是一个二维矩阵,行对应 IMF 序号,列对应时间采样点,行数由算法自适应决定,不会超过你设置的 IMF 数量上限。

3.2 核心参数怎么定:Nstd、NE 和筛选次数

这套算法里最常调的两个参数是噪声标准差Nstd和噪声添加次数NE。Nstd一般取原始信号标准差的 0.01 到 0.4 倍,取小了白噪声不足以覆盖信号的高频细节,模态混叠该出现还是出现;取大了则前几层 IMF 被噪声主导,分解结果失真。我一般先算std(x),再按 0.1 到 0.2 倍的标准差开始试。NE决定平均次数,100 到 200 之间通常能获得稳定的分解结果,超过 300 后计算时间明显拉长,而精度提升已经微乎其微。

筛选迭代次数(MaxIter)控制 EMD 内部循环的收敛。设成 0 时算法自己判断收敛条件,但如果你发现分解出的 IMF 高频段出现明显振荡或锯齿,多半是筛分次数过少、包络均值没充分收敛;反过来迭代次数过多会让 IMF 变成纯正弦形态,失去物理意义。我在实测中一般把最大迭代次数限制在 100 到 500 之间,视信号复杂度调整。

下面给一份参数速查表,方便你快速起手:

参数作用建议范围调参信号
Nstd噪声标准差0.01~0.2 倍信号标准差分解结果高频 IMF 明显混叠时增大
NE噪声添加次数100~200结果不稳定、多次运行不一致时增大
MaxIter最大筛选次数100~500高频 IMF 锯齿状时增大,过度平滑时减小
IMF 上限最大输出分量数8~12分解不充分时增大,计算量大时减小

3.3 怎么判断分解结果好不好:不看图就看这三个数

跑出 IMF 后别急着存图,先用数值指标判断分解质量。最实用的三个指标是相关系数、正交性指标和能量占比。相关系数衡量每个 IMF 与原始信号的关联程度,大于 0.3 说明该分量携带了有效信息,低于 0.1 的基本是噪声分量。正交性指标衡量各个 IMF 两两之间的独立性,理想值接近零,超过 0.1 说明分解不彻底或存在混叠。能量占比则看前几个 IMF 是否集中了信号的主要能量。

% 计算 IMF 与原始信号的相关系数 for k = 1:size(imfs,1) rho(k) = corr(x(:), imfs(k,:)'); end % 找出相关系数大于 0.3 的有效 IMF valid_idx = find(rho > 0.3); fprintf('有效 IMF 数量:%d\n', length(valid_idx));

这段代码的corr函数计算的是皮尔逊相关系数,x(:)把信号展开成列向量是为了保证维度一致。有效 IMF 数量可以作为后续特征提取的依据——把相关系数低的 IMF 直接丢弃,只保留有效分量做希尔伯特边际谱或包络谱分析,能省下不少无用功。

4. 避坑指南:EMD 系列算法最常见的五个翻车现场

4.1 翻车现象:同一段信号每次分解出的 IMF 数量都不一样

原因:EEMD 和 CEEMDAN 涉及随机噪声的添加,每次运行生成的噪声序列不同。如果你的NE不够大(低于 50),平均效应不充分,分解结果就会带明显的随机性。

解决:把NE提升到 100 以上;设置随机数种子,即运行前加一句rng(42),保证实验可复现。我在写论文时对每一个实验都会固定种子,这是最基本的职业习惯。

4.2 翻车现象:分解出的第一个 IMF 全是高频噪声,看不到有效成分

原因:Nstd设置过大。白噪声幅值超过信号真实高频成分时,第一层 IMF 基本被噪声淹没。

解决:降低Nstd到信号标准差的 0.05 倍以下,同时检查原始信号是不是本身信噪比太低。如果信号本身就脏,先做带通滤波再做分解。滤波范围参考频谱图上的有效频带,别一刀切。

4.3 翻车现象:运行 MATLAB 代码报错 “Undefined function or variable”

原因:没有把代码包里的所有函数放在 MATLAB 搜索路径下,或者当前工作目录被切换到了别的文件夹。这套代码是函数式结构,主脚本与子函数之间有相对调用关系,路径不对必然报错。

解决:解压整个目录,右键文件夹选择“添加到路径(Add to Path)”,选择“包含子文件夹”。在脚本开头加两句:

% 强制把代码目录加入路径 fileDir = fileparts(mfilename('fullpath')); addpath(genpath(fileDir));

fileparts提取当前脚本所在路径,genpath递归添加所有子目录,这样换电脑、换目录跑都不会断路径。

4.4 翻车现象:分解速度慢到怀疑人生

原因:CEEMDAN 每层迭代都要做样条插值和包络均值计算,复杂度是普通 EMD 的 5 到 10 倍。如果信号长度超过 10 万点,且NE设置过高,跑一次可能要十几分钟。

解决:先对信号做降采样或分段处理,把长度控制在 5000 点到 10000 点;NE从 200 降到 100,优先保证结果的趋势正确,再决定要不要加大精度。处理长信号时我会先做滑动窗口分析,每段 2048 点,而不是一次性喂进整段数据。

4.5 翻车现象:分解出的 IMF 在首尾段明显偏大或偏小

原因:端点效应作祟。MATLAB 的实现中有些版本用了镜像延拓,有些用的多项式延拓,对信号两端变化剧烈的场景,延拓误差会直接反映在包络均值上。

解决:先用 MATLAB 自带的smoothdata或者 detrend 函数去除趋势项和直流分量再分解;分解完截掉首尾 5% 的采样点再做后续分析。如果你做的是故障诊断,注意只分析稳态段,不要包含升降速过程的数据。

5. 把 IMF 用起来:从分解结果到边际谱和故障特征

5.1 用希尔伯特变换求瞬时频率与边际谱

分解只是第一步,大家真正关注的是每个 IMF 里包含的瞬时频率和能量分布。对每个 IMF 做希尔伯特变换,求出瞬时幅值和瞬时频率,再把所有 IMF 的瞬时频谱累积到一张图上,就是希尔伯特谱。把希尔伯特谱对时间积分,就得到希尔伯特边际谱——它和 FFT 频谱类似,但更适合非平稳信号。

% 对第 3 个 IMF 做希尔伯特变换 h = hilbert(imfs(3,:)); % 解析信号 inst_amp = abs(h); % 瞬时包络 inst_freq = diff(unwrap(angle(h))) * fs / (2*pi); % 注意 diff 后长度减 1,画图时要去掉末尾一个点 figure; plot(t(1:end-1), inst_freq); xlabel('时间 (s)'); ylabel('瞬时频率 (Hz)');

hilbert返回的是复数解析信号,实部是原始信号,虚部是它的希尔伯特变换,所以abs(h)是包络,angle(h)的导数除以 2π 再乘以采样率就是瞬时频率。注意inst_freq的长度比h少 1,因为差分运算会损失一个点,这是新手容易忽略的细节。

5.2 用 IMF 做故障特征提取:包络谱分析实例

在滚动轴承故障诊断中,一个经典做法是对包含共振频带的高频 IMF 做包络谱。流程是:先用 CEEMDAN 分解振动信号,找到相关系数最高的那个 IMF,再对它做希尔伯特变换提取包络,最后对包络做 FFT 得到包络谱。

% 轴承故障特征频率示例:内圈故障 bpfi = 156.6; % 根据轴承参数计算的理论故障频率 % 找出相关系数最高的 IMF [~, best] = max(rho); envelope = abs(hilbert(imfs(best,:))); env_fft = abs(fft(envelope)); f_axis = (0:length(env_fft)-1) * fs / length(env_fft); % 查看特征频率附近是否有明显峰值 [~, idx] = min(abs(f_axis - bpfi)); fprintf('理论故障频率 %.1f Hz 处的幅值 %.3f\n', ... f_axis(idx), env_fft(idx));

这段代码里bpfi是通过轴承的节径、滚动体数、接触角等参数计算出来的理论值,实际工作中你会在包络谱上看到它以及它的倍频。如果bpfi处幅值明显高于周围噪声底,就可以初步判断存在内圈故障。这里的关键点是选对 IMF——选错了分析对象,包络谱上的特征峰会淹没在干扰里。

5.3 验证分解质量的一个实操技巧:用已知信号做自检

我每次拿到新的分解工具,都会先做一次自检:构造一段已知成分的合成信号,比如三个正弦波叠加一个衰减冲击,让每个成分的频率、幅值都已知,然后分解,看算法能不能把各个成分分到对应的 IMF 里。这个习惯帮我避免了不少把算法问题误判成信号问题的弯路。对应到这套代码,你可以把第二节 demo 里的信号改成自己构造的混合信号,用分解结果和真实成分的相关系数来判断算法有没有忠实还原。

从那以后我每次做信号分解,都会把“固定随机种子、设置已知参数、先自检再分析”这三步走完。步骤琐碎,但能在正式实验前筛掉一大半低级错误。代码包里已经有合成信号和实测信号两套测试用例,跑通配置后,替换成你自己的数据就能直接开工,希望帮到你。

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

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

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

立即咨询