SciPy调用find_peaks检测时序数据频率时误检多峰值问题求解
问题描述
我有一个包含数十万个(time, value)采样点的数据文件,希望基于这些数据点估算对应波形的频率。我原本尝试使用SciPy的find_peaks函数,为每个真实峰值匹配单个采样点,通过计算相邻峰值的平均时间间隔求解频率,但实际调用find_peaks函数时,单个真实峰值被误识别为多个峰值,检测全局效果如下图所示:
单个峰值的局部放大效果如下:
我尝试通过设置prominence参数过滤噪声导致的假峰值,但始终未达到预期过滤效果,相关实现代码如下:
peak_idxs = list( sig.find_peaks( data, prominence=0.15, width=(0, 0.05 * sample_freq), )[0] ) troph_idxs = list( sig.find_peaks( np.negative(data), prominence=0.15, width=(0, 0.05 * sample_freq), )[0] ) peak_criticals = [timestamps[i] for i in peak_idxs] troph_criticals = [timestamps[i] for i in troph_idxs] t_criticals_freqs = [ 1 / (b - a) for a, b in zip(peak_criticals[0:], peak_criticals[1:]) ] + [1 / (b - a) for a, b in zip(troph_criticals[0:], troph_criticals[1:])] freq = statistics.mean(t_criticals_freqs) ax.plot(timestamps, data) ax.plot( [timestamps[i] for i in peak_idxs + troph_idxs], [data[i] for i in peak_idxs + troph_idxs], "r*", )
需要调整参数或检测方案,实现准确识别真实波峰波谷、正确计算时序波形频率的目标。
解决方案
单峰被误识别为多个峰值的核心原因是信号叠加了高频噪声,峰值顶部的小幅抖动被find_peaks判定为独立峰值,单纯调整prominence和width参数很容易在滤除假峰的同时漏掉真实峰值,可按以下方案调整:
- 检测前增加信号平滑预处理:在峰值检测前对原始信号做低通滤波或者滑动平均平滑,消除峰值顶部的高频毛刺。如果不想引入相位偏移,优先使用
scipy.signal.medfilt中值滤波,窗口大小设为采样率下1~2个噪声抖动周期的长度,通常取int(0.005*sample_freq)到int(0.01*sample_freq)的奇数即可,既可以磨平尖峰噪声,又不会明显改变真实峰值的位置。 - 给
find_peaks增加distance参数:该参数会强制两个检测到的峰值之间的最小采样点间隔,直接从规则层面避免同一个宽峰顶部的多个抖动被重复识别。参数值按信号的最小预估周期设置即可,比如估算信号基频在f0附近,就设distance=int(0.8/f0 * sample_freq),保证同一个真实峰范围内只会保留幅值最高的一个峰值点,波谷检测可复用同样的参数逻辑。 - 增加检测后峰值合并逻辑:如果不想对原始信号做平滑处理,可以在峰值检测完成后做一次聚类合并:把时间间隔小于预设最小峰间距的峰值归为同一组,每组只保留幅值最大的点作为真实峰值,波谷检测用相同逻辑处理即可。
- 频率稳定场景可换用FFT计算:如果被测信号的频率整体比较稳定,不需要追踪局部瞬时频率,可以直接对信号做快速傅里叶变换,取幅值最大的频率分量作为结果,该方法抗噪性远高于峰值间隔法,针对数十万采样点的计算速度也非常快。
注意:使用滤波预处理时不要把窗口设置过大,否则会磨平真实波形特征,导致峰值位置偏移,最终引入频率计算误差。
内容的提问来源于stack exchange,提问作者Jeff L
相关产品推荐
相关产品推荐

