资讯详情

MIT-BIH心电数据解析与MATLAB绘图:从212格式到R峰标注

📅 2026/10/17 4:44:59 | 华诺云谱 👁 阅读
MIT-BIH心电数据解析与MATLAB绘图:从212格式到R峰标注
简介MIT-BIH ECG心电数据库由麻省理工学院与波士顿布莱根妇女医院联合发布是临床心律失常研究和ECG信号处理的经典数据源。这份资源面向心电信号分析的学习者与研究人员内含1分钟心电记录及采样率等信息可用于理解数据特性并基于MATLAB完成滤波、特征提取和心率变异性分析等实践。包体信息压缩包共4个文件包括MAT数据文件、绘图脚本plotATM.m、数据说明info与自动保存文件整体仅20KB体量轻便。目前已有7597人学习下载。学习价值下载后可对照程序详解在MATLAB中加载ECG数据通过绘图函数直观识别P波、QRS波群与T波进而分析心率、心律和QT间期等参数。同时可借助MATLAB的滤波器设计工具去除噪声实现自动检测与标注QRS波群是入门ECG数据处理和深入研究的实用参考资料。1. MIT-BIH ECG 数据 MATLAB 绘图先过数据字典这一关再谈画图MIT-BIH 心电数据库几乎是所有做 ECG 信号处理的人绕不开的第一个真实数据集。第一次拿到这套数据时最容易犯的错不是绘图代码不会写而是不知道 dat 文件里到底存的是整数还是浮点数、采样率到底是 250 Hz 还是 360 Hz、注释文件里的 QRS 位置单位是秒还是采样点。之前在模拟项目 X 里有同学直接按 250 Hz 去绘制某条记录结果波形被整体拉宽R 峰标注全部错位。这套资源的核心价值不在于“怎么用 MATLAB 画一条曲线”而在于把数据字典里的字段吃透再把原始信号、标注信息、导联关系一起可视化出来。适合刚开始接触 ECG 数据、准备做心电信号处理或特征提取的人也适合那种一遇到二进制数据就发怵、想在读文件阶段少走弯路的从业者。2. 数据文件三件套头文件、dat 流与注释文件怎么读MIT-BIH 一条完整记录由三个同前缀文件组成.hea 头文件、.dat 数据文件、.atr 注释文件。缺了任何一个后面画图、分析、对齐标注都无从谈起。很多人只拿到 .dat 就开始读以为二进制里就是波形读了半天发现全是无意义的整数这就是没搞懂数据格式的结果。下面按读取顺序逐个说透。2.1 先从 .hea 头文件里拿到增益、基线和采样率.hea 文件是纯文本第一行记录了一串全局参数记录名、导联数、采样率、总采样点数。默认情况下 MIT-BIH 大多数记录是 2 导联、360 Hz、65 万个采样点约 30 分钟时长。第二行开始每个导联占一行描述该导联的信号文件名、存储格式、增益、ADC 分辨率、ADC 零点、基线值、校验和、偏移量。这些参数里绘图时必须用到的是增益和基线。增益的单位是 ADC/mV代表信号从模拟到数字的放大倍数基线的含义是 ADC 无输入时输出的中心数字码。两者缺一画出来的纵轴要么是没单位的工程码要么是整体偏移的浮空波形。function hdr read_hea(heaPath) fid fopen(heaPath, r); if fid -1 error(打不开头文件: %s, heaPath); end firstLine fgetl(fid); c textscan(firstLine, %s %d %d %d); hdr.record c{1}{1}; hdr.nSig c{2}; hdr.fs c{3}; hdr.nSamples c{4}; for k 1:hdr.nSig line fgetl(fid); f strsplit(strtrim(line)); hdr.ch(k).gain str2double(f{3}); hdr.ch(k).res str2double(f{5}); hdr.ch(k).adcZero str2double(f{6}); hdr.ch(k).baseline str2double(f{7}); hdr.ch(k).offset str2double(f{10}); end fclose(fid); end解析时要注意字段序号。f{3} 是增益f{5} 是 ADC 位数f{6} 是零点f{7} 是基线f{10} 是偏移量。中间几个字段单位字符串、校验和等参与解析但不影响绘图用空格分隔后按位取即可。如果某个导联的增益值小于 100 或大于 500就要警惕了。常见 MIT-BIH 记录增益多为 200 左右异常值通常意味着头文件被编辑过或者文件来源本身不规范。遇到这种情况不要强行继续画图先回到头文件里核对字段列位置。首次解析头文件时建议把 hdr 结构体整体打印出来跟 .hea 文件原样比对一遍确认没有错位。2.2 从 .dat 中真实解码 212 格式的 12 位心电样本.dat 是纯二进制流MIT-BIH 大多数记录采用 format 212 的压缩存储。212 的含义是两个 12 位有符号整数以 3 字节为一组打包。心电信号实际有效精度是 11 位加一个符号位正好 12 位若按 16 位存储会多耗 30% 空间212 格式把它们紧凑地放在 3 字节里是早期心电数据库为了节约存储设计的约定。解包规则是三字节 b1、b2、b3 中样本一是 b1 的低 8 位加上 b2 低 4 位左移 8 位的结果样本二是 b3 的低 8 位加上 b2 高 4 位左移 8 位的结果。解出来的是 12 位无符号整数但实际是补码第 12 位为 1 时表示负数需要减去 4096。function [ecg1, ecg2, t] read_mitbih_dat(heaPath, datPath) hdr read_hea(heaPath); fid fopen(datPath, rb); if fid -1 error(打不开数据文件: %s, datPath); end raw fread(fid, [3, hdr.nSamples], uint8); fclose(fid); b1 double(raw(1, :)); b2 double(raw(2, :)); b3 double(raw(3, :)); s1 b1 bitshift(bitand(b2, 15), 8); s2 b3 bitshift(bitand(b2, 240), 4); s1(s1 2047) s1(s1 2047) - 4096; s2(s2 2047) s2(s2 2047) - 4096; ecg1 (s1 - hdr.ch(1).baseline) / hdr.ch(1).gain; ecg2 (s2 - hdr.ch(2).baseline) / hdr.ch(2).gain; t (0:hdr.nSamples-1) / hdr.fs; end提示符号扩展的判断依据是第 12 位对应数值 2048。很多人混淆 2048 和 4096导致负数全部被当成正数波形完全失真。这里必须先判断是否大于 2047再决定是否减去 4096。这段代码用整块 fread 读取全部数据再用向量化位运算解包比逐点 for 循环快得多。65 万采样点原始字节约 2 MB一次性读入内存对现代机器毫无压力若以后换到超长记录或嵌入式环境可以改成每次读几万字节、循环拼接的方式解包逻辑不变。解包后的 s1 是 ADC 数字码不是电压值。单位换算必须严格走 m V (数字码 - 基线) / 增益 这条公式。很多人直接拿整数画图形状虽然对但纵轴物理单元丢了后续做 R 峰振幅、ST 段分析就没有量化依据。这个点在项目交付里反复出现两张图几乎一样唯一区别是纵轴标签但评审就是要看有没有 mV 单位。2.3 注释文件 .atrR 峰位置和节律标签怎么抽.atr 文件存的是专家注释包含每个心拍的类别、R 峰所在采样点序号以及节律变更标记。绘图时最常用的是 R 峰位置用于在波形的 QRS 尖峰上叠加标记直观展示心拍检测结果。节律标签则在做长时段分析时更有用比如判断某一段是否为室速或房颤。读取 .atr 有两条路线。如果环境里装有支持 WFDB 格式的工具箱直接调用 rdann 是最省事的。rdann 返回的矩阵里第一列是采样点索引第二列是注释类型码。绘图只需要第一列。% 读取专家注释返回的 ann 是 n×2 矩阵 ann rdann(100, atr); rPeakIdx ann(:, 1); % 第一列是 R 峰采样点位置 rPeakTime rPeakIdx / 360; % 除以采样率得到秒 fprintf(共 %d 个R峰第一个在 %.3f 秒\n, ... length(rPeakIdx), rPeakTime(1));用 rdann 时有几个参数容易踩坑记录名不能带 .hea 后缀注释类型必须传 atr传 qrs 拿到的是自动检测结果不是专家标注返回值单位是采样点不是秒。换算时间坐标时采样率建议从头文件读取而不是想当然用 360因为 250 Hz 的派生版本在不少教学资源里也流通。如果后续要做心拍分类还需要第二列的注释类型码这时候再对照 WFDB 注释字典映射类别但单纯画 R 峰标注不需要这一步。3. MATLAB 绘图实战从一条导联到多导联联动视图拿到 mV 级信号后绘图反而成为主要事项但要注意的事情不少时间轴怎么设、多导联怎么排、R 峰标注怎么精确对上。这一章按实际工程顺序展开。3.1 单导联波形时间轴、幅度轴与采样率的关系画单导联的首要任务是时间轴换算。常见错误是直接把样本序号当横坐标画图这样横轴单位变得模糊。正确做法是在绘图前完成 t (0:N-1)/fs 的换算把每个样本映射到物理秒。fs 360; dur 10; n dur * fs; t (0:n-1) / fs; figure(Color, w, Position, [100 100 1000 320]); plot(t, ecg1(1:n), b, LineWidth, 0.8); grid on; xlim([0 dur]); xlabel(时间 (s)); ylabel(电压 (mV)); title(MIT-BIH 记录MLII 导联前 10 秒);LineWidth 取 0.8 是长期试出来的折中值0.5 以下打印后线条发虚1.5 以上会把 QRS 尖峰的锐利边缘磨钝。Figure Position 的宽高比也有讲究宽屏比例贴近心电监护仪的走纸样式便于肉眼分辨 P 波和 T 波如果用默认正方形波形被纵向拉伸形态判断容易失真。如果一次画完 30 分钟全程窗口里就是一条密集的蓝色色带P-QRS-T 完全看不出来。遇到这种情况不要急着调渲染参数先把 xlim 压缩到 10 秒。10 秒大约是 3600 个采样点既能覆盖十来个心拍又不会信息过密。幅度轴方面MLII 导联正常范围通常在 ±1.5 mV 之间但不同记录的基线漂移程度不同建议先看 ylim 自动结果再根据实际信号微调不要从一开始就硬编码固定值。3.2 多导联绘制subplot 布局与坐标联动双导联绘图我习惯用上下 subplot不太建议把两条曲线叠在同一个 axes 里。MIT-BIH 的 MLII 和 V1 幅度不同基线也可能有差异叠在一起幅度小的导联细节会被压平。上下排列能保留各自的动态范围对比同一时刻两个导联的形态也更直观。figure(Color, w, Position, [60 60 1100 620]); ax1 subplot(2, 1, 1); plot(t, ecg1(1:n), Color, [0.10 0.30 0.70], LineWidth, 0.8); ylabel(MLII (mV)); title(MIT-BIH 记录前 10 秒双导联); grid on; ax2 subplot(2, 1, 2); plot(t, ecg2(1:n), Color, [0.75 0.25 0.20], LineWidth, 0.8); ylabel(V1 (mV)); xlabel(时间 (s)); grid on; linkaxes([ax1, ax2], x);linkaxes 把两个子图的 x 轴范围锁定拖动任何一个子图的时间窗口另一个会同步跟随。这在对比同一时刻两导联形态变化时非常关键否则人工分别缩放后两图的 R 峰位置会错开无法对照。如果想要各自独立的 y 轴缩放linkaxes 的第二个参数保持 x 即可改 xy 会同时联动纵向缩放但 V1 导联幅度通常比 MLII 小y 轴联动会把小幅度导联放大到不合适。另外不建议手动调子图的 OuterPosition默认布局在 print 导出时更稳定手动拉伸容易导致标题和刻度标签被裁切。3.3 叠加标注信息把 R 峰、节律标签画到图上R 峰标注是心电绘图里最常被追问的点为什么红点到不了波形尖峰上绝大多数情况是坐标单位不匹配。plot 的横轴是秒标注坐标却用了样本序号自然对不齐。正确方式是把索引先除以采样率转换成时间坐标。hold on; plot(rPeakTime, ecg1(rPeakIdx), rv, MarkerSize, 6, LineWidth, 1.2); legend(MLII, R峰);这里两个关键向量是 rPeakTime 和 ecg1(rPeakIdx)。rPeakTime 是 R 峰在时间轴上的位置ecg1(rPeakIdx) 是该采样点的幅值。多导联绘图时红色标注必须与波形数组来自同一个导联用错数组时红点会落到其他导联的波形之外。标注密度也要控制。10 秒窗口内通常有 12 个左右 R 峰MarkerSize 设 6 视觉比较清楚如果画 60 秒窗口标注会挤成串这时把 MarkerSize 缩到 3或只标注前 15 秒。颜色上用红倒三角在蓝色波形上辨识度最高绿色容易被网格线干扰黑色在灰度打印时倒是比较稳妥。4. 绘图踩坑与信号预处理采样率、基线漂移和标注对齐这一章把多个项目里遇到的共性问题整理成几条可对照的记录。每条按“现象、原因、解决”三段式写跑数据时看到类似症状可以直接排查。这些坑大多不是绘图本身造成的而是读文件或预处理阶段埋下的隐患。4.1 采样率设置错误导致时间轴被拉宽或压扁现象波形形态看起来正常但从相邻 R 峰间隔推算心率时数值明显偏大或偏小。正常窦性心律的 RR 间隔在 0.8 秒左右如果算出来是 1.2 秒时间轴多半有问题。原因很多网上流传的教学数据不是原始 360 Hz而是被重采样到 250 Hz 甚至 128 Hz 的派生版本。如果读代码里硬编码 fs 360换算成秒后时间轴整体被拉长心拍间隔和心率全部失真。解决每次读取记录前先从头文件取 fs 字段不要在代码里写死。同时检查数据来源标注确认记录名对应的是原始库还是派生库。我的一般做法是在 read_hea 之后立刻打印一行采样率确认数据异常时第一件事就看这里。这个习惯救过我好几次有两次是拿到的数据集文件名完全一样但采样率一个 360 一个 250混用后连标注都对不上。4.2 基线漂移让波形浮在空中画图前要不要滤波现象整段波形在纵轴上缓慢起伏R 峰振幅忽高忽低整体像被风吹动的丝带自动 ylim 被拉得很宽。原因MIT-BIH 原始数据虽然做过带通处理但部分记录仍残留极低频基线漂移一般来自电极接触阻抗变化或呼吸运动。频率低于 0.5 Hz 的漂移不影响 R 峰检测但会让绘图的美观度和自动坐标轴失效。解决画图前做一次 0.5 Hz 高通滤波。重点是必须用零相位滤波普通 filter 会引入相位延迟导致波形横向平移几个采样点R 峰标注跟着错位。filtfilt 能避免这个问题。% 高通去基线漂移截止频率 0.5 Hz [b, a] butter(2, 0.5 / (fs / 2), high); ecg_filt filtfilt(b, a, ecg1);提示高通截止频率选 0.5 Hz 只是绘图常用值。如果后续要做心律失常分类滤波器可能损伤 ST 段和 T 波低频成分务必先评估再决定是否滤波。4.3 R 峰标注与波形对不齐甚至差出几个采样点现象红点落在 QRS 波形的上升沿或下降沿上而不是尖顶偏差有时候看得出来有时候只差一两个样本。原因rdann 返回的是专家标注的参考点标注者之间本身有 ±1 个采样点的差异另一个常见来源是滤波引入的相位延迟。filtfilt 没有这个问题但 filter 会实实在在把波形平移。解决绘图前用 filtfilt 代替 filter。如果仍对不齐可以做 R 峰微调把每个 R 峰索引更新到前后 5 个采样点内的幅值极大值位置。这个“R 峰微调”技巧在心电特征提取里几乎是必备操作。for i 1:length(rPeakIdx) lo max(1, rPeakIdx(i) - 5); hi min(length(ecg1), rPeakIdx(i) 5); [~, j] max(abs(ecg1(lo:hi))); rPeakIdx(i) lo j - 1; end这段循环的关键是取绝对值后找极大值因为 V1 这类导联的 QRS 主波方向可能向下不取绝对值只对正向主波的导联有效。做完微调后重新计算 rPeakTime红点才能严丝合缝地压在尖峰上。微调窗口选 5 个采样点而不是更大范围是为了避免把红点拉到相邻的 T 波上去。4.4 导出的图片发虚、锯齿感明显打印后看不清现象保存成 png 后放大看锯齿明显插入论文后线条发虚与正文文字清晰度不匹配。原因默认的 saveas 导出分辨率偏低而且 MATLAB 对图窗大小和导出缩放的匹配处理比较黑匣子。很多人直接 saveas(gcf, a.png)出来的图字号大但线条密度低放大后就露馅。解决用 print 命令显式指定分辨率和格式。print 的直译是印刷输出它真正按目标分辨率重新栅格化图形对象比 saveas 更适合正式场合。print(gcf, ecg_plot, -dpng, -r600);-r600 表示 600 dpi适合期刊初稿如果投稿只要求 300 dpi就改 -r300。注意 print 前不要让 figure 处于缩放状态最好保持窗口未缩放这样导出的尺寸比例和屏幕上一致。矢量图则用 -depsc 输出 eps后续在论文排版里任意放大也不会失真。5. 进阶批量绘图与出版级输出规范化当手里同时有十几条 MIT-BIH 记录时逐条打开文件、调整坐标轴、保存图片的重复操作是最耗时间的。把读文件、绘图、导出打包成一个函数用记录名单循环调用可以把一小时的重复劳动压缩到几分钟。5.1 批量生成多条记录的预览图records {100, 101, 102, 103}; for k 1:length(records) rec records{k}; heaPath [rec, .hea]; datPath [rec, .dat]; [ecg1, ecg2, t] read_mitbih_dat(heaPath, datPath); figure(Color, w, Position, [60 60 1100 620]); ax1 subplot(2, 1, 1); plot(t(1:3600), ecg1(1:3600), b, LineWidth, 0.8); title([rec, MLII]); grid on; ax2 subplot(2, 1, 2); plot(t(1:3600), ecg2(1:3600), r, LineWidth, 0.8); xlabel(时间 (s)); grid on; linkaxes([ax1, ax2], x); print(gcf, [ecg_ rec], -dpng, -r600); close(gcf); end循环里每次都重建 figure 再关闭避免 MATLAB 窗口堆积占用内存。批量脚本的意义在于保证所有记录用同一套绘图参数后续评审要求换配色时只需改一处全部重跑一遍即可。我在实际项目里会把 records 列表从文本文件读入这样新增记录不用改代码只改名单文件。5.2 排版参数与提交经验出版级排版其实有规律可循。我在项目里总结了一套模板参数每次画图前对照执行字体统一用 set(gca, FontName, Helvetica)避免默认字体在导出后出现兼容问题坐标轴范围固定xlim 和 ylim 不在循环内随机改动网格线用细虚线不抢波形的视觉焦点同一张图内颜色不超过三种主信号蓝、副信号红、标注黑。导出的文件名建议带记录号和导联信息比如 ecg_100_MLII.png多人协作时不会互相覆盖。如果要在论文里用矢量图把 print 参数改成 -depsceps 格式在 Word 和 LaTeX 里都比较稳妥不会有位图放大发虚的问题。另外导出前用 set(gcf, PaperPositionMode, auto) 锁定版面比例可以避免 print 出来的图四周出现多余白边。从那以后我每次处理 MIT-BIH 数据都会强制走一遍固定流程先读头文件核对采样率和基线再解包 dat 做单位换算然后绘图前确认 R 峰索引用的采样率和波形数组来自同一个导联。这套流程看着繁琐但恰恰是这些确认步骤帮我拦截过不止一次翻车。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