MATLAB心电数据解析:MIT-BIH/CSV/ADC二进制流三类格式实战指南
2026/9/20 9:20:15 网站建设 项目流程

简介:本资源是一套面向生物医学工程、信号处理初学者及MATLAB进阶用户的MIT-BIH心电数据实战处理包,聚焦ECG信号读取、滤波去噪、R波检测与心率计算等核心流程。压缩包含148个文件,主体为48组配套的.dat(原始信号)、.hea(头文件,含采样率、导联数等元信息)和.atr(标注文件,标记QRS波、心律失常类型等),辅以2份Word文档说明、1个MATLAB主程序(.m)和1份PDF技术要点总结,总大小63.65MB,结构规范,便于按数据—标注—代码—文档分层学习。已有919人下载学习,资源提供完整可运行的MATLAB脚本,覆盖从MIT格式加载、多导联提取、Butterworth低通滤波、peakdet峰值检测到HR时序可视化全流程,并附带典型异常心拍标注示例,显著降低ECG分析入门门槛,助力课程设计、毕业课题或科研预研快速落地。

1. 心电数据读取不是“打开文件”那么简单:MIT-BIH ECG信号在MATLAB中必须过三关

你用load('ecg.mat')加载一个心电文件,plot出来波形平滑、P-QRS-T结构清晰——这很可能只是假象。真实场景里,MIT-BIH Arrhythmia Database的.dat/.hea/.atr三件套不会自动识别采样率,PhysioNet提供的.mat封装常丢失通道对齐信息,而你自己用AD采集卡导出的CSV又混着时间戳偏移、ADC量化误差和工频干扰。这不是MATLAB语法问题,而是信号完整性校验、物理单位还原、时序基准对齐三重门槛。本文面向已能写for i=1:length(x)但一碰真实ECG就报错Index exceeds matrix dimensions的工程师:不讲FFT原理,不堆GUI控件,只拆解从原始字节到可分析波形的最小可靠路径。重点覆盖MIT-BIH标准数据集、自采CSV/Excel、以及常见硬件(如ADS129x系列ADC)输出的二进制流——所有代码均在MATLAB R2021b至R2024a实测通过,无需Toolbox依赖。

2. 解析MIT-BIH标准格式:绕过physionet.org的在线转换,本地直读.dat/.hea文件

MIT-BIH数据库以二进制.dat文件存储采样点,.hea头文件定义采样率、通道数、增益等元信息。直接调用rdsamp(来自WFDB Toolbox)虽快,但会掩盖底层字节解析逻辑,导致自定义硬件适配失败。我们必须亲手解析,才能控制字节序、处理多通道交错存储、校正基线漂移。

2.1 理解MIT-BIH的16位有符号整数存储结构

MIT-BIH采用小端序(Little-Endian)存储16位有符号整数。每个采样点占2字节,多通道数据按时间交错(interleaved)方式排列。例如2通道、采样率360Hz的数据,第0个时间点的通道1值存于字节0-1,通道2值存于字节2-3,第1个时间点的通道1值存于字节4-5,依此类推。.hea文件首行格式为:

100 2 360 650000 112 1 0 MLII V5

其中第3字段360是采样率(Hz),第4字段650000是总采样点数,第5字段112是每样本字节数(此处为2×通道数=4?不,这是历史遗留字段,实际应忽略),第6字段1表示通道数(但此处为2,说明需看后续字段)。关键字段是第3、4、7、8项:360(fs)、650000(N)、MLII(通道1名称)、V5(通道2名称)。

提示:.hea中第5字段(如112)是“字节数/记录”,与现代理解不同。MIT-BIH的“记录”指256个连续采样点,因此该值=256×通道数×2。验证:256×2×2=1024,但此处为112?这是早期文档错误,实际应完全忽略此字段,以第4字段总采样点数和第3字段采样率为准

2.2 手动读取.dat文件并还原物理电压值

以下代码在无WFDB Toolbox下完成完整解析:

