☰
Python心电算法全流程:从滤波到房颤分类的工程实践
2026/10/3 3:32:05 网站建设 项目流程

简介:这是一份完整的Python心电算法源码工程,面向生物医学信号处理和医疗数据分析方向的开发者与学生,覆盖ECG进入分析流程的核心环节。压缩包共91个文件、约30.81MB,含17个py算法源码(滤波、R波识别、特征提取、心律失常分类)、16组dat/hea/atr心电记录与标注、7个xws中间文件、6张结果对比图及4篇参考论文;data目录另含MIT-BIH心律失常数据转换脚本,tests目录配套验证用例。内容包括巴特沃斯等数字滤波、基于小波变换的R波定位与R-R间期心率计算,进一步提取QRS幅度、QT间期等特征,用SVM、随机森林识别房颤、室颤/室速,并针对导联脱落等伪差干扰专门处理;matplotlib可视化可展示原始波形、滤波结果与R波位置。已有673人学习下载,适合课程设计、科研预实验或算法岗工程参考。

1. 用 Python 写心电算法:从滤波到房颤分类,这套代码仓库把全流程都跑通了

心电信号处理的难点,从来不只是“调用几个库”。真正折磨人的是导联脱落产生的伪差、基线漂移、50Hz 工频干扰,以及最容易被误判的 T 波——它长得比 R 波还像 R 波。这套ecg-algorithm-main源码仓库,把滤波、R 波识别、心率计算、心律失常分类、房颤识别、室颤室速检测和可视化全部串成了一条可运行的流水线,而且自带 mit-bih-arrhythmia 和 mit-bih-noise 两个标准数据库的读取脚本,不是那种只有一个 README 的玩具项目。我拆完整个代码树后发现,最有价值的不是某个单一算法,而是它把“从原始信号到诊断结论”的全链路打通了,中间还附带了伪差干扰研究子项目和论文列表。适合正在做心电信号处理课程设计、毕业设计,或者刚进医疗算法岗想快速搭一套基线系统的工程师。

2. 滤波预处理:平滑信号,但别把 QRS 波群抹平了

2.1 为什么滤波顺序不能乱

心电信号里混杂的噪声源很固定:50Hz 工频干扰、肌电噪声、呼吸引起的基线漂移。这套代码在signal_process.py里专门封装了信号预处理模块,用scipy.signal做数字滤波。处理顺序值得注意——不是所有滤波都能任意排列。

常见的处理链路是先做带通滤波保留 0.5Hz 到 40Hz 的有效频段,再做 50Hz 陷波去掉工频。如果你先陷波再带通,陷波器在低频段可能引入相位畸变,后续带通滤波会把这种畸变放大。更隐蔽的问题是 Python 的scipy.signal里butter和filtfilt的组合——filtfilt是零相位滤波,适合离线处理,但如果你用lfilter做实时处理,相位延迟会直接影响 R 波定位的精度。

from scipy.signal import butter, filtfilt def bandpass_filter(data, fs=250.0, lowcut=0.5, highcut=40.0, order=4): nyquist = 0.5 * fs low = lowcut / nyquist high = highcut / nyquist b, a = butter(order, [low, high], btype='band') return filtfilt(b, a, data)

这段代码是心电预处理的基础模板。order=4是巴特沃斯滤波器的常用阶数,高阶数衰减更陡但相位失真更大;lowcut=0.5Hz是为了切掉基线漂移,highcut=40Hz是为了保留 QRS 波群的主要能量同时去掉高频肌电噪声。注意filtfilt传入的是零相位滤波,对整段信号做两次滤波抵消相位偏移,代价是不能流式处理。

2.2 滑动窗口滤波的适用边界

热词里反复出现“滑动窗口滤波”,这套代码在伪差干扰研究子模块里有用到类似的思路。滑动窗口其实是个双刃剑——窗口太大,信号被过度平滑,P 波和 T 波这种低幅波可能被抹掉;窗口太小,噪声过滤不干净。

def moving_average(data, window_size=5): kernel = np.ones(window_size) / window_size return np.convolve(data, kernel, mode='same')

window_size=5在 250Hz 采样率下对应 20ms 窗口,这个量级对高频毛刺有压制效果,但不会损伤 QRS 主波。如果你发现滤波后 R 波振幅反而变小了,先检查window_size是不是设成了 15 甚至更大——那种参数适合做基线估计,不适合做信号保真。

对实时性有要求的场景,我一般会把mode='same'换成mode='valid',后者不补零,输出长度会缩短,但不会引入边界伪影。心电分析里边界伪影是个很坑的问题,filtfilt本身在信号起止段也可能产生振铃,处理长记录时建议丢弃前后各 200 个采样点。

