在Flutter Dart中实现Pan_Tompkins_QRS检测以计算ECG实时心率
用Dart实现Pan-Tompkins QRS检测算法计算实时心率
核心需求
- 硬件配置:通过AD8232心电传感器、蓝牙模块、RP2040开发板获取ECG原始信号读数
- 目标:完全基于Dart实现Pan-Tompkins QRS检测算法,实时计算心率,替代Chaquopy方案以避免应用体积过度增大
Pan-Tompkins算法Dart实现思路
Pan-Tompkins算法通过多阶段信号处理识别QRS波(心电信号中对应心跳的特征波),进而通过RR间期计算心率。核心步骤如下:
- 带通滤波:去除基线漂移(低频干扰)和工频干扰(如50/60Hz高频噪声)
- 微分运算:增强QRS波的斜率特征,区分QRS波与其他心电成分
- 平方运算:放大QRS波的幅值差异,抑制非QRS成分
- 移动窗口积分:整合QRS波的能量,生成便于阈值判断的波形
- 自适应阈值判定:识别QRS波峰,统计RR间期并计算实时心率
关键代码实现示例
1. 带通滤波实现(二阶巴特沃斯滤波)
class BandpassFilter { // 针对250Hz采样率,通带0.5-40Hz的巴特沃斯滤波系数 final List<double> _b = [0.0003, 0.0013, 0.0019, 0.0013, 0.0003]; final List<double> _a = [1.0, -3.7749, 5.2478, -3.3381, 0.8653]; List<double> _x = List.filled(5, 0.0); List<double> _y = List.filled(5, 0.0); double process(double input) { _x[0] = _x[1]; _x[1] = _x[2]; _x[2] = _x[3]; _x[3] = _x[4]; _x[4] = input; _y[0] = _y[1]; _y[1] = _y[2]; _y[2] = _y[3]; _y[3] = _y[4]; _y[4] = _b[0]*_x[4] + _b[1]*_x[3] + _b[2]*_x[2] + _b[3]*_x[1] + _b[4]*_x[0] - _a[1]*_y[3] - _a[2]*_y[2] - _a[3]*_y[1] - _a[4]*_y[0]; return _y[4]; } }
2. 微分、平方与移动窗口积分
class QRSProcessor { final BandpassFilter _filter = BandpassFilter(); List<double> _diffBuffer = List.filled(4, 0.0); List<double> _integralBuffer = List.filled(30, 0.0); // 250Hz采样下120ms窗口 double processSample(double ecgSample) { // 1. 带通滤波 double filtered = _filter.process(ecgSample); // 2. 微分运算 _diffBuffer[0] = _diffBuffer[1]; _diffBuffer[1] = _diffBuffer[2]; _diffBuffer[2] = _diffBuffer[3]; _diffBuffer[3] = filtered; double diff = (-(1*_diffBuffer[0]) + 1*_diffBuffer[1] + 2*_diffBuffer[2] + 1*_diffBuffer[3]) / 8; // 3. 平方运算 double squared = diff * diff; // 4. 移动窗口积分 _integralBuffer.removeAt(0); _integralBuffer.add(squared); double integral = _integralBuffer.reduce((a, b) => a + b) / 30; return integral; } }
3. QRS波峰检测与心率计算
class HeartRateCalculator { final QRSProcessor _processor = QRSProcessor(); double _threshold = 0.0; double _noiseThreshold = 0.0; List<int> _rrIntervals = []; int _sampleCount = 0; static const int sampleRate = 250; // 与AD8232采样率匹配 int? processEcgSample(double ecgSample) { _sampleCount++; double processed = _processor.processSample(ecgSample); // 自适应阈值初始化(前2秒数据) if (_sampleCount < sampleRate * 2) { _threshold = processed * 0.5; _noiseThreshold = processed * 0.25; return null; } // QRS波峰判定 if (processed > _threshold) { // 记录RR间期(毫秒) if (_rrIntervals.isNotEmpty) { int interval = ((_sampleCount - _rrIntervals.last) * 1000) ~/ sampleRate; _rrIntervals.add(_sampleCount); // 保留最近10个RR间期计算平均心率 if (_rrIntervals.length > 10) _rrIntervals.removeAt(0); // 计算心率:60000ms / 平均RR间期 int avgInterval = _rrIntervals.sublist(1).map((e) => e - _rrIntervals[_rrIntervals.indexOf(e)-1]).reduce((a, b) => a + b) ~/ (_rrIntervals.length - 1); return 60000 ~/ avgInterval; } else { _rrIntervals.add(_sampleCount); return null; } } else if (processed > _noiseThreshold) { // 更新噪声阈值 _noiseThreshold = 0.9 * _noiseThreshold + 0.1 * processed; } // 更新信号阈值 _threshold = 0.9 * _threshold + 0.1 * processed; return null; } }
注意事项
- 采样率匹配:代码中默认250Hz采样率,需与AD8232实际配置一致,否则需调整滤波系数、积分窗口等参数
- 实时性处理:通过Dart异步流(
Stream)处理蓝牙传输的ECG数据,避免阻塞UI线程 - 基线校正:若原始信号基线漂移严重,可在滤波前加入滑动平均基线校正步骤
- 阈值优化:可根据实际信号调整阈值更新的权重系数,提升QRS波识别准确率
内容的提问来源于stack exchange,提问作者PremchandGat
相关产品推荐
相关产品推荐

