4. 信号预处理技术:去趋势项、异常值剔除、重采样、数字滤波、窗函数选择
各位工程师朋友,大家好。今天我们来聊聊信号预处理。说实话,我在风机故障诊断这行干了十几年,见过太多因为预处理没做好,导致后续分析全盘皆输的案例。你想想看,原始信号就像一块璞玉,预处理就是打磨的过程。打磨不好,再好的诊断算法也白搭。
这一节,我把自己踩过的坑、积累的经验,一股脑儿都倒出来。咱们一个一个说。
4.1 为什么要做信号预处理?
风机运行环境太恶劣了。传感器采集到的信号,里面掺杂着各种乱七八糟的东西:
- 趋势项:传感器零漂、温度变化引起的缓慢变化
- 异常值:电磁干扰、传感器瞬间失灵产生的毛刺
- 噪声:风噪、机械噪声、电气噪声
- 无关频率成分:比如工频干扰、其他设备的振动
这些干扰不处理掉,你做的频谱分析、特征提取,全是错的。我有个血的教训——曾经有一次,一个风场报故障,我远程看频谱,发现有个明显的边频带,以为是齿轮箱故障。结果到现场一查,是传感器底座松了。你说冤不冤?
核心原则:预处理的目标是保留故障特征,剔除干扰成分。不是越干净越好,过度处理会丢失有用信息。
4.2 去趋势项
趋势项就是信号中缓慢变化的成分。比如传感器随着温度升高,输出缓慢漂移。这个漂移在时域上看着不明显,但一做频谱分析,低频段会多出一大坨能量,把真正的故障特征都淹没了。
怎么做? 我常用的方法是最小二乘法拟合多项式趋势,然后从原始信号中减去。
import numpy as np
import matplotlib.pyplot as plt
def remove_trend(signal, order=1):
"""
去除多项式趋势项
order: 多项式阶数,一般1阶(线性)或2阶就够了
"""
t = np.arange(len(signal))
# 拟合多项式系数
coeffs = np.polyfit(t, signal, order)
# 构造趋势项
trend = np.polyval(coeffs, t)
# 去趋势
detrended = signal - trend
return detrended, trend
# 示例:模拟带线性趋势的振动信号
t = np.linspace(0, 10, 1000)
vibration = np.sin(2 * np.pi * 5 * t) + 0.05 * t # 5Hz信号 + 线性趋势
clean, trend = remove_trend(vibration, order=1)
我的经验:对于风机振动信号,一般用1阶线性去趋势就够了。如果信号有明显弯曲,可以试试2阶。但千万别用高阶,否则会把真实的低频故障特征也给拟合掉。
4.3 异常值剔除
异常值,说白了就是信号里的「野点」。比如一个突然的尖峰,幅值比正常值大好几倍。这种点不处理,做FFT时会产生虚假的频率成分,误导诊断。
我常用的方法:
- 3σ准则:计算信号的均值和标准差,超出均值±3倍标准差的点视为异常值
- 中值滤波:用滑动窗口的中值代替中心点,对孤立尖峰特别有效
- 局部离群因子(LOF):更复杂,但能处理连续异常段
def remove_outliers_3sigma(signal, threshold=3):
mean = np.mean(signal)
std = np.std(signal)
# 标记异常值位置
outliers = np.abs(signal - mean) > threshold * std
# 用前后有效值的均值替换
cleaned = signal.copy()
for i in np.where(outliers)[0]:
# 简单处理:用相邻点均值
left = max(0, i-1)
right = min(len(signal)-1, i+1)
cleaned[i] = (signal[left] + signal[right]) / 2
return cleaned, outliers
注意:异常值剔除要谨慎。我曾经遇到一个案例,把真正的冲击故障特征当成异常值给剔除了,导致漏报。建议先做时域波形观察,确认是干扰还是故障特征。
4.4 重采样
重采样就是把信号从一个采样率转换到另一个采样率。为什么要做?
- 统一采样率:不同传感器、不同采集系统,采样率可能不一样
- 降采样:数据量太大,计算资源有限,可以降低采样率
- 升采样:提高时间分辨率,便于分析
核心问题:重采样会引入混叠误差。降采样前必须做抗混叠滤波。
from scipy import signal
def resample_signal(signal, orig_fs, target_fs):
"""
重采样,自动做抗混叠滤波
"""
# 计算重采样比例
num = target_fs
den = orig_fs
# 使用scipy的resample函数
resampled = signal.resample(signal, int(len(signal) * num / den))
return resampled
我的习惯:对于风机振动信号,我一般保留1024Hz或2048Hz的采样率。太低会丢失高频故障特征,太高数据量太大。你想想看,一个风场几十台风机,每台十几个测点,数据量是惊人的。
4.5 数字滤波
滤波是预处理的重头戏。说白了,就是让特定频率的信号通过,把其他频率的滤掉。
4.5.1 滤波器类型
| 类型 | 作用 | 典型应用 |
|---|---|---|
| 低通 | 保留低频,滤除高频 | 去除噪声,保留轴承故障特征 |
| 高通 | 保留高频,滤除低频 | 去除趋势项,保留冲击特征 |
| 带通 | 保留特定频带 | 提取齿轮啮合频率附近的特征 |
| 带阻 | 滤除特定频带 | 去除工频干扰(50Hz/60Hz) |
4.5.2 滤波器设计要点
我常用的滤波器:巴特沃斯滤波器。它的通带最平坦,不会引入额外的波动。切比雪夫滤波器虽然过渡带更陡,但通带有纹波,对振动信号不太友好。
from scipy.signal import butter, filtfilt
def butter_lowpass(cutoff, fs, order=4):
"""
设计巴特沃斯低通滤波器
cutoff: 截止频率 (Hz)
fs: 采样率 (Hz)
"""
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = butter(order, normal_cutoff, btype='low', analog=False)
return b, a
def apply_filter(data, b, a):
"""
应用滤波器,使用filtfilt实现零相位延迟
"""
filtered = filtfilt(b, a, data)
return filtered
关键点:一定要用filtfilt而不是lfilter。filtfilt是零相位滤波,不会引入相位偏移。对于振动信号分析,相位信息很重要,尤其是做轴心轨迹分析时。
4.6 窗函数选择
做FFT时,信号是有限长的。这相当于给无限长的信号加了一个矩形窗,会导致频谱泄漏。窗函数的作用就是减少这种泄漏。
4.6.1 常见窗函数对比
| 窗函数 | 主瓣宽度 | 旁瓣衰减 | 适用场景 |
|---|---|---|---|
| 矩形窗 | 最窄 | -13dB | 瞬态信号、冲击信号 |
| 汉宁窗 | 较宽 | -31dB | 连续振动信号(最常用) |
| 海明窗 | 较宽 | -41dB | 频率分辨率要求不高,旁瓣抑制要求高 |
| 布莱克曼窗 | 最宽 | -57dB | 需要极低旁瓣,可接受频率分辨率损失 |
4.6.2 我的选择经验
我个人习惯:
- 常规振动分析:汉宁窗。它兼顾了频率分辨率和旁瓣抑制,是振动分析的标准配置
- 冲击信号分析:矩形窗。冲击信号本身就很短,用其他窗会削弱冲击特征
- 需要精确幅值:海明窗。它的旁瓣衰减更大,能减少相邻频率成分的干扰
- 极低旁瓣要求:布莱克曼窗。但频率分辨率会变差,慎用
import numpy as np
def apply_window(signal, window_type='hann'):
"""
应用窗函数
"""
n = len(signal)
if window_type == 'hann':
window = np.hanning(n)
elif window_type == 'hamming':
window = np.hamming(n)
elif window_type == 'blackman':
window = np.blackman(n)
elif window_type == 'rect':
window = np.ones(n)
else:
raise ValueError("不支持的窗函数类型")
return signal * window
避坑指南:我曾经犯过一个错误——用矩形窗做连续振动信号的FFT,结果频谱上出现了很多虚假的旁瓣,把真正的故障频率给掩盖了。后来换成汉宁窗,问题迎刃而解。记住:除非你明确知道自己在做什么,否则默认用汉宁窗。
4.7 知识体系总览
下面这张图,是我梳理的信号预处理知识体系。你可以把它当作一个检查清单,每次做预处理时对照着来。
好了,信号预处理这部分就讲到这里。记住,预处理不是越复杂越好,关键是理解每个步骤的作用,根据实际情况灵活选择。下一节我们聊聊时域分析,到时候我会分享一些实战中的小技巧。