本节摘要:一阶导数把恒定与线性背景清零、以过零点标记峰位;二阶导数更进一步,把肩峰顶成独立的负谷。代价是噪声被急剧放大——同样的平滑窗口下,二阶导的噪声标准差是原始谱的近七十倍。导数是整备车间的最后一道工序,也是"细节与噪声"这场交易的最激进报价。
整备接近尾声,档案还藏着最后一类秘密:叠在强峰肩膀上的小峰。原始谱上它们只是一点不对称,肉眼难辨、寻峰算法也抓不住。导数光谱换一个观察量——不看高度看变化率——肩峰立即现形。它承接 2.5 的标尺工序(在统一度量衡后放大细节才有意义),通往第 3 章的自动寻峰:那里用的特征表,很多就来自本节的负谷位置。
零阶导就是原谱。一阶导数处处是斜率:峰顶处斜率为零(过零点),左肩斜率为正、右肩为负——过零点精确标记峰位,而且任何恒定或线性变化的背景在求导后直接消失,这是它附送的基线校正能力。二阶导数是曲率:峰顶曲率最大且为负,于是在二阶导谱上每个峰对应一个向下的负谷;肩峰处的曲率变化被放大成独立的谷,两个熔在一起的重叠峰就此分家。更高阶导数理论上更锐,但噪声放大会失控,实践中极少越过二阶。
计算从不直接做差分——差分是噪声放大器。标准做法是 SG 求导:窗口内拟合多项式时直接取多项式的导数值,平滑与求导一步完成,窗口与阶数两个旋钮沿用 2.1 的经验。
构造一个典型难题:两个高斯峰相距 0.60、半高宽 0.59——间距与峰宽几乎相等,在原始谱上必然熔成一个包:
# 导数光谱:重叠肩峰的分离 import numpy as np from scipy.signal import savgol_filter, find_peaks rng = np.random.default_rng(11) x = np.linspace(0, 10, 400) clean = 0.8*np.exp(-0.5*((x-5.0)/0.25)**2) + 0.55*np.exp(-0.5*((x-5.6)/0.25)**2) y = clean + rng.normal(0, 0.01, x.size) # 信噪比 80:1 的档案 ys = savgol_filter(y, 21, 3) # 平滑版原谱 d1 = savgol_filter(y, 21, 3, deriv=1, delta=x[1]-x[0]) # SG 一阶导 d2 = savgol_filter(y, 21, 3, deriv=2, delta=x[1]-x[0]) # SG 二阶导 pk, _ = find_peaks(ys, prominence=0.1) # 原谱寻峰 print("smoothed raw peaks:", np.round(x[pk], 3), "(真值 5.00 与 5.60)") region = (x >= 4.0) & (x <= 7.0) zc = np.where(region)[0][np.where(np.diff(np.sign(d1[region])) < 0)[0]+1] print("d1 zero-crossings:", np.round(x[zc], 3)) pk2, props = find_peaks(-d2[region], prominence=1.0) # 负谷寻峰 print("d2 negative lobes:", np.round(x[region][pk2], 3), "prominence:", np.round(props['prominences'], 2))
输出:
smoothed raw peaks: [5.038] (真值 5.00 与 5.60) d1 zero-crossings: [5.038] d2 negative lobes: [4.987 5.639] prominence: [11.59 5.96]
三行输出是一场完整的"翻案"。平滑后的原谱只找到一个峰(5.038)——两个峰熔成了包,表观峰位被次峰拖偏了 0.038;一阶导的过零点同样只有 5.038,它只认"总斜率为零"的地方,救不了重叠;二阶导一次揪出两个负谷:4.987 与 5.639,与真值 5.00、5.60 各差不到 0.04。两个负谷的显著度(11.59 与 5.96)还顺便报出了相对峰高信息——主峰的谷更深。近红外分析里"二阶导加 SNV"被称为黄金组合,原因就在这里:近红外谱峰宽而重叠,不做导数基本没有可用峰形。
导数放大细节,也放大噪声。定量测一下:同一窗口参数下,对纯噪声逐阶求导:
# 噪声放大定量:同一窗口对纯噪声逐阶求导 noise = rng.normal(0, 0.01, x.size) for order in [0, 1, 2]: dd = savgol_filter(noise, 21, 3, deriv=order, delta=x[1]-x[0]) print(f"deriv={order} noise std={dd.std():.5f}")
输出:
deriv=0 noise std=0.00304 deriv=1 noise std=0.03314 deriv=2 noise std=0.20975
平滑本身把噪声从 0.01 压到 0.00304(约三分之一),一阶导回弹到 0.03314,二阶导冲到 0.20975——相对平滑谱放大了 69 倍,相对原始噪声放大了 21 倍。这笔账解释了三条工程铁律。其一,求导前的平滑不是可选项,是生存必需,且窗口通常要比 2.1 单纯去噪时更宽(本例 21 点,约为肩峰宽度的三分之一)。其二,窗口拉太宽又会抹掉导数想放大的细节——窗口、阶数、导数阶三者构成三角约束,第 6 章参数调优会把这三个旋钮放进同一张网格里搜。其三,信噪比低的档案别上二阶导,先回采集端攒积分时间,账算不平的处理都是自欺。
历史脚注:早期仪器时代没有 SG 算法,工程师用两个盒式滤波器错开做差来近似导数(Norris 导数滤波),思路与 SG 一致——先平滑后差分——只是滤波器形状更粗糙。今天的软件里两个名字都还在,见到 Norris 滤波不必陌生。
⚠️ 常见坑:二阶导谱的负谷常被误读成"倒峰"。它不是倒峰,是曲率信号——比对谱库时务必对同阶导数谱比对,拿二阶导谱去比原始谱库,匹配必然失败。
整备车间的六道工序到此走完:噪声画像、频域小波、两派基线、统一标尺、导数放大。下一章档案进鉴定台——先把曲线翻译成"峰位、峰高、峰宽"的特征表,那是自动寻峰的战场。