
简介一份面向心电图ECG分类的完整代码资料包使用Python与MATLAB双语言实现适合生物医学工程、数据分析与机器学习方向的学习者对照实践也可作为课程设计或科研入门的参考模板。包内共825个文件压缩后大小约6.25MB文件类型涵盖py脚本、m脚本、c与h源码、hea/atr/dat等心电数据与标注文件以及WFDB相关工具、readme说明、结果图表等结构完整便于按模块检索与复现。内容覆盖心电信号预处理噪声去除、基线漂移校正、波形特征提取RR间期、QT间期、P-QRS-T段参数以及基于SVM、随机森林等机器学习模型的分类与评估能够帮助读者跑通从原始数据到分类结果的完整链路同时对比Python与MATLAB在信号处理和建模上的不同写法。该资源已有445人学习下载适合希望快速上手心电分析、理解生物医学信号处理流程的读者。1. 一个 ECG 分类压缩包为什么值得从 00README 开始拆第一次解压 ecg_classification-master.zip 时我本准备看一堆 .py 和 .m 脚本结果先看到 00README 和一排带数字尾巴的文件wave.1、bxb.1、nst.1、a2m.1、parsescp.1 等。这是 WFDB 波形数据库工具链的 Unix 手册页说明这份资源给的是从读取、滤波、R 波定位、特征提取到分类评估的一整条 ECG 分类闭环。它用 Python 和 MATLAB 各实现一遍完整流程特别适合被 MIT-BIH 标注格式、时间单位、评估口径卡住的从业者——无论你是想入门 ECG 分类还是想把灵敏度和阳性预测率写到能让评审挑不出毛病。下面按预处理、训练、避坑、验收四步拆。2. ECG 分类的原理与选型预处理为什么比模型更决定上限2.1 信号先验P 波、QRS 波群和 T 波决定滤波与切片参数ECG 信号不是白噪声它有强先验结构。一个正常心拍由 P 波心房去极化、QRS 波群心室去极化和 T 波复极化组成各波段有相对固定的时序和形态。心律失常分类正是靠这些波形的时序混乱比如 RR 间期不齐和形态变异比如 QRS 变宽来判别。MIT-BIH 数据集是比较常见的基准48 条 30 分钟双导联记录MLII 和 V5采样率 360 Hz幅度是 ADC 单位、实测电压要除以 200 得到 mV。下游代码里几乎每个魔法数字都能从这三个先验推出来。滤波参数就是这样选出来的带通下限 0.5 Hz 是为了切掉呼吸和电极移动引起的基线漂移上限 45 Hz 是为了压掉高频肌电干扰这两个值基本覆盖了 P-QRS-T 的主要能量带。R 波检测里的最小间隔 0.3 秒对应心室不应期防止把 T 波当成 R 波。切片长度取 R 峰前 0.25 秒、后 0.4 秒是为了把前一个 T 波结尾和新心拍的 P 波都装进去。新手最常翻车的地方就是跳过这一步直接拿原始信号进分类器——基线漂移会让 ST 段特征面目全非模型再好也是白搭。2.2 Python 与 MATLAB 的选型为什么这个包要写两遍很多读者是冲 matlab_ecg 分类下载的打开后发现还有整套 Python 实现第一反应是冗余。实际上两条路线各管一段。维度Python 路线MATLAB 路线主要库numpy / scipy.signal / scikit-learnSignal Processing Toolbox / Statistics Toolbox滤波与特征butter、filtfilt、welch 自己拼工具箱封装好拼装少、出图快多分类器sklearn 的 SVC、RandomForestfitcecoc、fitcknn 一条命令批量实验脚本改参数方便适合跑批也能跑但启动和授权开销大环境成本python 安装 pip 装库需要 MATLAB 授权和对应工具箱我一般这样分工探索期用 MATLAB 快速验证滤波参数和特征组合方案定下来后用 Python 重写一遍做批量跑批和最终可视化。这套包两条腿都给了等于把「验证 → 落地」两个阶段的工作都覆盖了。要注意的是想靠 matlab 在线网页版这一类免安装途径跑 fitcecoc 基本不现实本地装好授权和工具箱再动手。2.3 包里的 .1 工具链为什么评估环节离不开命令行zip 里那排带 .1 后缀的文件不是乱码是 WFDB 命令的手册页。它们对应的工具各有分工wave 是交互式波形浏览器plt 是批量绘图pschart 输出 ST 段测量图表a2m 把注释文件转成 MATLAB 格式nst 给信号加标定噪声做鲁棒性测试parsescp 解析 SCP-ECG 标准文件bxb 和 epicmp 则是把两套注释逐拍对比并输出灵敏度、阳性预测率的评分工具。这套工具链存在的意义是让评估不依赖你个人写的脚本。网上那些 dqn、ppo 的 MATLAB 强化学习代码走的是另一条路线而这套 ECG 分类走的是经典信号处理加机器学习预处理 → 特征提取 → SVM/ECOC → 交叉验证。在样本量只有几万个心拍、每拍几十个特征的场景下这条路线的可复现性和解释性比黑匣子深度学习强得多而且纯 CPU 就能跑通。读代码时先看它提了哪些特征再看分类器顺序别搞反。3. Python 侧预处理与特征提取实录先把 MIT-BIH 变成特征矩阵3.1 两种数据入口原始 .dat/.hea 和已导出的 CSV/MAT无论你是按 ecg python 关键词找来的还是冲 matlab 版本来的打开脚本之前都要先确认样例数据是哪一种形态。常见做法是两种都兼容一种是标准 MIT-BIH 的 .dat/.hea 原始记录另一种是已经导出好的 CSV 或 MAT 特征表。前者用 wfdb 库读后者用 pandas 或 numpy 直接读。# 方式一wfdb 库直接读 MIT-BIH 原始记录 import wfdb record wfdb.rdrecord(100, sampto7200, channels[0]) # 前 20 秒避免整条读入内存 sig record.p_signal[:, 0] # MLII 导联 fs record.fs # 360 Hz ann wfdb.rdann(100, atr, sampto7200) # 专家参考注释 r_peak_samples ann.sample # R 波所在样本点sampto7200 是“读到第 7200 个样本”的意思360 Hz 下正好 20 秒调试阶段永远只读一小段半小时的整条记录有 64 万样本全读进来不仅慢还占内存。rdann 的 sample 字段是样本序号不是秒转秒要除以 fs这个单位问题后面避坑章还会再踩一次。# 方式二包里的 CSV 特征矩阵 import pandas as pd df pd.read_csv(features.csv) X df.iloc[:, :-1].values y df.iloc[:, -1].valuesCSV 是包作者导出的特征矩阵列一般是特征最后一列是标签。拿到后先用 df.info() 和 df.isna().sum() 检查有没有空值和混入表头的脏行这一步在 ECG 数据里特别重要——MIT-BIH 原始注释经常有边界情况导出时容易产生 NaN。3.2 预处理三件套带通、零相位、基线校正import numpy as np from scipy.signal import butter, filtfilt from scipy.ndimage import median_filter def preprocess_ecg(sig, fs360.0, low0.5, high45.0, order4): # 带通0.5 Hz 切基线漂移45 Hz 切肌电尽量保留 P-QRS-T 能量 b, a butter(order, [low / (0.5 * fs), high / (0.5 * fs)], btypeband) filtered filtfilt(b, a, sig) # 零相位R 波位置不被平移 # 基线再校正0.5 秒中值窗口跟住缓慢基线减掉后 ST 段不再倾斜 baseline median_filter(filtered, sizeint(fs / 2)) return filtered - baseline几个参数值得抠。filtfilt 是零相位滤波正向反向各滤一次输出没有相位延迟如果换成 lfilterR 波位置会被整体平移后面所有特征跟着错位。order4 是 Butterworth 的阶数阶数越高过渡带越陡但数值稳定性变差一般 4 到 6 阶够用。中值滤波窗口取 fs/2 即 0.5 秒这个尺度能跟住基线慢变化又不会吃掉 QRS 本身的形态。注意 butter 的 fs 写法是 scipy 新版的风格老代码里常见 nyq 写法如果你用新版本 scipy 跑老脚本会看到 FutureWarning 甚至报错环境问题集中放到避坑章处理。3.3 R 波定位与心拍切片from scipy.signal import find_peaks def loc_rpeaks(filtered, fs360.0): d np.abs(np.diff(filtered)) # 差分增强 QRS 陡峭边沿 peaks, _ find_peaks(d, height0.3 * np.max(d), distanceint(0.3 * fs)) return peaks / fs # 转成秒这里用差分而非原始幅度是因为 QRS 的斜率远大于 P 波和 T 波差分后 R 波顶点是明显极大值。height 取最大值的 0.3 倍相当于一个自适应阈值避免不同记录间幅度差异导致漏检distance0.3*fs 是 300 毫秒不应期专门防 T 波被误检。工程上更完整的是 Pan-Tompkins 那套积分窗口但先跑通这个简化版再看代码里的 R 波路径思路更清楚。拿到 R 波位置后切片每个心拍取 R 峰前 0.25 秒、后 0.4 秒共 234 个样本360×0.65按拍对齐后就能提特征。切片时注意边缘拍要丢掉首尾不足一个窗口的片段会引入半截波形。注意切片后把每一拍的标签和 R 波样本序号一起保存成一个 numpy 数组后面 MATLAB 侧训练和 bxb 评估都要用同一套索引索引对不上是最常见的串行错误。3.4 特征表哪些特征对心律失常真的有区分度特征计算方式主要判别对象RR 均值/标准差diff(r_peak) 取均值、标准差房颤时 RR 无规律rr_std 明显升高QRS 宽度Q 起点到 S 终点室性早搏 QRS 宽大畸形常大于 120 msQT 间期R 峰到 T 波终点长 QT 综合征、药物反应T 波幅度/斜率T 峰相对基线心肌缺血时 T 波低平或倒置频域 LF/HFwelch 功率谱比值自主神经张力相关常作为辅助特征RR 间期相关特征在心律失常分类里几乎是区分度最高的一组尤其是房颤这种“绝对不齐”的节律。QRS 宽度和 T 波形态则更偏室性事件。注意特征有两种切片粒度心拍级每拍一行和片段级每 10 秒一段包里的 Python 脚本通常两种都算分类任务如果是分拍识别就用心拍级如果是判断一段记录有没有事件就用片段级别混着用。4. MATLAB 侧训练闭环从特征矩阵到 fitcecoc 交叉验证很多人搜 matlab_ecg 分类搜到的代码要么是单分类器小 demo要么是读不懂的旧版脚本。这套包的 MATLAB 部分结构比较规矩读特征 → 标准化 → fitcecoc → 十折交叉验证 → 混淆矩阵。我把它重写成一份可以直接抄的模板。4.1 数据进 MATLABCSV/MAT 导入与标签编码T readtable(features.csv); % 特征矩阵最后一列是标签 X table2array(T(:, 1:end-1)); Y_raw string(T.Label); % 注意 Label 可能是分类变量 % 按 AAMI 标准把五类心拍编码成 1~5 Y zeros(size(Y_raw)); Y(Y_raw N) 1; Y(Y_raw S) 2; Y(Y_raw V) 3; Y(Y_raw F) 4; Y(Y_raw Q) 5;readtable 读 CSV 时Label 列经常被识别成 categorical 或 cell先转成 string 再比较能省掉一堆类型报错。标签编码这事看起来小但编码顺序不一致会让混淆矩阵的类别对应关系错位后面配 bxb 评估时更乱。4.2 标准化与 fitcecoc 参数为什么不能直接丢原始特征Xz zscore(X); % SVM 对量纲敏感不标准化的话幅度特征会碾压 RR 间期特征 tmpl templateSVM(KernelFunction, rbf, BoxConstraint, 1, KernelScale, auto); mdl fitcecoc(Xz, Y, Learners, tmpl);zscore 这一步是血泪经验。RR 间期是几百毫秒的量级而 QRS 幅度是毫伏量级不标准化的话 SVM 的核距离基本被幅度特征主导模型等于只看了形态没看节律。templateSVM 里 KernelScale 用 auto 让 MATLAB 自己按数据尺度估计核宽省得人去瞎调BoxConstraint 默认 1 通常够用过大会过拟合。fitcecoc 是纠错输出编码的多分类框架内部自动把五分类拆成若干二分类比手写 one-vs-rest 干净。提示不同 MATLAB 版本的 fitcecoc 默认学习器不一定相同代码里不显式写 templateSVM换台机器跑出来结果就可能对不上。把 Learners 参数写死别依赖默认值。装 MATLAB 时也先确认 Statistics and Machine Learning Toolbox 在缺工具箱的话 fitcecoc 会直接报错。4.3 十折交叉验证与混淆矩阵别只盯准确率rng(42); % 固定随机种子实验可复现 cvp cvpartition(Y, KFold, 10); % 分类问题默认按类别比例分层 cval crossval(mdl, CVPartition, cvp); loss kfoldLoss(cval); [pred, ~] kfoldPredict(cval); C confusionmat(Y, pred); confusionchart(C, {N,S,V,F,Q}); per_class_sens diag(C) ./ sum(C, 2); % 每类召回率 per_class_ppv diag(C) ./ sum(C, 1); % 每类精确率cvpartition 对分类任务默认做分层切分每折里五个类别的比例和总体一致这比 random split 靠谱。kfoldLoss 给出的是误分率我一般直接看 kfoldPredict 出来的逐拍预测手工算 per-class 的 Sens 和 PPV——这两个指标在 ECG 论文里比全局准确率重要得多。因为 MIT-BIH 里正常心拍占绝大多数模型全猜 N 也能有 80% 以上准确率所以一定要看混淆矩阵的对角线和非对角线的分布方向。提示如果结果要写进论文把十折里每一折的混淆矩阵累加后再算指标而不是把十折指标做平均——后者会人为缩小方差审稿人常抓这一点。5. 常见问题与避坑清单标注对齐、时间单位、加噪与样本不均衡5.1 坑一bxb 对比前没对齐时间窗口Sens 和 PPV 集体失真现象用 bxb 对比参考注释和自己的检测注释出来的匹配拍数明显偏少灵敏度掉到 80 以下而且怎么调参数都救不回来。原因bxb 按记录内的绝对时间对齐两个注释文件。如果你在预处理时裁剪了信号片段、但注释文件没同步裁剪两个注释序列覆盖的根本不是同一段时间对比结果自然错乱。解决裁剪数据时同步裁剪注释。用 wfdb 读取时 rdann 也要加 sampto 参数或者用 bxb 的 -f/-t 参数显式指定起止时间保证两个注释序列落在同一窗口内。我一般会在跑 bxb 前先打印两个注释文件的起止样本点做一次校准确认时间窗一致再放行。5.2 坑二a2m 转出来的注释时间单位是样本点不是毫秒现象用 a2m 把 WFDB 注释转进 MATLAB 后把时间字段直接乘 1000 当毫秒填进特征表QT 间期全部偏大得离谱。原因WFDB 注释内部时间戳以样本点为单位存储a2m 导出时保留的是样本序号而毫秒需要先除以采样率 360 得到秒再乘 1000。解决拿到时间字段先除以 fs 再转单位更省事的做法是直接在 Python 里用 rdann 的 sample/fs 换算别再倒一手 a2m。这个单位问题在包的手册页里其实写明了但很少有人读 man page 读到这一行。5.3 坑三nst 加噪的 SNR 是 RMS 信噪比不是峰值信噪比现象用 nst 加 5 dB 噪声测试鲁棒性肉眼看上去波形都快被噪声埋了怀疑工具写错了。原因nst 的信噪比定义是 20*log10(rms_signal / rms_noise)基于均方根幅度不是峰值信噪比。ECG 的峰值R 波远大于 RMS所以同样的 dB 数RMS 口径下的噪声能量感官上更猛。解决按 RMS 口径理解参数。先算信号段的 RMS反推要加的噪声 RMSnst 提供 baseline wander、muscle artifact、electrode motion 三种噪声源按 dB 指定信噪比跑完看输出报告里的实际 SNR不要凭肉眼判断噪声大小。5.4 坑四样本不均衡让准确率虚高多数类掩盖少数类错误现象测试集准确率 97%打开混淆矩阵发现 V 类和 F 类几乎全错少数类样本全被吞掉了。原因MIT-BIH 里正常心拍占绝对多数分类器只要全猜 N 就能拿到高分准确率对少数类完全没有约束力。解决指标改成逐类 Sens、PPV 和 F1并给出宏平均训练侧可以对少数类做重采样或者给 fitcecoc 的损失加权。我看这类 ECG 分类代码时有个习惯先看混淆矩阵右下角再看左上角最后才看总体准确率。5.5 坑五环境问题——python 版本、scipy API 和 MATLAB 工具箱缺一不可现象python 脚本在新环境里跑butter 报错或者告警刷屏MATLAB 脚本在别的机器上跑结果和原 README 对不上。原因scipy 在较新版本里改了 butter 等函数的 fs 参数写法老代码用 nyq 写法会报 FutureWarning极端情况直接抛异常MATLAB 侧则是 fitcecoc 默认学习器随版本变过不锁定参数就没法复现。解决python 环境按 requirements.txt 锁版本或者统一改成新版 fs 写法在 vscode 里配 python 环境时直接用 conda 建一个干净环境MATLAB 侧把 templateSVM 等参数写死在脚本里。包的 00README 里如果写了环境要求先照做再谈改代码。6. 用 bxb 收口评估灵敏度和阳性预测率的最后一公里6.1 一条命令把算法输出和专家标注对齐# 参考注释用 atrMIT-BIH 专家标注算法输出用 qrs逐拍对比 bxb -r 100 -a atr qrsbxb 的输出表里TOT 行是总拍数MISS 和误报行是错配的明细最后两行直接给出灵敏度Sens和阳性预测率PPV。这两个数就是论文里最常引用的 ECG 检测指标Sens 是专家标了而你也检出来的比例PPV 是你检出来且专家也确认的比例。跑批时把每条记录的 Sens、PPV 汇总成一张表最后一行写平均值这是我验收任何检测器的固定动作。6.2 双确认习惯命令行评分和 sklearn 指标对照着看指标命令行工具Python 对照灵敏度 Sensbxb 输出行classification_report 里的 recall逐类阳性预测率 PPVbxb 输出行classification_report 里的 precision综合指标bxb 无直接 F1sklearn 的 f1-score 宏平均我做对照时的做法是把同一批预测结果分别喂给 bxb 和 sklearn两边数字差超过 1 个百分点就停下来查对齐问题——大多数情况下都是注释时间窗口没对齐而不是算法本身变差了。从那以后我每次在报告里写 ECG 分类准确率之前都会强制走一遍「注释对齐 → 单位换算 → 类别映射 → 双工具交叉验证」这条流水线把这四个最容易造假的地方过掉再谈模型好坏。这套流程已经被我写进团队的验收清单也希望能帮到你。本文还有配套的精品资源点击获取