☰
WFDB Toolbox for Matlab:PhysioNet生理信号读取与心电HRV分析实战
2026/10/14 17:20:30 网站建设 项目流程

接着前两篇的进度,今天终于要聊到 MATLAB 环境里处理 PhysioNet 生理信号的重量级工具:WFDB Toolbox for Matlab。长期和心电图、脑电图、脉搏波等打交道的朋友,应该对 PhysioNet 平台不陌生,它上面存放着大量的公开生理信号数据库,比如最经典的 MIT-BIH 心律失常数据库、PTB 心电图诊断数据库、MIMIC 重症监护数据库。这些数据库里的信号文件并不是常见的 CSV 或者 TXT 格式,而是使用了一种专门为生物医学信号设计的 WFDB 格式。如果你尝试过直接按字节读取.dat文件,一定会被里面各种位宽、偏移、增益、基线设置搞到怀疑人生。WFDB Toolbox 就是为了解决这个问题而存在的,它把这套格式的读写、注释解析、常见信号处理算法全部封装成了 Matlab 函数,让我们能像调用内置函数一样处理这些公开数据。

这篇博文主要讲清楚三件事:WFDB Toolbox 是什么、怎么装、怎么用,以及真正跑数据时你一定会遇到的几个坑。适合正在做生物医学信号处理课程设计、研究生课题,或者在读心电数据准备做深度学习模型的同学。即使你之前完全没接触过 WFDB 格式,跟着下面的步骤走一遍,也能够在半小时内完成从数据读取到计算心率变异性的完整流程。

1. 先搞清楚 PhysioNet 和 WFDB Toolbox 是干什么的

1.1 Physiology 数据到底长什么样

在开始敲代码之前,建议先花一分钟理解 WFDB 文件格式本身。PhysioNet 上的信号记录通常由两部分组成:一个扩展名为.hea的头部文件,以及一个或多个.dat的数据文件。.hea文件是纯文本,里面记录了信号通道数、采样频率、每帧样本数、ADC 位数、增益、基线、信号格式等信息。.dat文件则是二进制的波形数据,按照头部文件里声明的格式存储。比如一段常见的 MIT-BIH 记录里,头部可能是这样的:

100 2 360 650000 100.dat 212 360 11 1024 995 0 0 0 0 100.dat 212 360 11 1024 995 0 0 0 0 # 100.txt

这段文字看着很简略,但信息量巨大:记录名是 100,有 2 个通道,采样率为 360 Hz,信号长度是 650000 个采样点。每个通道还有单独的增益和基线描述。如果不借助工具,你要自己写解析器去处理 212 格式这种按 12 位打包的二进制数据,还要处理增益系数和基线校正,工作量非常大。

WFDB 工具箱的价值就在于,它把这些细节全部隐藏起来,并提供统一的高层接口。你不需要知道一个波形在二进制文件里怎么存放,只要知道记录名、通道号和注释类型,就能拿到已经还原成物理单位(通常是 mV)的浮点数组。

1.2 为什么选择 WFDB Toolbox for Matlab

很多新一代研究者会倾向于直接用 Python 的 wfdb 库,这当然没问题。但在工程验证、传统信号处理教学以及部分实验室的既有代码库中,Matlab 的使用比例依然非常高。WFDB Toolbox for Matlab 是官方维护的版本,它提供了一整套与 C 语言版 WFDB 软件包对应的 MATLAB 函数接口,包括信号读取、注释读取、记录写入、格式转换、QRS 检测、RR 间期计算等。

相比自己逐字节解析,用工具箱主要有几个实际好处:第一是准确,官方版本经过了大量数据库验证,增益和格式处理不会出错;第二是注释兼容,PhysioNet 上的.atr、.qrs、.ecg等注释文件,用rdann函数可以一句命令读出来,并且自动处理注释类型和符号定义;第三是可重复性,你写的代码只要换一个数据库名字,就能直接处理其他 PhysioNet 上的记录,不需要修改底层解析逻辑。对于做科研的同学来说,这种可重复性特别重要,因为实验数据可能来自多个数据库,格式略有差异,统一接口能少踩不少坑。

2. 环境搭建:下载、安装、验证三步走

