简介MIT-BIH ECG心电数据集与MATLAB绘图详解是一份面向心电图研究初学者与进阶用户的入门实操资源聚焦临床心律失常数据读取与可视化分析。资源包内共4个文件涵盖MAT格式心电数据、MATLAB绘图脚本、辅助脚本及数据说明文件能帮助读者在MATLAB环境中直接加载并绘制心电信号波形识别P波、QRS复合波与T波等关键特征为进一步计算心率、QT间期及开展心率变异性分析打下基础。压缩包整体仅20KB轻量易用适合信号处理课程设计、科研入门或工程复现。目前已有7597人学习使用可见其在ECG分析场景中的实用价值。通过这份资料读者可以掌握从数据读取、波形显示到基础特征提取的完整流程同时理解采样率、奈奎斯特定理等核心概念从而更高效地开展心脏电生理研究、滤波降噪和自动QRS检测等后续任务。1. MIT-BIH ECG 心电数据 MATLAB 绘图别在格式解析上浪费一周你从公开镜像下载了 MIT-BIH 心电记录满心期待打开文件夹发现全是 .dat、.hea、.atr 这类没有见过的文件没有现成的 CSV第一张波形愣是画不出来。这不是个例。MIT-BIH 的二进制存储格式和普通文本完全不一样采样率换算、基线偏移、增益处理这些细节只要错一个画面就是一片噪点或者一条悬在半空的直线。这份资源把数据样本和 MATLAB 绘图脚本打包在一起覆盖了 212 格式解析、导联参数读取、注释叠加、滤波前后对比这整条流程你拿到手改几个路径就能跑不用从零啃格式文档。适合做心电算法验证、课程设计需要出波形图、以及想复现论文实验图的同学和工程师。下面按我实际拆这个数据包的过程把每一道坎都讲清楚。2. 先搞懂三个文件HEA 是配方DAT 是波形ATR 是注释2.1 一条记录长什么样采样率 360 Hz、双导联、30 分钟每条 MIT-BIH 记录本质上是某个受试者 30 分钟的双导联心电采样率 360 Hz。360 这个数字不是随手定的它有两个实用价值一是能覆盖心电信号 0.05100 Hz 的有效频段二是刚好是 60 Hz 工频的整数倍做工频陷波时相位对齐非常方便。我一般拿到一条记录先确认三个文件是否齐全。文件后缀内容在 MATLAB 绘图里的角色.hea文本格式的头部描述采样率、导联数、增益、基线提供 fs、gain、baseline 参数.dat二进制波形数据212 编码画波形的主体数据.atr二进制注释数据变长结构叠加心拍标记、节律标记.hea 是说明书.dat 是真正干活的波形文件.atr 是心电专家预先标注好的心拍位置和类型比如正常心拍 N、室性早搏 V、房性早搏 A。画一张合格的心电波形图波形来自 .dat标注来自 .atr坐标刻度来自 .hea。三者对应关系理清了后面所有操作都不会乱。2.2 HEA 头文件里藏着绘图需要的一切参数打开任意一个 .hea 文件内容类似这样我删掉了部分不常用字段100 2 360 650000 100.dat 212 200 11 1024 9952 100.dat 212 200 11 1024 9952第一行四个字段依次是记录名、导联数、采样率、采样点数。100 号记录双导联、360 Hz、65 万个采样点对应 1800 秒乘 360 Hz约 30 分钟时长。第二行开始是逐导联描述这里我重点讲三个绘图必用的参数格式 212、增益 200、基线 1024。增益 200 的含义是 200 个 ADC 单位对应 1 mV。也就是说 .dat 里读出来的原始数值要除以 200才是物理单位毫伏。基线 1024 表示零幅值对应的 ADC 读数采集时心电信号叠加了一个直流偏置把整个波形抬高到了 1024 附近。绘图前不把这个偏置减掉波形会悬在距离零线 1024 的位置看起来像加了 5 mV 的直流电平QRS 形态完全走样。我习惯在脚本开头写一个读 HEA 的小函数把 fs、gain、baseline 读成变量而不是写死在代码里。因为 100 号记录的增益和 101 号记录可能不同写死参数换一条记录就得改源码这个亏我吃过。function [fs, gain, baseline] read_hea_info(heaFile) fid fopen(heaFile, r); if fid -1 error(打不开 HEA 文件: %s, heaFile); end % 第一行记录名 导联数 采样率 样本数 line1 fgetl(fid); parts strsplit(strtrim(line1)); fs str2double(parts{3}); % 第二行第一个导联的格式、增益、位数、基线 line2 fgetl(fid); parts2 strsplit(strtrim(line2)); gain str2double(parts2{3}); baseline str2double(parts2{5}); fclose(fid); end逻辑说明第一行第三个字段是采样率第二行第三个字段是增益第五个字段是基线。代码按标准两导联格式取数两个导联增益一致时完全够用。参数说明如果遇到某个记录的第二个导联增益和第一个不同需要把第二行也存进数组按导联号索引。这个函数返回的三个变量后面解析和绘图都要用。3. WFDB 工具箱路线十分钟跑通第一张波形图3.1 安装与路径配置装完先验证 rdsamp 能读通WFDB 工具箱是读取 MIT-BIH 最省事的路线它把 212 格式拆包、基线偏移、增益换算、注释读取全部封装好了不需要自己处理底层字节。安装方式有两种。第一种是把工具箱文件夹放到任意位置然后用 addpath 引入addpath(D:\toolbox\wfdb\matlab); savepath;savepath 是为了下次启动 MATLAB 不用重新 addpath。如果当前目录没有写权限保存失败就在工程入口脚本里每次启动时 addpath。我一般单独建一个 init.m 管理所有工具路径换电脑迁移工程时不丢路径。第二种方式是用 MATLAB 附加功能界面图形化安装装完即用。装好之后先验证再画图[~, sig] rdsamp(100, [], 1); % 读 100 号记录第一导联全部数据 size(sig)如果 size 返回的是几十万行的一列说明读取正常。不同版本工具箱的 rdsamp 参数顺序可能有差异保险起见先 help rdsamp 确认一下签名。3.2 用官方函数画第一张图时间轴必须除以采样率读数据之后画图的代码非常短但有两个细节常被忽略[~, sig] rdsamp(100, [], 1); fs 360; t (0:length(sig)-1) / fs; figure; plot(t, sig, k, LineWidth, 0.6); xlim([0 10]); % 只看前 10 秒画整条会压成黑带 xlabel(时间 (s)); ylabel(幅值 (mV)); grid on;逻辑说明rdsamp 返回的 sig 已经完成基线和增益换算单位是 mV所以 y 轴直接标 mV。时间轴必须除以 360否则横轴是样本索引和真实时间对不上后续叠加注释时也会错位。参数说明xlim 限制到 010 秒是关键操作。心电波形里 QRS 群、ST 段的细节都在几十到几百毫秒尺度10 秒窗口能看 20 个左右心拍既能观察节律又能看清单拍形态。画整条 30 分钟波形只会得到一团黑。3.3 画图前先看 min / max数据范围能筛掉一半问题我拿到任何一条记录画图之前都会先打印数据范围fprintf(min %.3f mV, max %.3f mV\n, min(sig), max(sig));正常心电信号经增益换算后幅值应该在 ±1 mV 量级剧烈情况下也不超过 ±5 mV。如果 min 和 max 是几十甚至几千说明数据还没正确转成物理值。常见原因有三个记录本身的格式不是 212比如 108 号记录是 311 格式rdsamp 调用时没指定导联返回了双导联拼接的矩阵或者工具箱版本较老返回值顺序不一样。上次给 A同学调代码他读 108 号记录一直得到一个 60 万行的大矩阵后来才发现是忘写导联参数工具箱默认把双导联 130 万样本全返回了。官方工具箱能覆盖读数据、画波形、加注释这三件事适合快速出图验证。但它不能解决所有问题比如自定义批处理、特殊格式记录、滤波流程嵌入这些还是需要理解底层格式。第 4 章的手写解析器就是干这个的。4. 手写解析器从 DAT 字节流到 MATLAB 波形4.1 212 格式拆包三字节装载两个导联样本MIT-BIH 最常见的 .dat 文件使用 212 格式存储意思是每 3 个字节存放两个 12 位样本两个导联交错打包。具体排列是第一个字节作为低 8 位第二个字节的低 4 位作为高 4 位组成导联 1 的样本第二个字节的高 4 位作为低 4 位第三个字节的 8 位左移 4 位作为高 8 位组成导联 2 的样本。整条波形就是这样一组三字节一路排下来的。function [ecg1, ecg2] read_dat_212(filename, numSamples) fid fopen(filename, rb); if fid -1 error(无法打开 DAT 文件: %s, filename); end % 每组三字节对应同一时刻的两个导联样本 groupCount numSamples; raw fread(fid, [3, groupCount], uint8); fclose(fid); b1 uint16(raw(:, 1)); b2 uint16(raw(:, 2)); b3 uint16(raw(:, 3)); % 导联1b1 全部 8 位作低位b2 低 4 位作高位 ecg1 b1 bitshift(bitand(b2, 15), 8); % 导联2b2 高 4 位作低位b3 全部 8 位左移 4 位作高位 ecg2 bitshift(b3, 4) bitshift(b2, -4); ecg1 double(ecg1) - 1024; ecg2 double(ecg2) - 1024; end逻辑说明fread 按 [3, groupCount] 把整个文件切成三行多列每列是一组三字节之后按列拆成 b1、b2、b3 三个向量。ecg1 和 ecg2 各得到 numSamples 个 12 位整数这是两个导联完整的采样序列。最后减 1024 是去掉 HEA 文件里写明的基线偏移让零幅值回到零点。参数说明这里有三个容易被忽略的细节。一是 bitshift 作用于 uint8 时左移会截断到 8 位必须先把字节转成 uint16否则 b3 左移 4 位后高 4 位全部丢失这是 MATLAB 的典型坑。二是 numSamples 是单导联的样本数也就是 HEA 第一行的 650000不是双导联总数。三是减 1024 后得到的仍是 ADC 计数值绘图时还要再除以增益才能标 mV。4.2 校验解析结果和官方工具箱对拍一次手写解析最容易出隐性问题最稳的验证方法是用官方工具箱做对照。读同一条记录、同一个导联把前几秒的波形叠在一起[~, sigOfficial] rdsamp(100, [], 1); [ecg1, ~] read_dat_212(100.dat, 650000); figure; plot(ecg1(1:3600)/200, b); hold on; plot(sigOfficial(1:3600), r, LineWidth, 0.4); legend(手写解析, 官方工具箱);如果两条线完全重合说明拆包逻辑正确。如果不重合先看差值形态差一个固定常数说明基线没减干净差一个比例说明增益没除波形完全错位说明字节拼接方向反了。这个方法比对着格式文档猜快得多我把这个对拍习惯保留到了现在。第 3 章用官方工具箱第 4 章又手写一套解析看上去重复但各有用途。官方工具箱适合快速出图手写解析器适合嵌入批量流程。比如要跑 50 条记录、逐条提取 ST 段特征用工具箱逐条调用会产生大量重复 IO手写解析器读一次 HEA 参数就能循环处理所有文件速度差一个量级。4.3 顺手处理基线漂移一个移动平均就够心电信号自带呼吸引起的基线漂移频率很低大约在 0.10.5 Hz。绘图前不处理波形会像波浪一样上下起伏ST 段判断很容易被干扰。我用移动平均估计漂移再从原信号里减去windowSize round(fs * 0.5); % 0.5 秒窗口360 Hz 时等于 180 baselineDrift movmean(ecg1, windowSize); ecg1Clean ecg1 - baselineDrift;逻辑说明movmean 在 0.5 秒窗口内求局部平均得到的是一条贴着原始波形缓慢变化的曲线也就是基线漂移的估计。原信号减去它基线基本回零。参数说明windowSize 用 fs 乘 0.5 秒计算换采样率时公式依然成立。注意移动平均只能处理低频呼吸漂移不要拿它替代带通滤波否则 QRS 的高频成分也会被削平具体坑在第 5 章讲。5. 避坑指南五个让新手翻车的格式细节5.1 波形整体反向或数值乱跳bit 拼接方向反了现象手写解析画出来的波形像镜像QRS 主波朝下或者数值在正负几千之间乱跳。原因212 格式里 12 位样本的拼接顺序写反了。常见错误是把 ecg1 写成了 b1 右移加 b2 左移把高低位颠倒另一个隐患是没把 uint8 转成 uint16bitshift 左移直接截断丢位数值范围立刻错乱。解决先打印前 20 个样本的数值正常应该在 1024 附近上下波动。如果数值范围不在 02047 之间先检查字节类型转换再检查拼接方向。对照官方工具箱对拍是最终的确认手段。5.2 时间轴错位样本索引被当成秒现象画出来的横轴刻度是 0 到 650000波形密集到看不清单看一个心拍要手动算很久。原因直接用样本索引做了 x 轴没有除以采样率。360 Hz 下 1 秒是 360 个点索引和秒数差了 360 倍。解决t (0:length(sig)-1) / fs。横轴换成秒之后10 秒窗口能自然显示 10 个心拍单个 PQRST 的宽度一目了然。这条规则对所有生理信号绘图都适用不只是 MIT-BIH。5.3 ATR 注释解析翻车别自己硬读可变长结构现象rdann 读注释很顺利但自己 fopen 读 .atr 时注释位置和波形完全对不上越往后偏差越大。原因.atr 用的是变长注释结构带辅助信息的注释比普通注释多几个字节逐字节硬解析时偏移量会积累错误。注释时间戳的单位是采样点序号而不是秒也容易混。解决用官方 rdann 读注释它内部处理了变长结构。拿到的是采样点索引绘图时注意 MATLAB 数组下标从 1 开始而注释索引是 C 风格从 0 开始画图时索引加 1 才是对应数据点。5.4 全图变成一团黑65 万点挤在一个 figure 里现象plot 整条 30 分钟波形图形窗口里只有一条浓黑曲线放大后还有明显锯齿。原因一个像素宽的线条根本画不下 65 万个数据点大量数据点叠在同一列像素上波形糊成一条黑带。解决画细节图时先用 xlim 限制到 510 秒画全局概览图时先降采样用 decimate(sig, 10) 把采样率降到 36 Hz 再画能看到整体节律趋势。我习惯细节图和概览图分开输出一张看形态一张看全貌。5.5 滤波后 QRS 没了窗口选得太长现象做移动平均滤波后R 波明显变矮甚至消失波形变得平滑但心拍特征没了。原因移动平均窗口选了 1 秒甚至更长把 QRS 的高频成分当成噪声一起抹掉了。QRS 持续约 80120 毫秒窗口太长必然削平。解决移动平均只用于基线漂移估计窗口 0.30.5 秒真正的心电滤波用 butterworth 带通比如 butter(2, [0.5 45]/(fs/2))保留 0.545 Hz 这个有效频段。滤波后务必做一次幅值范围检查确认 R 波幅度没有明显下降。6. 进阶绘图从裸波形到可发表的注释图6.1 叠加 ATR 注释让心拍标记和波形对齐基础波形画出来之后最实用的进阶操作是把 ATR 注释叠到图上这样能直接看到每个心拍的专家标注位置。我用的是官方 rdann 加手写波形混搭的方式[annIdx, annType] rdann(100, atr); annIdx annIdx 1; % C 风格索引转 MATLAB 下标 tAnn annIdx / fs; figure; plot(t, ecg1/gain, k); hold on; % 先用圆点标出所有注释位置形态和类型再做筛选 plot(tAnn, ecg1(annIdx)/gain, bo, MarkerSize, 4); xlim([0 10]);逻辑说明rdann 返回的 annIdx 是采样点索引转换为 MATLAB 下标要加 1。ecg1(annIdx) 直接从波形数据里取对应幅值这样注释点和波形天然对齐。参数说明先全量标注观察分布再按 annType 筛选具体心拍类型不同类型的映射关系以工具箱文档为准。只看一个心拍时把 xlim 改成 [5 6] 1 秒窗口内能看到完整的 P 波、QRS 群和 T 波。这条路走通之后批处理也顺理成章循环读取记录、读取注释、切片提取单拍特征全部可以脚本化。我自己刚开始接触这个数据集时在某条记录上卡了两天最后发现就是 bit 位拼错了一个方向。从那以后每次拿到新记录都强制走一遍固定流程先读 HEA 校验采样率和增益再画前 10 秒看波形形态再叠注释对 QRS最后才敢说这条数据解析正确。这套配套脚本已经是按这个顺序整理好的下载后把记录路径改成你本地的文件夹位置第一张图就能直接出。希望帮到你。本文还有配套的精品资源点击获取