基于C#的实时PPG信号峰值检测算法开发求助
PPG实时峰值检测的C#实现方案
核心算法思路
针对PPG信号的实时峰值检测,推荐采用滑动窗口+自适应阈值+斜率判定的组合方案,兼顾实时性和准确性,同时能应对基线漂移与噪声干扰:
- 预处理:移动平均滤波:用固定长度滑动窗口对原始信号做平滑,去除高频噪声,适合实时计算场景。
- 峰值判定三条件:
- 当前点斜率由正转负(信号从上升趋势转为下降)
- 当前点幅值高于自适应阈值(阈值基于窗口内信号的均值+倍数标准差,可动态适配信号波动)
- 与上一个检测到的峰值保持最小时间间隔(避免误检重复峰或噪声尖峰)
C#代码实现示例
using System; using System.Collections.Generic; public class PpgPeakDetector { // 可根据设备采样率调整的参数 private const int WindowSize = 30; // 滑动窗口长度(假设采样率100Hz,对应0.3秒) private const int MinPeakInterval = 15; // 最小峰间距(采样点数) private const double ThresholdMultiplier = 1.5; // 阈值为均值+1.5倍标准差 private Queue<double> _rawSignalQueue = new Queue<double>(); private Queue<double> _filteredSignalQueue = new Queue<double>(); private int _samplesSinceLastPeak = 0; // 移动平均滤波:平滑原始信号,去除高频噪声 private double ApplyMovingAverage(double newSample) { _rawSignalQueue.Enqueue(newSample); if (_rawSignalQueue.Count > WindowSize) { _rawSignalQueue.Dequeue(); } double sum = 0; foreach (var sample in _rawSignalQueue) { sum += sample; } return sum / _rawSignalQueue.Count; } // 计算窗口内信号的均值与标准差,用于生成自适应阈值 private (double mean, double stdDev) GetWindowStats() { if (_filteredSignalQueue.Count == 0) return (0, 0); double sum = 0; foreach (var val in _filteredSignalQueue) sum += val; double mean = sum / _filteredSignalQueue.Count; double sumSq = 0; foreach (var val in _filteredSignalQueue) sumSq += Math.Pow(val - mean, 2); double stdDev = Math.Sqrt(sumSq / (_filteredSignalQueue.Count - 1)); return (mean, stdDev); } // 实时峰值检测接口:输入原始采样值,返回是否检测到峰值及峰值大小 public bool DetectPeak(double rawSample, out double peakValue) { peakValue = 0; _samplesSinceLastPeak++; // 1. 预处理:对原始信号做滤波 double filteredSample = ApplyMovingAverage(rawSample); _filteredSignalQueue.Enqueue(filteredSample); if (_filteredSignalQueue.Count > WindowSize) { _filteredSignalQueue.Dequeue(); } // 窗口数据不足时,暂不检测 if (_filteredSignalQueue.Count < WindowSize) return false; // 2. 判定斜率变化:当前点由上升转为下降 var filteredArray = _filteredSignalQueue.ToArray(); int currentPos = filteredArray.Length - 1; double prevSlope = filteredArray[currentPos] - filteredArray[currentPos - 1]; double currSlope = filteredArray[currentPos - 1] - filteredArray[currentPos - 2]; bool slopeCondition = prevSlope <= 0 && currSlope > 0; if (!slopeCondition) return false; // 3. 检查最小峰间距,避免误检 if (_samplesSinceLastPeak < MinPeakInterval) return false; // 4. 检查是否超过自适应阈值 var (mean, stdDev) = GetWindowStats(); double threshold = mean + ThresholdMultiplier * stdDev; if (filteredSample < threshold) return false; // 满足所有条件,判定为峰值 peakValue = filteredSample; _samplesSinceLastPeak = 0; return true; } } // 调用示例 public class Program { public static void Main() { var detector = new PpgPeakDetector(); // 这里替换为从PPG设备实时读取的数据流 double[] simulatedPpgData = { 0.2, 0.3, 0.5, 0.8, 1.2, 1.0, 0.7, 0.4, 0.3, ... }; foreach (var sample in simulatedPpgData) { if (detector.DetectPeak(sample, out double peak)) { Console.WriteLine($"检测到峰值: {peak:F2}"); } // 模拟采样间隔,对应设备实际采样率(比如100Hz则间隔10ms) System.Threading.Thread.Sleep(10); } } }
调优建议
- 根据PPG设备实际采样率调整
WindowSize和MinPeakInterval:比如采样率200Hz时,窗口大小可设为60,最小峰间距设为30 - 若基线漂移严重,可在预处理阶段加入差分滤波(计算当前点与前一点的差值),进一步消除基线影响
- 阈值乘数
ThresholdMultiplier可根据信号质量调整:噪声较大时适当增大,信号平稳时可减小
内容的提问来源于stack exchange,提问作者GORI
相关产品推荐
相关产品推荐