2.1 从 PhysioNet 官网获取工具箱源码

WFDB Toolbox for Matlab 的官方发布页面在 PhysioNet 网站上,进入后搜索 WFDB Toolbox 即可找到。一般来说,有打包好的压缩包,也有 GitHub 仓库。我建议优先下载从官方页面提供的稳定版本,避免开发分支的潜在 bug。

下载完成后,把压缩包解压到一个你习惯放置工具箱的目录,例如D:\MATLAB_Tools\wfdb-app-toolbox。这里要注意,工具箱目录里包含mcode、mex等多个子文件夹,其中mcode是纯 Matlab 代码,mex是经过编译的二进制 MEX 文件。Matlab 在调用时,需要将正确的文件夹加入搜索路径。

2.2 添加路径并处理 MEX 编译问题

打开 Matlab,在命令行窗口执行以下命令:

addpath(genpath('D:\MATLAB_Tools\wfdb-app-toolbox')); savepath;

genpath会把工具箱下所有子目录都加进去,省得漏掉某个子文件夹。执行完savepath后,路径会永久保存,下次启动 Matlab 不需要重复添加。

如果你使用的是比较老版本的工具箱,或者 Matlab 版本较新导致 MEX 文件不兼容,可能会遇到调用rdsamp或rdann时报“无效的 MEX 文件”这种错误。这时候需要进入mex子目录,手动重新编译。编译前先执行mex -setup确认 C 编译器已配置,然后进入对应目录执行:

cd('D:\MATLAB_Tools\wfdb-app-toolbox\mex'); make;

这里需要注意,新版工具箱里有些 MEX 函数可能已经改为纯 Matlab 版本,如果make时报错,不一定影响全部功能。你可以先试试纯 mcode 版本是否可用,再决定是否编译。

2.3 快速验证安装是否成功

安装完成后,需要确认函数能正常读取 PhysioNet 上的数据库。如果你还没有下载任何数据库,可以先使用工具箱自带的测试数据库。在 Matlab 命令窗口执行:

[tm, sig] = rdsamp('sample-data/100', [], 'ma');

这里sample-data/100是工具箱样例数据中的一条记录。执行后,如果sig返回一个矩阵,说明基本函数可以工作。你还可以读取注释:

ann = rdann('sample-data/100', 'atr');

ann是一个列向量,返回的是注释点的样本位置。这一步成功,说明注释解析也正常。如果两个命令都顺利输出,环境就算搭建完成了。

3. 核心函数拆解:读信号、读注释、做转换

3.1 rdsamp:读取真实信号数据

rdsamp是 WFDB 工具箱中最核心的读取函数。标准调用方式是:

[tm, sig] = rdsamp(recordName, N, M, 'ma');

其中recordName是记录名,可以是本地文件路径,也可以是已下载的数据库记录名。N表示起始帧,M表示结束帧,缺省时读取整个记录。'ma'表示将输出转换为毫伏(mV)单位,这是处理心电图时最常用的选项,因为原始数据是整数 ADC 码值,只有还原成物理单位才能直接绘图和计算。

需要特别注意的是,输出tm是时间轴向量,单位是秒。sig是采样点×通道数的矩阵。如果记录有多个通道,通常第一个通道是 MLII 类导联,第二个通道是 V1 或 V2 导联。比如 MIT-BIH 的 100 号记录,sig(:,1)和sig(:,2)分别对应两个导联的心电信号。

使用rdsamp时,很多人容易忽略采样频率。你可以用sampfreq函数查询:

fs = sampfreq('sample-data/100');

这个函数读取头部文件中的采样频率,返回以 Hz 为单位的数字。拿到fs后,时间轴还可以自己按(0:length(sig)-1)/fs生成,结果和tm一样。

3.2 rdann:读取注释文件

生理信号数据库的价值不仅在于波形,更在于专家的标注。比如 MIT-BIH 心律失常数据库提供了atr类型的注释,其中包含心拍类型、心律失常事件、信号质量标记等。rdann的常见用法是:

ann = rdann(recordName, annotator);

