发布于2026-07-15 阅读(0)
扫一扫,手机访问
先说个真实案例。之前处理传感器数据时,用for循环写了个降噪函数,1000次迭代跑下来整整45秒。这个速度在产线上根本没法用,于是动手优化了一遍,最终把耗时压到了2秒以内。整个过程有不少值得记录的地方,顺便把三种常见降噪算法的效果也做了实测对比。

def smooth_slow(signal, window=11):
n = len(signal)
result = np.zeros(n)
half = window // 2
for i in range(half, n - half):
s = 0
for j in range(i - half, i + half + 1):
s += signal[j]
result[i] = s / window
return result
# 测试
import time
np.random.seed(42)
signal = np.random.randn(200) + 1000 # 模拟200点传感器数据
start = time.time()
for _ in range(1000):
_ = smooth_slow(signal)
print(f"for循环: {time.time() - start:.2f}s")
# for循环: 45.23s
光是跑1000次就得45秒,这在产线上根本没法用。问题出在哪儿?看看代码就明白了——两层嵌套循环,每次都要遍历窗口内的所有数据点,计算量可想而知。
def smooth_numpy(signal, window=11):
kernel = np.ones(window) / window
return np.convolve(signal, kernel, mode='same')
start = time.time()
for _ in range(1000):
_ = smooth_numpy(signal)
print(f"NumPy: {time.time() - start:.2f}s")
# NumPy: 3.09s
一个简单的优化,就带来了14.6倍的提升。为什么?因为NumPy的底层是C语言写的,还用了SIMD指令集并行计算。把循环交出去,让C去处理,速度自然就上来了。
from scipy.signal import sa vgol_filter
start = time.time()
for _ in range(1000):
_ = sa vgol_filter(signal, 11, 3)
print(f"Sa v-Gol: {time.time() - start:.2f}s")
# Sa v-Gol: 2.85s
这一轮,Sa vitzky-Golay滤波不仅比NumPy还快,效果也更好。它用的是多项式拟合,而不是简单平均,所以在保留峰形方面表现更出色。
为了更直观地比较,用带噪声的模拟数据测试了移动平均、高斯平滑和Sa vitzky-Golay三种方法。测试数据模拟了三个不同位置、不同强度的信号峰:
import numpy as np
from scipy.signal import sa vgol_filter, find_peaks
from scipy.ndimage import gaussian_filter1d
# 生成测试数据
np.random.seed(42)
x = np.arange(380, 782, 2)
n = len(x)
baseline = 1000 + 50 * np.sin((x - 380) / 100)
p1 = 800 * np.exp(-((x - 450) / 15) ** 2)
p2 = 600 * np.exp(-((x - 550) / 20) ** 2)
p3 = 400 * np.exp(-((x - 680) / 25) ** 2)
true_signal = baseline + p1 + p2 + p3
raw = true_signal + np.random.normal(0, 30, n)
# 三种方法
ma = np.convolve(raw, np.ones(11)/11, mode='same')
gauss = gaussian_filter1d(raw, sigma=2)
sg = sa vgol_filter(raw, 11, 3)
# 看峰位误差
def peak_error(filtered, true):
pf, _ = find_peaks(filtered, height=1200, distance=20)
pt, _ = find_peaks(true, height=1200, distance=20)
if len(pf) != len(pt): return 999, 999
wl_err = np.mean(np.abs(x[pf] - x[pt]))
int_err = np.mean(np.abs(filtered[pf] - true[pt]) / true[pt]) * 100
return wl_err, int_err
print(f"移动平均 : 峰位误差 {peak_error(ma, true_signal)[0]:.2f}nm, 峰高误差 {peak_error(ma, true_signal)[1]:.1f}%")
print(f"高斯平滑 : 峰位误差 {peak_error(gauss, true_signal)[0]:.2f}nm, 峰高误差 {peak_error(gauss, true_signal)[1]:.1f}%")
print(f"Sa v-Gol : 峰位误差 {peak_error(sg, true_signal)[0]:.2f}nm, 峰高误差 {peak_error(sg, true_signal)[1]:.1f}%")
结果非常直观:
移动平均 : 峰位误差 0.00nm, 峰高误差 8.2%
高斯平滑 : 峰位误差 0.00nm, 峰高误差 5.1%
Sa v-Gol : 峰位误差 0.00nm, 峰高误差 0.3%
Sa vitzky-Golay可以说是碾压级别的表现。峰位一个不差,峰高误差只有0.3%,而其他两种方法都在5%以上。这意味着在需要精确分析信号峰的场景下,算法选择直接决定了结果质量。
| 方法 | 1000次耗时 | 相对速度 | 峰位误差 | 峰高误差 | 推荐度 |
|---|---|---|---|---|---|
| for循环 | 45.2s | 1x | - | - | ❌ |
| 移动平均 | 3.1s | 14.6x | 0nm | 8.2% | ⭐⭐ |
| 高斯平滑 | 3.2s | 14.1x | 0nm | 5.1% | ⭐⭐⭐ |
| Sa v-Gol | 2.8s | 16.1x | 0nm | 0.3% | ⭐⭐⭐⭐⭐ |
基于Sa vitzky-Golay滤波,把降噪和寻峰功能封装成了一个类,方便产线调用:
from scipy.signal import sa vgol_filter, find_peaks
import numpy as np
class FastProcessor:
def __init__(self, x_axis):
self.x = np.array(x_axis)
def process(self, raw):
# 降噪
filtered = sa vgol_filter(raw, 11, 3)
# 寻峰
peaks, _ = find_peaks(filtered, height=np.mean(filtered)*1.5, distance=20)
return {
'filtered': filtered,
'peaks': peaks,
'peak_x': self.x[peaks],
'peak_y': filtered[peaks]
}
def benchmark(self, raw, n=1000):
import time
_ = self.process(raw) # 预热
t0 = time.time()
for _ in range(n):
_ = self.process(raw)
return (time.time() - t0) / n * 1000 # ms/次
# 用
proc = FastProcessor(np.arange(380, 782, 2))
result = proc.process(raw)
print(f"检测到 {len(result['peaks'])} 个峰")
print(f"单次处理: {proc.benchmark(raw):.2f}ms")
测试下来单次处理只需要2.8毫秒,产线实时处理完全没问题。
必须提醒的是,Sa vitzky-Golay滤波的窗口长度必须是奇数,而且不能大于数据长度。第一次写的时候把window设成201,数据只有200个点,直接报错。
一个经验公式:window ≈ 2 * (数据点数 / 峰宽) + 1,记得取奇数。这样既能保证滤波效果,又不会因为窗口过大而丢失信号细节。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8