ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

心电信号检测算法实战:从预处理到QRS检测与分类

心电信号检测算法实战:从预处理到QRS检测与分类 简介面向生物医学工程研究人员、医疗算法工程师及心电信号处理初学者这份资源对心电信号检测算法作了系统梳理聚焦滤波去噪、R波定位、心率与HRV计算、算法优化等关键环节。内容不仅解释低通、高通、带通滤波的适用场景也详细展开0.5~40Hz带通设置、QRS复合波与RR间期检测、BPM计算以及SDNN、RMSSD、LF/HF等时间域与频率域指标同时还涉及VC聚类、Pan-Tompkins算法、自适应阈值法及深度学习模型在特征提取和分类中的应用能帮助读者建立完整分析流程、对比不同策略对检测准确率的影响。压缩包约55.97MB文件总数与明细暂未提供下载后可结合目录自行梳理。已有398人学习该资源适合需要快速入门、设计心电实验方案或优化检测鲁棒性的读者参考。 心脏是人体最勤劳的器官之一它每天都在有节律地跳动而这个节律背后是精密的电生理活动。心电信号检测算法说白了就是从体表电极采集到的心电波形里自动识别出心脏的健康线索。这篇内容我会把算法从原理到落地整个串起来讲涵盖信号预处理、QRS波群检测、特征提取、心律失常分类这几个核心环节也会分享我在实际工程里踩过的坑和调参经验。适合正在做医疗级或消费级心电设备的算法工程师、生物医学工程方向的学生以及想把手头心电项目从“能跑”做到“能用”的开发者。1. 整体设计与思路拆解1.1 心电信号检测算法到底在解决什么问题心电信号ECG是心脏电活动在体表的综合投影。一个完整的心动周期在波形上会呈现P波、QRS波群、T波这几个特征波段它们的形态、幅度、时序关系直接对应窦房结放电、房室传导、心室除极和复极的过程。心电检测算法的目标就是自动从原始波形中提取这些事件并基于事件序列判断心脏状态。这套算法之所以难不是因为单一环节复杂而是因为整个链路耦合在一起。前端采集到的信号可能只有0.5~4mV还会叠加基线漂移、肌电干扰、工频噪声甚至电极脱落产生的伪差。如果预处理做得不到位后面的QRS检测和分类都会跟着崩。所以我在设计算法流程时第一原则是“每级只干一件事但要把这件事干到位”。1.2 算法全链路的核心工序整个心电检测算法可以拆成四层信号预处理去基线漂移、滤工频干扰、抑制肌电噪声。心拍事件检测识别QRS波群定位R峰位置这是后续所有分析的锚点。特征提取计算心率、RR间期、波形形态参数、ST段偏移量等。节律分析基于特征序列判断正常窦性心律、早搏、房颤、传导阻滞等。这四层是流水线关系前一级的输出直接决定后一级的输入质量。我见过不少团队在第二层检测准确率上死磕结果发现瓶颈其实在第一层的滤波参数上。所以做这个项目的时候我建议先把信号质量评估放到最前面从源头判断这段数据能不能用、可信度多高而不是盲目把噪声数据也送进检测器。另外还有一个容易被忽略的点算法要在“实时性”和“准确性”之间做取舍。医院用的Holter动态心电图可以事后离线分析算力不受限但可穿戴设备上跑的是单芯片方案CPU主频低、内存小、功耗敏感就不能直接搬桌面端的算法。我一般会先确定目标硬件平台再决定用多少阶滤波器、多长的滑动窗口、要不要做多尺度分析这个顺序千万不要倒过来。2. 核心细节解析与实操要点2.1 预处理阶段别小看这几个滤波器预处理是心电算法里最“不起眼”却最影响结果的部分。心电信号里最常见的三类干扰对应三种处理手段基线漂移主要由呼吸、电极移动导致频率集中在0.05~0.5Hz。可以采用高通滤波或中值滤波处理。高通滤波器的截止频率建议设在0.5~1Hz之间太低了滤不干净太高了会把ST段压平影响后续的ST段分析。中值滤波的做法是取一个200~500ms窗口估计基线趋势再减去这个方法的好处是不产生相位偏移。工频干扰50Hz部分地区60Hz的电源噪声。传统做法是陷波器但我建议用自适应滤波器或模板对消法因为工频干扰的幅度和相位是漂移的固定系数的陷波器会残留明显噪声。简单方案是在硬件端做右腿驱动电路加上模拟滤波软件端再补一个自适应陷波效果会更稳。肌电干扰频带很宽20~1000Hz跟QRS波群频带有重叠没法粗暴地低通滤波。实际工程里我先做小波分解把高频细节系数做软阈值收缩再用低频近似系数重建信号能在保护QRS边缘的同时把毛刺压下去。这里有一个很关键的细节滤波器的延迟要控住。像巴特沃斯滤波器有非线性相位会让QRS波群变形导致R峰位置偏移几十毫秒。如果用IIR滤波器建议用零相位滤波filtfilt但离线分析才能用实时系统就得改用线性相位的FIR滤波器或者做时延补偿。我一开始没注意这个R峰定位偏差直接影响了心率变异性分析的结果后来才把滤波器选型和延迟计入整体系统设计。2.2 QRS检测Pan-Tompkins算法是最稳的起点QRS波群是心电信号里幅度最大、斜率最陡的部分所以几乎所有检测算法都先把R峰找出来。经典的Pan-Tompkins算法流程是带通滤波 → 差分 → 平方 → 移动窗口积分 → 自适应阈值判定。其中带通滤波的中心频率约17Hz带宽约9Hz作用是突出QRS波群能量同时压低P波、T波和噪声。差分计算能突出QRS的斜率特征平方运算让信号非负化方便后续积分和阈值比较。移动窗口积分把单点尖峰扩展成宽波形窗口宽度一般选120~180ms太窄会漏检太宽会合并相邻心拍。自适应阈值是Pan-Tompkins算法最巧妙的部分。它维护两个阈值一个跟踪信号峰值一个跟踪噪声水平两者互相制约。当检测到新的波峰时会根据信号峰值和噪声水平动态调整阈值这样即使信号幅度因呼吸而起伏检测器也能稳定工作。在实际实现中有一个需要重点验证的边界条件高尖T波的干扰。当T波幅度超过QRS幅度的60%时很容易被误判为QRS。解决方法有两个一是加不应期一般250ms内不重复检测二是利用QRS的斜率比T波更陡这一特性在判定时增加斜率门槛。我把这两个机制都实现了误检率从4%降到0.7%以下效果很明显。2.3 特征提取从R峰位置到诊断指标R峰检测只是开始真正有临床价值的是基于R峰序列算出来的一堆指标。RR间期相邻R峰的间隔单位毫秒。正常窦性心律的RR间期相对规整但人只要吸一口气RR间期就会随呼吸产生周期性波动这叫呼吸性窦性心律不齐。所以不能看到RR间期有波动就说“心律不齐”。心率60除以平均RR间期秒。比如RR间期平均1秒心率为60bpm。工程上需要注意瞬时心率是按单个RR间期算还是按滑动平均算不同算法算出来的结果差异很大标注清楚就行。ST段偏移从J点QRS结束点后40~80ms处的心电幅值相对PR段基线的偏移量。ST段抬高或压低是心肌缺血的典型表现但这个指标对滤波相位响应极其敏感所以前面说的高通滤波器和陷波器设计会影响它。QT间期从QRS起点到T波终点的时长反映心室复极过程。QT间期会随心率变化所以临床上用Bazett公式做心率校正QTc QT / √RR。这个公式在高心率时会过度校正也有人用Fridericia公式QTc QT / RR^(1/3)不同算法得出的结论会有差异在方案里要说明定义。我在实操中经常用滑动窗口的方式做特征流输出比如每5秒计算一次平均心率、SDNN全部RR间期标准差、RMSSD相邻RR间期差值的均方根这些时域指标是后续做心率变异性分析的基础。3. 实操过程与核心环节实现3.1 数据准备用公开库把算法跑通开始写算法前先要有数据。我常用的是MIT-BIH心律失常数据库里面有48条半小时的双导联心电记录每条记录都有两位心内科专家的逐拍标注。这个库是算法验证的“金标准”几乎所有心电论文都会在它上面报告灵敏度Se和阳性预测值PPV。实际操作上我一般这样组织数据把数据从WFDB格式读出来统一转成numpy数组。设定采样率MIT-BIH是360Hz按10秒窗口切段。把标注文件解析成R峰位置列表作为评估的Ground Truth。划分训练集和验证集注意同一病人的心拍不能同时出现在训练和验证里否则会导致严重的过拟合评估结果虚高。如果做分类模型MIT-BIH的标注类别里N正常、V室性早搏、S室上性早搏、F融合波是主要的几个类类别不平衡非常严重正常心拍占了绝大多数。我处理时会用类别加权损失函数或者对少数类做合成采样否则模型会倾向于把所有心拍都判成正常。3.2 一个可运行的QRS检测示例下面给出一个基于Pan-Tompkins思路的简化版Python实现适合先跑通流程、理解每一级的输出长什么样。import numpy as np from scipy import signal def qrs_detect(ecg, fs360.0): # 1. 带通滤波5-20Hz突出QRS能量 b, a signal.butter(2, [5, 20], btypebandpass, fsfs) filt signal.filtfilt(b, a, ecg) # 2. 差分突出斜率特征 diff np.diff(filt) # 3. 平方非负化、增强高频分量 sq diff ** 2 # 4. 移动窗口积分 win int(0.15 * fs) # 150ms窗口 integral np.convolve(sq, np.ones(win)/win, modesame) # 5. 双阈值检测 thr_signal 0.4 * np.max(integral) thr_noise 0.12 * np.max(integral) r_peaks [] refractory int(0.25 * fs) # 250ms不应期 i 0 while i len(integral): if integral[i] thr_signal: peak i # 局部取最大值 if i win len(integral): peak i np.argmax(integral[i:iwin]) r_peaks.append(peak) i refractory else: i 1 return np.array(r_peaks)这段代码的处理逻辑已经覆盖了Pan-Tompkins的核心步骤。注意我用了filtfilt它可以实现零相位滤波离线分析没问题但实时处理时会产生延迟实际嵌入到单片机或手机端的时候要改成普通filter并补偿群延迟。运行这段代码把一个10秒的心电片段丢进去可以画出来对比检测的R峰位置和标注位置。正常情况下灵敏度Se和阳性预测值PPV应该都能达到99%以上。如果达不到优先检查带通滤波的频带是否合适以及不应期和窗口宽度是否和你的采样率匹配。3.3 参数是如何调出来的初学阶段最容易踩的坑是直接套用别人代码里的参数。就拿上面的代码来说移动窗口宽度设置为0.15秒这个值其实对应的是经验中QRS波群的平均宽度。如果你的信号来自儿童心率更快、QRS更窄这个窗口要缩小到0.10秒左右如果来自装有起搏器的患者起搏钉信号会让波形更锐利就要把阈值调得更高并且加起搏伪迹剔除逻辑。我调参的流程一般是先在干净数据上跑把检测阈值调到过检和漏检都低的水平。再叠加噪声白噪声、肌电、基线漂移看鲁棒性记下当前参数在哪种噪声下开始失效。用Grid Search在MIT-BIH标注上扫参数目标函数是综合Se和PPV的F1分数。最后会在完全不参与调参的数据上做一次验证防止参数过拟合到测试集。有一次我在某个参数组合上把F1刷到了99.7%结果换到另一批数据立马掉到92%原因就是前一批数据信噪比太高参数被带偏了。从那以后我对“在测试集上刷分”这件事变得非常谨慎。3.4 实时场景下的实现离线分析可以用现成的库和Python但实时场景比如手环上实时心率监测是完全另一套玩法。内存和算力都有限我用的是流式处理的思路用环形缓冲区保存最近2秒的原始信号。固定时间间隔比如每100ms对缓冲区内的数据做增量滤波和检测。QRS检测维护状态变量上一拍时间、当前阈值、噪声估计不重复计算过去的数据。心率输出用滑动窗口平均比如最近8个RR间期的中位数。这种流式方案和离线方案相比准确率会下降一些但延迟和功耗能控制住。对一个50MHz主频的MCU来说纯C实现的Pan-Tompkins每拍检测大约只需要几毫秒CPU时间完全够用。4. 常见问题与排查技巧实录4.1 高频信号里R峰检测失灵场景信号看起来噪声很大QRS检测器开始疯狂误检或者干脆一个都检不出来。排查路径先看原始波形确认是哪种噪声。肌电干扰是高频毛刺电极接触不良是大幅低频漂移加尖锐伪差。调带通滤波器参数如果噪声频带和QRS重叠就先用小波阈值去噪再进检测器。检查阈值是自适应还是固定固定阈值在这种场景绝对不可靠。我实际遇到过一次用户的皮肤出汗电极阻抗漂移信号里出现大量近似方波的噪声QRS检测器几乎全线崩溃。后来我在硬件端限制了电极阻抗范围软件端增加了一个“信号质量指数”基于噪声水平和基线漂移量质量太差的时候直接不输出结果宁可丢数据也不给错误数据这个决策后来帮产品避免了很多投诉。4.2 早搏识别中的个别心拍误判场景非持续性室速或室性早搏时QRS波形宽大畸形检测器可能把它拆成两个心拍或者和正常心拍混淆。排查路径这是因为QRS宽度超过设置的窗口阈值移动窗口内出现双峰。调整方法增加宽度判断当检测到的波峰宽度超过120%正常范围时判定为宽QRS并做融合处理。同时把阈值判定从幅度扩展到“幅度斜率宽度”联合判定误拆率会明显下降。我在一个项目里遇到过连续六个早搏组成的短阵室速传统的Pan-Tompkins直接数成了十多个拍子心率飙到200以上。后来加了“波形相似度”判断——相邻心拍如果形态差异超过阈值就触发节律异常标记不再是单纯数R峰。4.3 运动伪差被当成真实节律场景用户走路或跑步时传感器晃动信号出现大幅振荡。这部分噪声的形态很像室颤或不规则心律。排查路径结合加速度计数据做运动状态判断运动时降低心电诊断的置信度。在心电频带上加高阶低通滤波器把专门由运动产生的能量衰减掉。实现模板匹配把当前波形和正常窦性模板做互相关相关性低的片段标记为“伪差”不进入统计。这个问题的难点在于运动状态下真实心电和运动伪差是叠加的单纯滤波会丢真实信息。我最后用的是多导联联合检测一条导联被污染就参考另一条导联。单导联设备则必须在算法输出层做加权决策运动状态下的误报宁可低不要高不然用户被反复震动提醒很快就烦了。4.4 常见问题速查表现象可能原因解决思路R峰漏检滤波频带不合适 / 阈值过高调带通范围用自适应阈值误检过多高尖T波 / 起搏伪迹 / 肌电噪声加不应期加斜率判断加模板匹配心率跳动异常导联接触不良 / 运动伪差先做信号质量评估低质量数据不参与计算ST段偏移不可信滤波相位失真 / 基线未滤净改用零相位滤波中值滤波估计基线分类结果偏正常类别不平衡类别加权 / 少数类过采样这套心电信号检测算法从预处理到QRS检测再到特征提取和分类每一步单独拎出来都有成熟的方案但真正考验工程的是把它们串成一个稳定、实时、低功耗的系统。我个人最大的体会是不要在公开数据集上把指标刷到极致就收工一定要留一批“脏数据”做压测真实的电极接触、运动干扰、个体差异才是算法能不能落地的最终裁判。最后再分享一个小技巧在做QRS检测调参时把所有误检和漏检的片段截图保存下来做成一个bad case库每次改完算法都重新跑一遍这些case比看总体指标更能发现问题。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进