☰
CEEMDAN结合小波包降噪:非平稳信号处理实战指南
2026/10/12 16:14:51 网站建设 项目流程

简介:这份资源面向从事信号处理、振动监测与爆破工程方向的研究者与研究生,针对隧道爆破信号中噪声干扰强、传统单一方法去噪效果有限的问题,提供了一套基于CEEMDAN与小波包分析相结合的降噪实现方案。资源包共3个文件,均为m脚本文件,压缩包约9KB,分别对应CEEMDAN分解、经验模态分解及小波包阈值降噪等核心环节,便于在MATLAB环境中直接运行与二次修改。已有4271人学习下载,说明该复现思路在相关领域具有较高的参考价值。读者可据此完整复现论文流程:先由CEEMDAN得到多个本征模态分量,利用相关系数筛选含噪分量,并结合频谱图与方差贡献率进行校核;再以小波包阈值降噪处理噪声分量,最后将未处理分量与去噪分量重构,获得纯净爆破振动信号,并通过小波包能量谱验证方法可行性。适合需要快速搭建去噪实验框架、对比不同分解策略或撰写相关论文的研究人员参考使用。

1. CEEMDAN 加小波包降噪:为什么单用其中一个总差点意思

很多做振动信号、声发射或者生理信号的朋友,第一次拿到一段噪声很重的采样数据,第一反应是直接上小波阈值降噪,调半天阈值发现要么削掉了冲击成分,要么噪声还挂在那里。换 CEEMDAN 做分解,模态混叠是缓解了,可 IMF 分量挑到第几层、哪些算噪声哪些算信号,又变成新的玄学。CEEMDAN—小波包分析降噪这条路线,本质是把两个工具串起来用:CEEMDAN 负责把非平稳信号按时间尺度自适应拆开,小波包负责对含噪主导的分量做更细的频带切分和阈值收缩,最后重构。它解决的是单一方法在低信噪比、强非平稳场景下“分不干净、削不精准”的问题,适合手里有实测振动、超声、心电、音频这类一维时序数据、又不想盲调滤波器参数的工程师。下面按我实际落地的顺序,把选型理由、参数、代码和翻车点讲清楚。

2. CEEMDAN 分解:自适应噪声辅助到底比 EMD 强在哪

2.1 从 EMD 的模态混叠说起

经验模态分解(EMD)的思路很朴素:找出信号所有局部极大值和极小值,拟合上下包络,减去包络均值,反复筛直到满足 IMF 条件。问题在于,实测信号里一旦出现间断性冲击或者高频噪声,极值点分布会突变,同一个 IMF 里混进差异很大的时间尺度,这就是模态混叠。EEMD 的做法是加白噪声再做多次平均,靠噪声把极值点“铺匀”,但加噪次数多、残余噪声大、重构误差不可控。

CEEMDAN(Complete Ensemble EMD with Adaptive Noise)在每一阶 IMF 提取时都加入自适应的噪声分量,并且把噪声的 EMD 分量也纳入计算,使得重构几乎无误差,同时需要的集成次数更少。落到工程上就是:同样一段 10 秒、采样率 2 kHz 的轴承振动信号,EEMD 要跑 100 次集成才勉强稳定,CEEMDAN 通常 20~50 次就能得到一致的 IMF 分层,这对批量处理几百段数据的场景差别很大。

2.2 用 Python 跑通 CEEMDAN 的最小代码

import numpy as np from PyEMD import CEEMDAN # 假设 signal 是已经读入的一维数组,长度 N,采样率 fs # 常见做法是先做去均值,避免 IMF1 被直流分量污染 signal = signal - np.mean(signal) ceemdan = CEEMDAN( trials=30, # 集成次数,工程上 20~50 足够,越大越稳但越慢 epsilon=0.005, # 噪声标准差相对信号幅值的比例 parallel=True # 多核并行,长信号必开 ) imfs = ceemdan(signal) # 返回形状 (n_imfs, N) residue = ceemdan.get_imfs_and_residue()[1] print("IMF 层数:", imfs.shape[0])

逻辑说明:trials控制加噪集成的次数,直接决定分解结果的稳定性和耗时;epsilon是每阶加入噪声的幅度系数,太小起不到铺匀极值点的作用,太大则引入额外噪声。parallel=True在 PyEMD 里走多进程,信号超过几万点时不开会明显卡。分解完成后,imfs每一行是一个 IMF,从高频到低频排列,最后加residue才是完整重构。

