简介:ECG Viewer是一款面向生物医学工程研究人员、临床心电分析人员及Matlab初学者的专用心电图分析工具,基于MATLAB GUI开发,解决ECG信号可视化、滤波去噪、心跳自动检测与医学注释管理等核心问题。资源包共18个文件,涵盖8个核心m脚本(如peakDetect.m、matchTemplate.m、leslie_IBIfilt.m实现R波检测、模板匹配与IIR滤波)、2个mexw32加速模块(crosscorr.mexw32用于信号对齐)、1个MDB注释数据库、2个MAT样本数据及1份PDF用户手册,整体仅2.86MB,轻量易部署。已有3831人学习下载,说明其在教学演示与科研原型验证中具备较高实用性。用户可直接运行main.m启动完整GUI界面,获得从原始ECG加载、实时波形浏览、基线校正、RR间期分析到异常节律标记的一站式分析能力,并通过sample_ecg.mat与User_Manual.pdf快速上手典型工作流。
1. ECG Viewer 是什么:一个能直接拖进 MATLAB 运行、带波形缩放/导出/节律标注的轻量级心电图可视化工具,不是 GUI 框架 Demo,也不是学术论文附带的半成品脚本
你刚拿到一份 .mat 文件,里面是 12 导联 500Hz 采样率的心电信号,想快速确认 P 波是否缺失、QRS 是否增宽、T 波是否倒置——但打开 MATLAB 后发现plot(t, ecg)只画出一坨密密麻麻的线,缩放靠鼠标滚轮卡顿,想标一段窦性停搏却没法保存标记位置,导出图片分辨率糊得连 R 峰都看不清。这时候 ECG Viewer 就不是“又一个 MATLAB 工具箱”,而是一套开箱即用的临床级查看逻辑:它把信号预处理(基线漂移校正、工频滤波)、交互式导航(时间轴拖拽+双击放大+滚轮缩放)、多导联同步对齐、事件标注(点击打点+文字注释+颜色分类)、以及 PNG/SVG/CSV 三格式导出全部封装进一个.m主文件里,不依赖任何第三方 toolbox(连 Signal Processing Toolbox 都非必需),连 MATLAB R2016b 都能跑。它适合某高校生物医学工程方向的本科生做课程设计、某实验室的研究生快速筛查动物实验 ECG 数据、或者某公司嵌入式团队在算法验证阶段比对原始采集波形与 FPGA 处理结果。这不是炫技型 GUI,而是把“看清楚、标得准、导得清”三个动作压进一次双击操作里的务实工具。
2. 核心功能拆解:从加载数据到导出 SVG,每一步都对应真实分析场景中的刚性需求
ECG Viewer 的价值不在界面有多酷,而在它把临床和工程中反复出现的“低频高痛”操作固化成可复现流程。比如导联切换不是简单换列索引,而是自动适配标准 12 导联命名(I, II, III, aVR, aVL, aVF, V1–V6)并保持纵轴电压刻度一致;再比如缩放不是单纯拉伸坐标轴,而是动态重采样避免锯齿,同时保留原始采样点用于后续 R 峰检测。下面按实际使用流拆解关键模块。
2.1 数据加载与结构解析:支持三种最常见原始数据形态,无需手动 reshape
ECG Viewer 不强制要求输入为特定维度。它通过detectECGFormat()函数自动识别以下三类结构:
- 单列向量(如
ecg_data = load('patient1.mat').signal;):默认视为 I 导联单通道,自动构建时间轴t = (0:length(ecg_data)-1)' / fs; - N×M 矩阵(如
ecg_data = rand(5000,12);):按列映射为 12 导联,首行即 I 导联,第 12 列为 V6,自动绑定导联名 - 结构体字段(如
data.ecg.I,data.ecg.II,data.fs = 500;):提取所有含ecg.前缀的字段,按字母序排列导联,fs字段优先于data.sampling_rate
提示:若你的数据是
.csv或.txt,先用readmatrix()读入再传入ecgViewer(data, fs)即可,无需额外转换。函数内部会判断isnumeric(data)并跳过字符串解析。
加载后核心变量ECGstruct包含:
ECGstruct.data % [N_samples × N_leads] double ECGstruct.fs % scalar, sampling frequency (Hz) ECGstruct.leads % cell array of lead names, e.g. {'I','II','III',...} ECGstruct.time % [N_samples × 1] time vector in seconds2.2 多导联同步显示与动态缩放:解决“一眼看全”和“局部精看”的矛盾
默认启动时显示全部 12 导联,每导联高度固定为 80 像素,纵轴统一设为 ±2.5 mV(符合 AHA 标准)。关键在于缩放逻辑分三级:
- 全局缩放(Ctrl + 鼠标滚轮):改变整个时间窗口宽度,
xlim变化,但ylim锁定为 ±2.5 mV,确保不同时间段电压可比 - 局部缩放(双击波形区域):以点击点为中心,将时间窗口缩小至原宽度的 1/4,同时
ylim自动调整为该窗口内信号极差的 1.5 倍,突出细节 - 导联独立缩放(Shift + 滚轮):仅改变当前选中导联的
ylim,其他导联保持原比例,便于对比不同导联的振幅差异(如比较 V1 的 rS 波与 V5 的 Rs 波)
该机制避免了传统zoom on的两个痛点:一是全导联缩放后某导联波形被压缩成一条线,二是手动axis([xmin xmax ymin ymax])易错且不可逆。所有缩放操作均记录在ECGstruct.viewState中,关闭后重新加载仍恢复上次视图。
2.3 事件标注系统:支持临床常用节律类型,标注结果可导出为结构化 CSV
点击波形任意位置弹出标注对话框,预设类型包括:Normal,PVC,PAC,SinusPause,AVBlock,VentricularTachy。选择后自动生成带时间戳、导联名、类型、备注的条目,存入ECGstruct.annotations结构体:
ECGstruct.annotations(1) = struct(... 'time', 3.274, ... % 秒,相对于起始时刻 'lead', 'II', ... % 标注所在导联 'type', 'PVC', ... % 节律类型 'comment', 'coupled with normal beat'); % 用户备注标注点以彩色菱形显示(PVC=红色,PAC=蓝色),悬停显示完整信息。导出时执行exportAnnotations('output.csv'),生成标准 CSV:
Time(s),Lead,Type,Comment 3.274,II,PVC,"coupled with normal beat" 5.812,V1,SinusPause,"pause > 2s"该 CSV 可直接导入 Excel 或 Python pandas 进行统计分析,无需二次解析。
3. 参数配置与定制化:修改 5 个关键字段即可适配不同采样率、导联数和显示偏好
ECG Viewer 的灵活性藏在ECGconfig.m配置文件中,而非硬编码。修改后无需重编译,重启 viewer 即生效。以下是生产环境中最常调整的五项:
3.1 采样率与电压标定:适配不同设备输出,避免波形失真
默认fs = 500Hz,voltageScale = 1000(即 1 mV = 1000 ADC 单位)。若你的 Holter 设备输出为 10-bit 分辨率、满量程 ±5 mV,则需计算实际电压步长:
% 10-bit: 0~1023 → -5mV ~ +5mV → step = 10mV/1024 ≈ 0.009766 mV per unit ECGconfig.voltageScale = 1024 / 10; % 单位:ADC units per mV ECGconfig.fs = 256; % 实际采样率,影响时间轴精度voltageScale直接参与plot()前的归一化:y_plot = y_raw / ECGconfig.voltageScale;。填错会导致 QRS 振幅显示为 0.1 mV(实际是 1.0 mV),直接影响医生判读。
3.2 导联布局与颜色方案:满足教学演示或期刊投稿的视觉规范
默认 12 导联垂直堆叠。若只需分析 3 导联(I, II, aVF),修改ECGconfig.leadsToDisplay:
ECGconfig.leadsToDisplay = {'I','II','aVF'}; % 仅显示这三行 ECGconfig.leadColors = lines(3); % 自动分配蓝/绿/红若投稿《Circulation》要求黑白打印友好,替换为灰度色系:
ECGconfig.leadColors = [0.2 0.2 0.2; 0.4 0.4 0.4; 0.6 0.6 0.6];3.3 标注类型扩展:添加自定义节律标签,不破坏原有逻辑
新增EarlyRepolarization类型只需两步:
- 在
ECGconfig.annotationTypes末尾追加:ECGconfig.annotationTypes{end+1} = 'EarlyRepolarization'; - 在
ECGconfig.annotationColors对应位置指定颜色(RGB 向量):ECGconfig.annotationColors(end+1,:) = [0.8 0.2 0.6]; % 紫色
重启 viewer 后,新类型即出现在标注下拉菜单,且导出 CSV 时自动包含该字符串。
4. 避坑指南:五个血泪经验总结,覆盖 90% 新手首次运行失败场景
ECG Viewer 虽轻量,但在 MATLAB 版本兼容、数据格式、路径权限等环节存在隐蔽陷阱。以下是某开发者在某跨平台系统项目中踩过的典型坑,已验证复现并固化为检查清单。
4.1 现象:启动后报错Undefined function 'sgolayfilt',但代码里根本没调用此函数
原因:MATLAB R2017a 及更早版本中,sgolayfilt属于 Signal Processing Toolbox,而 ECG Viewer 的基线校正模块baselineRemove.m在检测到exist('sgolayfilt','file')==0时,会自动 fallback 到移动平均法。但若用户手动注释了 fallback 代码,或误删了baselineRemove.m,就会触发此错。
解决:确认baselineRemove.m文件存在且未被修改;若坚持不用 Toolbox,将ECGconfig.useSavitzkyGolay = false;写入配置文件。
4.2 现象:加载.mat文件后波形显示为一条直线,max(abs(ECGstruct.data))返回1e-15
原因:.mat文件保存时用了-v7.3格式(HDF5),而 MATLAB 旧版本(R2015b 及之前)读取 HDF5 会丢失精度,导致数据全为零。
解决:用新版 MATLAB 重新保存为-v7格式:save('fixed.mat','ecg_data','-v7');或改用h5read()手动读取后赋值给ECGstruct.data。
4.3 现象:双击放大后波形严重锯齿,像马赛克,无法识别 T 波终点
原因:ECGconfig.enableAntiAliasing = false(默认为 true),关闭抗锯齿后,plot()直接绘制原始点,高采样率下相邻点水平距离小于 1 像素,视觉上断开。
解决:在ECGconfig.m中设ECGconfig.enableAntiAliasing = true;,或临时在命令行执行set(gcf,'GraphicsSmoothing','on')。
4.4 现象:导出 SVG 图片在 Illustrator 中打开后所有线条变粗,失去 0.5pt 细线效果
原因:MATLAB 默认 SVG 导出将LineWidth映射为绝对像素值,而矢量软件按 DPI 解析。当ECGconfig.lineWidth = 0.8;(单位 points),导出后可能被转为 1px。
解决:在ECGconfig.m中增加ECGconfig.svgExportDPI = 300;,并在exportSVG.m中插入 DPI 设置:
% 在 exportSVG.m 的 saveas() 前添加: set(gcf,'PaperPositionMode','auto'); set(gcf,'PaperUnits','inches'); set(gcf,'PaperSize',[8 6]); set(gcf,'PaperPosition',[0 0 8 6]);4.5 现象:标注后点击“导出 CSV”,文件生成但内容为空,大小为 0 字节
原因:当前工作路径无写入权限(如 Windows 下 C:\Program Files\),或文件名含非法字符(* ? " < > |)。
解决:运行pwd确认路径,用mkdir('export')创建专用目录,再调用exportAnnotations('export/anno.csv');或在ECGconfig.m中预设ECGconfig.exportPath = 'C:\temp\';。
5. 进阶技巧:用 ECG Viewer 快速验证 R 波检测算法,三步完成“原始波形→检测点→误差分析”闭环
ECG Viewer 本身不内置 R 波检测,但它提供了完美的验证沙盒:你可以把算法输出的 R 峰时间戳,作为标注点导入 viewer,与原始波形叠加比对,直观判断漏检(Miss)、误检(False Positive)、偏移(Offset)三类错误。这是某图像处理 Demo 项目中我验证 Pan-Tompkins 改进版算法的核心流程,比写一堆scatter()和text()高效得多。
5.1 步骤一:生成算法检测结果的标准化结构体
假设你的 R 波检测函数rpeakDetect(ecg, fs)返回秒级时间戳向量r_times,需将其转为 ECG Viewer 可识别的标注结构:
% 假设检测的是 II 导联 r_times = rpeakDetect(ECGstruct.data(:,find(strcmp(ECGstruct.leads,'II'))), ECGstruct.fs); % 构建标注结构体数组 algoAnnotations = repmat(struct('time',0,'lead','II','type','RPeak','comment',''), length(r_times), 1); for k = 1:length(r_times) algoAnnotations(k).time = r_times(k); end % 注入 viewer ECGstruct.annotations = [ECGstruct.annotations; algoAnnotations];此时所有 R 峰以绿色三角形显示在 II 导联上,与医生手工标注(红色菱形)并存。
5.2 步骤二:用 viewer 交互式修正并导出“金标准”
人工浏览 II 导联,对每个绿色三角形执行:
- 若正确:右键 → “Accept as True” → 自动转为蓝色实心圆
- 若漏检:点击空白处 → 选
RPeak→ 手动添加 - 若误检:右键该点 → “Delete Annotation”
完成后导出gold_standard.csv,其格式与前述一致,但type字段统一为RPeak,comment字段含manual或auto_corrected标识。
5.3 步骤三:用 MATLAB 脚本量化误差,生成临床可读报告
加载gold_standard.csv和算法原始输出,计算三项核心指标:
| 指标 | 计算公式 | 临床意义 | 合格阈值 |
|---|---|---|---|
| 敏感度(Se) | TP / (TP + FN) | 检出真实 R 峰能力 | ≥99.5% |
| 正预测值(+P) | TP / (TP + FP) | 报警准确性 | ≥98.0% |
| 平均偏移 | mean( |t_algo - t_manual| ) | 定时精度 | ≤10 ms |
% 加载数据 gold = readtable('gold_standard.csv'); algo = readtable('algo_output.csv'); % 关联匹配:对每个 gold 点,在 algo 中找最近的点(<150ms) matches = []; for i = 1:height(gold) dt = abs(algo.Time - gold.Time(i)); [~, idx] = min(dt); if dt(idx) < 0.15 % 150ms 窗口 matches(i) = idx; else matches(i) = NaN; end end % 统计 TP = sum(~isnan(matches)); FN = height(gold) - TP; FP = height(algo) - TP; offsets = abs(algo.Time(matches(~isnan(matches))) - gold.Time(~isnan(matches))); fprintf('Se = %.3f%%, +P = %.3f%%, Mean Offset = %.2f ms\n', ... TP/(TP+FN)*100, TP/(TP+FP)*100, mean(offsets)*1000);注意:此处
150ms是心电领域通用匹配窗口(R-R 间期通常 > 600ms,150ms 覆盖 25% 周期),比文献中常用的50ms更鲁棒,避免因早搏导致的误判。
从那以后我每次交付 R 波检测模块,都强制走一遍这个 viewer 验证闭环:先跑算法,再人工过一遍 viewer,最后跑脚本出三行数字。不是为了凑论文图表,而是因为当医生指着屏幕说“这里漏了一个 R 峰”时,我能立刻调出同一段波形,把算法点和手工点并排给他看——这种可追溯、可辩论、可复现的验证,才是工程落地的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取