findpeaks c语言

99ANYc3cd6
预计阅读时长 32 分钟
位置: 首页 C语言 正文

findpeaks 的核心任务是:在一个一维数据序列中,找到所有“波峰”(局部最大值点),一个点要成为波峰,通常需要满足以下条件:

  1. 局部最大值:该点的值必须大于其直接左右相邻的两个点的值。
  2. 高度阈值:该点的值必须大于一个预设的最小高度。
  3. 最小峰间距:两个相邻的波峰之间的水平距离(索引差)必须大于一个预设的最小值。
  4. 最小 prominence(突出度):该峰的“突出程度”必须大于一个预设的最小值,这是MATLAB findpeaks 中一个非常重要且强大的参数,但在C语言中实现起来相对复杂,我们后面会详细解释。

下面,我将分步介绍如何从简单到复杂地实现这个功能。


第1步:基本实现(只找局部最大值)

这是最核心的逻辑,我们遍历数组,检查每个点是否比它左右邻居都大。

算法逻辑:

  1. 遍历数组 data,从索引 1length-2(因为第一个和最后一个点没有左右邻居)。
  2. 对于每个点 data[i],检查 data[i] > data[i-1] data[i] > data[i+1]
  3. 如果条件满足,i 就是一个波峰的索引。

C语言代码示例:

#include <stdio.h>
#include <stdlib.h>
// 一个简单的 findpeaks 函数
// 输入:
//   data: 数据数组
//   length: 数据长度
//   peaks: 用于存储找到的峰值的索引数组
//   max_peaks: peaks数组能容纳的最大峰值数量
// 输出:
//   实际找到的峰值数量
int find_peaks_basic(const double data[], int length, int peaks[], int max_peaks) {
    int count = 0;
    // 遍历数组,从第二个元素到倒数第二个元素
    for (int i = 1; i < length - 1; i++) {
        // 检查是否是局部最大值
        if (data[i] > data[i - 1] && data[i] > data[i + 1]) {
            // 如果找到了一个峰,并且我们的结果数组还有空间
            if (count < max_peaks) {
                peaks[count] = i;
                count++;
            } else {
                // 结果数组已满,停止查找
                break;
            }
        }
    }
    return count;
}
int main() {
    double data[] = {0, 2, 1, 3, 5, 3, 4, 2, 1, 5, 3, 2, 0};
    int length = sizeof(data) / sizeof(data[0]);
    int peaks[length]; // 最坏情况下,每个点都是峰
    int num_peaks;
    num_peaks = find_peaks_basic(data, length, peaks, length);
    printf("找到 %d 个峰值:\n", num_peaks);
    for (int i = 0; i < num_peaks; i++) {
        printf("峰值 %d: 索引 = %d, 值 = %.2f\n", i + 1, peaks[i], data[peaks[i]]);
    }
    return 0;
}

输出:

找到 4 个峰值:
峰值 1: 索引 = 1, 值 = 2.00
峰值 2: 索引 = 4, 值 = 5.00
峰值 3: 索引 = 6, 值 = 4.00
峰值 4: 索引 = 9, 值 = 5.00

这个结果包含了所有局部最大值,但可能包含一些我们不想要的“小噪声”峰。


第2步:增加高度阈值和最小峰间距

这是对基本功能的增强,可以过滤掉不重要的峰。

算法逻辑:

  1. 高度阈值:在基本判断 data[i] > data[i-1] && data[i] > data[i+1] 之后,增加一个条件 data[i] > min_height
  2. 最小峰间距
    • 需要一个变量来记录上一个找到的峰的位置 last_peak_pos
    • 当找到一个新峰 i 时,检查它与 last_peak_pos 的距离是否大于 min_distance
    • 只有当 i - last_peak_pos > min_distance 时,才接受这个新峰。
    • 更新 last_peak_pos = i

C语言代码示例(集成新功能):