3. R 波检测:Pan-Tompkins 算法与 T 波误检的博弈

3.1 从双斜率到自适应阈值

R 波识别是整个心电分析链条的基石。心率计算、HRV 分析、心律失常分类都依赖准确的 R 波位置。这套代码里用的是 Pan-Tompkins 的变体实现——先对信号求导,再平方,再滑动积分,通过自适应阈值定位 R 波。这是目前工程落地最稳的方案,深度学习模型虽然准,但可解释性和计算量都吃亏,不适合实时场景。

def pan_tompkins_qrs_detect(data, fs=250.0): diff_signal = np.diff(data) squared = diff_signal ** 2 window_width = int(0.12 * fs) kernel = np.ones(window_width) / window_width integrated = np.convolve(squared, kernel, mode='same') threshold = 0.3 * np.max(integrated[:fs * 2]) peaks = [] for i in range(1, len(integrated) - 1): if integrated[i] > threshold and integrated[i] > integrated[i-1] and integrated[i] > integrated[i+1]: peaks.append(i) return peaks

这段逻辑的核心在于三处:平方运算放大了 R 波与 T 波的幅值差距;0.12 * fs的窗口对应 120ms,恰好是 QRS 波群的典型宽度;阈值取前两秒信号最大值的 30%,这是自适应策略——信号幅值变化时阈值跟着变,避免固定阈值在振幅突变时失效。我实际跑下来的经验是,这个 30% 阈值在数据质量好的标准库上表现不错,但遇到噪声大的移动心电数据,要降到 20% 左右。

3.2 误检的高发区:T 波和噪声尖峰

Pan-Tompkins 最经典的失败模式是 T 波误检。当心率升高时,T 波振幅可能接近 R 波,特别是在 V2、V3 导联。排错时先看 R-R 间期的直方图——如果出现一个很短的间隔(比如 200~300ms)和一个正常间隔交替出现,几乎可以断定是 T 波被当成 R 波了。

处理办法是加不应期:检测到 R 波后 200ms 内不允许再次检测,这是利用心肌不应期的生理特征。如果要更精细的边界,可以对比相邻 R-R 间期,如果后一个间期小于前一个的 60%,多半是误检,需要回溯调整阈值。

4. 心率计算与 RR 间期分析:数据清洗比公式更重要

4.1 从 R 波位置到心率值

心率计算本身很简单:RR 间期取倒数乘以 60。但真正决定心率曲线质量的,是 R-R 间期的清洗。采集过程中电极松动会产生异常尖峰,导致某个 R-R 间期突然变成 250ms 或 2000ms,如果不处理,瞬时心率会跳到 240bpm 或降到 30bpm——这种数据医生根本没法看。

def compute_heart_rate(r_peaks, fs=250.0): rr_intervals = np.diff(r_peaks) / fs valid_indices = (rr_intervals > 0.4) & (rr_intervals < 2.0) clean_intervals = rr_intervals[valid_indices] inst_hr = 60.0 / clean_intervals avg_hr = np.mean(inst_hr) return rr_intervals, inst_hr, avg_hr

0.4 到 2.0 秒的过滤区间对应 30~150bpm,这是一个临床合理的动态范围。低于 0.4 秒要怀疑 T 波误检,高于 2 秒要怀疑漏检。注意这里删掉的只是异常间期,不是直接丢数据点——保留位置信息和对应的时间戳,后续分析伪差分布还要用到。

4.2 HRV 特征:时域指标是分类器的前菜

如果目标不只是看心率,还要做心律失常分类,RR 间期本身就能算出一组特征:SDNN(全部 RR 间期的标准差)、RMSSD(相邻差值的均方根)、pNN50(相邻差值超过 50ms 的比例)。这些时域指标在feature_extraction相关模块里都有实现,复杂度不高但很实用——房颤最典型的表现就是 RR 间期绝对不齐,RMSSD 会显著偏高。

5. 源码仓库结构拆解:每个目录是干什么的,改造从哪里下手

5.1 目录映射与数据流

把项目解压后,整个目录树是研究导向的工程结构,不是教学示例那种单文件脚本。核心入口是ecg包,basic_alg和deep_learning两个子目录分别对应传统信号处理和深度学习方法;data下按 mit-bih-arrhythmia、mit-bih-noise、aha 三个目录存放不同来源的数据集和读取脚本,wfdb_read.py是专门读 MIT-BIH 格式的。docs里放了论文列表paper_list.md——我扫了一遍,里面收录的文献从实时 QRS 检测到小波变换划分都有,属于经典索引,适合刚入门的人按图索骥找精读材料。

