资讯详情

资讯详情

MATLAB findpeaks 峰值检测全解:从原理到实战参数配置

名字叫 findpeaks 的这个函数我前后用了快六年第一次真正把它研究明白却是被一段满是毛刺的心电信号逼的。导师丢给我一组数据说“把 R 波全找出来”我盯着波形上那些高高低低的山头下意识就想找“高点”结果找出来的要么是噪声要么同一个宽峰被反复数了好几遍。后来我才意识到一维信号峰值检测这件事远不是“局部最大值”四个字能概括的而 MATLAB 里最常用的 findpeaks恰恰是把这些坑提前帮你踩平的工具。它可以从一条时序波形里稳定地把“峰”的位置、高度、宽度、突出度全部提取出来广泛用于心电分析、振动监测、音频处理、光谱识别、传感器阈值判断等场景。这篇文章我不打算照抄官方文档而是按我自己调参的真实路径把 findpeaks 的用法、参数、应用和坑一条条讲透给你可以直接抄走的配置方案。1. 峰值检测这件事为什么说没有看起来那么简单1.1 什么才是一个“峰”这是个判断题一维信号里的“峰”直观理解就是比左右两边都高的点。但真实数据几乎不会给你一个干干净净的三角形它可能叠加了噪声、基线漂移、多个峰互相重叠甚至有的峰本身就是一个宽平台。这时候“比两边高”这个定义就站不住了噪声毛刺也算局部高点宽平台的最高点可能有一大片基线抬升时整个区域都高你按绝对高度选峰必然翻车。我习惯打一个比方你站在一片山区里找“山头”不是看哪块石头冒得最高而是看它相对于周围山脚有没有明显的高度落差。一块 50 米高的小山包如果周围的谷底只有 10 米那它的突出程度就有 40 米而一座 500 米的高台如果周围地面已经抬到 480 米它突出只有 20 米。真正有意义的“峰”恰恰是那种从局部背景里明显“鼓出来”的结构。这个直觉对应到 findpeaks 里就是官方文档里那个最不起眼却最重要的概念prominence中文常翻译成“突出度”。很多新手只会用 MinPeakHeight觉得“峰”一定要超过某个绝对高度结果基线一漂移就惨了。正确思路是先确定关心的峰在局部范围内有多突出再根据突出度过滤最后才用绝对高度兜底。这个顺序搞反了后面调参就会越来越乱。我自己的调参流程永远是先看哪几个峰是我真正想要的然后问自己三个问题它比周围背景高出多少它离相邻峰大概多远它的宽度大概多宽这三个问题对应的就是 findpeaks 的三大核心参数MinPeakProminence、MinPeakDistance、MaxPeakWidth/MinPeakWidth。把这三个问题想清楚参数基本就定了。1.2 findpeaks 在 MATLAB 工具箱里的定位findpeaks 属于 Signal Processing Toolbox不是 MATLAB 基础函数。换句话说如果你用的是基础版许可证可能调用时会直接报错“未定义函数”。我第一次遇到这个问题时还以为是代码拼错了后来才发现是工具箱授权的问题。这是个很容易忽略的前置条件你在项目开始前先确认一下license(test,Signal_Toolbox)是否返回 1能省很多麻烦。和它容易混淆的是另一个基础函数islocalmax。islocalmax 干的事很纯粹返回逻辑数组标记哪些点是局部最大值它不管这个峰够不够高、够不够突出、离别的峰够不够远。findpeaks 则是“全家桶”除了找局部最大值还集成了滤波、距离限制、宽度限制、突出度计算、排序、作图等一系列操作。所以只要不是极端追求内存性能日常场景直接用 findpeaks 就是最省事的方案。还有一点值得注意findpeaks 只处理一维向量但绝不限于“时间序列”。你可以对频谱数据做峰值检测来找主频对光谱数据做峰值检测来找特征波峰对距离像做峰值检测来找目标只要信号是一维数组它都能用。很多人在图像处理里想找“峰值”实际上是找二维亮点那就得换imregionalmax或houghpeaks不要拿 findpeaks 硬套二维矩阵这个边界我踩过也经常在论坛上看到有人问。2. 从一次最简单的调用说起2.1 用一段带噪信号跑通第一个 findpeaks我先带你跑一个最小可复现的例子。假设有一段时间信号包含两个明显的峰叠加了随机噪声fs 1000; % 采样率 1000 Hz t 0:1/fs:2; % 2 秒时长 x 0.5*sin(2*pi*5*t) 1.2*exp(-((t-0.5)/0.02).^2) ... 0.9*exp(-((t-1.3)/0.03).^2) 0.05*randn(size(t)); plot(t, x);这里我在 0.5 秒和 1.3 秒附近人为构造了两个高斯形状的峰高度分别约 1.2 和 0.9同时混入了 0.05 方差的噪声。直接调用 findpeaks[pks, locs] findpeaks(x);返回的locs是峰值点在数组里的索引pks是对应的高度。因为没传采样率所以 locs 是“第几个点”不是“第几秒”。要把索引转成时间可以locs_time (locs-1)/fs;注意 MATLAB 数组从 1 开始所以减 1 才是相对 t0 的偏移。如果嫌弃自己画图麻烦findpeaks 可以直接在图上标出来findpeaks(x, t, Annotate, extent);这样 MATLAB 会自动画出信号曲线并用竖线标出每个峰的位置同时显示峰的突出度范围。我第一次看到这个图的时候才真正理解 prominence 指的是哪一段高度强烈建议你无论懂不懂先跑这么一行看看。2.2 返回值里 pks、locs、w、p 分别是什么官方文档里 findpeaks 最多可以返回四个输出[pks, locs, w, p] findpeaks(...)pks峰值高度也就是每个检测到的峰的最大值。locs峰值位置。如果输入只给 x这就是索引如果输入findpeaks(x, fs)或findpeaks(x, t)locs 会按时间轴输出。w峰的宽度默认是半峰全宽即峰高一半处的宽度。宽峰窄峰的区分主要靠它。p峰的突出度就是前面讲的“山头相对山脚的高度差”。这个值才是判断一个峰是不是“真峰”最稳健的指标。四个输出的组合是我调试时的第一眼依据。我通常会先执行[pks, locs, w, p] findpeaks(x, t, MinPeakProminence, 0.3);然后列个表看每个候选峰的这四个值很快就知道应该收紧哪个参数。比如 p 普遍很小说明噪声或背景干扰很重w 普遍很宽说明可能是宽平台被当成多个峰了locs 间距小于某个值说明需要调大 MinPeakDistance。需要注意的是w的计算默认基于信号值下降到峰高一半的位置但这个定义对重叠峰有时会失效因为峰谷处达不到半高findpeaks 会用内插或最近的有效点估算宽度只是个参考值不要拿着它当精确物理量用。2.3 画图辅助选项和调试视图findpeaks 提供了几个挺实用的画图选项调试阶段能帮你省下大量时间。除了Annotate,extent之外还有两个我常用的组合。第一个是Annotate,peaks它只标记峰值点和高度适合快速看检测结果第二个是Annotate,threshold它会把 Threshold 参数对应的水平线画出来方便观察哪些峰高于阈值。如果你设置了 MinPeakHeight图上也会出现对应的虚线。这些可视化不是装饰而是调整参数的重要辅助。我的习惯是“先睁眼看图再闭眼调参”没有可视化就去试参数跟盲人摸象没有区别。3. 参数拆解MinPeakHeight、MinPeakProminence、MinPeakDistance 到底怎么选3.1 MinPeakHeight一刀切的绝对高度门限MinPeakHeight 是所有参数里最好理解的找到一个局部最大值后如果它的高度低于你设定的值就丢掉。适合的场景是基线平稳、峰的绝对幅度基本已知比如某些阈值检测场景信号低电平是 0高电平是 5 V你要找 3 V 以上的峰直接设 MinPeakHeight 就有奇效。但这个参数有个致命弱点它对基线漂移毫无抵抗力。信号整体上移 2 V原来的阈值就完全失效。另外如果信号里既有关心的大峰也有幅度较小但形状很重要的峰MinPeakHeight 会把小峰全部杀掉这时候你损失的不仅是“检出的峰少了”还有可能丢失真实事件。所以我建议MinPeakHeight 只作为“最后一道保险”不要作为首选过滤手段。3.2 MinPeakProminence官方文档里最值得细读的参数MinPeakProminence 是我最常用的参数没有之一。它的意思是一个峰的突出度必须至少达到这个值才被认可。突出度的官方定义比较复杂大致可以理解为从峰顶出发分别向左和向右找到比这个峰更高的点如果一路都没有更高点就一直延伸到数据两端在这两个边界之间找到信号的最低谷值峰顶高度减去这个最低谷值就是突出度。如果同一侧存在更高的峰那就以那个更高的峰作为墙突出度只算到墙为止。听起来有点绕但用山丘类比就清楚了一个山头比左侧山谷高出 10 米但右边 100 米外有一座更高的山那么这座山相对本地的突出度至少要从两座山之间的鞍部算起而不是从山脚算起。这也是为什么突出度能比绝对高度更准确地区分“真峰”和“背景上的小起伏”。举一个实际例子一段心电信号里R 波幅度高、T 波幅度低如果你用 MinPeakHeight 过滤 T 波得先观察平缓基线的幅度但如果你用 MinPeakProminence只要 T 波相对周围的突出度明显小于 R 波就能很自然地滤掉。也就是说prominence 是相对特征能自动适应局部地形。3.3 MinPeakDistance防止同一个峰被反复数MinPeakDistance 的作用是限制两个峰之间的最小间隔。默认值是 1也就是只要求不是紧挨着的点但真实数据里的一个宽峰可能因为局部抖动出现多个相邻高点。比如一个高斯峰顶附近有噪声findpeaks 有可能会在它周围找出五六个“峰”虽然高度相近但实际上是同一个事件。把 MinPeakDistance 设为某个合理值后findpeaks 会先找出所有候选峰然后检查那些距离太近的峰只保留最高或者按你要求的 SortStr 顺序保留的那一个。注意这个参数的单位取决于你传的输入如果只传信号 x单位是“样本点”如果传了findpeaks(x, fs)或findpeaks(x, t)单位就是“秒”。很多人在传了采样率之后还把 MinPeakDistance 设成 50结果一个峰都没剩下就是因为单位搞混了。经验上MinPeakDistance 可以取“你预期两个相邻事件之间的最短时间间隔”。比如心电 R 波正常人心率最快也就 200 次/分折算成 R-R 间隔是 0.3 秒那 MinPeakDistance 设 0.3 秒是合理的再小就容易把 T 波误判成 R 波。3.4 MaxPeakWidth、MinPeakWidth 与 Threshold宽度参数在光谱和振动场景里很常用。MinPeakWidth 和 MaxPeakWidth 用宽度来过滤候选峰单位同样取决于信号轴。比如你要找窄脉冲冲击可以把 MaxPeakWidth 设成 0.01 秒把宽大的平台噪声排除你要找宽缓的光谱峰可以把 MinPeakWidth 设成 0.1 秒避免噪声毛刺混进来。Threshold 参数和前面的都不太一样它要求的是“峰与相邻点之间的最小下降/上升幅度”可以理解为局部最小梯度门限。默认值是 0也就是只要左右某侧存在下降量就认可。这个参数对噪声抑制有一定帮助但它不如 MinPeakProminence 直观调起来容易困惑。我的经验是能用 MinPeakProminence 解决的问题就别用 ThresholdThreshold 更适合用于「峰顶周围有几个采样点高度接近」的特殊情况。3.5 NPeaks 与 SortStr只要最高的那几个峰有些场景不需要所有峰只要最大的 k 个。比如频谱分析里你只关心能量最强的 5 个频率分量这时候可以用NPeaks, 5让 findpeaks 只返回前 5 个峰。默认情况下NPeaks 限制的是按“出现顺序”取前几个但如果配合SortStr, descend它就会先按峰高从高到低排序再取前 5 个。排序策略里还有none和ascend选项分别代表按原顺序和升序。我经常把这段组合写成[pks, locs] findpeaks(x, fs, ... SortStr, descend, NPeaks, 5, ... MinPeakDistance, 0.2);这条命令的作用就是先在每隔至少 0.2 秒里找一个最高点再把这些候选峰按高度从高到低排序最后只留 5 个。在机械故障诊断里我拿它找前几阶转频效果比手动数谐波稳定得多。4. 不同应用场景下的参数配置和实操4.1 心电 R 波检测的典型配置心电信号是 findpeaks 最经典的使用场景之一。R 波的特点是幅度高、斜率大、间隔有生理意义干扰则来自基线漂移、肌电噪声和幅度不小的 T 波。如果按照常规做法设 MinPeakHeight基线漂移一出现就完蛋更稳的做法是用 prominence 和距离双重约束。一份可以直接参考的配置长这样% 假设 x 是心电信号fs 250 Hz [pks, locs] findpeaks(x, fs, ... MinPeakHeight, 0.3 * max(x), ... MinPeakProminence, 0.15 * max(x), ... MinPeakDistance, 0.3, ... MaxPeakWidth, 0.15);解释一下0.3 * max(x)是绝对高度兜底防止某些低幅 R 波和噪声混淆0.15 * max(x)是突出度门限强调 R 波相对 T 波和基线必须明显“鼓出来”0.3秒的最小间隔对应最快心率约 200 次/分0.15秒的最大峰宽用来排除比较宽缓的 T 波或漂移段。我第一次这么设的时候还是漏了几个被肌电干扰包住的 R 波后来加了 50 Hz 陷波和 0.5–40 Hz 带通滤波情况立刻好了很多。这里的关键心得是findpeaks 只负责“在给定信号上找峰”信号预处理决定它能看到什么。先把工频、基线漂移处理掉参数才好调。4.2 音频节拍检测与频谱峰值提取音频信号里节拍检测通常分两步先对包络求峰值或先做短时傅里叶变换得到频谱再找谱峰。无论哪一步最后都会落到 findpeaks 上。以频谱峰值提取为例假设你有一段钢琴音频经过 FFT 得到幅度谱mag和频率轴f想让 findpeaks 找出主要的谐波频率。频谱里的峰往往成簇出现谐波和主瓣之间距离比较固定所以我会这样设置[pks, locs] findpeaks(mag, f, ... MinPeakProminence, 0.2 * max(mag), ... MinPeakDistance, 20); % 频率轴上间隔 20 Hz这里MinPeakDistance单位是 Hz因为它跟随频率轴 f。20 Hz 的间隔可以在钢琴最低音区比较宽松但在高音区有可能把相邻半音间距几十赫兹丢掉所以实际使用时我会先看音高范围再定。音频峰值检测电路在硬件上可能用比较器和窗口检测来做但上位机算法里findpeaks 几乎是封装最完整的方案省去大量手写状态机的麻烦。4.3 机械振动冲击信号与光谱宽峰识别机械振动里的冲击信号特点是峰值持续时间极短、间隔不固定但重复出现。找冲击峰的关键是限制峰宽和距离。举个例子一个齿轮箱的振动加速度信号里混有周期性冲击和随机噪声我用过这样的配置[pks, locs] findpeaks(x, fs, ... MinPeakHeight, 3 * median(abs(x)), ... MaxPeakWidth, 0.005, ... MinPeakDistance, 0.02);3 * median(abs(x))比max(x)更抗噪声因为中位数受强冲击影响小MaxPeakWidth0.005 秒说明我只认足够尖锐的冲击MinPeakDistance0.02 秒对应 50 Hz 的最大冲击重复率远比齿轮旋转频率高所以不会误伤相邻冲击。光谱信号则相反很多光谱峰形状很宽噪声毛刺窄。如果把 MinPeakDistance 设大了宽峰本身也许没问题但如果一个宽峰被噪声切割成几个小峰就需要同时用 MinPeakWidth 过滤宽度太小的假峰。典型配置[pks, locs, w] findpeaks(y, xaxis, ... MinPeakProminence, 0.01, ... MinPeakWidth, 0.1, ... MaxPeakWidth, 2.0);这里的 MinPeakWidth 0.1 是光谱横轴的单位要根据实际波数范围定。总体思路是先明确你要找的“峰”在宽度和间隔上有多大范围再把这些物理量直接翻译成参数值。别指望一个万能参数组合信号类型不同配置最好不同。5. 踩坑记录findpeaks 常见问题和排查技巧5.1 为什么检到的峰位置总是偏一位或偏几个点这是很多初学者遇到的第一类问题。findpeaks 的默认峰值位置在离散数据里就是某个样本点的索引它本身不会做插值。但实际波形峰值可能落在两个采样点之间特别是采样率偏低时视觉上的“最高点”可能在两个相邻样本之间来回横跳findpeaks 选择的是“第一个满足局部最大值条件的点”所以看起来会偏一个点。如果信号在峰顶附近有几个样本接近相等findpeaks 默认取平台中间位置也可能和你肉眼判断的位置不一样。解决办法有三种一是提高采样率让峰顶附近有更多样本二是先对信号做平滑或插值再用 findpeaks三是拿到粗位置后自己做一个抛物线插值细化。抛物线插值不复杂大致思路是用峰值点和左右邻点拟合一元二次方程然后取顶点坐标。我在精密测距项目里用过这种方法能把峰值位置精度从几个样本提升到亚样本级但要注意这只适用于近似对称的峰对严重偏斜的峰效果有限。5.2 基线漂移严重时误检一堆基线漂移是峰值检测的头号杀手。信号整体缓慢起伏会让原本不构成峰的低频趋势内部出现大量局部高点findpeaks 会把这些高点当成峰。如果发现检出结果里峰和峰紧紧挨着间距接近 1而且高度变化很缓慢大概率就是基线漂移在作怪。我的排查顺序是先画原始信号看看是不是整体趋势很明显接着用detrend(x)去掉线性趋势或者用高通滤波器滤掉 0.5 Hz 以下成分然后再跑 findpeaks。如果不想改信号只调参数的话可以大幅调高MinPeakProminence让峰必须比周围的“局部地面”高出足够多这样一些由缓变趋势形成的假峰就会被滤掉。但彻底的做法还是预处理findpeaks 不是滤波器它不应该承担所有脏活。5.3 长时间序列运行慢或者内存吃紧findpeaks 对几十万点的向量处理还算快但如果你在循环里反复调用或者信号长度达到几百万点速度可能就成问题了。我的建议是能向量化就别循环findpeaks 本身就是向量化实现如果数据过大可以先降采样但要保证峰值信息不丢或者分段做峰值检测再合并结果。分段检测合并不是简单的拼接因为段边界处的峰可能被截断或者同一个峰在相邻段里各检出一次。我常用一个笨办法分段时让相邻段有 50% 重叠检测完所有峰后再按MinPeakDistance对全局峰列表做一次融合把距离太近的两个峰保留较高的那个。这个办法有点暴力但胜在思路清晰而且用sortrows和差分运算实现起来也就几行代码。如果项目对实时性要求高那应该考虑直接用 C/MEX 或 GPU而不是在 MATLAB 脚本层面硬优化。5.4 Annotate 和阈值显示的小技巧前面提到Annotate,extent能可视化突出度范围这是最被低估的调试功能。每次调参前我都会先[pks, locs, w, p] findpeaks(x, t, MinPeakProminence, 0.2, Annotate, extent);然后看图看哪些峰被标出来、哪些没有、prominence 的范围线画得对不对。如果发现某一片区域里你想捡起来的峰没被画出来就把MinPeakProminence往下调一点如果噪声峰也被画出来就往上调。这个过程通常两三轮就能定下合适的值。比对着文档盲猜效率高得多。还有一个容易忽略的细节findpeaks(x, fs)和findpeaks(x, t)虽然都会让 locs 变成时间坐标但 t 必须是有序等间隔的向量否则 MATLAB 内部处理可能不正常。如果你只有一个采样率就用findpeaks(x, fs)如果你有非均匀时间轴最好先插值成均匀网格再传给 findpeaks。非均匀轴上直接做峰值检测结果含义会比较模糊。下面是常见问题速查表适合贴在手边症状最可能原因优先调整方向峰太多间距很小噪声毛刺被当作独立峰加大 MinPeakDistance、MinPeakProminence明显的大峰没被检到高度低于 MinPeakHeight或宽度超出范围降低 MinPeakHeight放宽 MaxPeakWidth/MinPeakWidth检出的峰位置不准确采样率低或峰顶平坦提高采样率、插值或抛物线细化基线漂移造成假峰信号未做趋势处理先 detrend 或高通滤波再调大 prominence同一宽峰被检出多个峰MinPeakDistance 太小加大 MinPeakDistance只要最强的几个峰没有设置排序加 SortStrdescendNPeaksk我的经验是凡是遇到“峰检不准”先不要急着改代码而是把信号、峰值位置、prominence 范围一起画出来看着图调一遍参数。很多问题一眼就能看出来比在数值里猜快得多。如果你刚开始用 findpeaks我给你的建议就一句话先跑一遍带Annotate,extent的示例再动手调参。等你亲眼看到“突出度范围线”是怎么把山脚到山顶的距离框出来的你对 MinPeakProminence 的理解就会有质的提升。后期再做复杂项目时可以把几个常用配置封装成函数把 fs、MinPeakDistance、MinPeakProminence 作为输入参数这样换一组数据时只需要改阈值和距离不用重写调用逻辑。最后再分享一个小技巧当你要处理的数据量很大又担心 findpeaks 漏掉微弱但真实的峰时可以先把候选峰按突出度 p 排序然后人工抽查前 20 个和后 20 个峰确认尾部是否还有值得保留的信号。这比闷头调一个参数更实用也更容易建立“什么样的峰在你的数据里算真峰”的判断直觉。峰值检测的本质是一个权衡问题findpeaks 只是帮你把权衡的过程变得可调、可视、可复现而已。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →