You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在心率检测图表中过滤小峰值,准确识别有效峰值?

如何在摄像头心率检测中过滤无效小峰值,仅统计有效心跳峰值?

当前基于手机摄像头的心率检测器图表输出效果良好,但峰值计算时会出现黄色标记的小峰值(有效峰值间可能出现多个),需要排除这类无效峰值,只保留对应心跳的有效峰值。此前尝试平滑处理效果不佳,用“最近5个值是否递增”的逻辑识别峰值稳定性很差,现有代码如下:

void _scanImage(CameraImage image) {
  _actual = DateTime.now();

  _avgRedValue = image.planes.first.bytes.reduce((value, element) => value + element) / image.planes.first.bytes.length;

  if (_data.length >= _displayLength) {
    _data.removeAt(0);
  }

  setState(() {
    _data.add(Value(_actual, 255 - _avgRedValue));

    if (_data.length >= 7) {
      int lastIndex = _data.length - 1;
      List<double> last7Values = _data
          .sublist(lastIndex - 5, lastIndex)
          .map((value) => value.value)
          .toList();
      double lastValue = _data[lastIndex].value;
      double previousValue = _data[lastIndex - 1].value;

      if (lastValue > previousValue && lastValue > _data[lastIndex - 2].value &&
          _isIncreasing(last7Values)) {
        // 检测到潜在峰值
        peakValues.add(lastValue);
      } else if (peakValues.isNotEmpty && lastValue < previousValue) {
        // 检测到心跳
        double heartbeatValue = peakValues.reduce((a, b) => a > b ? a : b);
        peakValues.clear();
        int beatNow = DateTime.now().millisecondsSinceEpoch;

        if(previous != 0) {
        }
          if(beatNow - previous > 500){
        print('Heartbeat detected: $heartbeatValue');

        heartbeatCount++;

       
        }

      }


    }
  });
}

bool _isIncreasing(List<double> values) {
  if (values.length < 2) {
    return false;
  }

  for (int i = 1; i < values.length; i++) {
    if (values[i] <= values[i - 1]) {
      return false;
    }
  }

  return true;
}

解决方案

可以从峰值显著性、心跳时间周期、峰值判定逻辑三个维度优化,过滤无效小峰值:

1. 基于滑动基线的阈值过滤

通过计算最近一段时间数据的均值作为基线,只有显著高于基线的峰值才判定为有效,过滤掉正常波动产生的小峰值。

2. 结合心率范围的时间过滤

正常心率范围为60-180次/分钟,对应心跳间隔333ms-1000ms。限制两次有效峰值的时间间隔在这个范围内,避免短时间内的连续小峰值被误判。

3. 改进峰值判定逻辑

放弃“连续严格递增”的严苛要求,改为判定当前值是局部范围内的最高点(比如前后3个点中的最大值),减少局部波动的干扰。

修改后的代码实现

// 先在类中添加几个参数变量
int _lastValidPeakTime = 0;
final int _minBeatInterval = 333; // 最小心跳间隔(对应180BPM)
final int _maxBeatInterval = 1000; // 最大心跳间隔(对应60BPM)
final double _peakThresholdRatio = 1.15; // 峰值需高于基线的比例(可根据实际调整)

void _scanImage(CameraImage image) {
  _actual = DateTime.now();

  _avgRedValue = image.planes.first.bytes.reduce((value, element) => value + element) / image.planes.first.bytes.length;

  if (_data.length >= _displayLength) {
    _data.removeAt(0);
  }

  setState(() {
    _data.add(Value(_actual, 255 - _avgRedValue));

    // 确保有足够的数据计算基线和判定峰值
    if (_data.length >= 10) {
      int lastIndex = _data.length - 1;
      double lastValue = _data[lastIndex].value;
      double prevValue = _data[lastIndex - 1].value;
      double prevPrevValue = _data[lastIndex - 2].value;

      // 1. 判定是否为局部峰值:比前后至少2个点都高
      bool isLocalPeak = lastValue > prevValue && lastValue > prevPrevValue;
      if (lastIndex >= 3) {
        isLocalPeak &= lastValue > _data[lastIndex - 3].value;
      }

      if (isLocalPeak) {
        // 计算滑动基线:取最近20个数据的均值
        int baselineStart = max(0, lastIndex - 20);
        List<double> baselineData = _data.sublist(baselineStart, lastIndex)
            .map((value) => value.value)
            .toList();
        double baseline = baselineData.reduce((a, b) => a + b) / baselineData.length;
        
        // 2. 检查是否高于阈值
        bool meetsThreshold = lastValue > baseline * _peakThresholdRatio;
        
        // 3. 检查时间间隔是否符合心率范围
        int currentTime = _actual.millisecondsSinceEpoch;
        bool meetsTimeInterval = currentTime - _lastValidPeakTime > _minBeatInterval;

        if (meetsThreshold && meetsTimeInterval) {
          int beatNow = currentTime;
          // 同时确保间隔不超过最大心率范围
          if (_lastValidPeakTime != 0 && beatNow - _lastValidPeakTime <= _maxBeatInterval) {
            print('心跳检测到: $lastValue,间隔: ${beatNow - _lastValidPeakTime}ms');
            heartbeatCount++;
          }
          _lastValidPeakTime = beatNow;
        }
      }
    }
  });
}

关键优化点说明

  • 滑动基线:用最近20个数据的均值作为基线,能自动适应光照等环境变化,避免固定阈值的局限性
  • 时间间隔过滤:严格限制有效峰值的时间范围,符合人体心率的生理规律,直接过滤掉密集的小峰值
  • 局部峰值判定:扩大参考范围到前后3个点,减少单次波动带来的误判

内容的提问来源于stack exchange,提问作者Leader ride

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 15:10:20