完整数据流是:wfdb_read.py读取信号 →signal_process.py预处理 → R 波检测 → 特征提取 → 分类器(sklearn或tensorflow)。这条链路就是整个仓库的骨架。

5.2 测试工程:不跑一遍tests等于没拿到这个仓库

tests目录里带着基础算法的 demo 脚本。这些测试用例的写法适合当模板学——每个算法模块对应一组输入输出断言,测试数据用的是真实心电信号而不是正弦波模拟。运行方式很简单:

python -m pytest tests/ -v

如果环境缺依赖,先检查requirements.txt。跑测试的作用不只是验证功能,也是校准环境参数——比如scipy版本不同,butter滤波器的数值行为会有细微差别,先跑测试能提前发现版本不兼容的问题。

6. 避坑与常见问题:4 条踩过的坑,照着检查能省一天时间

6.1 现象:滤波后信号变成一条直线

原因:带通滤波参数设错,可能是lowcut大于highcut,或者采样率fs和信号实际频率不匹配。有个非常隐蔽的细节是scipy.signal.butter的归一化用的是奈奎斯特频率,如果你把 250Hz 的采样率写成 250,但normal=True的写法对不上,滤波器设计出来全是 NaN。解决:打印滤波器的b、a系数,检查是否有 NaN;再画出滤波前后的频谱图对比,确认通带是否落在 0.5~40Hz。

6.2 现象:R 波检测结果里全是密集的小尖峰

原因:阈值设太低,或者信号里有高频毛刺没滤干净。最常见的是先看了滤波代码就把滤波直接注释掉了,或者用了order=8的高阶滤波导致相位畸变,把噪声放大成锯齿波。解决:先跑通滤波模块,把order降到 4,并把阈值改成“前 2 秒最大值的 30%”。看检测结果,如果尖峰间距普遍小于 300ms,把window_width调大到 0.15 秒。

6.3 现象:波形显示正常但心率结果来回跳

原因:R 波检测结果里存在漏检。漏检的典型表现是 R-R 间期图解上出现一个长间期,导致瞬时心率瞬间掉到 40bpm 以下。解决:可视化 RR 间期序列,标注出大于 1.5 秒的异常间期,检查对应位置的信号——多数情况是该处 R 波振幅偏低,低于阈值导致漏检,降低阈值或改用自适应双阈值可以解决。

6.4 现象:分类模型精度到 70% 就上不去

原因:特征不够或标签不均衡。这套仓库里带了深度学习和传统机器学习两套路线,但高级特征(QT 间期、ST 段斜率)依赖准确的波形边界识别,如果 R 波检测本身就带偏差,分类精度自然停滞。解决:先做误差传播分析,把 R 波位置可视化叠加在原始信号上,确认所有 PQRST 波群位置准确,再优化分类器。

7. 落地技巧:把原始心电信号和标注一起可视化

7.1 解析 MIT-BIH 格式并画图

MIT-BIH 数据是 212 格式的二进制文件,wfdb_read.py里做了读取封装。拿到信号后,用ecg_display.py里的可视化工具把原始波形和 R 波标注画在同一张图里——这一步是我每次拿到新数据的第一个动作。

import matplotlib.pyplot as plt from wfdb_read import load_ecg_record from ecg_display import plot_ecg_with_rpeaks signal, rpeaks = load_ecg_record('data/mit-bih-arrhythmia/100') plot_ecg_with_rpeaks(signal, rpeaks, fs=360) plt.show()

7.2 伪差干扰的调参实践

mit-bih-noise数据库的存在,就是为了做伪差研究。实际心电图机里最常见的伪差是导联脱落和运动伪差,后者表现为基线大幅摆动叠加高频尖刺。处理这类数据时,带通滤波的阶数要比处理标准库高 1~2 阶,否则基线漂移滤不干净,R 波阈值也会被带偏。

7.3 参数即后悔药

每次调完滤波器参数就把配置存成一个字典——这份仓库里没有现成配置系统,但极适合自己补上。记录的数据包括:采样率、滤波阶数、窗口宽度、阈值比例。三个月后再翻项目,看着这组参数你能立刻回想起当时为什么这么选。从那以后我每次处理新数据库,都强制自己先跑一遍测试用例、再画一张原始波形带 R 波标注的图、最后才动算法参数。这三步走完,数据质量心里就有数了,后面分类调参翻车的概率能降一半。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询