annotator就是注释文件名后缀,常见的有atr、qrs、ecg、st等。ann返回的是注释所在样本点位置组成的向量。如果你还想知道每个注释对应的符号类型,可以加一个输出参数:

[ann, type, subtype, chan, num] = rdann(recordName, annotator);

其中type是字符数组,每个元素是代表心拍类型的符号,比如 N 代表正常窦性心拍,V 代表室性早搏,A 代表房性早搏,L 代表左束支传导阻滞心拍。这些符号的含义可以在 PhysioNet 的注释文档中查到。实际用的时候,你可以通过type=='N'这样的逻辑索引筛选出正常心拍,然后计算平均心率或做 HRV 分析。

特别注意:rdann返回的样本点是基于原始采样率的整数位置。在计算时间时,需要除以采样频率,不能直接当作毫秒数用。这是新手最容易出错的地方。

3.3 wfdb2mat 与 mat2wfdb:数据格式转换

有时我们希望把 WFDB 记录转成.mat文件,方便在没有安装工具箱的其他机器上分析,或者出于调试方便想导出成通用格式。这时候可以用wfdb2mat:

[tm, sig, Fs] = wfdb2mat('sample-data/100');

执行后,当前目录下会生成100.mat和100.hea两个文件。wfdb2mat不仅返回数据,还会把数据保存为 mat 文件。这个函数现在更多被rdsamp替代,因为rdsamp更轻量,不生成中间文件。但如果你想保留一份离线的 mat 版本,wfdb2mat仍是一个简单选择。

反向操作mat2wfdb则可以把你的自定义信号写为 WFDB 格式,方便在 PhysioNet 工具链中继续处理。这个函数适用于当你要保存处理后的信号,并需要和原始注释对齐的情况。它的调用方式是:

mat2wfdb(sig, recordName, Fs);

sig是 m×n 矩阵,每一列代表一个通道。执行后会在当前目录生成对应的.dat和.hea文件。写入之后,你可以直接用rdsamp再读回来验证。

3.4 从注释到 RR 间期:ann2rr 的妙用

心率变异性分析中,需要从心拍标注序列中提取 RR 间期。如果手工计算两个连续注释点之间的间隔,还要处理漏检和异位心拍,比较麻烦。WFDB 工具箱提供了ann2rr函数,专门做这件事:

rr = ann2rr(recordName, 'atr', 'ecg');

这里'ecg'表示根据 ECG 通道的注释提取 RR 间期。返回值rr是一个向量,每个元素是连续两个有效心拍之间的间隔,单位是秒。有了这个向量,你可以直接计算均值、标准差、RMSSD 等 HRV 指标。如果注释文件中包含不同类型的心拍,ann2rr默认会排除部分由异位搏动产生的异常间期,不过具体策略还是要查一下文档。实际使用时,我通常会在ann2rr之后再做一次异常值滤波,排除小于 0.3 秒或大于 2 秒的 RR 间期。

4. 实战:从下载心电数据到计算 HRV 的全流程

4.1 获取本地数据记录

为了稳定复现,我建议先从 PhysioNet 官网下载一个小型数据库,比如 MIT-BIH 心律失常数据库中的几条记录。不必下载整个数据库,只需要下载你感兴趣的记录对应的.hea、.dat、.atr文件,放到本地一个文件夹,比如D:\data\mitdb。

如果你希望代码自动从网络获取数据,也可以使用wfdb_config或者网络工具,但那样依赖网络环境,可能不够稳定。我更推荐本地数据分析流程,效率高且可重复。

4.2 加载信号并检查基本质量

假设你已下载记录 100 到D:\data\mitdb。先设置工作目录,然后执行:

cd('D:\data\mitdb'); [tm, sig] = rdsamp('100', [], 'ma'); fs = sampfreq('100');

这时sig是一个 650000×2 的矩阵,采样率 360 Hz,总时长约为 30 分钟。核对一下信号长度和采样率,是数据检查的第一步。然后快速绘制其中一个通道的信号:

t = (0:length(sig)-1)/fs; plot(t, sig(:,1)); xlim([0 5]); xlabel('时间 (s)'); ylabel('幅度 (mV)');

