1. 引言

在处理数据时,一个常见的任务是识别输入信号中的峰值(peak)。例如在电信号、地震波、风速等场景中,我们经常需要从数据流中找出显著的波峰。

本文将介绍几种常用的峰值检测方法,并探讨它们的适用场景与局限性。我们会从最简单的峰值检测开始,逐步深入到更复杂的噪声处理和宽峰识别技术。


2. 简单峰值检测

当我们面对一个信号数据流时,最直观的方法是直接找出最大值点。这种方法适用于背景值稳定、峰值非常突出的场景。

例如下图中,峰值出现在时间点18,非常容易识别:

Screenshot-2020-08-17-at-07.51.42

2.1. 算法实现

该算法非常简单:遍历整个信号数组,记录当前最大值及其索引。遍历完成后返回最大值的索引即可。

algorithm NaivePeakFinding(signal):
    // INPUT
    //    signal = an array of signal values
    // OUTPUT
    //    index = the index of the peak value in the signal

    peakIndex <- null
    peakValue <- null 
    for index, value in signal: 
        if peakValue = null or value > peakValue:
            peakIndex <- index
            peakValue <- value
    return peakIndex

✅ 优点:实现简单,效率高
❌ 缺点:仅适用于单个显著峰值,无法处理多个或噪声干扰下的峰值


3. 多峰值检测 – 使用基线法

上述方法只能找到一个最大值。如果我们希望识别多个峰值,比如下面这个信号:

Screenshot-2020-08-17-at-08.12.45

信号中有三个明显峰值,但简单方法只能找到最大值所在的24时刻。

此时,我们可以通过设定一个“基线”(baseline)来识别多个峰值。当信号值高于基线时,记录当前最大值;当信号值低于基线时,说明当前峰值结束,可以将其加入结果并重置。

3.1. 算法实现

algorithm MultiplePeakFinding(signal):
    // INPUT
    //    signal = an array of signal values
    // OUTPUT
    //    indices = an array of indices of the peak values in the signal

    peakIndices <- an empty array
    peakIndex <- null
    peakValue <- null
    baseline <- average(signal) 
    for index, value in signal: 
        if value > baseline:
            if peakValue = null or value > peakValue:
                peakIndex <- index
                peakValue <- value
        else if value < baseline and peakIndex != null:
            peakIndices.append(peakIndex)
            peakIndex <- null
            peakValue <- null
    if peakIndex != null:
        peakIndices.append(peakIndex)
    return peakIndices

✅ 优点:可识别多个峰值
⚠️ 注意:基线设置对结果影响较大,需根据信号特点调整


4. 噪声信号处理

当信号中存在较大噪声时,简单的基线法可能产生大量误判。例如下图中的信号:

Screenshot-2020-08-18-at-07.57.48

如果直接使用平均基线,会识别出7个峰值,而实际我们期望只有2个。

4.1. 解决方案:移动平均

为了减少噪声影响,我们可以使用移动平均(moving average)来平滑信号。例如,对每个点取其前后两个点的平均值进行处理:

处理后信号如下图所示,峰值更清晰:

Screenshot-2020-08-18-at-08.00.08

4.2. 算法实现

algorithm NoisyPeakFinding(signal):
    // INPUT
    //   signal = an array of signal values
    // OUTPUT
    //   indices = an array of indices of the peak values in the signal

    smoothed <- []
    for index in signal:
        smoothed.append(average(signal[index-2 : index + 2]))
    
    peakIndices <- an empty array
    peakIndex <- null
    peakValue <- null
    baseline <- average(smoothed) 
    for index, value in smoothed: 
        if value > baseline:
            if peakValue = null or value > peakValue:
                peakIndex <- index
                peakValue <- value
        else if value < baseline and peakIndex != null:
            peakIndices.append(peakIndex)
            peakIndex <- null
            peakValue <- null
    if peakIndex != null:
        peakIndices.append(peakIndex)
    return peakIndices

✅ 优点:能有效抑制噪声干扰
⚠️ 注意:移动窗口大小需根据信号特征调整


5. 宽峰识别

有时我们希望识别的不是一个点,而是一段连续的高值区域。例如下面这个信号:

Screenshot-2020-08-20-at-07.24.20

该信号中存在一个明显的“高值区域”,而不是单一峰值。我们希望将整个区域识别为一个“宽峰”。

5.1. 实现思路

不再寻找单一最大值,而是将所有高于基线的点都标记为峰值区域的一部分。

5.2. 算法实现

algorithm WidePeakFinding(signal):
    // INPUT
    //    signal = an array of signal values
    // OUTPUT
    //    indices = an array of indices that are part of a wide peak in the signal

    peakIndices <- an empty array
    baseline <- average(signal) 
    for index, value in signal: 
        if value > baseline:
            peakIndices.append(index)
    return peakIndices

✅ 优点:适合识别“宽峰”或信号“开启”区域
⚠️ 注意:需要后续处理将连续索引分组为独立峰值


6. 基于标准差的峰值检测

当信号中存在多个波动但只有少数几个是显著异常时,使用标准差(standard deviation)可以更精确地识别出“异常”峰值。

例如下图中,我们希望识别出两个显著高峰,而不是所有高于基线的小峰:

Screenshot-2020-08-21-at-07.49.09

使用标准差后,我们只识别出这两个显著峰值:

Screenshot-2020-08-21-at-07.50.14

6.1. 算法实现

algorithm Dispersion(signal, lag, influence, threshold):
    // INPUT
    //    signal = an array of signal values
    //    lag = the number of values to consider for the moving average
    //    influence = the influence of a peak on the moving average
    //    threshold = the standard deviation multiplier for peak detection
    // OUTPUT
    //    indices = an array of indices considered to be peaks based on dispersion

    peakIndices <- an empty array
    processedSignal <- signal[0 : lag]
    
    for index <- lag to length(signal):
        y <- signal[index]
        avg <- average(processedSignal[(index-lag) : index])
        sd <- stdev(processedSignal[(index-lag) : index]) 
        if y - avg > sd * threshold:
            peakIndices.push(index)
            adjustedValue <- (influence * y) + ((1 - influence) * processedSignal[index - 1])
        else:
            processedSignal.push(y)
    
    return peakIndices

参数说明:

  • lag:用于计算移动平均的前N个点数
  • threshold:标准差倍数,决定多少才算异常
  • influence:异常点对后续移动平均的影响程度(0~1)

✅ 优点:能识别显著异常峰值,对噪声有较好鲁棒性
⚠️ 注意:参数敏感,需根据数据特征调整


7. 总结

本文介绍了几种常见的信号峰值检测方法:

方法 适用场景 特点
简单最大值法 单个显著峰值 简单高效
基线法 多个峰值识别 需合理设置基线
移动平均 噪声信号处理 可平滑信号
宽峰识别 区域性高值 适合“开启”检测
标准差法 异常峰值识别 精度高,参数敏感

在实际应用中,应根据信号特性、噪声水平和业务需求选择合适的算法,必要时结合多种方法进行优化。

建议:对于复杂信号,建议先做可视化分析,再选择算法,避免“踩坑”。


原始标题:Peak Detection in a Measured Signal