☰
基于小波变换的脉搏信号分类:MATLAB资源包实战与避坑指南
2026/10/22 19:20:47 网站建设 项目流程

简介:这份由同济大学完成的脉搏识别资料包,面向生物医学工程、信号处理方向的学习者与科研人员,聚焦小波分析在脉搏信号去噪、特征提取与分类识别中的完整实现。包内共8个文件,以4个m脚本为核心,配合2个doc与1个docx说明文档、1个xlsx数据表,压缩包约39KB,体积轻巧却覆盖了从采样频率设定、滤波器设计到参数提取要求的全流程。代码部分包含小波分解、带通与高低通滤波等模块,可帮助读者理解如何将一维脉搏波转化为多尺度表示,并提取周期性、振幅变化等关键特征,进而结合分类模型区分正常与异常脉搏。文档与数据表则补充了实验参数与结果记录,便于对照复现。目前已有237人学习下载,适合希望快速上手脉搏信号分析、掌握小波变换实战流程的读者参考。

1. 拆开这个脉搏识别包:小波变换做信号分类,到底能不能跑通

前阵子有个做可穿戴设备的朋友问我,手上拿到一段脉搏波形,想区分正常搏动和异常搏动,但信号里混着基线漂移和工频干扰,直接扔进分类器准确率惨不忍睹。这个问题在生物医学信号处理里太典型了——脉搏信号本身就是非平稳的,你拿傅里叶变换去看它,时间信息全丢了,根本抓不住上升沿、下降沿这些关键瞬时特征。小波分析恰好补上这个短板,它能在时间和频率两个维度同时做局部化分析,把脉搏波形拆成不同尺度上的细节系数,噪声和有效成分自然分层。这份脉搏识别资源包就是围绕这条技术路线展开的,里面包含完整的 MATLAB 源码、采样频率说明文档、参数提取要求文档以及一份脉搏数据表格。适合正在做生理信号课程设计的学生,也适合想快速验证小波特征提取加分类这套流程的工程师。下面我按实际拆包和复现的顺序,把每个文件的作用、参数怎么调、哪里容易翻车讲清楚。

2. 文件结构与信号预处理:从 data.xlsx 到干净脉搏波形

2.1 资源包里的文件分工

先把压缩包解开,文件不多但分工明确。code文件夹是核心,里面放着myfun.m、butterlow.m、butterband.m、butterhigh.m这几个 MATLAB 脚本。data.xlsx是原始脉搏数据,通常是一列时间戳加一列幅值,采样频率在采样频率.doc里说明。参数提取要求.doc规定了从信号里要提取哪些特征,说明.docx是整体流程的补充描述。我一般拿到这种包,先不急着跑代码,而是把文档过一遍,确认采样率和特征维度,否则后面滤波器设计全是白费功夫。

常见做法是先用butterlow.m和butterhigh.m分别做低通和高通滤波,再用butterband.m做带通。这三个脚本本质上都是巴特沃斯滤波器的封装,区别在于截止频率和阶数。脉搏信号的有效频段一般在 0.5 Hz 到 10 Hz 之间,低于 0.5 Hz 的是基线漂移,高于 10 Hz 的多半是工频干扰或肌电噪声。所以带通滤波的上下限就卡在这个范围。

2.2 读取数据与滤波器参数设置

先看数据读取和滤波这一步。data.xlsx里如果是两列,第一列时间第二列幅值,用xlsread或readmatrix都行。我习惯用readmatrix,返回的是纯数值矩阵,省得处理 cell 数组。

% 读取脉搏数据,假设第一列是时间,第二列是幅值 raw = readmatrix('data.xlsx'); t = raw(:, 1); % 时间轴 x = raw(:, 2); % 原始脉搏幅值 % 根据采样频率.doc 确认 fs,这里假设 fs = 200 Hz fs = 200;

采样频率这个参数必须从文档里确认,不能猜。如果采样频率.doc写的是 200 Hz,那奈奎斯特频率就是 100 Hz,带通上限设 10 Hz 完全没问题。如果实际采样率只有 50 Hz,那上限就得压到 20 Hz 以下,否则滤波器设计出来也是混叠的。

接下来设计带通滤波器。butterband.m里大概率是这么写的:

function y = butterband(x, fs, f1, f2, order) % 巴特沃斯带通滤波 % x: 输入信号 % fs: 采样频率 % f1, f2: 通带上下限频率 % order: 滤波器阶数 wn = [f1, f2] / (fs / 2); % 归一化频率 [b, a] = butter(order, wn, 'bandpass'); y = filtfilt(b, a, x); % 零相位滤波,避免波形偏移 end

这里有几个参数要盯住。order一般取 2 到 4,阶数太高会导致通带边缘陡峭但相位非线性严重,阶数太低则阻带衰减不够。filtfilt而不是filter,是因为filtfilt做了正向和反向两次滤波,相位响应为零,脉搏波形的峰值位置不会偏移。这一点在提取上升沿时间的时候特别关键,用filter的话峰值会往后挪,算出来的脉搏波传导时间全是错的。