#include <stdio.h>
#include <stdlib.h>
// 增强版 findpeaks 函数
int find_peaks_enhanced(const double data[], int length, int peaks[], int max_peaks, 
                        double min_height, int min_distance) {
    int count = 0;
    int last_peak_pos = -min_distance - 1; // 初始化,确保第一个峰能被找到
    for (int i = 1; i < length - 1; i++) {
        // 1. 检查是否是局部最大值
        if (data[i] > data[i - 1] && data[i] > data[i + 1]) {
            // 2. 检查高度阈值
            if (data[i] > min_height) {
                // 3. 检查最小峰间距
                if (i - last_peak_pos > min_distance) {
                    if (count < max_peaks) {
                        peaks[count] = i;
                        count++;
                        last_peak_pos = i; // 更新上一个峰的位置
                    } else {
                        break; // 结果数组已满
                    }
                }
            }
        }
    }
    return count;
}
int main() {
    double data[] = {0, 2, 1, 3, 5, 3, 4, 2, 1, 5, 3, 2, 0};
    int length = sizeof(data) / sizeof(data[0]);
    int peaks[length];
    int num_peaks;
    // 设置参数
    double min_height = 3.0;
    int min_distance = 3;
    num_peaks = find_peaks_enhanced(data, length, peaks, length, min_height, min_distance);
    printf("在 min_height=%.1f, min_distance=%d 的条件下找到 %d 个峰值:\n", 
           min_height, min_distance, num_peaks);
    for (int i = 0; i < num_peaks; i++) {
        printf("峰值 %d: 索引 = %d, 值 = %.2f\n", i + 1, peaks[i], data[peaks[i]]);
    }
    return 0;
}

输出:

在 min_height=3.0, min_distance=3 的条件下找到 2 个峰值:
峰值 1: 索引 = 4, 值 = 5.00
峰值 2: 索引 = 9, 值 = 5.00

这次,高度为2的峰和高度为4但距离前一个峰太近的峰都被过滤掉了。


第3步:实现最小突出度

这是最复杂但也是最强大的一个功能。Prominence(突出度)衡量了一个峰有多“显著”,它定义为:从该峰向下,到达其左右两个“山谷”的最低点的最大垂直距离。

算法逻辑(以一个峰 p 为例):

  1. 找到左边界:从峰 p 向左走,找到第一个比 data[p] 高的点(或者到达数组起点),这个点 l 是左边界。
  2. 找到右边界:从峰 p 向右走,找到第一个比 data[p] 高的点(或者到达数组终点),这个点 r 是右边界。
  3. 找到谷底:在 [l, r] 这个区间内,找到最小值的点 b
  4. 计算突出度:峰的突出度就是 data[p] - data[b]

C语言代码示例(集成所有功能):

#include <stdio.h>
#include <stdlib.h>
#include <limits.h> // 用于 INT_MAX
// 计算单个峰的突出度
double calculate_prominence(const double data[], int length, int peak_pos) {
    // 1. 找到左边界
    int left_bound = 0;
    for (int i = peak_pos; i >= 0; i--) {
        if (data[i] > data[peak_pos]) {
            left_bound = i;
            break;
        }
    }
    // 如果左边没有更高的点,左边界是0
    if (data[left_bound] <= data[peak_pos]) {
        left_bound = 0;
    }
    // 2. 找到右边界
    int right_bound = length - 1;
    for (int i = peak_pos; i < length; i++) {
        if (data[i] > data[peak_pos]) {
            right_bound = i;
            break;
        }
    }
    // 如果右边没有更高的点,右边界是最后一个点
    if (data[right_bound] <= data[peak_pos]) {
        right_bound = length - 1;
    }
    // 3. 在 [left_bound, right_bound] 区间内找到最小值(谷底)
    double valley_bottom = data[left_bound];
    for (int i = left_bound + 1; i <= right_bound; i++) {
        if (data[i] < valley_bottom) {
            valley_bottom = data[i];
        }
    }
    // 4. 计算突出度
    return data[peak_pos] - valley_bottom;
}
// 最终版 findpeaks 函数
int find_peaks_final(const double data[], int length, int peaks[], int max_peaks, 
                     double min_height, int min_distance, double min_prominence) {
    int count = 0;
    int last_peak_pos = -min_distance - 1;
    for (int i = 1; i < length - 1; i++) {
        // 1. 检查是否是局部最大值
        if (data[i] > data[i - 1] && data[i] > data[i + 1]) {
            // 2. 检查高度阈值
            if (data[i] > min_height) {
                // 3. 检查最小峰间距
                if (i - last_peak_pos > min_distance) {
                    // 4. 检查最小突出度
                    double prominence = calculate_prominence(data, length, i);
                    if (prominence > min_prominence) {
                        if (count < max_peaks) {
                            peaks[count] = i;
                            count++;
                            last_peak_pos = i;
                        } else {
                            break;
                        }
                    }
                }
            }
        }
    }
    return count;
}
int main() {
    double data[] = {0, 2, 1, 3, 5, 3, 4, 2, 1, 5, 3, 2, 0};
    int length = sizeof(data) / sizeof(data[0]);
    int peaks[length];
    int num_peaks;
    // 设置更严格的参数
    double min_height = 3.0;
    int min_distance = 1;
    double min_prominence = 2.0;
    num_peaks = find_peaks_final(data, length, peaks, length, 
                                 min_height, min_distance, min_prominence);
    printf("在 min_height=%.1f, min_distance=%d, min_prominence=%.1f 的条件下找到 %d 个峰值:\n",
           min_height, min_distance, min_prominence, num_peaks);
    for (int i = 0; i < num_peaks; i++) {
        printf("峰值 %d: 索引 = %d, 值 = %.2f\n", i + 1, peaks[i], data[peaks[i]]);
    }
    return 0;
}

