当前位置: 首页 > news >正文

Matlab GUI界面编程下的脉搏信号处理:滤波、去噪、实时回放及小波分析计算脉率

脉搏信号处理 Matab GUI界面编程,并实现滤波、去噪、实时回放、小波分析 计算脉率

做生理信号采集实验的时候,每次都要敲一堆命令行调参数、看结果真的烦,索性攒了个Matlab的GUI小工具,把脉搏信号处理常用的功能都打包进去了——滤波去噪、实时回放、小波分析算脉率,折腾了小一周总算能用了,今天唠唠整个过程。

首先得搭个界面,我用的是Matlab自带的App Designer,拖拖拽拽比老版GUIDE舒服多了。界面大概分三块:左边三个轴分别放原始信号、滤波后信号、去噪后的信号,右边放参数调节控件(比如滤波截止频率滑块、小波基下拉框),底部放脉率计算的结果图,再堆了一排按钮对应各个功能。

先写加载数据的按钮回调,毕竟啥都没有的话其他功能都是空谈:

function LoadDataButtonPushed(app, event) [file,path] = uigetfile({'*.mat;*.txt','脉搏数据文件'},'选择脉搏信号文件'); if isequal(file,0) return; end fullpath = fullfile(path,file); % 兼容mat和txt两种常见格式,txt直接按数组读,mat直接加载变量 if endsWith(file,'.mat') load(fullpath); else pulse_signal = load(fullpath); Fs = 1000; % 大部分脉搏采集卡都是1kHz采样率,默认填这个 end app.RawSignalAxes.UIAxes.Visible = 'on'; plot(app.RawSignalAxes, linspace(0,length(pulse_signal)/Fs,length(pulse_signal)), pulse_signal); title(app.RawSignalAxes,'原始脉搏信号'); xlabel(app.RawSignalAxes,'时间/s'); ylabel(app.RawSignalAxes,'幅值/mV'); % 把数据存在app对象里,不然下次用的时候变量就丢了 app.pulse_data = pulse_signal; app.Fs = Fs; end

这段代码其实没啥花活,就是选文件、读数据、画个图存一下变量。一开始我没把数据存在app的私有属性里,每次调其他功能都要重新加载,结果改个参数就报错,折腾了半天才想起Matlab App Designer里所有临时变量都得挂在app对象上。

接下来是滤波模块,脉搏信号的有效频段一般是0.5~4Hz(对应30~240次/分钟的脉率),所以用巴特沃斯带通滤波最合适,而且一定要用零相位滤波,不然信号会整体偏移,看起来特别别扭:

function ButterworthFilterButtonPushed(app, event) if ~isfield(app,'pulse_data') uialert(app.UIFigure,'先加载脉搏数据哦!','提示'); return; end % 从滑块拿截止频率,用户可以自己拖滑块调 f_low = app.LowFreqSlider.Value; f_high = app.HighFreqSlider.Value; [b,a] = butter(6, [f_low, f_high]/(app.Fs/2), 'bandpass'); % filtfilt不会有相位延迟,比filter好用太多 filtered_signal = filtfilt(b,a,app.pulse_data); app.FilteredSignalAxes.UIAxes.Visible = 'on'; plot(app.FilteredSignalAxes, linspace(0,length(filtered_signal)/app.Fs,length(filtered_signal)), filtered_signal); title(app.FilteredSignalAxes,['巴特沃斯滤波后(',num2str(f_low),'-',num2str(f_high),'Hz)']); xlabel(app.FilteredSignalAxes,'时间/s'); ylabel(app.FilteredSignalAxes,'幅值/mV'); app.filtered_data = filtered_signal; end

我一开始偷懒用了filter,结果画出来的信号整体往左移了大概0.3秒,被导师吐槽“你这信号都跑到未来去了”,后来赶紧换成filtfilt才搞定。6阶滤波刚好,阶数太低滤不干净噪声,太高又会把脉搏的小峰给抹掉。

脉搏信号处理 Matab GUI界面编程,并实现滤波、去噪、实时回放、小波分析 计算脉率

然后是小波去噪,比简单的滑动平均靠谱多了,不会把有用的细节磨平:

function WaveletDenoiseButtonPushed(app, event) if ~isfield(app,'filtered_data') uialert(app.UIFigure,'先做巴特沃斯滤波哦!','提示'); return; end wavelet_name = app.WaveletDropDown.Value; level = 3; % 3层分解刚好,太多会把信号磨平 % 用软阈值去噪,sqtwolog阈值法简单好调 denoised_signal = wden(app.filtered_data, 'sqtwolog', 's', 'mln', level, wavelet_name); app.DenoisedSignalAxes.UIAxes.Visible = 'on'; plot(app.DenoisedSignalAxes, linspace(0,length(denoised_signal)/app.Fs,length(denoised_signal)), denoised_signal); title(app.DenoisedSignalAxes,['小波去噪(',wavelet_name,',',num2str(level),'层)']); xlabel(app.DenoisedSignalAxes,'时间/s'); ylabel(app.DenoisedSignalAxes,'幅值/mV'); app.denoised_data = denoised_signal; end

试了好几种小波基,最后发现sym4对脉搏峰的检测最准,所以把默认值设成了sym4。一开始用了db4,结果找出来的峰总是歪歪扭扭的,换了sym4之后瞬间舒服了。还有分解层数别超过5层,不然连脉搏的细微波动都给滤没了。

接下来是实时回放,这个是最折腾的,一开始直接循环plot直接卡成PPT:

function RealTimePlayButtonPushed(app, event) if ~isfield(app,'denoised_data') uialert(app.UIFigure,'先做小波去噪哦!','提示'); return; end play_data = app.denoised_data; chunk_size = round(app.Fs * 0.5); % 每次刷新0.5秒的数据,不会太跳 figure('Name','实时回放','Position',[100,100,800,400]); ax = axes; xlabel('时间/s'); ylabel('幅值/mV'); title('实时回放脉搏信号'); hold on; grid on; % 提前创建plot对象,每次只更新数据而不是重画,效率高很多 h = plot(ax, 0,0); for i = 1:chunk_size:length(play_data) current_chunk = play_data(i:min(i+chunk_size-1,length(play_data))); t = (i-1)/app.Fs : 1/app.Fs : (i+length(current_chunk)-2)/app.Fs; set(h, 'XData',t, 'YData',current_chunk); ax.XLim = [(i-1)/app.Fs, (i+length(current_chunk))/app.Fs]; drawnow; % 强制刷新界面,不然会卡 pause(0.05); % 放慢回放速度,方便看清楚每个峰 end end

这里的坑真的多,一开始没加drawnow,界面完全不动;没提前创建plot对象,每次都重画的话,电脑差一点就卡爆;没调X轴范围的话,整个画面会一直往左拉,根本看不清。改了这几个地方之后,回放终于流畅了。

最后是算脉率,核心就是找脉搏峰然后算时间差:

function CalcHRButtonPushed(app, event) if ~isfield(app,'denoised_data') uialert(app.UIFigure,'先做小波去噪哦!','提示'); return; end % 找峰值,过滤掉太小的噪声峰,最小峰间距设为0.8秒(对应75次/分钟的最慢脉率) [pks,locs] = findpeaks(app.denoised_data, 'MinPeakHeight',0.5*max(app.denoised_data), 'MinPeakDistance',round(app.Fs*0.8)); if length(locs) < 2 uialert(app.UIFigure,'没找到足够的脉搏峰,请调整参数或换更长的数据','提示'); return; end % 计算平均脉率,转换成次/分钟 time_diff = diff(locs)/app.Fs; avg_hr = 60/mean(time_diff); % 把找到的峰标在图上 app.PulseHRPlotAxes.UIAxes.Visible = 'on'; plot(app.PulseHRPlotAxes, linspace(0,length(app.denoised_data)/app.Fs,length(app.denoised_data)), app.denoised_data); hold(app.PulseHRPlotAxes, 'on'); scatter(app.PulseHRPlotAxes, locs/app.Fs, pks, 'r','filled'); hold(app.PulseHRPlotAxes, 'off'); title(app.PulseHRPlotAxes,['平均脉率:',num2str(round(avg_hr)),' 次/分钟']); xlabel(app.PulseHRPlotAxes,'时间/s'); ylabel(app.PulseHRPlotAxes,'幅值/mV'); end