调用的时候:

x_filtered = butterband(x, fs, 0.5, 10, 3);

低通butterlow.m和高通butterhigh.m的用法类似,只是butter的第三个参数改成'low'或'high'。如果信号里基线漂移特别严重,可以先高通再低通,分两步走,比直接带通更可控。

2.3 去噪后的效果验证

滤波完别急着往下走,先画个图看一眼。原始信号和滤波后信号叠在一起,如果滤波后的波形把原始信号里的毛刺去掉了,但主峰和次峰的相对位置没变,说明参数合理。如果滤波后波形整体变形,比如主峰变矮、次峰消失,那多半是通带设窄了或者阶数太高。我一般会对比filtfilt和filter的结果,确认相位没跑偏。

提示:如果data.xlsx里的数据没有时间列,只有幅值列,那就得根据采样频率自己生成时间轴t = (0:length(x)-1)/fs,否则后面做小波变换时尺度参数会对不上。

3. 小波变换做特征提取:myfun.m 里的尺度怎么选

3.1 为什么选小波而不是傅里叶

脉搏信号是非平稳的,它的频率成分随时间变化。傅里叶变换把整个信号扔进去,出来的是全局频谱,你没法知道某个频率成分出现在哪个时刻。小波变换用一组伸缩平移的基函数去逼近信号,尺度小的时候看高频细节,尺度大的时候看低频趋势。对于脉搏信号,高频细节对应上升沿和下降沿的陡峭程度,低频趋势对应一个心动周期内的整体形态。myfun.m大概率就是封装了小波分解和特征提取的流程。

常见的小波基有db4、sym4、coif2这些。db4在生物医学信号里用得最多,因为它的消失矩和正则性比较平衡,分解出来的系数既稀疏又能保留波形特征。sym4的对称性更好,如果你特别在意波形的形状保真度,可以换sym4。我一般先用db4跑一遍,看看各层系数的能量分布,再决定要不要换。

3.2 小波分解层数与特征向量构造

分解层数怎么定?有个经验公式:level = floor(log2(N)),N 是信号长度。但实际用的时候不用分那么多层,脉搏信号的有效成分集中在 3 到 5 层。假设采样率 200 Hz,做 5 层分解,第 5 层近似系数的频带大概是 0 到 3.125 Hz,正好覆盖脉搏的主频段。细节系数 d1 到 d5 分别对应不同的高频段,噪声主要落在 d1 和 d2。

% 小波分解与特征提取 wname = 'db4'; level = 5; [c, l] = wavedec(x_filtered, level, wname); % 提取各层近似系数和细节系数 a5 = appcoef(c, l, wname, level); d5 = detcoef(c, l, 5); d4 = detcoef(c, l, 4); d3 = detcoef(c, l, 3); % 构造特征向量:各层系数的能量、均值、标准差 feat = []; for k = 1:level dk = detcoef(c, l, k); feat = [feat, sum(dk.^2), mean(abs(dk)), std(dk)]; end feat = [feat, sum(a5.^2), mean(abs(a5)), std(a5)];

这段代码的逻辑是:wavedec做多尺度分解,detcoef取各层细节系数,appcoef取最后一层近似系数。特征向量由每层系数的能量、绝对均值和标准差拼成。能量反映该频段的强度,绝对均值反映平均波动幅度,标准差反映波动的不稳定程度。这三个统计量加起来,一个 5 层分解就能得到 18 维特征,足够喂给分类器了。

参数方面,wname换成sym4或coif2会改变分解结果,但特征构造的逻辑不变。level如果设成 6 或 7,低频分辨率更高,但计算量也上去了,而且高层近似系数可能已经把脉搏的周期性信息平滑掉了。我一般从 5 层开始试,看分类准确率再调整。

3.3 特征归一化与降维

特征向量拼出来之后,各维度的量纲不一样,能量值可能是几百,标准差可能只有零点几。直接扔进 SVM 的话,能量那一维会主导距离计算,其他维度等于白给。所以必须做归一化。常见做法是 z-score 标准化:

feat_norm = (feat - mean(feat)) ./ std(feat);

如果样本量够大,可以按列做,每个特征维度单独标准化。如果样本量小,比如只有几十个样本,那就按整个特征向量做一次全局标准化,避免过拟合。

降维这一步不是必须的,但如果特征维度太高而样本量少,PCA 是个稳妥的选择。pca函数返回的主成分按方差贡献率排序,取前几个累计贡献率超过 85% 的成分就行。不过要注意,PCA 之后的主成分物理意义不明确,如果你需要解释哪些特征对分类贡献大,那就别降维,直接用原始特征加特征重要性分析。

4. 分类模型训练与避坑:从特征到标签的最后一公里

4.1 分类器选型与训练流程

参数提取要求.doc里应该规定了标签体系,比如正常搏动标 0,异常搏动标 1。特征矩阵X是 n 行 m 列,n 是样本数,m 是特征维度,标签向量Y是 n 行 1 列。分类器可以用 SVM、随机森林或者简单的神经网络。SVM 在小样本上表现稳定,核函数选 RBF,用交叉验证调C和gamma。

