Python/SciPy 的峰值查找算法
- 2025-01-22 08:45:00
- admin 原创
- 137
问题描述:
我可以通过查找一阶导数的零交叉点或其他东西自己编写一些东西,但它似乎是一个足够常见的函数,可以包含在标准库中。有人知道吗?
我的特定应用是二维数组,但通常它用于查找 FFT 中的峰值等。
具体来说,在这类问题中,有多个强峰值,然后有很多较小的“峰值”,这些峰值只是由应该忽略的噪声引起的。这些只是示例;不是我的实际数据:
一维峰值:
二维峰值:
峰值查找算法会找到这些峰值的位置(而不仅仅是它们的值),理想情况下会找到真正的样本间峰值,而不仅仅是具有最大值的索引,可能使用二次插值或类似方法。
通常,您只关心几个强峰值,因此要么因为它们高于某个阈值而被选中,要么因为它们是按振幅排序的有序列表的前n 个峰值。
正如我所说,我知道如何自己编写类似的东西。我只是问是否有已知运行良好的预先存在的函数或包。
更新:
我翻译了一个 MATLAB 脚本,它在一维情况下运行良好,但可以更好。
更新更新:
sixtenbe为 1-D 情况创建了一个更好的版本。
解决方案 1:
函数scipy.signal.find_peaks
,顾名思义,对此很有用。但重要的是要很好地理解其参数width
,threshold
,distance
最重要的prominence
是获得良好的峰值提取。
根据我的测试和文档,突出性的概念是“有用的概念”,用于保留好的峰值并丢弃嘈杂的峰值。
什么是(地形)突出度?它是“从山顶下降到任何较高地形所需的最小高度”,如下所示:
这个想法是:
突出程度越高,山峰就越“重要”。
测试:
我故意使用了(嘈杂的)频率变化正弦波,因为它显示出许多困难。我们可以看到,该width
参数在这里不是很有用,因为如果将最小值设置得width
太高,那么它将无法跟踪高频部分中非常接近的峰值。如果设置得width
太低,信号的左侧部分将会出现许多不必要的峰值。同样的问题distance
。threshold
仅与直接邻居进行比较,这在这里没有用。prominence
是提供最佳解决方案的那个。请注意,您可以组合这些参数中的许多参数!
代码:
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()
解决方案 2:
我正在研究一个类似的问题,我发现一些最好的参考资料来自化学(来自质谱数据中的峰值查找)。要全面了解峰值查找算法,请阅读此文。这是我遇到的关于峰值查找技术最清晰的评论之一。(小波最适合在嘈杂的数据中查找此类峰值。)
看起来您的峰值定义清晰,没有隐藏在噪声中。在这种情况下,我建议使用平滑的 savtizky-golay 导数来查找峰值(如果您只是区分上面的数据,您将得到一堆假阳性。)。这是一种非常有效的技术,而且非常容易实现(您确实需要一个具有基本操作的矩阵类)。如果您只是找到第一个 SG 导数的零交叉点,我想您会很高兴。
解决方案 3:
scipy 中有一个名为的函数scipy.signal.find_peaks_cwt
听起来很适合您的需要,但是我没有使用过它,所以我无法推荐。
http://docs.scipy.org/doc/scipy/reference/ generated/scipy.signal.find_peaks_cwt.html
解决方案 4:
对于那些不确定在 Python 中使用哪种峰值查找算法的人,这里是替代方案的快速概述:https://github.com/MonsieurV/py-findpeaks
我想要一个与 MatLabfindpeaks
函数相当的函数,我发现Marcos Duarte 的detect_peaks 函数是一个很好的选择。
相当容易使用:
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))
这将为你带来:
解决方案 5:
为了检测正峰值和负峰值,PeakDetect很有帮助。
from peakdetect import peakdetect
peaks = peakdetect(data, lookahead=20)
# Lookahead is the distance to look ahead from a peak to determine if it is the actual peak.
# Change lookahead as necessary
higherPeaks = np.array(peaks[0])
lowerPeaks = np.array(peaks[1])
plt.plot(data)
plt.plot(higherPeaks[:,0], higherPeaks[:,1], 'ro')
plt.plot(lowerPeaks[:,0], lowerPeaks[:,1], 'ko')
解决方案 6:
已经对如何可靠地检测频谱中的峰值进行了大量研究,例如 80 年代对音乐/音频信号的正弦建模的所有研究。在文献中查找“正弦建模”。
如果您的信号与示例一样干净,那么简单的“给我一个幅度高于 N 个邻居的信号”应该可以很好地工作。如果您的信号有噪声,一个简单但有效的方法是及时查看峰值并跟踪它们:然后检测频谱线而不是频谱峰值。换句话说,您在信号的滑动窗口上计算 FFT,以获得一组时间频谱(也称为频谱图)。然后您查看频谱峰值随时间的变化(即在连续窗口中)。
解决方案 7:
有标准的统计函数和方法可以查找数据的异常值,这可能是您在第一种情况下所需要的。使用导数可以解决您的第二种问题。但是,我不确定是否有一种方法可以同时解决连续函数和采样数据。
解决方案 8:
我不认为 SciPy 提供了您正在寻找的内容。在这种情况下,我会自己编写代码。
scipy.interpolate 的样条插值和平滑效果非常好,可能对拟合峰值并找到其最大值的位置非常有帮助。
解决方案 9:
首先,如果没有进一步说明,“峰值”的定义是模糊的。例如,对于以下系列,您会将 5-4-5 称为一个峰值还是两个峰值?
1-2-1-2-1-1-5-4-5-1-1-5-1
在这种情况下,您至少需要两个阈值:1)一个高阈值,只有高于该阈值的极值才会被视为峰值;2)一个低阈值,以便低于该阈值的小值分隔的极值将成为两个峰值。
峰值检测是极值理论文献中一个研究得很好的课题,也被称为“极值去聚类”。它的典型应用包括根据连续读取环境变量来识别危险事件,例如分析风速以检测风暴事件。
解决方案 10:
正如本页底部所述,峰值没有通用定义。因此,如果不引入额外的假设(条件、参数等),通用的峰值查找算法就无法工作。本页提供了一些最精简的建议。以上答案中列出的所有文献都是或多或少迂回的方式来做同样的事情,所以请随意选择。
无论如何,您有责任根据您的经验和相关光谱(曲线)的属性(噪声、采样、带宽等)缩小特征需要具备的属性,以便将其归类为峰值。
扫码咨询,免费领取项目管理大礼包!