正常情况下,你应该看到清晰的心电波形,R 波幅度明显,T 波紧随其后。如果波形出现大量平直段或剧烈毛刺,说明数据文件损坏或读取参数设置错误。

4.3 读取注释并筛选正常心拍

读取atr注释,并查看前几个命中位置:

[ann, type] = rdann('100', 'atr'); disp(ann(1:10)); disp(type(1:10));

你会看到前几个注释点可能在样本位置 18、81、144 附近,对应 360 Hz 采样下每秒 1 个左右的心拍。type前半段多数字符是N,偶尔夹杂A或V。做 HRV 分析时,我通常保留类型为 N 和 L/R 等正常心拍,剔除 V 型室早和 A 型房早,避免异位搏动扭曲 RR 间期。

筛选正常心拍的代码如下:

valid = (type=='N') | (type=='L') | (type=='R'); ann_valid = ann(valid);

4.4 计算 RR 间期与 HRV 指标

使用ann2rr直接计算所有 RR 间期,然后做筛选:

rr = ann2rr('100', 'atr', 'ecg'); rr_valid = rr(rr>0.3 & rr<2.0);

有了rr_valid,就可以计算常用指标:

rr_mean = mean(rr_valid); rr_std = std(rr_valid); rmssd = sqrt(mean(diff(rr_valid).^2));

这些指标分别对应 HRV 时域分析中的平均 RR 间期、标准差和相邻差方根。根据平均 RR 间期,还可以换算平均心率:

hr = 60 / rr_mean;

如果你想做得更细致,还可以对 RR 间期序列做频域分析,提取低频和高频功率。不过在写论文或者报告时,要重点关注这些指标的意义和适用范围,别只丢一堆数字。

4.5 批量处理多条记录

实际项目很少只分析一条记录,通常需要处理整个数据库。批量处理的框架很简单:先用一个 cell 数组存记录名列表,然后循环处理。比如:

recordNames = {'100', '101', '102', '103', '104'}; results = table(); for i = 1:length(recordNames) rec = recordNames{i}; [~, sig] = rdsamp(rec, [], 'ma'); fs = sampfreq(rec); rr = ann2rr(rec, 'atr', 'ecg'); rr_valid = rr(rr>0.3 & rr<2.0); results(i).record = rec; results(i).hr = 60 / mean(rr_valid); results(i).rmssd = sqrt(mean(diff(rr_valid).^2)); end disp(results);

这里用表格存储结果,方便后续导出到 Excel。注意每轮循环前不需要clear全部变量,只需要确保下一轮记录覆盖前一轮结果即可。如果需要更高效的批处理,建议先读取所有注释再循环,避免频繁打开文件。

5. 常见报错和处理技巧

5.1 找不到记录或文件路径错误

最常见的报错是:

Cannot find record "100".

出现这种错误,通常是因为当前工作目录不在数据文件所在目录,或者recordName没有包含路径。解决办法有两种:要么用cd切换到数据目录,要么在记录名中包含完整路径,比如'D:\data\mitdb\100'。这里注意,Windows 下路径分隔符建议使用反斜杠或正斜杠,Matlab 都能识别。

另外,PhysioNet 上的记录有时分布在多个分段,比如100_01、100_02,这时recordName需要用双引号把分片组合起来,WFDB 工具箱支持这种分片读取。如果你只需要第一条分片,直接写基础记录名即可。

5.2 MEX 文件不兼容导致崩溃

在较新的 Matlab 版本(比如 R2022b 之后)运行老版本 WFDB 工具箱,可能会遇到动态链接库加载失败或 MEX 文件无效。这时候不要慌,先执行mex -setup配置好 C 编译器,然后在工具箱的mex目录下重新执行make。有些机器上编译会报错,原因可能是缺少编译工具或编译器版本太老。一个很实用的替代方案是:在mcode目录下用纯 Matlab 函数,比如使用rdsamp的 m 文件版本,虽然运行速度慢一些,但胜在稳定兼容。特别是对于较短的数据记录,纯 Matlab 版本的性能完全可以接受。

5.3 信号幅度和单位错误