% 假设 X 是特征矩阵,Y 是标签向量 X = feat_norm; % 归一化后的特征 Y = labels; % 标签 % 划分训练集和测试集 cv = cvpartition(Y, 'HoldOut', 0.3); X_train = X(training(cv), :); Y_train = Y(training(cv), :); X_test = X(test(cv), :); Y_test = Y(test(cv), :); % 训练 SVM 模型 svm_model = fitcsvm(X_train, Y_train, ... 'KernelFunction', 'rbf', ... 'Standardize', true, ... 'CrossVal', 'on'); % 预测与评估 Y_pred = predict(svm_model.Trained{1}, X_test); accuracy = sum(Y_pred == Y_test) / length(Y_test);

fitcsvm的Standardize参数设为true时,内部会再做一次标准化,和前面的手动归一化叠加,效果更稳。CrossVal设为on会做 10 折交叉验证,返回的模型是交叉验证后的集成模型,用Trained{1}取第一个折的模型做预测。如果样本量很少,交叉验证的折数要相应减少,否则每折的测试集太小,准确率波动大。

4.2 避坑与常见问题排查

现象一:分类准确率一直在 50% 左右,跟随机猜差不多。原因通常是特征没有区分度,或者标签和特征对不上。先检查data.xlsx里的样本顺序和标签顺序是否一致,再画个特征散点图,看两类样本在特征空间里是否混在一起。如果混在一起,要么换小波基,要么增加分解层数,要么补充时域特征比如脉搏波传导时间、反射波增强指数。

现象二:训练集准确率 95%,测试集只有 60%。这是典型的过拟合。原因可能是特征维度太高而样本太少,或者 SVM 的C设得太大。解决办法是降维、增加正则化、减少特征数量。我一般会把特征维度控制在样本数的十分之一以下,比如 50 个样本就用 5 维特征。

现象三:滤波后波形整体偏移,峰值位置对不上。这是用了filter而不是filtfilt导致的相位延迟。脉搏信号的峰值位置对应心脏射血时刻,偏移几个采样点就会让传导时间算错。换成filtfilt就能解决,代价是计算量翻倍,但离线分析不在乎这点开销。

现象四:小波分解报错“索引超出矩阵维度”。多半是信号长度不够,做 5 层分解至少需要 2^5 = 32 个采样点,实际建议至少 256 个点。如果信号太短,减少分解层数,或者对信号做补零延拓。

现象五:data.xlsx读取后全是 NaN。检查表格里是否有合并单元格或文本表头。readmatrix遇到文本会返回 NaN,用readcell或指定Range参数跳过表头行。

注意:如果参数提取要求.doc里规定的特征和myfun.m里实际提取的特征不一致,以文档为准。代码可能是早期版本,文档才是最终要求。

5. 把分类结果落回临床场景:一个验证技巧和参数固化习惯

跑通流程只是第一步,真正要验证这套小波特征加分类的方案是否靠谱,得看它在不同受试者之间的泛化能力。我一般会做一个留一受试者交叉验证:每次留一个受试者的全部样本做测试,其余受试者的样本做训练。如果留一受试者的准确率比随机划分的准确率低很多,说明特征里混入了受试者相关的个体差异,比如皮肤厚度、血管弹性这些和病理无关的因素。这时候要么做受试者间的特征对齐,要么改用对个体差异不敏感的特征,比如归一化后的波形形态参数。

另一个实用技巧是固定随机种子。MATLAB 的cvpartition和fitcsvm内部都有随机过程,每次跑出来的准确率会有波动。在脚本开头加rng(42),保证每次运行结果可复现。这个习惯在调参阶段特别重要,否则你改了参数但不知道是参数起作用还是随机波动。

参数固化方面,我习惯把采样频率、滤波截止频率、小波基名称、分解层数、SVM 的C和gamma全部写进一个config.m文件,主脚本开头run('config.m')。这样换数据集的时候只改配置文件,不用翻遍所有脚本。采样频率.doc里的数值直接抄进config.m,避免手误。

% config.m fs = 200; % 采样频率,来自采样频率.doc f_low = 0.5; % 带通下限 f_high = 10; % 带通上限 filter_order = 3; % 巴特沃斯阶数 wname = 'db4'; % 小波基 level = 5; % 分解层数 svm_C = 1; % SVM 惩罚系数 svm_gamma = 0.5; % RBF 核宽度 rng(42); % 固定随机种子

最后说一个我踩过的坑:有一次我把butterband.m里的filtfilt改成了filter,因为想省点计算时间,结果分类准确率掉了 15 个百分点。排查了一下午才发现是相位偏移导致峰值时间特征全错了。从那以后我每次改滤波器相关代码,都强制跑一遍波形对比图,确认峰值位置没偏移才继续。希望这些经验能帮你少走点弯路。

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

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

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

立即咨询