一开始我没加MinPeakDistance,结果找出来一堆假峰,算出来的脉率有三百多次每分钟,差点把自己忽悠瘸了。后来加了这个参数,终于正常了。如果数据太短的话,找出来的峰不够,就会弹出提示,这个小细节能省掉用户很多疑问。

整体跑下来的话,流程就是加载数据→滤波→去噪→看波形→算脉率→实时回放,整个流程不用敲一行命令行,点几下按钮就搞定了,比之前方便太多。

对了,最后还加了个保存结果的按钮,把处理后的信号和脉率数据存成mat文件,方便后续分析。整个工具大概几百行代码,折腾下来最大的收获就是摸透了Matlab App Designer的各种坑,还有生理信号处理里的一些细节。要是有人需要完整的代码,随便改改就能用,毕竟都是用的Matlab内置函数,没啥黑科技。

http://www.jsqmd.com/news/528432/

相关文章:

  • 深入QS100的SDR架构:除了NB-IoT,它如何通过‘可扩展协议’支持LoRa等自定义通信?
  • SlopeCraft:突破像素艺术边界,轻松打造Minecraft立体地图画(从图片到游戏场景的无缝转换方案)
  • STM32看门狗增强框架:IWDG/WWDG协同与RTOS集成
  • PX4飞控实战:5分钟搞定自定义MAVLink消息与QGC通信(附Python示例)
  • ResNet背后的设计哲学:为什么残差连接如此有效?
  • 想找整屋定制靠谱供应商,大庆诺放全屋定制口碑怎么样? - 工业设备
  • YOLOv12网络协议交互:处理403 Forbidden等常见网络错误
  • OmenSuperHub终极指南:释放惠普游戏本隐藏性能的免费开源神器
  • 大庆能实现效果图还原定制的整屋定制工厂,多少钱一平 - myqiye
  • 深入解析ORA-12154与TNS-03505:从监听配置到客户端TNS问题的全面排查指南
  • FANUC数控机床数据自动采集与智能分析实战指南
  • Verilog实现超前进位加法器:为什么比串行进位快3倍?附完整代码
  • 因果本源化学革新航天火箭燃料:90%工业落地方案与10%核心理论
  • 为什么程序员都爱用MobaXterm?这些隐藏功能让你的SSH效率翻倍
  • 新能源电芯清洁度分析系统:西恩士从定性到定量,构建电芯洁净度闭环分析能力 - 工业设备研究社
  • Windows11 一键美化成macOS 详细教程 无需放弃兼容性,小白也能秒上手
  • 为什么92%的Dify Multi-Agent项目卡在阶段同步?揭秘stateful workflow引擎的4层状态一致性设计
  • 注意力缺陷症是什么?对情绪问题影响有哪些?
  • 告别‘盲打’!用pybind11_stubgen为你的C++扩展自动生成pyi文件(附VSCode/PyCharm配置)
  • STM32F103C8T6标准库项目实战:从零DIY一个温湿度监测器(OLED显示+ESP8266上传)
  • 京东e卡回收教程:高价回收指南! - 团团收购物卡回收
  • Visio流程图总在Word里排版错乱?试试这个‘复制粘贴’的隐藏技巧,一键搞定对齐和缩放
  • BGE-Reranker-v2-m3合规检查:敏感词过滤与排序联动部署
  • 探寻重庆火锅美味,2026口碑分析助你选好店,市面上专业的重庆火锅实力厂家关键技术和产品信息全方位测评 - 品牌推荐师
  • 南京高端腕表保养周期全指南:从百达翡丽到理查德米勒的养护时序与科学依据 - 时光修表匠
  • NotImplementedError: Meta Tensor复制困境与torch.nn.Module.to_empty()的救赎之路
  • JavaScript反混淆利器:基于AST解析的代码还原工具深度剖析
  • 2026年全功能客服平台,集成工单知识库自动回复多功能体系 - 品牌2026
  • 2026年3月优选:3公里内的宠物医院推荐 - 品牌推荐师
  • 避坑指南:用MicroPython驱动240x240 OLED时遇到的5个典型问题(附ST7789解决方案)