第三章 信号预处理技术:信号去噪方法、趋势项去除、信号重采样与截断
各位工程师朋友,大家好。欢迎来到《风机轴承故障诊断实战手册》的第三章。
上一章我们聊了传感器怎么选、怎么装,拿到了原始振动信号。但说实话,现场采集到的信号,那叫一个「脏」。有环境噪声、有齿轮箱的干扰、有安装面的共振,甚至还有电缆的工频干扰。你要是直接把这种信号拿去分析,结果基本没法看。
所以,信号预处理这一步,我把它叫做「洗菜」。菜不洗干净,再好的厨子也做不出好菜。今天我们就来聊聊这三道「清洗工序」:去噪、去趋势、重采样与截断。
3.1 信号去噪方法
现场采集的振动信号,噪声来源五花八门。我见过最夸张的一次,是在某风电场的机舱里,传感器信号里居然混进了对讲机的电磁干扰。你想想看,那频谱图上一堆莫名其妙的尖峰,差点把我带沟里去。
去噪的方法很多,但做轴承故障诊断,我个人最常用的是两种:小波去噪和EMD去噪。下面一个一个说。
3.1.1 小波阈值去噪
小波去噪的原理,说白了就是:信号在小波域里,有用成分的能量集中在少数大系数上,而噪声均匀分布在小系数上。我们设一个阈值,把小于阈值的系数干掉或压缩,再重构回来,噪声就没了。
这里有个关键点——阈值怎么选?
- 硬阈值:小于阈值的直接置零。好处是信号边缘保留得好,坏处是容易产生伪吉布斯震荡,信号看起来「毛刺刺」的。
- 软阈值:小于阈值的置零,大于阈值的向零收缩。平滑性好,但会损失一点幅值精度。
- 自适应阈值:根据每层小波系数的统计特性动态调整。我一般用这个,效果最稳。
重要经验:小波基的选择直接影响去噪效果。做轴承故障诊断,我推荐 db4、sym8 或 coif5。分解层数一般设 3~5 层,太多会把有用信号也滤掉。
下面给一段 Python 代码,用的是 PyWavelets 库。这段代码我在好几个项目里直接复用,改改参数就能用。
import pywt
import numpy as np
def wavelet_denoise(signal, wavelet='db4', level=4, mode='soft'):
"""
小波阈值去噪
signal : 原始信号 (1D array)
wavelet: 小波基名称
level : 分解层数
mode : 'soft' 或 'hard'
"""
# 小波分解
coeffs = pywt.wavedec(signal, wavelet, level=level)
# 计算阈值(通用阈值公式)
sigma = np.median(np.abs(coeffs[-1])) / 0.6745
threshold = sigma * np.sqrt(2 * np.log(len(signal)))
# 对每一层细节系数进行阈值处理(近似系数保留)
coeffs_thresh = [coeffs[0]] # 近似系数不动
for i in range(1, len(coeffs)):
coeffs_thresh.append(pywt.threshold(coeffs[i], threshold, mode=mode))
# 重构信号
denoised = pywt.waverec(coeffs_thresh, wavelet)
return denoised
# 使用示例
# clean_signal = wavelet_denoise(raw_signal, wavelet='sym8', level=4, mode='soft')
我的小技巧:如果你不确定小波基选哪个,可以先用 sym8 试试。我在多个风机轴承数据上对比过,sym8 在去噪和保留冲击特征之间平衡得最好。
3.1.2 EMD 去噪
EMD(经验模态分解)是另一种思路。它不需要预设基函数,完全靠数据驱动,把信号分解成若干个 IMF(本征模态函数)。
噪声通常集中在高频 IMF 分量里。我们只需要把前几个高频 IMF 扔掉,用剩下的 IMF 重构信号就行。
但这里有个坑——模态混叠。有时候噪声和有用信号会混在同一个 IMF 里,你一刀切掉,有用信息也跟着没了。我曾经在一个齿轮箱轴承的案例里吃过这个亏,去噪后故障特征反而变弱了。
怎么解决?我建议用 EEMD(集合经验模态分解) 或 CEEMDAN,它们通过添加白噪声来抑制模态混叠,效果要好得多。
from PyEMD import EEMD
import numpy as np
def eemd_denoise(signal, max_imf=8, noise_width=0.2, trials=100):
"""
EEMD 去噪
signal : 原始信号
max_imf : 保留的最大 IMF 阶数(前几阶高频去掉)
noise_width: 添加白噪声的幅值(标准差比例)
trials : 集成次数
"""
eemd = EEMD(trials=trials, noise_width=noise_width)
imfs = eemd(signal)
# 去掉前 max_imf 个高频 IMF,其余重构
denoised = np.sum(imfs[max_imf:], axis=0)
return denoised
# 使用示例
# clean_signal = eemd_denoise(raw_signal, max_imf=3, noise_width=0.2, trials=100)
注意:EEMD 的计算量比较大,100 次集成在长信号上可能要跑几十秒。如果现场需要实时处理,建议用小波去噪。离线分析用 EEMD 没问题。
3.2 趋势项去除
什么是趋势项?就是信号中缓慢变化的、低频的漂移成分。比如传感器温漂、安装基座的缓慢变形、甚至风机的偏航动作,都会在振动信号里叠加一个缓慢变化的趋势。
这个趋势如果不去掉,在频谱分析时会在低频段产生很大的能量,把轴承故障特征频率给淹没了。我见过有人拿着带趋势项的信号去做包络谱,结果低频段一片红,根本找不到故障频率在哪。
去除趋势项,我常用的方法有两种:
3.2.1 多项式拟合去除
思路很简单:用多项式拟合出信号的趋势,然后用原始信号减去这个趋势。
- 一次多项式:去除线性趋势。适合传感器温漂这类情况。
- 二次或三次多项式:去除非线性趋势。适合更复杂的漂移。
阶次怎么选?我的经验是:先试一次,如果频谱低频还有明显隆起,再试二次。一般不超过三次,阶次太高会把有用信号也拟合进去。
import numpy as np
def remove_trend(signal, order=1):
"""
多项式拟合去除趋势项
signal : 原始信号
order : 多项式阶数
"""
t = np.arange(len(signal))
coeffs = np.polyfit(t, signal, order)
trend = np.polyval(coeffs, t)
detrended = signal - trend
return detrended
# 使用示例
# clean_signal = remove_trend(raw_signal, order=1)
3.2.2 高通滤波去除
另一种思路是用高通滤波器,直接把低频趋势滤掉。截止频率一般设在 0.5 Hz ~ 2 Hz 之间。
这里要注意:一定要用零相位滤波(比如 scipy 的 filtfilt),否则滤波后的信号会有相位偏移,影响后续的时域分析。
from scipy.signal import butter, filtfilt
def highpass_filter(signal, cutoff=1.0, fs=2560, order=4):
"""
高通滤波去除趋势项
signal : 原始信号
cutoff : 截止频率 (Hz)
fs : 采样频率 (Hz)
order : 滤波器阶数
"""
nyquist = 0.5 * fs
normal_cutoff = cutoff / nyquist
b, a = butter(order, normal_cutoff, btype='high', analog=False)
filtered = filtfilt(b, a, signal)
return filtered
# 使用示例
# clean_signal = highpass_filter(raw_signal, cutoff=1.0, fs=2560)
我的建议:如果趋势项比较平滑(比如温漂),用多项式拟合更干净。如果趋势项里还混着一些低频干扰(比如 0.5 Hz 的摆动),用高通滤波更省事。我一般两个都试一下,看哪个效果更好。
3.3 信号重采样与截断
3.3.1 重采样
重采样,说白了就是改变信号的采样率。什么时候需要重采样?
- 降采样:原始采样率太高(比如 25.6 kHz),数据量太大,分析起来慢。降到 2560 Hz 或 5120 Hz 就够用。
- 升采样:需要提高时间分辨率,或者要匹配另一个信号的采样率。
- 角域重采样:在阶次分析中,把等时间间隔的信号变成等角度间隔的信号。这个我们后面章节会细讲。
降采样时有个大坑——抗混叠滤波。直接降采样会产生频率混叠,高频成分会折叠到低频段,让你误以为有故障特征。所以降采样前必须先做低通滤波,截止频率设为新采样率的一半。
from scipy.signal import resample, decimate
# 方法1:使用 scipy.signal.resample(基于 FFT,适合整数倍重采样)
def resample_signal(signal, orig_fs, target_fs):
num_samples = int(len(signal) * target_fs / orig_fs)
resampled = resample(signal, num_samples)
return resampled
# 方法2:使用 scipy.signal.decimate(自带抗混叠滤波,适合降采样)
# new_signal = decimate(raw_signal, q=10) # q 为降采样倍数
重要提醒:重采样后,信号的频率分辨率会变化。比如原始信号 1 秒有 2560 个点,降采样到 1280 Hz 后,1 秒只有 1280 个点,频率分辨率从 1 Hz 变成 0.5 Hz?不对,实际上是变差了。你想想看,采样率降低,相同时间内的点数变少,频率分辨率 = 采样率 / 点数,所以分辨率不变?嗯,这里容易搞混,建议你实际算一下。
3.3.2 信号截断
信号截断,就是从一个长信号里切出一段来用。为什么要截断?
- 风机轴承信号通常连续采集几十分钟甚至几小时,我们只需要分析其中一段稳态数据。
- FFT 分析要求输入信号长度是 2 的幂次(比如 1024、2048、4096),方便快速计算。
- 有时候需要截取包含故障冲击的特定时间段。
截断时要注意频谱泄漏。直接截断相当于加了一个矩形窗,会在频谱上产生旁瓣,把能量泄漏到相邻频率上。解决办法是加窗函数,比如汉宁窗、海明窗。
import numpy as np
def truncate_and_window(signal, start, length, window='hann'):
"""
信号截断并加窗
signal : 原始信号
start : 起始点
length : 截取长度
window : 窗函数类型
"""
segment = signal[start:start+length]
if window == 'hann':
win = np.hanning(length)
elif window == 'hamming':
win = np.hamming(length)
elif window == 'blackman':
win = np.blackman(length)
else:
win = np.ones(length) # 矩形窗
return segment * win
# 使用示例
# segment = truncate_and_window(raw_signal, start=10000, length=4096, window='hann')
核心经验:做轴承故障诊断时,我习惯截取 4096 或 8192 个点。这个长度在频率分辨率和计算速度之间比较平衡。如果故障特征频率很低(比如 10 Hz 以下),可能需要更长的截断长度来提高频率分辨率。
本章小结
好了,信号预处理的三板斧讲完了。我们来捋一捋:
- 去噪:小波去噪适合大多数场景,速度快、效果好;EMD 去噪适合非平稳信号,但计算量大。
- 去趋势:多项式拟合和高通滤波都能用,看趋势项的特点来选。
- 重采样与截断:降采样别忘了抗混叠滤波,截断记得加窗。
这些预处理步骤,我建议你每次拿到新数据都走一遍。别嫌麻烦,预处理做得好,后面的分析事半功倍。我曾经在一个项目里,光是在去噪参数上调试了两天,最后故障特征清晰得像教科书一样。
下一章我们会进入真正的「诊断」环节——时域分析。到时候你会看到,预处理做没做好,在时域波形上一眼就能看出来。