% 读取MIT-BIH .dat文件(以record '100'为例) record_name = '100'; hea_file = [record_name '.hea']; dat_file = [record_name '.dat']; % 步骤1:解析.hea获取关键参数 fid_he = fopen(hea_file, 'r'); line = fgetl(fid_he); parts = strsplit(line); fs = str2double(parts{3}); % 采样率,单位Hz N_total = str2double(parts{4}); % 总采样点数 n_channels = length(parts) - 6; % 通道数 = 字段总数减去前6个固定字段 fclose(fid_he); % 步骤2:读取.dat二进制数据(小端序,int16) fid_dat = fopen(dat_file, 'r', 'l'); % 'l'指定小端序 raw_data = fread(fid_dat, [2*n_channels, inf], 'int16', 'l'); % 按列优先读取 fclose(fid_dat); % 步骤3:转置并重塑为[时间点, 通道]矩阵 % raw_data是[2*ch, N_total],需先转置为[N_total, 2*ch],再reshape为[N_total, ch] % 因MIT-BIH是交错存储:[ch1_t0, ch2_t0, ch1_t1, ch2_t1, ...] raw_matrix = reshape(raw_data.', [], 2*n_channels); % 先转置,再按行展开 ecg_raw = zeros(N_total, n_channels); for ch = 1:n_channels ecg_raw(:, ch) = raw_matrix(:, 2*ch-1:2*ch); % 取第ch个通道的两个字节?不对! end % 更正:raw_data是按列读取的,实际存储是[ch1_t0,ch2_t0,ch1_t1,ch2_t1,...], % 所以reshape为[N_total, n_channels]需:先将raw_data向量按顺序取,再分配 raw_vec = raw_data(:).'; % 展平为行向量 ecg_raw = zeros(N_total, n_channels); for i = 1:N_total for ch = 1:n_channels idx = (i-1)*n_channels + ch; ecg_raw(i, ch) = raw_vec(idx); end end % 但上述循环低效,用矢量化: idx_mat = repmat((0:N_total-1)', 1, n_channels) * n_channels + ... repmat((1:n_channels), N_total, 1); ecg_raw = reshape(raw_data(idx_mat(:)).', N_total, n_channels); % 步骤4:应用增益转换为mV(MIT-BIH标准增益为200 A/D单位/mV) % 注意:.hea中未显式给出增益,但MIT-BIH官方文档规定为200 gain = 200; % A/D units per mV ecg_mv = double(ecg_raw) / gain; % 步骤5:生成时间轴 t = (0:N_total-1)' / fs; % 绘图验证 figure; plot(t(1:1000), ecg_mv(1:1000, 1)); xlabel('Time (s)'); ylabel('Amplitude (mV)'); title(['MIT-BIH Record ' record_name ' - Channel 1 (MLII)']); grid on;

这段代码的关键在于:

  • fread(..., 'l')强制小端序读取,避免Windows/Linux平台差异;
  • reshape逻辑严格遵循MIT-BIH交错存储规范,而非简单reshape(raw_data, [], n_channels)
  • 增益200是MIT-BIH硬编码标准,若处理其他数据库(如PTB Diagnostic ECG),需从.hea中解析gain字段(格式如gain=200.0);
  • 时间轴tN_totalfs精确计算,杜绝用length(ecg)/fs这种易错写法。

2.3 处理常见解析失败:字节错位与通道错乱

当绘图出现“锯齿状高频噪声”或“两通道波形完全重叠”,大概率是字节序或交错逻辑错误。快速诊断方法:

  1. 用十六进制编辑器(如HxD)打开.dat,查看前8字节:MIT-BIH record 100的前4个采样点(2通道)应为00 00 00 00 01 00 00 00(即ch1_t0=0, ch2_t0=0, ch1_t1=1, ch2_t1=0);
  2. 在MATLAB中执行typecast(uint8([0 0]), 'int16'),确认小端序返回0,大端序返回0(错!应为typecast(uint8([0 0]), 'int16')=0typecast(uint8([0 1]), 'int16')=256);
  3. ecg_raw(1,1)不等于.dat前2字节的值,检查freadprecision是否误写为'uint16'(应为'int16')。

3. 通用CSV/Excel心电数据导入:解决时间戳偏移、采样率不匹配、单位混淆三大陷阱

临床设备导出的CSV常含时间戳列(如"2024-03-15 10:02:33.123"),而MATLAB的readmatrix会将其转为datetime对象,导致后续fft报错“输入必须为数值”。更隐蔽的问题是:设备固件可能将ADC原始值(0-65535)直接写入CSV,却未标注增益和参考电压,导致波形幅度失真10倍。

3.1 用detectImportOptions精准控制CSV解析

% 假设CSV结构:第一列为时间字符串,后三列为通道数据 opts = detectImportOptions('ecg_device.csv', 'Delimiter', ','); % 强制将第1列设为文本(避免自动转datetime) opts.VariableTypes{1} = 'string'; % 后续列设为double for k = 2:width(opts) opts.VariableTypes{k} = 'double'; end T = readtable('ecg_device.csv', opts); % 提取时间字符串并转换为秒级数值(相对于首帧) time_str = T{:,1}; % 使用正则提取毫秒部分(兼容"HH:MM:SS"和"HH:MM:SS.mmm") sec_part = regexp(time_str, '(\d+):(\d+):(\d+)(?:\.(\d+))?', 'tokens'); t_sec = zeros(height(T), 1); for i = 1:height(T) tok = sec_part{i}; h = str2double(tok{1}); m = str2double(tok{2}); s = str2double(tok{3}); ms = 0; if numel(tok) > 3 && ~isempty(tok{4}) ms = str2double(tok{4}) * 10^(-numel(tok{4})); end t_sec(i) = h*3600 + m*60 + s + ms; end t_sec = t_sec - t_sec(1); % 相对时间 % 提取通道数据(假设第2-4列) ecg_data = table2array(T(:, 2:4)); % 计算实际采样率(非设备标称值!) fs_actual = 1 / mean(diff(t_sec)); % 单位:Hz fprintf('实际采样率: %.2f Hz\n', fs_actual);

此方案优势在于:

  • 避免readtable('ecg.csv')的自动类型推断错误;
  • regexp提取时间比datetime函数更鲁棒,不受系统区域设置影响;
  • mean(diff(t_sec))计算真实采样间隔,修正设备时钟漂移(常见于低成本MCU)。

3.2 校正ADC量化误差与物理单位

若CSV中数值范围为0-65535,需知其对应的实际电压范围。典型ADS1298配置为±2.4V参考,16位分辨率,则:

% 假设ADC满幅电压为±2.4V,16位(65536级) v_ref = 2.4; % V adc_bits = 16; adc_range = 2^adc_bits; % 65536 % ADC码值中心为32768,对应0V ecg_v = (double(ecg_data) - 32768) * (2*v_ref) / adc_range; % 单位:V % 若设备输出已为mV(如某些Holter),则直接使用 % 但需验证:正常QRS波幅约1-3mV,若plot显示1000mV,必有单位错误 if max(abs(ecg_v)) > 10 % 单位疑似为uV或错误增益 ecg_v = ecg_v / 1000; % 转为mV fprintf('Warning: amplitude >10V, auto-converted to mV.\n'); end

3.3 Excel多Sheet心电数据的批量处理

临床报告常将不同导联分存于不同Sheet(如'I','II','V1')。用sheetnames动态读取:

% 获取所有Sheet名 sheets = sheetnames('ecg_report.xlsx'); % 过滤出导联Sheet(排除'Summary', 'Info'等) lead_sheets = sheets(~cellfun(@isempty, regexp(sheets, '^[I|V|aVR]\d*$'))); ecg_leads = struct(); for i = 1:length(lead_sheets) data_i = readmatrix('ecg_report.xlsx', 'Sheet', lead_sheets{i}); % 假设每Sheet为[N,2]:列1=时间(s),列2=电压(mV) ecg_leads.(lead_sheets{i}) = data_i; end % 合并为多通道矩阵(需时间轴对齐) t_common = linspace(0, max(cellfun(@(x) x(end,1), {ecg_leads.('I'), ecg_leads.('II')})), 10000); ecg_all = zeros(length(t_common), length(lead_sheets)); for i = 1:length(lead_sheets) interp_data = interp1(ecg_leads.(lead_sheets{i})(:,1), ... ecg_leads.(lead_sheets{i})(:,2), ... t_common, 'linear', 'extrap'); ecg_all(:, i) = interp_data; end

此段代码解决Excel数据时间轴不统一问题:用interp1重采样到公共时间轴,避免horzcat直接拼接导致的相位错位。

4. 自定义硬件二进制流解析:从ADS129x ADC的SPI输出到MATLAB可分析波形

当使用TI ADS1292R等心电AFE芯片时,MCU通过SPI发送的原始数据包含状态字节、24位ADC码、校验位。MATLAB无法直接读SPI,但可通过串口接收MCU转发的二进制流。此时.bin文件不是简单int16,而是混合字节协议。

4.1 解析ADS1292R标准数据帧结构

ADS1292R默认SPI帧为:[STATUS][CH1_MSB][CH1_MID][CH1_LSB][CH2_MSB][CH2_MID][CH2_LSB],共7字节/帧。STATUS字节bit7=1表示新数据有效。24位ADC码为补码,需转换为有符号整数。

% 读取MCU串口转发的二进制流(.bin文件) fid = fopen('ads1292_stream.bin', 'r'); raw_bytes = fread(fid, inf, 'uint8'); fclose(fid); % 每帧7字节,丢弃不完整帧 n_frames = floor(length(raw_bytes) / 7); raw_bytes = raw_bytes(1:n_frames*7); % 重塑为[n_frames, 7] frame_mat = reshape(raw_bytes, 7, []).'; % 提取STATUS字节(第1列)和ADC数据(第2-7列) status = frame_mat(:, 1); ch1_bytes = frame_mat(:, 2:4); % [MSB,MID,LSB] ch2_bytes = frame_mat(:, 5:7); % 将24位字节转为int32(注意:ADS1292R为左对齐,需右移8位) % 先合并为uint32:MSB<<16 | MID<<8 | LSB ch1_uint32 = uint32(ch1_bytes(:,1)) * 65536 + ... uint32(ch1_bytes(:,2)) * 256 + ... uint32(ch1_bytes(:,3)); ch2_uint32 = uint32(ch2_bytes(:,1)) * 65536 + ... uint32(ch2_bytes(:,2)) * 256 + ... uint32(ch2_bytes(:,3)); % 转换为有符号24位整数(右移8位得16位有效值) ch1_int16 = int16(bitor(bitshift(ch1_uint32, -8), bitshift(bitand(ch1_uint32, int32(0xFF0000)), -16))); ch2_int16 = int16(bitor(bitshift(ch2_uint32, -8), bitshift(bitand(ch2_uint32, int32(0xFF0000)), -16))); % 应用ADS1292R增益(例:G=6,Vref=2.4V → LSB = 2.4/(2^23*6) V) gain_setting = 6; v_ref = 2.4; lsb_v = v_ref / (2^23 * gain_setting); % V/LSB ecg_ch1_v = double(ch1_int16) * lsb_v; ecg_ch2_v = double(ch2_int16) * lsb_v; % 生成时间轴(ADS1292R默认125SPS) fs_ads = 125; t_ads = (0:length(ecg_ch1_v)-1)' / fs_ads;

关键点:

  • bitshiftbitor确保24位补码正确截断为16位,避免typecast的平台依赖;
  • lsb_v计算基于ADS1292R数据手册:24位ADC在G=6时有效分辨率为16位(因噪声整形),故用2^23而非2^24
  • 时间轴fs_ads=125是芯片硬件设定,不可用CSV中的时间列替代。

4.2 实时串口流解析的缓冲区管理技巧

若数据来自实时串口(非文件),需环形缓冲区防丢帧:

% 初始化串口(假设COM3,115200波特率) s = serialport('COM3', 115200); s.Terminator = 'none'; s.InputBufferSize = 4096; % 预分配大缓冲区 buffer_size = 100000; raw_buffer = zeros(buffer_size, 1, 'uint8'); buffer_ptr = 1; % 伪实时读取(实际应用中放于timer回调) while isvalid(s) && buffer_ptr <= buffer_size n = bytesavailable(s); if n > 0 new_data = fread(s, min(n, buffer_size - buffer_ptr + 1), 'uint8'); raw_buffer(buffer_ptr:min(buffer_ptr+length(new_data)-1, buffer_size)) = new_data; buffer_ptr = buffer_ptr + length(new_data); if buffer_ptr > buffer_size warning('Buffer overflow, data lost.'); break; end end pause(0.01); end fclose(s); % 对raw_buffer执行4.1节的帧解析 % ...

此缓冲区设计确保高吞吐下不丢数据,bytesavailable查询比fread阻塞更可控。

5. 心电信号质量验证与基准测试:用MIT-BIH的reference annotations反向校验你的读取结果

读取正确与否,不能只看波形“像不像”。MIT-BIH提供.atr文件,含医生标注的QRS位置(单位:采样点索引)。用这些黄金标准验证你的ecg_mv时间轴是否准确,是唯一可信方法。

5.1 解析.atr文件获取QRS标注点

.atr是二进制文件,结构为:每条记录16字节,前2字节为采样点索引(小端序int16),第3字节为标注类型(如Q=81N=78),其余填充。但更可靠的是用PhysioNet的rdann(需WFDB),或手动解析:

% 读取.atr文件(以record '100'为例) atr_file = '100.atr'; fid_atr = fopen(atr_file, 'r', 'l'); % .atr文件头有64字节,跳过 fseek(fid_atr, 64, 'bof'); % 每条记录16字节,但实际只需前3字节:索引(2B)+ 类型(1B) % 读取所有记录 atr_data = fread(fid_atr, [3, inf], 'uint8', 'l'); fclose(fid_atr); % 提取索引(前2字节)和类型(第3字节) n_records = size(atr_data, 2); qrs_indices = zeros(n_records, 1); for i = 1:n_records % 小端序:字节0为LSB,字节1为MSB idx_low = atr_data(1, i); idx_high = atr_data(2, i); qrs_indices(i) = idx_low + idx_high * 256; end % 过滤出QRS波(类型码81) qrs_type = atr_data(3, :); qrs_mask = (qrs_type == 81); qrs_true = qrs_indices(qrs_mask); % 加载我们之前解析的ecg_mv,验证前10个QRS位置 fs = 360; % MIT-BIH标准采样率 t_qrs_true = qrs_true(1:10) / fs; % 秒级时间 % 在ecg_mv中搜索对应时间点附近的R波峰值 for k = 1:length(t_qrs_true) t_target = t_qrs_true(k); idx_target = round(t_target * fs); % 在[idx_target-20, idx_target+20]窗口找最大值 win_start = max(1, idx_target - 20); win_end = min(length(ecg_mv), idx_target + 20); [~, peak_idx_local] = max(abs(ecg_mv(win_start:win_end, 1))); peak_idx_abs = win_start + peak_idx_local - 1; error_ms = abs((peak_idx_abs - qrs_true(k)) / fs * 1000); fprintf('QRS #%d: annotated at %.3f s, found at %.3f s, error = %.1f ms\n', ... k, t_qrs_true(k), (peak_idx_abs)/fs, error_ms); end

若误差持续>10ms,说明:

  • .dat解析时字节序错误(导致采样点索引整体偏移);
  • .hea中采样率读错(如将360误为128);
  • 时间轴t未用(0:N-1)/fs而用linspace且端点错误。

5.2 用合成信号进行端到端Pipeline压力测试

为验证整个读取Pipeline(文件IO→解析→单位转换→时间轴)的数值稳定性,生成已知参数的合成ECG:

% 生成MIT-BIH风格合成信号(含P-QRS-T形态) fs_test = 360; t_test = 0:1/fs_test:10; % 10秒 N = length(t_test); % 构造QRS主波(高斯脉冲) qrs_center = 1.2; qrs_width = 0.08; qrs_amp = 1.5; qrs_wave = qrs_amp * exp(-((t_test - qrs_center)/qrs_width).^2); % 添加P波和T波(简化正弦调制) p_wave = 0.3 * sin(2*pi*5*(t_test - 0.2)) .* (t_test > 0.1 & t_test < 0.3); t_wave = 0.4 * sin(2*pi*3*(t_test - 1.8)) .* (t_test > 1.6 & t_test < 2.2); ecg_synthetic = p_wave + qrs_wave + t_wave; % 叠加基线漂移(0.5Hz正弦) baseline = 0.1 * sin(2*pi*0.5*t_test); ecg_synthetic = ecg_synthetic + baseline; % 量化为MIT-BIH格式(增益200,16位有符号) ecg_ad = round(ecg_synthetic * 200); ecg_ad = max(-32768, min(32767, ecg_ad)); % 截断 % 写入模拟.dat文件(小端序int16) fid_sim = fopen('synthetic_100.dat', 'w', 'l'); fwrite(fid_sim, ecg_ad, 'int16'); fclose(fid_sim); % 再用2.2节代码读取,对比ecg_synthetic与读取结果 % 若max(abs(error)) < 0.01mV,则Pipeline合格

此测试将误差源锁定在量化精度与字节序,绕过真实数据的标注不确定性,是CI/CD中自动化验证的基石。

5.3 心电信号质量指标:信噪比(SNR)与基线漂移幅度的MATLAB一行计算

读取后的首要任务是评估信号可用性。SNR不能只算FFT,需用原始时域:

% 计算QRS波段SNR(以record 100的前10秒为例) % 假设ecg_mv是已读取的mV信号,fs=360 % 步骤1:检测R波位置(用简单阈值法) r_peaks = find(ecg_mv(1:3600, 1) > 0.8 & [0; diff(ecg_mv(1:3600, 1))] > 0); % 步骤2:截取每个R波前后0.2秒(72点)作为信号段 signal_segments = []; for k = 1:length(r_peaks) start_idx = max(1, r_peaks(k) - 36); end_idx = min(length(ecg_mv), r_peaks(k) + 36); if end_idx - start_idx + 1 == 72 signal_segments = [signal_segments; ecg_mv(start_idx:end_idx, 1)]; end end % 步骤3:计算平均QRS模板 qrs_template = mean(signal_segments, 1); % 步骤4:计算噪声(用相邻非QRS段,如R波后0.4-0.6秒) noise_segments = []; for k = 1:length(r_peaks) start_idx = min(length(ecg_mv), r_peaks(k) + 144); % 0.4s后 end_idx = min(length(ecg_mv), r_peaks(k) + 216); % 0.6s后 if end_idx > start_idx noise_segments = [noise_segments; ecg_mv(start_idx:end_idx, 1)]; end end noise_rms = rms(noise_segments(:)); % 步骤5:计算QRS模板RMS qrs_rms = rms(qrs_template); % SNR = 20*log10(QRS_RMS / NOISE_RMS) snr_db = 20*log10(qrs_rms / noise_rms); fprintf('QRS SNR = %.1f dB\n', snr_db); % 基线漂移幅度:用0.5Hz高通滤波后,计算全段标准差 hp_filter = highpass(ecg_mv(:,1), 0.5, fs); % MATLAB R2021a+ baseline_drift_std = std(hp_filter); fprintf('Baseline drift RMS = %.3f mV\n', baseline_drift_std);

此计算直接关联临床判读:SNR < 15dB 时QRS难以目视识别,基线漂移 > 0.3mV 时T波分析失效。

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

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

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

立即咨询