恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

基于小波变换的脉搏信号去噪与分类识别实战

  • 首页
  • 资讯中心
  • /
  • 基于小波变换的脉搏信号去噪与分类识别实战

相关资讯

网络驱动重装实战指南:从掉线到恢复的完整排查方法 2026/10/9 20:59:25
Minecraft指令系统完全指南:从入门到自动化建造实战 2026/10/9 20:59:25
Agent-Reach:多智能体协作的触达保障与智能路由实践 2026/10/9 20:59:24

最新资讯

CLH自旋锁深度解析:从缓存行颠簸到高并发性能优化
数据库课程设计选课系统:从建表到事务锁的避坑指南
Windows下PyQt6安装配置与打包exe全流程详解
Anaconda下载慢?用清华镜像源配置conda和pip加速环境部署
AI短视频与漫剧制作培训全解析:从工具链到成品实战
MySQL教材源码zip实操指南:解压、导入、避坑与项目改造

今日推荐

AI编程智能体实战:从写代码到指挥代码的架构与落地
多模态大模型全栈能力拆解:从数据对齐到弹性推理
大模型Agent开发入门:从工具调用循环到落地避坑指南

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

基于小波变换的脉搏信号去噪与分类识别实战

发布时间:2026/10/9 20:59:25
基于小波变换的脉搏信号去噪与分类识别实战 简介这份由同济大学完成的脉搏识别资料包面向生物医学工程、信号处理方向的学生与科研人员聚焦小波分析在脉搏信号去噪、特征提取与分类识别中的完整实现。包内共8个文件以4个m脚本为核心配合2个doc与1个docx说明文档、1个xlsx数据表整体约39KB涵盖采样频率设定、参数提取要求及butterlow、butterband、butterhigh等滤波函数便于直接运行与二次修改。已有237人学习下载。读者可借此掌握小波变换将一维脉搏信号转为多尺度表示、提取周期性与振幅特征并借助SVM或神经网络区分正常与异常脉搏的完整流程是理解信号识别与分类落地的实用参考。1. 脉搏信号分析从一份压缩包开始小波变换加分类到底能解决什么一份名为「脉搏识别」的压缩包里面塞着采集到的脉搏波形、一段小波处理脚本、一个分类器训练流程这大概是很多做生理信号方向的开发者都见过的场景。脉搏信号分析这件事核心难点不在采集而在于波形太「软」——它幅度小、频率低、极易被基线漂移和工频干扰淹没直接拿原始序列去喂分类器准确率往往惨不忍睹。小波变换在这里扮演的角色就是把一维时域波形拆成不同尺度上的细节系数和近似系数让噪声和有效成分在频带上分开再从中挑出真正有判别力的特征送进分类模型。这套「小波去噪 特征提取 分类」的组合适合做可穿戴健康监测、中医脉象辅助分析、运动心率变异性研究的人也适合手里已经有一批脉搏数据、想跑通一个端到端识别流程的工程师。下面我按自己实际搭过的路径把这条链路拆开讲清楚。2. 脉搏波形为什么必须先过小波从采样到预处理的完整链路脉搏信号本质是动脉血管壁随心脏搏动产生的压力波典型基频在 0.82 Hz 之间但它的形态信息藏在更高频的细节里比如重搏波切迹、上升支斜率。问题在于采集设备一动、呼吸一深基线就会漂市电和电磁环境又会往信号里灌 50 Hz 左右的干扰。如果直接做 FFT你只能看到全局频率分布看不到「这个切迹出现在第几个心跳周期」这种时间定位信息。小波变换的价值就在于它同时保留时间和尺度信息这是脉搏分析里绕不开的一步。2.1 小波去噪的三个参数怎么定小波基、分解层数、阈值规则先明确一件事脉搏信号去噪不是层数越多越好。分解层数太深低频近似系数会把重搏波这种关键形态抹平层数太浅高频噪声又压不干净。我一般按采样率来推假设采样率 fs脉搏有效成分上限大约在 40 Hz 以内那么分解层数 L 满足fs / 2^L ≈ 40就够了。比如 fs500 HzL 取 34 层比较稳。小波基的选择上db6 和 sym8 是我在脉搏数据上试下来比较平衡的前者消失矩高、对平滑波形友好后者近似对称、相位失真小。阈值规则用软阈值比硬阈值更平滑不容易在重构波形上留下台阶状伪影。import pywt import numpy as np def wavelet_denoise(signal, waveletdb6, level4, modesoft): 对脉搏信号做小波去噪 signal: 一维 numpy 数组原始脉搏序列 wavelet: 小波基脉搏常用 db6 / sym8 level: 分解层数按 fs/2^level ≈ 40Hz 估算 mode: 阈值模式soft 更平滑hard 保留尖峰 # 1. 小波分解拿到近似系数和各层细节系数 coeffs pywt.wavedec(signal, wavelet, levellevel) # 2. 用第一层细节系数估计噪声标准差Donoho 通用阈值 sigma np.median(np.abs(coeffs[-1])) / 0.6745 threshold sigma * np.sqrt(2 * np.log(len(signal))) # 3. 对每一层细节系数做阈值收缩近似系数不动 coeffs_thresh [coeffs[0]] for c in coeffs[1:]: coeffs_thresh.append(pywt.threshold(c, threshold, modemode)) # 4. 重构回时域 denoised pywt.waverec(coeffs_thresh, wavelet) return denoised[:len(signal)] # 重构长度可能多 1 个点截断对齐这段代码里最关键的是阈值计算那一步。np.median(np.abs(coeffs[-1])) / 0.6745是 Donoho 提出的鲁棒噪声估计用中位数而不是标准差是为了避免个别大幅值细节系数把噪声估计拉高。阈值本身用sigma * sqrt(2 * ln(N))N 是信号长度信号越长阈值越高这是符合统计规律的。如果你发现去噪后波形变「肉」了、切迹变钝就把 level 降一层如果还残留明显毛刺就把 mode 换成 hard 或者手动把 threshold 乘 1.2。2.2 从去噪波形到特征向量时域和频域各取什么去噪只是第一步分类器不认波形只认特征向量。脉搏特征我一般分两组取。时域这组主波峰幅度、上升支时间、重搏波相对幅度、相邻峰间隔也就是脉率、峰间隔标准差反映节律稳定性。频域这组对去噪信号做小波包分解取各频带的能量占比尤其是 010 Hz 和 1040 Hz 两个带的能量比这个比值对区分不同脉象类型有实际判别力。import numpy as np from scipy.signal import find_peaks def extract_pulse_features(signal, fs): 从去噪后的脉搏信号提取时域频域特征 signal: 去噪后的一维脉搏序列 fs: 采样率 返回: 特征字典 feats {} # --- 时域特征 --- # 找主波峰距离至少 0.4s对应最高 150bpm prominence 过滤小波动 peaks, props find_peaks(signal, distanceint(0.4 * fs), prominence0.3 * np.std(signal)) if len(peaks) 2: return None # 有效搏动太少这条样本丢弃 feats[peak_amplitude_mean] float(np.mean(signal[peaks])) intervals np.diff(peaks) / fs # 相邻峰间隔单位秒 feats[pulse_rate] float(60.0 / np.mean(intervals)) feats[interval_std] float(np.std(intervals)) # --- 频域特征小波包能量占比 --- wp pywt.WaveletPacket(signal, db4, modesymmetric, maxlevel4) total_energy np.sum(signal ** 2) 1e-12 low_band np.sum(wp[aaaa].data ** 2) # 0~约10Hz feats[low_band_ratio] float(low_band / total_energy) return featsfind_peaks里的distance参数是防止把重搏波误判成主波峰0.4 秒对应理论上限 150 bpm正常静息脉搏不会超过这个值。prominence用标准差的 0.3 倍做门槛是为了滤掉基线残余抖动。小波包那部分maxlevel4配合 db4在 fs500 Hz 时aaaa节点大致覆盖 015 Hz正好是脉搏主能量区。这些参数不是死的你换数据集就得重新看一眼峰检测结果别闭着眼睛套。3. 分类器怎么选怎么训从特征矩阵到可复现的识别流程特征提出来之后摆在面前的问题是选什么分类器。脉搏识别这种场景样本量通常不大几百到几千条特征维度也就十几维。这种「小样本、低维度」的局上深度学习是杀鸡用牛刀还容易过拟合。我一般先用 SVM 打底再拿随机森林做对照两者在脉搏数据上的表现往往很接近但 SVM 调参更直观。3.1 特征标准化和数据集划分别在划分之前做标准化这是血泪经验标准化一定要在训练集上 fit再 transform 到测试集。如果你先把整个数据集标准化再划分测试集的均值方差信息就泄漏进训练过程了交叉验证分数会虚高上线就翻车。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report import numpy as np # X: (n_samples, n_features) 特征矩阵, y: 标签 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.25, stratifyy, random_state42 ) # 标准化只在训练集 fit scaler StandardScaler() X_train_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) # 注意这里是 transform不是 fit_transform # SVMRBF 核C 和 gamma 用网格搜索定 svm SVC(kernelrbf, C10, gammascale, class_weightbalanced) svm.fit(X_train_s, y_train) print(classification_report(y_test, svm.predict(X_test_s))) # 随机森林做对照 rf RandomForestClassifier(n_estimators200, max_depth8, random_state42) rf.fit(X_train_s, y_train) print(classification_report(y_test, rf.predict(X_test_s)))stratifyy保证划分后各类比例一致样本不均衡时这个参数必须加。class_weightbalanced让 SVM 对少数类样本加大惩罚避免模型偏向多数类。随机森林的max_depth8是防过拟合的脉搏特征维度低树太深没意义。两个模型跑完对比一下如果 SVM 明显好于 RF说明特征和类别边界比较平滑如果 RF 反超可能是特征里有非线性交互可以考虑加点组合特征。3.2 交叉验证和混淆矩阵看清模型到底错在哪一类单看准确率没意义脉搏识别里不同类别的误判代价不一样。比如把「节律不齐」判成「正常」比反过来严重得多。所以必须看混淆矩阵。from sklearn.model_selection import cross_val_score, StratifiedKFold from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt # 5 折分层交叉验证看分数稳定性 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(svm, X_train_s, y_train, cvcv, scoringf1_macro) print(fCV F1-macro: {scores.mean():.3f} ± {scores.std():.3f}) # 混淆矩阵 cm confusion_matrix(y_test, svm.predict(X_test_s)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(Predicted) plt.ylabel(True) plt.show()用f1_macro而不是 accuracy是因为它对每个类别一视同仁不会被多数类拉高。如果 CV 的 std 超过 0.05说明数据划分对结果影响大要么样本太少要么特征区分度不够得回去看特征工程。混淆矩阵里如果某一列特别亮说明模型对这个类有系统性偏好检查一下是不是这个类的样本在特征空间里和别的类重叠了。4. 脉搏识别落地时最容易踩的五个坑这一章是我自己踩过、也见过别人反复踩的地方按「现象 → 原因 → 解决」写你对着排查能省不少时间。4.1 去噪后波形整体偏移峰检测全乱现象小波重构出来的波形和原始波形对不上整体上下平移find_peaks找出来的峰位置全错。原因小波重构时边界处理没做好waverec默认的边界延拓方式在信号两端会产生畸变而且重构长度可能比原信号多一个点直接拿去对齐就错位了。解决重构后强制截断到原长度denoised[:len(signal)]并且对首尾各 10% 的数据不做峰检测或者改用modeperiodization做分解边界畸变会小很多。4.2 分类准确率虚高换一批数据就崩现象在自己数据集上交叉验证 95% 以上拿新采集的一批数据一测掉到 60%。原因标准化泄漏是最常见的一个另一个是特征里混进了和采集设备相关的量比如绝对幅度。不同设备增益不同绝对幅度根本没有泛化性。解决标准化严格在训练集 fit特征尽量用比值、斜率、时间间隔这类无量纲量少用绝对幅度。如果必须用幅度先做 z-score 归一化。4.3 样本不均衡导致少数类全被吞现象混淆矩阵里少数类几乎全被判成多数类recall 接近 0。原因脉搏数据里正常样本远多于异常样本分类器默认优化整体准确率就把少数类牺牲了。解决class_weightbalanced是最省事的再进一步可以用 SMOTE 做少数类过采样但注意 SMOTE 只能在训练集上做而且做完要重新标准化。我一般先试 class_weight不够再上 SMOTE。4.4 小波分解层数照搬教程结果切迹消失现象去噪后重搏波切迹变平特征里「重搏波相对幅度」全是接近 0 的值。原因分解层数太深近似系数把高频形态信息一起平滑掉了。解决按fs / 2^L ≈ 40重新算层数或者干脆把近似系数也纳入阈值处理范围别只处理细节系数。判断标准很简单去噪前后把波形叠在一起看切迹位置如果对不上就是层数深了。4.5 训练时随机种子没固定结果无法复现现象同样的代码跑两遍准确率差好几个百分点调参调了个寂寞。原因train_test_split、StratifiedKFold、随机森林的random_state没设每次划分和初始化都不一样。解决所有涉及随机的环节统一设random_state42包括划分、交叉验证、模型初始化。这不是玄学是工程纪律。5. 把脉搏识别做成可复用的模块一个特征管线的封装技巧走到这一步你已经能跑通「去噪 → 特征 → 分类」的完整链路了。但如果你打算把这个东西用到实际项目里散落的脚本迟早会变成维护噩梦。我的习惯是把整条链路封成一个Pipeline类用 sklearn 的BaseEstimator和TransformerMixin做接口对齐这样它能直接塞进GridSearchCV里调参也能和标准化、分类器串成一条流水线。from sklearn.base import BaseEstimator, TransformerMixin import pywt import numpy as np from scipy.signal import find_peaks class PulseFeatureExtractor(BaseEstimator, TransformerMixin): 把原始脉搏信号转成特征矩阵的 transformer 可直接放进 sklearn Pipeline def __init__(self, fs500, waveletdb6, level4): self.fs fs self.wavelet wavelet self.level level def _denoise(self, sig): coeffs pywt.wavedec(sig, self.wavelet, levelself.level) sigma np.median(np.abs(coeffs[-1])) / 0.6745 thresh sigma * np.sqrt(2 * np.log(len(sig))) coeffs [coeffs[0]] [pywt.threshold(c, thresh, soft) for c in coeffs[1:]] return pywt.waverec(coeffs, self.wavelet)[:len(sig)] def _features(self, sig): sig self._denoise(sig) peaks, _ find_peaks(sig, distanceint(0.4 * self.fs), prominence0.3 * np.std(sig)) if len(peaks) 2: return None intervals np.diff(peaks) / self.fs return [ float(np.mean(sig[peaks])), float(60.0 / np.mean(intervals)), float(np.std(intervals)), float(np.std(sig) / (np.mean(np.abs(sig)) 1e-12)), # 波形因子 ] def fit(self, X, yNone): return self def transform(self, X): # X 是 list of 1D arrays feats [self._features(s) for s in X] # 丢弃无效样本用 0 填充实际项目里建议记录索引 return np.array([f if f is not None else [0]*4 for f in feats])这个类的好处是fit什么都不做transform负责全部计算符合 sklearn 的无状态 transformer 约定。__init__里的参数全部暴露出来可以直接在GridSearchCV的param_grid里写extractor__level: [3, 4, 5]来搜。注意transform里对无效样本用 0 填充是个简化处理真实项目里应该记录哪些样本被丢弃避免把无效样本喂进分类器。封装完之后整条链路就是Pipeline([(feat, PulseFeatureExtractor()), (scaler, StandardScaler()), (clf, SVC())])一行grid.fit(X_raw, y)就能把特征参数和分类器参数一起搜。我现在的习惯是任何超过三个步骤的信号处理流程都先想清楚能不能封成 transformer能封就封后面调参和换模型的时候会感谢自己。这套东西值不值得投入取决于你后面还要不要反复迭代——如果只是跑一次实验脚本就够了如果要做成产品或者持续优化封装这一步省不掉。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号