4、光谱预处理-去噪:移动平均平滑、Savitzky-Golay滤波、小波去噪
做光谱分析这么多年,我最大的感触就是:原始光谱就像一块璞玉,不经过打磨,里面的信息根本没法用。噪声这东西,说白了就是仪器和环境的“私心杂念”,我们得想办法把它滤掉,只留下样品真实的“心声”。
今天咱们就聊聊三种最常用的去噪手段:移动平均平滑、Savitzky-Golay滤波,还有小波去噪。这三种方法我都在实际项目中摸爬滚打用过,各有各的脾气。
核心观点:去噪不是越干净越好,过度平滑会丢失关键特征。我的经验是——保留峰形,压制毛刺,两者之间找平衡。
4.1 移动平均平滑:最朴素的去噪法
移动平均平滑,说白了就是“拉帮结派”。把相邻的几个点拉过来,取个平均值,代替中间那个点。你想想看,噪声是随机上下跳的,一平均,正负抵消,自然就平滑了。
公式很简单:
y_smooth[i] = (y[i-k] + y[i-k+1] + ... + y[i] + ... + y[i+k-1] + y[i+k]) / (2k+1)
这里的 2k+1 就是窗口宽度。窗口越大,平滑效果越强,但峰也会被“抹平”。
我的经验:窗口宽度一般取数据点数的1%~5%。比如一条光谱有1000个点,窗口取11~51比较合适。我曾经试过取101,结果一个尖锐的峰直接变成了小土包,那叫一个心疼。
Python实现:
import numpy as np
def moving_average(y, window_size=11):
"""移动平均平滑"""
half = window_size // 2
y_smooth = np.copy(y)
for i in range(half, len(y) - half):
y_smooth[i] = np.mean(y[i-half:i+half+1])
return y_smooth
注意:移动平均会损失两端的数据点。窗口越大,损失越多。我一般用边界填充(比如复制端点值)来避免数据缩短。
4.2 Savitzky-Golay滤波:保峰形的利器
移动平均虽然简单,但有个硬伤——它会削峰。你想想看,峰顶本来是个尖尖,一平均,峰顶被周围的矮点拉低了,峰形就变了。
Savitzky-Golay滤波(简称SG滤波)就不一样了。它不是在窗口内简单平均,而是用多项式拟合窗口内的数据,然后用拟合值代替中心点。说白了,它假设局部数据可以用一个多项式来描述。
这样做的好处是:峰形保留得特别好。峰顶不会被削平,峰宽也不会被展宽。
| 参数 | 含义 | 我的建议 |
|---|---|---|
| 窗口宽度 | 参与拟合的数据点数(奇数) | 11~31,根据噪声水平调整 |
| 多项式阶数 | 拟合多项式的最高次数 | 2~4,阶数太高容易过拟合 |
Python实现(用scipy):
from scipy.signal import savgol_filter
# 窗口宽度21,多项式阶数3
y_sg = savgol_filter(y, window_length=21, polyorder=3)
避坑指南:窗口宽度必须大于多项式阶数,否则没法拟合。我见过有人设窗口=5,阶数=5,结果报错还一脸懵。另外,阶数越高,计算越慢,而且容易把噪声也拟合进去——那就适得其反了。
我个人习惯用SG滤波处理拉曼光谱和红外光谱。这两种光谱的峰形比较尖锐,用移动平均会损失分辨率,但SG滤波就能很好地保留。我在做聚合物材料分析时,曾经用SG滤波成功分离了两个重叠的峰,那感觉,就像在沙子里淘出了金子。
4.3 小波去噪:多尺度分析的王者
小波去噪,听起来高大上,其实原理不复杂。它把光谱分解成不同尺度(频率)的成分,然后只保留我们想要的尺度,把噪声所在的尺度扔掉。
你可以把光谱想象成一幅画。大尺度的成分是画的轮廓(基线、宽峰),小尺度的成分是画的细节(尖锐峰、噪声)。小波去噪就是:把噪声所在的小尺度细节抹掉,再重新合成光谱。
常用的步骤:
- 选择小波基:常用db4、db6、sym8等。我一般用db4,效果比较均衡。
- 确定分解层数:一般3~6层。层数越多,去噪越强,但也可能丢失有用信息。
- 阈值处理:对细节系数进行阈值处理,小于阈值的置零,大于阈值的保留或收缩。
- 重构:用处理后的系数重构光谱。
Python实现(用pywt):
import pywt
def wavelet_denoise(y, wavelet='db4', level=4, threshold=0.1):
"""小波去噪"""
# 分解
coeffs = pywt.wavedec(y, wavelet, level=level)
# 对细节系数进行阈值处理
coeffs_thresh = [coeffs[0]] # 近似系数不动
for i in range(1, len(coeffs)):
coeffs_thresh.append(pywt.threshold(coeffs[i], threshold, mode='soft'))
# 重构
y_denoised = pywt.waverec(coeffs_thresh, wavelet)
return y_denoised
小技巧:阈值怎么选?我一般用“通用阈值” threshold = sigma * sqrt(2 * log(N)),其中sigma是噪声标准差,N是数据长度。也可以用交叉验证来选,但计算量会大一些。
小波去噪最大的优势是:能同时处理不同频率的噪声。比如基线漂移(低频噪声)和随机毛刺(高频噪声),小波可以分层处理,各打各的板子。我在处理近红外光谱时,经常遇到基线漂移和随机噪声并存的情况,小波去噪的效果明显优于前两种方法。
注意:小波去噪的参数(小波基、层数、阈值)对结果影响很大。我建议先用一小段数据试跑,看看效果,再应用到全谱。不要一上来就全谱跑,万一参数不对,跑出来一团糟,还得重来。
4.4 三种方法怎么选?
说了这么多,到底用哪个?我总结了一个简单的选择逻辑:
- 噪声是均匀的高频毛刺,峰形不尖锐 → 移动平均平滑。简单快速,够用就行。
- 峰形尖锐,需要保留分辨率 → Savitzky-Golay滤波。这是最稳妥的选择,我80%的情况都用它。
- 噪声复杂(基线漂移+高频噪声),或者峰重叠严重 → 小波去噪。虽然参数调起来麻烦,但效果确实好。
嗯,这里要注意:没有万能的方法。我建议你把三种方法都试一遍,对比结果,选效果最好的。毕竟,数据不会骗人。
最后说一句:去噪只是预处理的第一步,后面还有基线校正、归一化、特征提取等着我们。每一步都马虎不得。好了,今天就聊到这儿,下次咱们接着讲基线校正。
公众号:蓝海资料掘金营,微信deep3321