如果你读取信号后画图,发现心电图幅度是几千而不是 1mV 左右的正常范围,多半是没有使用'ma'参数。默认情况下,rdsamp返回的是 ADC 原始码值,而不是物理单位。加上'ma'参数后,工具箱会按照头部文件中的增益和基线自动校准。对于需要后续定量分析,尤其是振幅测量和 QRS 检测的场景,这一步非常关键。

5.4 注释点与信号长度不匹配

处理某些记录时,你会遇到注释文件标注的最后一个样本点超出了数据文件长度,这通常是因为数据库记录了额外的边缘注释或者注释与信号文件版本不一致。在用注释筛选心拍时,加上一个范围限制可以避免越界错误:

ann_valid = ann(ann > 0 & ann < length(sig));

同时,要注意注释点是从 0 开始还是从 1 开始。WFDB 内部习惯从 0 开始计数,但 Matlab 数组索引从 1 开始。当你在代码中直接用ann去索引信号时,需要小心处理。不过大多数情况下,rdann返回的注释点可以直接用于绘制散点标记,因为绘图坐标也是基于同一个信号索引构建的。

6. 进阶扩展:从 WFDB 到深度学习与自定义工具

6.1 把数据导出给 Python 或深度学习框架使用

很多做深度学习的朋友会用 Python 读取 PhysioNet 数据,但你的预处理代码已经用 Matlab 写好了,这时可以把结果导出成通用格式。最简单的是直接保存为 mat 文件,Python 里用 scipy.io 可以读取。例如:

save('ecg_segments.mat', 'sig', 'ann_valid', 'fs');

然后在 Python 中:

import scipy.io data = scipy.io.loadmat('ecg_segments.mat') sig = data['sig'] ann_valid = data['ann_valid']

如果数据量很大,建议保存为 HDF5 格式,避免 mat 文件过大。Matlab 自带h5create和h5write函数,Python 侧用 h5py 读取,效率更高。

6.2 与 Signal Processing Toolbox 结合使用

WFDB 工具箱负责数据接入,而滤波、频谱分析等任务可以交给 Matlab 内置的信号处理工具箱。比如你想对心电信号做带通滤波,去掉基线漂移和工频干扰,典型流程是:

fs = sampfreq('100'); [b, a] = butter(2, [0.5 40]/(fs/2), 'bandpass'); sig_filt = filtfilt(b, a, double(sig(:,1)));

注意:filtfilt零相位滤波适合离线分析,不用顾虑相位延迟。滤波后可以直接绘图或者再做 R 峰检测。WFDB 工具箱里还有qrs检测器,但在某些数据上误检率较高,可以和传统阈值法结合使用。

6.3 自定义读取函数,减少依赖

如果你只是想偶尔读一个 WFDB 文件,不想安装整个工具箱,也可以写简化的读取函数。核心就是解析.hea头文件,然后根据格式类型读取.dat。比如对于最常见的 16 位格式format 16,读取逻辑非常简单:直接按 little-endian int16 读取矩阵即可。但对于 212 这种 12 位打包格式,就需要一点位操作技巧。不过,我建议还是优先使用官方工具箱,因为你无法预判不同数据库的格式变化,官方函数在这些边界情况上更可靠。

另外,WFDB Toolbox 还提供了wrsamp函数,可以将内存中的矩阵写入 WFDB 记录。比如你想保存滤波后的信号,并且希望保留原来的注释结构,可以先调用wrsamp生成新记录,再用rdann把原始注释复制过去。这一组合在信号预处理项目中非常实用。

我个人在实际操作中的体会是,WFDB Toolbox 最让你省心的并不是“读数据”本身,而是它把生理信号处理中那些约定俗成的规则也一并考虑了进去,比如注释符号解释、不同数据库的命名规范、以及增益校准方式。第一次用自己的代码跑通 MIT-BIH 数据库时,我对很多细节都没概念,是工具箱帮我一步步理清了这些门道。如果你也想快速入门生理信号处理,建议不要急着写底层读取代码,先把官方工具箱玩熟,再根据需求决定是否自己造轮子。最后分享一个小技巧:处理任何新数据库前,先用wfdbdesc或whos -file查看记录信息,了解采样率和通道数再动手,能避免一半的读数据报错。

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

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

立即咨询