
简介基于奇异谱SVD算法的ECG心电信号去噪Matlab实现面向生物医学工程、数字信号处理方向的本硕教研与算法复现人群。资源围绕心电信号中基线漂移、工频干扰及肌电噪声的滤除问题提供完整的SVD去噪思路既可作为课程实验的参考代码也可用于竞赛或毕业设计的前期验证。压缩包共5个文件包含核心的SSA1.m算法程序、ecg_8s.mat示例心电数据、说明文档txt以及运行结果图像jpg/png效果图整体大小仅192KB轻量且便于快速上手。已有176人学习下载。借助源码可直观学习奇异值分解、有效奇异值选取与信号重构的完整流程运行后能对比去噪前后的波形差异附带的示例数据与效果图降低了测试门槛适合需要快速验证算法性能或进一步做特征提取的开发者。1. 为什么心电去噪要选奇异谱 SVD从波形失真说起心电信号ECG去噪有个反直觉的结论带通滤波器设计得越“干净”QRS 波群越容易被削平。标准的 0.5~40Hz 带通 FIR 能把工频干扰压下去遇到肌电干扰时却会显著展宽 QRS 形态ST 段也容易失真。奇异谱分析SSA换了一条路它把一维 ECG 信号嵌入成轨迹矩阵再用奇异值分解SVD把矩阵拆成若干秩一成分丢弃代表噪声的小奇异值分量最后对角平均还原出干净波形。整个过程不预设滤波频带纯粹由数据自身的能量结构决定对不同个体、不同导联的心电波形更稳健。本文用 ecg_8s.mat 和 SSA1.m 这套 Matlab 实现把这个去噪流程从原理讲到参数调优适合本科、研究生在做心电预处理时直接复现。2. SSA 第一步轨迹矩阵的嵌入与奇异值分解2.1 把一维 ECG 映射到二维空间的理由SSA 的基本假设是一段有限长的时间序列其内部结构可以通过嵌入空间的几何结构捕捉。ECG 波形在相邻采样点之间有很强的短期相关性而噪声会打破这种相关性。通过延时嵌入信号的相关性被转化为轨迹矩阵的秩结构——信号成分让矩阵的行之间保持线性相关噪声则抬高矩阵的有效秩。一维的滤波问题由此变成二维的低秩逼近问题找出一个低秩矩阵使其在 Frobenius 范数下最接近观测矩阵。2.2 轨迹矩阵构造窗口长度 L 与 Hankel 结构假设信号向量 x 的长度为 N选定窗口长度 L需要满足 2 ≤ L ≤ N/2取 K N - L 1。然后循环构造轨迹矩阵% 轨迹矩阵嵌入函数 function X trajectory(x, L) N length(x); K N - L 1; X zeros(L, K); for j 1:K X(:, j) x(j : j L - 1); end end这段代码做了三件事第一行取出信号长度第二行计算滑动窗口的个数最后两行把从 j 开始的 L 个采样点作为一列写入矩阵。列 j 就是 x(j) 到 x(jL-1) 这一段局部波形由于第 i 行和第 i1 行之间只错了一个采样点这个矩阵具有 Hankel 结构。Hankel 结构的数学含义是同一对角线上所有元素对应同一个原始时间点后续对角平均正是利用这一点把矩阵还原成信号。有一点容易被忽略L 并非越大越好。当 L 小于 10每一列只覆盖 QRS 波的边缘几个采样点列与列之间的差异被压缩奇异值会纠缠在一起信号子空间与噪声子空间之间没有明显的界线当 L 超过 80窗口里包含多个心跳周期不同心跳之间的时序差异会把能量扩散到更多奇异值上r 的人工选取变得主观。所以开始写自己的 SSA 脚本前先对几种 L 画奇异值累计能量曲线找到趋势平缓的起点作为窗口长度。2.3 SVD 谱分解与特征三元组轨迹矩阵 X 的 SVD 分解写成 X UΣV^T。U 是 L×L 的左奇异向量每一列对应一个时间模式Σ 对角线上的奇异值按从大到小排列V 的列是每个模式在时间轴上的投影。用特征三元组表示每一个秩一成分是 sqrt(λi)·Ui·Vi^T其中 λi 是 XX^T 的特征值即奇异值的平方。在 ECG 场景里奇异值下降的形态非常有规律第一个奇异值通常对应基线漂移第二个和第三个对应 QRS 波群的主能量第四个到第六个承载 P 波和 T 波细节再往后奇异值数量多但数值小恰好是对噪声基底的一种低秩逼近。这个分布规律是后面选择 r 的依据也是 SSA 不需要预设频带的根本原因。窗口 L对应生理尺度按 360Hz去噪表现10约 28ms小于 QRS 上升支每个分量只覆盖局部斜率重构后 R 峰被有一定程度的衰减36约 100ms完整 QRS 波主分量集中承载 QRS 能量r 的选择余量较大80约 220ms覆盖 PR 与 QT 区间轨迹矩阵更宽噪声分量增多r 敏感、不容易稳定所以窗口长度 L 的选择应该让窗口恰好覆盖一个完整的 QRS 波。若你的数据采样率不是 360Hz按等比例折算即可若采样率是 1000HzL 100 附近通常是合理的起点。3. SSA1.m 核心拆解分组、重构与对角平均实现3.1 加载 ecg_8s.mat 和预处理流程解压这个压缩包后文件夹里有 ecg_8s.mat、说明.txt、运行结果.jpg、SSA1.m 和打开.png。打开.png 是作者给的步骤示意图说明.txt 里写的是路径配置提示。把全部文件放在同一个目录然后用 Matlab 打开 SSA1.m 并直接运行。如果当前工作区里没有 ecg_8s.mat可以用 addpath 手动把文件夹加入搜索路径。load 进工作区之后第一步是确定数据的实际组织和采样率。有些心电数据集会保存成双精度行向量有的保存成带时间戳的表格式结构体。直接在命令窗口运行whos可以看到变量名、维度和数据类型。我一般会在脚本入口统一做一次规范化和预处理的转换%% 加载 ecg_8s.mat 并做基础预处理 clear; clc; load(ecg_8s.mat); % 数据文件具体变量名以工作区为准 fs 360; % 采样率需要按实际数据修改 x ecg_8s(:); % 统一转为列向量避免循环嵌入时维度错误 x x - mean(x); % 去直流偏置否则第一个奇异值被常数项占据 N length(x); % 数据长度 L 36; % 窗口长度按采样率折算到 QRS 波宽 X trajectory(x, L); % 调用上一章写的轨迹矩阵函数这里的 fs 和 L 是绑定的。如果你的数据集采样率是 250HzQRS 波 100msL 应取 25 左右采样率 1000Hz 则取 100。去直流偏置是为了让轨迹矩阵的第一列接近零均值否则 SVD 会多出一个常数主成分浪费掉一个本可以用于信号重构的奇异值分量。这个坑在我第一次处理临床上采集的数据时出现过预处理脚本里少了 mean 减除结果第一个奇异值占比始终超过 80%。3.2 分组重构按奇异值大小选信号子空间轨迹矩阵做完 SVD 之后真正决定去噪效果的是怎么分组。SSA1.m 的思路非常直接按奇异值从大到小排序认为信号子空间只占前 r 个分量后面对应的全是噪声。代码可以这样实现%% 奇异值分解与分步重构 [U, S, V] svd(X, econ); lambda diag(S).^2; % 把奇异值转换为特征值 cum_ratio cumsum(lambda) / sum(lambda); % 累计能量贡献率 r 6; % 保留的信号子空间秩 % 只保留前 r 个秩一成分 X_r zeros(L, size(X, 2)); for k 1:r X_r X_r sqrt(lambda(k)) * U(:, k) * V(:, k); end这里用svd(X, econ)而不是svd(X)是因为轨迹矩阵的列数 K 2845 远大于行数 L 36。‘econ’ 选项只计算前 min(L, K) 个奇异向量得到一个 L×L 的 U 和一个 K×L 的 V内存占用从 2880×2880 降到 36×2845。循环累加代替矩阵连乘同样是有意的选择每个特征三元组可以被逐个观察方便判断第 k 个分量到底是生理波形还是噪声。如果用一整条矩阵乘法一次性重构调试时很难定位是哪个分量影响 R 峰形状。成分序号分量特征在 ECG 去噪中的角色1低频慢变分量呼吸引起的基线漂移应部分保留或全部丢弃2-3能量集中的振荡分量承载 QRS 主波群4-6中频过渡分量P 波和 T 波的细节7 以后能量低且数值平稳肌电噪声和随机噪声整组丢弃3.3 对角平均把矩阵还原为 8 秒 ECG 序列拿到重构后的轨迹矩阵 X_r 之后还要把它变回一维信号。对角平均是 SSA 的标准逆变换操作它遍历矩阵中每个元素把落在同一反对角线上ij-1 n的所有元素求平均赋给 x(n)。原因很简单原始信号中的第 n 个点被重复使用了多次每次进入不同的窗口列取平均能消除延迟嵌入带来的冗余误差。%% 对角平均矩阵还原为信号 function y diagonal_average(X, L, K, N) y zeros(N, 1); cnt zeros(N, 1); for i 1:L for j 1:K n i j - 1; y(n) y(n) X(i, j); cnt(n) cnt(n) 1; end end y y ./ cnt; end这个双循环的时间复杂度是 O(L×K)对于 N2880、L36 来说大约只有 10 万次累加Matlab 跑起来不到 0.1 秒完全可以接受。如果你要批量处理更长的连续心电数据建议把内层循环向量化用列索引 n (1:K) i - 1 一次完成一整行的累加速度会快几十倍。还需要注意边界效应n 靠近 1 或 N 时参与平均的矩阵元素个数会急剧减少还原出的首尾几十个点容易出现截断伪迹。后面参数调优部分会给出对应的处理技巧。4. 奇异值数量怎么定拐点判据、残差能量与参数调优4.1 先看奇异值谱的拐点位置r 是整个 SSA1.m 中唯一需要人工介入的参数也是最容易出错的地方。一个可复现的判断方法是画出奇异值能量分布曲线找到曲线从陡降变平缓的拐点。手工找拐点有一个可信的辅助工具就是日志坐标下的特征值下降图%% 绘制奇异值谱辅助确定 r figure; semilogy(lambda / sum(lambda), o-, LineWidth, 1.5); xlabel(奇异值序号 k); ylabel(特征值占比对数轴); title(奇异值能量分布); grid on;用对数轴而不是线性轴是因为前面的特征值可能比后面的大两三个数量级线性坐标会把噪声区的变化压成一条几乎水平的直线拐点很难分辨。从 ecg_8s.mat 的运行结果 jpg 看这类信号通常会在 k4 到 k6 之间出现明显转折转折之后曲线进入下降缓慢的噪声平台。r 取在转折点附近即可偏向噪声一侧一到两个位置通常比偏向信号一侧效果更好。如果想把 r 的选择自动化常见做法是计算奇异值的二阶差分找到差分绝对值最大的位置作为拐点。这个自动化判据在信噪比尚可时比较稳定一旦噪声变强曲线会出现多个局部拐点自动检测就会失效。所以 SSA1.m 里保留的是人工设定 r 的方式教研场景里可解释性优先于全自动。4.2 r 的不同取值对 QRS 波群的影响r 设小了QRS 主峰的能量被当作噪声丢到子空间之外重构出的 R 波幅度低于原始信号r 设大了噪声分量又混进信号子空间去噪结果出现高频毛刺。以 8 秒数据加 5dB 噪声后的实验为例取不同 r 的效果对比如下r 取值QRS 波形态特征噪声残余建议2R 峰幅度下降 15% 以上ST 段明显拉平极少不推荐使用6R 峰保留 95% 以上ST 段仅有轻微抖动很小推荐起始值10R 峰基本不变P 波细节保留完整可见中高频毛刺取决于下游目标36和原始信号几乎一致噪声未被有效抑制没有去噪意义另外要注意这个表格是在固定 L36、固定 5dB 加噪下得到的。信噪比更低时噪声能量在高维空间铺得更开单个噪声奇异值变小但个数增多拐点会向右偏移r 应适当增大信噪比很高时r 甚至可以降到 3~4 都不影响 QRS 形态。实际调参时把 r 从小往大试观察 QRS 波的主峰高度变化找到开始进入稳定区的位置就是合理的。4.3 用残差能量和波形形态验证 r 是否合理4.3.1 残差的随机性检验验证 r 是否合理不应该只靠看波形。把去噪后的信号从原始信号里减掉得到残差序列 ε x - x_reconstructed。如果 r 选择合适ε 应该是近似零均值的随机噪声时域上不保留明显的 QRS 尖峰频域上没有 50Hz 工频大小的突出谱峰。可以在 Matlab 里做一次快速检查%% 残差统计检查 noise x - y_den; k kurtosis(noise); % 高斯分布峰度接近 3 figure; histfit(noise, 100); % 画直方图与正态拟合曲线峰度明显高于 5 说明残差里还残留着尖峰状周期成分r 应该再调大一点峰度太接近 2 则说明信号可能有过度平滑。直方图能直观告诉你残差中心是否偏移到零之外如果中心明显偏离说明 r 把一部分有效信号丢给了噪声子空间。4.3.2 与带通滤波器配合使用的顺序问题SSA 擅长处理非平稳多次项的噪声但它并不是一个可调解的窄带滤波器。如果你的目标场景里还存在 50Hz 工频残留可以在 SSA 去噪之后再跟进一个窄带陷波器。顺序千万不要反过来先做带通滤波再做 SSA 会改变信号的时序结构SVD 分量中会出现振铃伪迹r 的可调节范围也会明显变窄。我一般在 SSA 之前只做去均值和去基线不做任何频率选择性滤波把频带净化留给 SSA 之后的陷波器或者小波细节系数。提示心电分析中 ST 段的微小形变比残差白噪声更致命不要为了追求残差纯随机而把 r 调得过小。优先保住 QRS 波和 ST 段的形态完整性再考虑抑制更多干扰。5. 用 SNR 和 RMSE 验证去噪效果实测 8s ECG 信号优化技巧5.1 人为加噪后计算 SNR 提升真实临床 ECG 很难拿到“无噪真值”所以在验证 SSA1.m 效果时最常用的方法是把 ecg_8s.mat 当作参考信号人为叠加高斯白噪声再通过去噪流程用 SNR 和 RMSE 两个指标量化效果。具体代码如下%% 量化评估去噪效果 y_noisy awgn(raw, 5, measured); % 加 5dB 高斯白噪声 y_den SSA_process(y_noisy, L, r); % 对应你从 SSA1.m 封装出的函数 snr_in 10*log10( sum(raw.^2) / sum((raw - y_noisy).^2) ); snr_out 10*log10( sum(raw.^2) / sum((raw - y_den).^2) ); rmse sqrt(mean((raw - y_den).^2)); fprintf(SNR %.2f - %.2f dB | RMSE %.4f\n, snr_in, snr_out, rmse);awgn是 Matlab 通信工具箱的加噪函数‘measured’ 选项会根据原信号功率自动调整噪声幅度避免手动换算 dB。SNR 提升不足 3dB 时优先检查 r 是不是取大了而不是去怀疑 SVD 算法本身出了问题。5.2 分段 SSA 去噪消除边界效应8 秒数据一次性做 SSA 没有明显的性能问题但连续心电记录通常是几分钟甚至几个小时。这时如果整段嵌入轨迹矩阵 L×K 里的 K 会膨胀到数十万列SVD 耗时大幅增加首尾边缘的对角平均误差也会被放大。常见的做法是把信号切成 2 秒一段相邻段重叠 0.5 秒每段独立完成 SSA 去噪后再线性加权拼接处理方式SVD 耗时8 秒边界伪迹适用场景整段嵌入约 0.4s首尾各 L/2 点失真离线单条测试2s 分段 重叠 0.5s4×0.1s几乎不可见长程批量分析分段后每一段的长度是 7202s × 360Hz窗口 L 仍取 36QRS 波在每段内保持完整重叠区域做线性淡入淡出保证拼接处没有突变。这个技巧对长程批处理非常实用也让 r 的选取更加稳定因为每一段内的心拍数量基本一致奇异值谱的形状变化不大。最后一个可操作的技巧把“调参”和“验证”分开在两个阶段完成。调参时只盯住运行结果.jpg 里那一段数据和 QRS 主峰的形态验证时再用人为加噪的整段数据算 SNR 和 RMSE。两件事不在同一轮里同时做可以避免参数选择一点点逼近验证集导致最终指标虚高。本文还有配套的精品资源点击获取