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

张开发
2026/4/4 19:22:41 15 分钟阅读
脉搏信号处理: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直接卡成PPTfunction 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(ichunk_size-1,length(play_data))); t (i-1)/app.Fs : 1/app.Fs : (ilength(current_chunk)-2)/app.Fs; set(h, XData,t, YData,current_chunk); ax.XLim [(i-1)/app.Fs, (ilength(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内置函数没啥黑科技。

更多文章