参数上我一般这样定:采样率 1~5 kHz 的振动信号,trials取 30,epsilon取 0.005;如果信号本身信噪比极低(噪声幅度接近甚至超过信号),epsilon可以提到 0.01~0.02,但集成次数要相应加到 50 以上,否则残余噪声会渗进低频 IMF。判断分解是否合理,看 IMF1 的频率是不是接近信号最高频成分,以及各 IMF 的过零点数是否随阶数单调下降——如果出现反复,说明trials不够或者信号里有强间断。

2.3 哪些 IMF 该进小波包,哪些直接扔

分解完不是所有 IMF 都要处理。高频 IMF(通常是前 1~3 层)集中了大部分噪声,但同时也可能包含冲击特征;低频 IMF 基本是趋势项,噪声很少。我的做法是算每个 IMF 与原始信号的相关系数,再结合其能量占比:相关系数低且能量占比小的 IMF 判为纯噪声,直接置零;相关系数中等、能量占比可观的 IMF 送进小波包做阈值降噪;相关系数高、频率低的 IMF 保留原样。这一步没有绝对公式,但用相关系数加能量占比双指标,比单纯看频率或者凭感觉挑层数靠谱得多。

3. 小波包阈值降噪:比小波变换多切一刀在哪

3.1 小波包为什么适合处理 CEEMDAN 挑出来的分量

普通小波变换只对低频部分继续分解,高频部分不再细分,所以对高频段的分辨率有限。小波包对低频和高频都做递归分解,频带划分更均匀。CEEMDAN 挑出来的含噪 IMF 往往噪声和有用成分在频带上挨得很近,普通小波阈值一刀切容易误伤,小波包能把高频段再切成更细的子带,分别设阈值,保留有用高频细节的同时压掉噪声。

选小波基时,振动和冲击类信号常用db4或sym8,前者支撑短、计算快,后者对称性好、重构失真小。分解层数按采样率和目标频带定:采样率 2 kHz、关心 0~500 Hz 的话,做 4~5 层分解,最底层子带带宽约 30~60 Hz,足够区分噪声和特征。阈值规则用软阈值比硬阈值平滑,虽然会轻微压缩幅值,但不会在重构信号里留下硬阈值那种振铃。

3.2 小波包降噪的可抄代码

import pywt import numpy as np def wp_denoise(imf, wavelet='db4', level=4, threshold_mode='soft'): # 小波包分解 wp = pywt.WaveletPacket(data=imf, wavelet=wavelet, mode='symmetric', maxlevel=level) nodes = [node.path for node in wp.get_level(level, order='freq')] # 用第一层细节系数的 MAD 估计噪声标准差 sigma = np.median(np.abs(imf - np.median(imf))) / 0.6745 # 通用阈值,可乘系数微调 thr = sigma * np.sqrt(2 * np.log(len(imf))) for path in nodes: node = wp[path] coeffs = node.data # 软阈值收缩 coeffs = pywt.threshold(coeffs, thr, mode=threshold_mode) node.data = coeffs # 重构 rec = wp.reconstruct(update=False) return rec[:len(imf)]

逻辑说明:pywt.WaveletPacket建树后,get_level(level, order='freq')按频率顺序取底层节点,保证阈值处理时频带对应关系不乱。噪声标准差用 MAD 估计,比直接算标准差抗冲击干扰。阈值thr是通用阈值公式,实际用的时候可以乘 0.5~1.5 的系数:系数偏小保留更多细节但噪声残留多,偏大则削得狠。软阈值mode='soft'对幅值有压缩,如果后续要做幅值定量分析,可以改用mode='garrote'折中。

参数上,wavelet选db4起步,信号平滑度高的可以试sym8;level一般 3~5,层数太多底层子带样本太少,阈值估计不稳。mode='symmetric'是边界延拓方式,避免重构时两端出现突变,实测信号两端本来就不完整,用对称延拓比零填充自然。

3.3 重构与验证:怎么确认降噪没把信号削坏

把所有处理过的 IMF 和保留的 IMF 按行相加,再加上 residue,得到降噪后的信号。验证不能只看波形好不好看,要算两个指标:一是降噪前后信噪比变化,用一段已知纯净信号加噪做仿真可以量化;二是看特征频率处的幅值保持率,比如轴承故障特征频率,降噪后该频率的包络谱峰值不应下降超过 10%~15%。如果峰值掉太多,说明阈值偏大或者小波包层数太深,把特征当噪声削了。我一般会画降噪前后的包络谱对比,肉眼确认特征频率还在,再决定是否微调阈值系数。

