python/scipy的寻峰算法

Peak-finding algorithm for Python/SciPy

我可以自己写一些东西,通过找到一阶导数的零交叉点或其他东西,但它似乎是一个足够通用的函数,可以包含在标准库中。有人知道吗?

我的特殊应用是一个二维数组,但通常它会用于在FFT等中查找峰值。

具体地说,在这类问题中,有多个强峰值,然后有许多较小的"峰值",这些都是由噪声引起的,应该忽略不计。这些只是例子,不是我的实际数据:

一维峰值:

FFT output with peaks

二维峰值:

Radon transform output with circled peak

寻峰算法可以找到这些峰的位置(不仅仅是它们的值),理想情况下可以找到真正的样本间峰,而不仅仅是具有最大值的指数,可能使用二次插值或其他方法。

一般来说,你只关心一些强峰,所以它们要么被选择是因为它们高于某个阈值,要么因为它们是按振幅排序的有序列表的前n个峰。

正如我所说,我知道如何自己写这样的东西。我只是在问是否有一个预先存在的函数或包可以很好地工作。

更新:

我翻译了一个matlab脚本,它可以很好地处理一维情况,但可能会更好。

最新更新:

Sixtenbe为一维案例创建了一个更好的版本。


我正在研究一个类似的问题,我发现了一些最好的参考资料来自化学(从质谱数据中的峰值发现)。为了对峰值查找算法进行全面的回顾,请阅读本文。这是对我所遇到的寻峰技术最清晰的评论之一。(小波是在噪声数据中寻找此类峰值的最佳方法。)

看起来你的峰很清晰,没有隐藏在噪音中。在这种情况下,我建议使用光滑的精明的戈莱导数来找到峰值(如果你只是区分上面的数据,你会发现一堆误报)。这是一种非常有效的技术,并且非常容易实现(您确实需要一个包含基本操作的矩阵类)。如果你简单地找到第一个S-G导数的零交叉点,我想你会很高兴的。


Scipy中有一个名为scipy.signal.find_peaks_cwt的函数,听起来很适合您的需要,但是我没有经验,所以我不能推荐。

http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.find_peaks_cwt.html


顾名思义,函数scipy.signal.find_peaks对此很有用。但要想得到一个好的提取峰,必须充分了解其参数widththresholddistance,尤其是prominence

根据我的测试和文档,突出的概念是"有用的概念",以保持良好的峰值,并丢弃噪声峰值。

什么是(地形)突出?这是"从山顶到任何更高地形所需的最低下降高度",如图所示:

enter image description here

这个想法是:

The higher the prominence, the more"important" the peak is.

测试:

enter image description here

我故意用(有噪音的)频率变化的正弦曲线,因为它显示出许多困难。我们可以看到,width参数在这里不是很有用,因为如果设置的最小width太高,那么它就无法跟踪高频部分非常接近的峰值。如果将width设置得太低,信号左侧会出现许多不需要的峰值。与distance相同的问题。threshold只与直接邻居比较,在这里没有用处。prominence是最好的解决方案。请注意,您可以组合这些参数中的许多!

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks

x = np.sin(2*np.pi*(2**np.linspace(2,10,1000))*np.arange(1000)/48000) + np.random.normal(0, 1, 1000) * 0.15
peaks, _ = find_peaks(x, distance=20)
peaks2, _ = find_peaks(x, prominence=1)      # BEST!
peaks3, _ = find_peaks(x, width=20)
peaks4, _ = find_peaks(x, threshold=0.4)     # Required vertical distance to its direct neighbouring samples, pretty useless
plt.subplot(2, 2, 1)
plt.plot(peaks, x[peaks],"xr"); plt.plot(x); plt.legend(['distance'])
plt.subplot(2, 2, 2)
plt.plot(peaks2, x[peaks2],"ob"); plt.plot(x); plt.legend(['prominence'])
plt.subplot(2, 2, 3)
plt.plot(peaks3, x[peaks3],"vg"); plt.plot(x); plt.legend(['width'])
plt.subplot(2, 2, 4)
plt.plot(peaks4, x[peaks4],"xk"); plt.plot(x); plt.legend(['threshold'])
plt.show()


对于那些不确定在python中使用哪种寻峰算法的人,这里快速概述了替代方法:https://github.com/monsieurv/py-findpeaks

我希望自己能与matlab findpeaks函数等价,我发现marcos duarte的detect_peaks函数是一个很好的方法。

非常容易使用:

1
2
3
4
5
6
import numpy as np
from vector import vector, plot_peaks
from libs import detect_peaks
print('Detect peaks with minimum height and distance filters.')
indexes = detect_peaks.detect_peaks(vector, mph=7, mpd=2)
print('Peaks are: %s' % (indexes))

这将给你:

detect_peaks results


以可靠的方式检测频谱中的峰值已经被研究了很多,例如80年代所有关于音乐/音频信号正弦建模的工作。在文献中寻找"正弦模型"。

如果你的信号和例子一样清晰,那么一个简单的"给我一个比n个邻居高的振幅"应该可以很好地工作。如果你有噪声信号,一个简单但有效的方法是及时观察你的峰值,跟踪它们:然后你检测光谱线而不是光谱峰。在信号的滑动窗口上计算FFT,得到一组光谱(也叫光谱图)。然后观察光谱峰在时间上的演变(即在连续窗口中)。


我不认为你要找的是Scipy提供的。在这种情况下,我自己编写代码。

从scipy.interpolate中得到的样条插值和平滑是非常好的,可能对拟合峰值和找到最大值的位置非常有帮助。


有标准的统计函数和方法来查找数据的异常值,这可能是您在第一种情况下需要的。使用导数可以解决第二个问题。但是,我不确定是否有一种方法可以同时解决连续函数和采样数据。


首先,如果没有进一步的规范,"峰值"的定义是模糊的。例如,对于以下系列,您会将5-4-5称为一个峰值还是两个峰值?

1-2-1-2-1-1-5-4-5-1-1-5-1

在这种情况下,您将需要至少两个阈值:1)一个高阈值,它只能高于这个阈值,一个极端值可以注册为一个峰值;2)一个低阈值,以便由它下面的小值分隔的极端值将成为两个峰值。

峰值检测是极值理论文献中研究得很好的一个课题,也称为"极值去聚类"。其典型应用包括根据环境变量的连续读数识别危险事件,例如分析风速以检测风暴事件。