排查 R-peak 检测异常:QvosAgent 如何调查 NeuroKit2 问题
发布时间:2026-05-08 | 标签:心电图, 信号处理, NeuroKit2, 调试, 开源AI
问题的发现
在分析 MIT-BIH 心律失常数据库的真实心电图数据时,用户注意到 QvosAgent 生成的可视化图中有一个奇怪的现象:标记 R-peak 的红点有几个并没有落在心电图波形的峰值上。有些点甚至出现在波谷之间,而不是波峰顶部。
这是计算错误?算法缺陷?还是更微妙的问题?
这正是自主 AI 代理能够展现能力的场景——不仅仅是运行代码,而是调查、研究、对比并解决复杂的技术问题。
调查过程
第一步:复现与分析
QvosAgent 首先使用 MIT-BIH 记录 100(正常窦性心律)复现了分析,这是一段 360Hz 采样率的 10 秒心电图信号。原始代码使用了 NeuroKit2 的 ecg_process() 函数:
df, info = nk.ecg_process(ecg_signal, sampling_rate=fs)
peaks = info['ECG_R_Peaks']
将检测到的峰值与 MIT-BIH 官方标注对比,发现了问题:
| 检测峰值 | 官方标注 | 偏差 |
|---|---|---|
| @2092 | @2044(房性早搏) | 133ms |
| @2375 | @2402(正常) | 75ms |
| @2686 | @2706(正常) | 56ms |
检测点落在了 Q波/S波的谷底(负值区域),而不是 R 波的峰顶(正值区域)——信号振幅差异超过 800mV。
第二步:社区调研
QvosAgent 搜索了 NeuroKit2 的 GitHub 仓库,找到了多个相关问题:
- Issue #620:用户报告默认
neurokit方法将 T 波误检为 R-peak - Issue #752:当 T 波振幅高于 R-peak 时被错误标记,导致心跳间隔翻倍
- Discussion #843:运动伪影导致长录音中 R-peak 定位偏移
- Issue #1064:即使使用
correct_artifacts=True,伪影仍被误检为 R-peak
社区共识很明确:NeuroKit2 默认的基于梯度的检测方法在异常波形下存在已知局限性。
第三步:方法对比
QvosAgent 测试了三种不同的峰值检测方法:
# 方法1:直接调用 ecg_findpeaks
peaks = nk.ecg_findpeaks(clean_signal, sampling_rate=fs, method='neurokit')
# 方法2:Nabian 2018(社区推荐)
peaks = nk.ecg_findpeaks(clean_signal, sampling_rate=fs, method='nabian2018')
# 方法3:Pan-Tompkins(经典算法)
peaks = nk.ecg_findpeaks(clean_signal, sampling_rate=fs, method='pantompkins1985')
出人意料的发现
| 方法 | 检测数 | 匹配数 | 平均偏差 | 最大偏差 |
|---|---|---|---|---|
| NeuroKit (ecg_findpeaks) | 12 | 12/12 | 0.5ms | 2.8ms |
| Nabian2018 | 11 | 11/11 | 3.0ms | 5.6ms |
| Pan-Tompkins | 12 | 12/12 | 14.4ms | 44.4ms |
| 12 | 9/12 | 22.5ms | 133.3ms |
根本原因不是算法本身,而是 API 使用方式的问题。 当 ecg_process() 内部调用峰值检测时,使用了与直接调用 ecg_findpeaks() 不同的实现路径。直接调用产生了近乎完美的结果,平均偏差仅 0.5ms。

关键经验
- 信号清洗与峰值检测分开进行 — 使用
ecg_process()清洗信号,然后用ecg_findpeaks()显式指定检测方法 - 社区知识很重要 — GitHub 问题揭示了这是已知局限性,且有文档化的解决方案
- 多种方法应该对比 — 测试三种算法揭示了最佳方案
- 自主调查有效 — QvosAgent 独立复现了问题、研究了社区讨论、对比了替代方案、找到了根本原因
推荐代码
import neurokit2 as nk
# 第一步:清洗信号
df, info = nk.ecg_process(ecg_signal, sampling_rate=fs)
clean_signal = df['ECG_Clean'].values
# 第二步:显式指定检测方法
peaks = nk.ecg_findpeaks(clean_signal, sampling_rate=fs, method='neurokit')
r_peaks = peaks['ECG_R_Peaks']
# 替代方案:Nabian 2018 方法
peaks = nk.ecg_findpeaks(clean_signal, sampling_rate=fs, method='nabian2018')
本分析完全由 QvosAgent 自主完成,QvosAgent 是一个开源的本地 AI 代理,能够自主调查和解决技术挑战。完整代码和数据可供复现。