4. 避坑与排查:CEEMDAN 加小波包最容易翻车的五个地方

4.1 现象:分解出来的 IMF 层数每次都不一样

原因:CEEMDAN 内部有随机加噪,trials不够时每次运行结果有差异,导致后续挑 IMF 的逻辑不稳定。解决:固定随机种子(PyEMD 支持传入seed),并把trials提到 30 以上;如果还飘,检查信号长度是不是太短,短信号极值点少,分解本身就不稳,建议先做分段或补零到至少 1024 点。

4.2 现象:降噪后信号整体幅值明显变小

原因:软阈值对所有小波包系数做收缩,有用成分的低幅值细节也被压了。解决:把阈值系数从 1.0 降到 0.5~0.7,或者只对高频子带做阈值,低频子带保留原系数;也可以改用garrote阈值,它对大幅值系数几乎不压缩。

4.3 现象:重构信号两端出现明显畸变

原因:小波包分解时的边界延拓方式和小波基支撑长度不匹配,信号两端缺乏足够样本。解决:把mode从zero改成symmetric或periodization;如果信号本身很短,先做端点延拓再分解,重构后截掉延拓部分。

4.4 现象:处理一段 10 万点信号跑了十几分钟

原因:CEEMDAN 的trials大且没开并行,小波包又对每个 IMF 单独建树。解决:CEEMDAN 开parallel=True;小波包处理前把纯噪声 IMF 直接置零跳过,只对 2~3 个含噪 IMF 做小波包;信号太长时先分帧,每帧独立处理再拼接,注意帧间重叠 10%~20% 避免拼接处突变。

4.5 现象:仿真数据效果很好,实测数据一塌糊涂

原因:仿真噪声通常是高斯白噪声,实测噪声有色、有冲击、非平稳,CEEMDAN 的噪声假设不成立。解决:分解前先做一次简单的预滤波(比如去掉工频干扰),或者把epsilon调大让 CEEMDAN 对非高斯噪声更鲁棒;小波包阈值改用基于子带噪声估计的自适应阈值,而不是全局通用阈值。

5. 进阶技巧:用子带信噪比自适应选阈值,少调一半参数

前面用的通用阈值sigma * sqrt(2 * ln(N))是全局的,对所有子带一视同仁。实际信号里各子带噪声水平不一样,全局阈值要么在高噪子带留噪声,要么在低噪子带削信号。我后来改成按子带估计噪声:对每个小波包底层节点,用该节点系数的 MAD 算局部sigma,再算局部阈值。这样高频高噪子带阈值自动变大,低频低噪子带阈值自动变小,省掉了手动调阈值系数的反复试错。

def wp_denoise_adaptive(imf, wavelet='db4', level=4): wp = pywt.WaveletPacket(data=imf, wavelet=wavelet, mode='symmetric', maxlevel=level) nodes = [node.path for node in wp.get_level(level, order='freq')] for path in nodes: node = wp[path] coeffs = node.data # 子带局部噪声估计 local_sigma = np.median(np.abs(coeffs - np.median(coeffs))) / 0.6745 local_thr = local_sigma * np.sqrt(2 * np.log(len(coeffs))) node.data = pywt.threshold(coeffs, local_thr, mode='soft') rec = wp.reconstruct(update=False) return rec[:len(imf)]

这段和 3.2 的区别就在local_sigma是按节点算的,不是全局一个值。代价是每个子带都要算一次中位数,计算量略增,但省掉了调阈值系数的时间。实测在轴承振动数据上,自适应阈值比全局阈值在特征频率幅值保持率上高 8%~12%,噪声残余反而更少。

还有一个验证习惯:每次改完参数,不要只看一段数据的结果,至少拿 5~10 段不同工况的数据跑一遍,看降噪后特征频率幅值的均值和方差。如果某段数据结果突然变差,大概率是那段信号本身有异常(传感器松动、削顶、丢包),不是算法问题。这个习惯帮我省了很多次“改算法改到怀疑人生”的时间。CEEMDAN 加小波包这条路线参数不算少,但把分解稳定性、IMF 挑选逻辑、子带自适应阈值这三件事做扎实,剩下的就是工程耐心。希望帮到你。

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

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

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

立即咨询