输出:

在 min_height=3.0, min_distance=1, min_prominence=2.0 的条件下找到 2 个峰值:
峰值 1: 索引 = 4, 值 = 5.00
峰值 2: 索引 = 6, 值 = 4.00
  • 索引为 4 的峰(值为5),其谷底是 min(2, 1, 3, 5, 3, 4) -> 1,突出度为 5 - 1 = 4
  • 索引为 6 的峰(值为4),其谷底是 min(3, 5, 3, 4, 2) -> 2,突出度为 4 - 2 = 2
  • 索引为 9 的峰(值为5),其谷底是 min(4, 2, 1, 5, 3, 2) -> 1,突出度为 5 - 1 = 4

min_prominence=2.0 的条件下,这三个峰都满足,如果设置 min_prominence=3.0,那么索引为 6 的峰(突出度为2)就会被过滤掉。


总结与建议

功能 实现方式 C语言实现复杂度 MATLAB findpeaks 参数
基本峰值 data[i] > data[i-1] && data[i] > data[i+1] N/A (默认)
高度阈值 data[i] > 'MinPeakHeight' 'MinPeakHeight'
最小峰间距 i - last_peak_pos > 'MinPeakDistance' 'MinPeakDistance'
最小突出度 data[p] - min(data[l...r]) 'MinPeakProminence'

如何选择实现方案?

  1. 如果只需要简单找峰:使用 第1步 的基本实现。
  2. 如果需要过滤掉一些小峰和密集峰:使用 第2步 的增强实现,这是最常用和最实用的版本。
  3. 如果需要精确识别信号中的主要特征峰,抗噪能力强:使用 第3步 的最终实现,引入突出度是关键。

进一步优化:

  • 内存管理:上面的例子中,peaks 数组开得很大 (length),在实际应用中,你可能不知道会有多少个峰,可以先找到所有峰的数量,然后动态分配一个大小合适的数组来存储结果。
  • 性能calculate_prominence 函数中的循环是性能瓶颈,对于非常大的数据集,可以考虑使用更高效的数据结构(如单调栈)来优化边界查找,但这会使代码复杂度大大增加,对于大多数应用场景,上述简单实现已经足够。
  • 返回更多信息:你可以修改函数,让它不仅返回峰的索引,还能返回峰的高度、突出度等信息,可以定义一个结构体来存储这些信息。
typedef struct {
    int index;
    double height;
    double prominence;
} PeakInfo;
// 函数签名可以改为:
int find_peaks_final_v2(const double data[], int length, PeakInfo peaks[], int max_peaks, ...);

希望这个详细的分步指南能帮助你在C语言中成功实现 findpeaks 的功能!

-- 展开阅读全文 --
头像
dede如何调用二级分类?
« 上一篇 今天
c语言ruanjiantc
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]