基于MATLAB加速度计的心率与呼吸频率检测方案
2026/9/8 1:34:03 网站建设 项目流程

做这个项目之前,我先说一句大实话:用加速度计测心率,一开始听起来像“用体重秤测体温”,不太搭。但真正跑通之后你会发现,这个思路在特定场景下比光学心率(PPG)和心电(ECG)都更抗造——不挑皮肤、不挑佩戴松紧、不怕环境光,甚至能一次性把呼吸频率也一起算出来。这套基于matlab加速度计的呼吸速率和心率检测方案,核心就是捕捉心脏泵血时身体产生的微机械振动,以及胸廓起伏带来的低频加扰运动,再通过数字信号处理把这些微弱信号分离出来。

这篇博文围绕这套方案的原理、硬件选型、matlab实现流程、常见坑位和报告撰写思路展开。不管你是做课设、研究生项目,还是刚接触传感器信号处理,只要能看懂基本的傅里叶变换和滤波概念,照着这个思路做,就能跑出一套能用的心率/呼吸率检测流程。

1. 项目思路与原理拆解

1.1 加速度计为什么能干心率检测的活

常规心率检测你肯定见过那几类方案:手指按在光电传感器上测PPG,或者胸口贴电极测ECG。这两种方案本质都在测“电”和“光”,而加速度计属于另一个赛道——它测的是“力”和“运动”。

这里有一个容易被忽略的生理事实:心脏每次收缩泵血,血液会以大动脉为通道快速冲击全身。这股反冲力虽然微小,但足以让身体表面产生肉眼看不到的微振动,频率和心跳严格同步。把高灵敏度加速度计贴在体表(比如胸口、侧腹),就能记录到这种振动信号,专业上叫心冲击图(BCG, Ballistocardiography)。

BCG信号有多微弱?通常只有几毫g到几十毫g级别。g是重力加速度,1g约等于9.8m/s²。你正常走路时身体晃动可能有几百毫g到几g,而心跳引起的表面振动连0.01g都不到。这就解释了为什么这个项目最大的难点不在“能不能测到”,而在“怎么在强运动噪声里把微小的心跳信号抠出来”。

呼吸检测的原理就相对直观。呼吸时胸廓周期性扩张收缩,人体重心会轻微上下移动,贴在胸腹部的加速度计会记录到这种低频摆动,频率范围通常在0.1Hz到0.6Hz之间。一个成年人安静时每分钟呼吸12到20次,换算成频率就是0.2Hz到0.33Hz左右。所以呼吸信号和心率信号在频带上天然分开,这就给后面的滤波分离提供了物理基础。

这套方案的优势在于:不需要光学窗口紧贴皮肤,不需要导电凝胶,对环境光零要求,甚至连衣物阻挡都不太影响BCG捕获。缺点是信号弱、容易受运动干扰,所以对算法处理能力要求更高。这也正是matlab适合做这件事的原因——滤波、频谱分析、自相关计算都有现成工具箱,能快速迭代算法。

1.2 呼吸速率与心率检测的信号分离思路

混合加速度信号里,大体可以按频率切成三块:

  • 0Hz到0.1Hz:缓慢基线漂移,温度变化、传感器零漂、人体缓慢姿态变化都会造成。
  • 0.1Hz到0.6Hz:呼吸带,胸廓起伏主导。
  • 0.8Hz到3Hz(对应48到180次/分钟):BCG心跳带,心率主导。
  • 3Hz以上:高频肌肉震颤、电子噪声、环境振动。

处理思路其实很朴素:呼吸信号用带通滤波器把低频段筛出来,BCG心率信号用带通滤波器把0.8到3Hz段筛出来,然后对滤波后的信号做频谱分析或者峰值检测,找出主导频率,再换算成每分钟次数。

这里有个必须提醒的坑:呼吸信号存在时,它的高次谐波可能落在心率带内。比如呼吸频率0.3Hz,二次谐波0.6Hz还算安全,但如果有三次谐波0.9Hz,正好落在心率带低端,就容易被峰值检测误判成心率。实际处理时不能只看频谱最高峰,要结合时域波形、峰值间隔一致性、甚至用滑动窗口做时间连续性校验,才能压住误检率。

理解了“频带划分”这个核心逻辑,后面所有matlab代码都是围绕它展开的。说白了,这个项目本质上就是一个“多通带滤波器设计+周期信号频率估计”的组合任务,难度不在代码,而在你对信号特征的把握。

2. 硬件选型与数据采集细节

2.1 传感器选择与佩戴位置

如果只是学习算法和跑通流程,不需要买专业医疗级传感器。市面上常见的方案有三种,我列个表给你对比一下:

方案量程分辨率/噪声采样率能力适用场景
手机内置加速度计常见±2g稳定但噪声一般可达100Hz以上随手实验、低成本验证
MPU6050六轴±2g至±16g可调噪声稍大,需平滑最高1kHz开发板原型项目
ADXL335模拟输出±3g模拟输出,依赖外部ADC取决于ADC模拟链路教学实验
专用高精度MEMS加速度计(如ADI系列)±2g小量程低噪声,适合BCG数百Hz足够科研级验证

个人建议:预算有限直接拿手机录数据就行,加速度采样率设在100到200Hz完全够用。如果用MPU6050这类开发板,注意把量程调成±2g。量程大了反而坏事——同样的振动信号,±16g量程下量化分辨率变差,BCG那点微振动可能直接被量化噪声吃掉。

佩戴位置直接影响信号质量。我实测下来,胸口正中偏左位置最好,因为离心脏近,BCG信号幅度大;其次是腹部,呼吸信号特别清晰。贴在手臂或手腕上效果会打折扣,因为软组织对微振动衰减太厉害。这个经验和“用手机测心率”类App的原理有点像,那些App也是靠加速度计感应指尖微小动脉搏动,但灵敏度要专门调校。

采集时尽量保持静止坐姿或平躺。别小看这个约束,它是后续所有信号处理结论成立的前提。如果项目要求“运动中也能测”,那要从运动伪迹去除方向做,复杂度至少翻一倍,普通课设不建议一开始就碰。

2.2 采样率、数据长度与轴向选择

采样率我建议设成100Hz到200Hz。低于100Hz,心率信号的高频边缘会损失掉,BCG特征点不够清晰;高于200Hz除了增加数据量,对结果没有质的提升,因为BCG有效能量集中在几十Hz以内。

数据长度的影响更大。频率分辨率是采样率除以数据点数的倒数。比如采样率Fs=100Hz,取N=600点,就是6秒窗口,频率分辨率约0.167Hz,换算成心率约10次/分钟误差。这个精度没法接受。把窗口拉长到30秒,N=3000点,频率分辨率提升到0.033Hz,心率误差压缩到约2次/分钟,勉强可用。所以我建议单次分析窗口至少30秒,如果能取60秒,结果稳定得多。

轴向选择方面,佩戴在胸口时,x、y、z三轴都有BCG成分,但通常垂直于胸口表面的那个轴信噪比最高。具体方案是:先各轴分别滤波,观察哪一轴的频谱峰值最明显,就固定用那一轴。也可以做三轴矢量和sqrt(ax²+ay²+az²),把三个方向的能量合并,但注意合成后方向性信息丢了,某些姿态下反而会引入更多噪声。我自己更喜欢逐轴尝试,找到主方向后固定,处理逻辑更清晰。

3. Matlab处理流程与关键实现

3.1 预处理:去趋势、降采样与零相位滤波

拿到原始数据后,直接滤波是大忌。第一件事是去掉趋势项。加速度计会有零漂,体温、电路发热都会让基线缓慢变化,这种变化如果不去除,后面频谱分析时会在低频端出现很大的伪峰。

matlab里去趋势最简单的方法是用detrend函数,它会自动拟合并去除线性趋势。如果数据有明显非线性漂移,可以用平滑样条或者高通滤波处理。我的习惯是:detrend之后,再用smoothdata做一次滑动平均,窗口设10到20个采样点,这样能把传感器高频毛刺先压一压。注意这一步只是粗处理,不能替代后面的带通滤波。

滤波是这个项目的灵魂操作。我强烈推荐用filtfilt做零相位滤波,而不是filter。普通filter会产生相位偏移,导致波形特征点整体平移,在时域峰值检测时误差很大。filtfilt做了双向滤波,相位延迟抵消为零,波形特征位置保真,对BCG这类需要精确峰值定位的场景友好得多。

下面是预处理和滤波的关键代码框架:

% 原始数据: acc (N x 3),采样率Fs Fs = 100; % 采集时人为设定 % 1. 去趋势 acc_detrend = detrend(acc); % 2. 选择主分析轴,这里以z轴为例 z_raw = acc_detrend(:, 3); % 3. 粗略平滑 z_smooth = smoothdata(z_raw, 'movmean', 15); % 4. 设计带通滤波器:BCG心率带 0.8Hz ~ 3Hz [b_bcg, a_bcg] = butter(4, [0.8 3] / (Fs/2), 'bandpass'); bcg = filtfilt(b_bcg, a_bcg, z_smooth); % 5. 设计带通滤波器:呼吸带 0.1Hz ~ 0.6Hz [b_resp, a_resp] = butter(4, [0.1 0.6] / (Fs/2), 'bandpass'); resp = filtfilt(b_resp, a_resp, z_smooth);

滤波器的阶数我选4阶巴特沃斯。阶数越高过渡带越窄,但相位失真和数值稳定性问题也越明显。4阶在BCG场景下是兼顾锐度和稳定的经验值。如果你想验证滤波器效果,用freqz(b_bcg, a_bcg, 1024, Fs)看幅频响应图,确认通带范围和衰减特性。这一步不能省,我就见过滤波器设计时采样率忘记折半,导致通带频率翻倍的案例。

3.2 心率估计:带通滤波与时域/频域峰值检测

滤波之后,心率估计有两条技术路线,建议两条都做,互相校验。

路线一是频域法:对BCG信号做FFT,找到通带内的最高谱峰,那个峰的频率就是心跳频率。这个方法计算简单,但缺点是频率分辨率受窗口长度限制,如果数据只有15秒,误差会相当明显。

路线二是时域法:用findpeaks在BCG信号里找周期性波峰,计算相邻峰之间的平均间隔,再换算成心率。这个方法更接近医生数脉搏的思路,峰值定位准确度更高,但容易把呼吸谐波或其他毛刺误判成心跳峰。

我的推荐组合是:先用频域法粗定心率范围,比如初步估计出72次/分钟,然后在时域峰值检测时,只接受落在该范围附近的峰值间隔,距离太远或太近的候选峰直接剔除,最后取平均。这样既利用了频域的整体性,又发挥了时域峰值检测的精确性。

% 频域粗估 N = length(bcg); win = hann(N); BCG_FFT = fft(bcg .* win); f_axis = (0:N-1) * Fs / N; mag = abs(BCG_FFT(1:floor(N/2))); % 限制在 0.8~3Hz范围内找峰值 idx_band = find(f_axis(1:floor(N/2)) >= 0.8 & f_axis(1:floor(N/2)) <= 3); [~, idx_max] = max(mag(idx_band)); freq_est = f_axis(idx_band(idx_max)); hr_freq = freq_est * 60; % 转换为次/分钟 % 时域精估 min_dist = round(Fs * 60 / (hr_freq + 10)); % 最小峰间隔(对应心率+10) max_dist = round(Fs * 60 / (hr_freq - 10)); % 最大峰间隔(对应心率-10) [pks, locs] = findpeaks(bcg, 'MinPeakHeight', std(bcg)*0.5, ... 'MinPeakDistance', min_dist, 'MaxPeakWidth', max_dist); if length(locs) >= 2 rr_intervals = diff(locs) / Fs; % 峰间隔秒数 hr_time = 60 / mean(rr_intervals); final_hr = (hr_freq + hr_time) / 2; % 频域时域融合 else final_hr = hr_freq; end

MinPeakHeight取0.5倍标准差这个参数,需要根据实际数据微调。数据干净时可以降到0.3倍,噪声大就提高。MinPeakDistance必须根据刚才的频域估计动态设置,否则容易在相邻干扰峰之间反复跳动。这里的动态范围±10次/分钟是一个宽松的约束,既能压制乱峰,又不会把真值排除。

3.3 呼吸速率估计与结果可视化

呼吸信号的处理比心率简单,因为它的频带更低、波形更规整。呼吸带滤波后,同样用FFT找峰值即可。呼吸率安静时为12到20次/分钟,对应0.2到0.33Hz。有些数据集可能低到0.1Hz,高到0.6Hz,所以滤波器通带范围放宽到0.1到0.6Hz,但找峰值时再精细一点:

% 呼吸频谱分析 win_resp = hann(length(resp)); R_FFT = fft(resp .* win_resp); f_resp = (0:length(resp)-1) * Fs / length(resp); mag_resp = abs(R_FFT(1:floor(length(resp)/2))); idx_resp = find(f_resp(1:floor(length(resp)/2)) >= 0.1 & f_resp(1:floor(length(resp)/2)) <= 0.6); [~, idx_resp_max] = max(mag_resp(idx_resp)); resp_freq = f_resp(idx_resp(idx_resp_max)); rr_value = resp_freq * 60; % 呼吸率,次/分钟

如果你用的是1分钟以上的长数据,还能观察呼吸波形的包络变化,画出呼吸幅度随时间的变化曲线,这个可以作为报告里的定性分析图表,很有说服力。

可视化建议画三张图,呈现在报告里会非常清楚:

  1. 原始信号与预处理后对比图,展示去趋势和滤波效果。
  2. BCG滤波后波形与检测到的峰值点叠加图,让读者直观看到心跳峰。
  3. 频谱图,标出心率峰和呼吸峰的位置与频率值。

matlab画图用subplot安排在一张图里,再加title和xlabel、ylabel,导出成300dpi的PNG或EPS,报告和答辩都能直接用。

4. 常见问题与排查技巧

4.1 运动伪迹与重力分量干扰

这是最典型的翻车场景。你以为人在安静坐着,但身体很诚实——哪怕只是轻微调整坐姿、深呼吸、或者脚不自觉抖动,加速度计都会记录到比BCG大几个量级的运动信号。这些运动伪迹频谱很宽,可能直接盖过心率带。

应对措施分两步。第一步是硬件层面的约束,采集过程中尽量保持姿势不变。第二步是算法层面的质检:计算滑动窗口内信号的标准差或动态范围,如果某一段幅度异常高(比如是正常段的10倍以上),直接标记为“运动污染段”,不参与心率估计。这不是投机取巧,而是实际产品里也在用的信号质量检测思路。matlab里可以用movstd快速算滑动标准差,超过阈值就把这段数据剔除或者用前后有效段补插。

我在实测中还发现一个反直觉的现象:有时候滤波后的BCG波形幅值正常,但频谱里心率峰旁边多出一个小鼓包,而且心率峰位置会跳动。后来排查发现是佩戴位置贴近肋骨,呼吸时胸廓带动传感器有微小旋转,导致重力分量在轴向上发生了几毫g的波动。这个波动频率和呼吸频率一致,但它会调制BCG信号,在频谱上形成边带。处理办法是尽量把传感器固定稳妥,减少旋转自由度;算法上则加大时域峰值检测的约束,用“峰值间隔一致性”来过滤这类调制干扰。

4.2 谐波干扰与频率分辨率不足

呼吸信号的谐波问题是新手最容易踩的坑。呼吸基频0.25Hz,三次谐波0.75Hz,四次谐波1.0Hz——后者已经进入心率带。如果你的BCG滤波通带设计的过宽(比如从0.5Hz开始),呼吸谐波就会堂而皇之混进来,导致心率检测结果“漂”到呼吸谐波频率上。

解决思路有三个层面。第一,BCG带通滤波器的低端起始频率尽量抬高,取0.8Hz起步,把大部分呼吸谐波挡在门外。第二,心率峰值搜索时,先对照呼吸基频,排除呼吸基频整数倍附近的候选峰。第三,检查“心率估计值”和“呼吸率估计值”之间是否存在整数倍关系,如果存在,大概率是谐波误检,需要重新判定。

频率分辨率不足的典型情况是:数据只有10秒,频谱主峰看起来在1.5Hz附近,但你换一段数据,主峰跳到1.7Hz,误差大得离谱。这说明窗口太短,谱峰“胖”到无法分辨。最简单的解决办法就是加长分析窗口到60秒。如果业务上必须用短窗口(比如实时系统),可以用自相关函数法替代FFT——自相关在短数据下估计周期更稳定。matlab里直接用xcorr(bcg, 'coeff')然后找自相关函数第一个明显副峰的位置,也能得到周期。

4.3 信号质量评估与窗口参数选择

很多初学者一上来就掐头去尾选一段“好看”的数据做分析,然后报告里只呈现漂亮结果,一换数据就崩。这样操作不能算错,但缺乏说服力。更专业的做法是引入信号质量评估,用定量指标说明你这套算法在什么条件下可靠。

我建议至少统计三个指标:

  • 信噪比:心率带内能量与带外能量之比。先分别做带通滤波,计算滤波后信号的方差,两个方差之比就是粗略的信噪比。
  • 峰值规律性:相邻峰间隔的变异系数CV = 标准差/平均值。CV小于10%说明节律规整,如果太大说明峰值检测不可靠。
  • 频域时域一致性:频域估计和时域估计的心率差值,差值小于3次/分钟认为结果可信,大于这个阈值就标记为低置信度。

窗口参数方面,我自己最终稳定的配置是:分析窗口60秒,去趋势用detrend,平滑用15点移动平均,BCG带通0.8到3Hz四阶巴特沃斯,呼吸带通0.1到0.6Hz四阶巴特沃斯,时域最小峰高取0.5倍标准差,最小峰间距根据频域估计动态生成。你在复现时不要盲目照抄,要素材不同信号质量差异很大,要按前面讲的方法自己调整参数,找到最适合当前设备的那组值。

5. 报告组织与结果呈现建议

5.1 验证实验结果对比

项目标题里说“含报告”,很多做课设的人容易把报告写成“README”,堆一堆示意图就交差。但评分和评审真正想看的,是“你的检测结果有没有可信度”。所以报告里一定要有验证实验部分。

建议最少做三组验证:

  • 静息状态连续测量5分钟,每30秒为一个分析窗口,输出一组心率和呼吸率,观察数值稳定性。理想情况下,心率波动应该在±3次/分钟以内。
  • 与标准设备对比。用手头的光学心率手环或者医用脉诊仪作为参考,同一时间记录,计算平均绝对误差和均方根误差。如果你实在没有标准设备,至少可以用人工数脉搏、数呼吸来粗略对照。
  • 呼吸率响应实验。让受试者按照节拍器以固定频率(比如15次/分钟和20次/分钟)呼吸,看算法输出的呼吸率是否跟随上,这个实验能直观证明呼吸检测通道是有效的。

报告里最好把验证结果做成表格,列出每组的估计值、参考值、误差,再配一段几十行的讨论,解释误差来源。这种写法比满屏截图有力得多。

5.2 报告结构与数据表输出

报告结构可以这样安排:绪论里说明研究背景和现有方法局限,引出加速度计方案的意义;原理部分讲BCG和呼吸信号的形成机制;系统设计部分给出硬件框图和处理流程;实验部分放前文说的验证结果;结论部分客观总结方案的适用范围和局限。

数据表输出方面,matlab可以直接把结果写成CSV或者Excel,方便后续统计。

results_table = table(time_stamp, hr_values, rr_values, 'VariableNames', ... {'Time_s', 'HeartRate_bpm', 'RespRate_brpm'}); writetable(results_table, 'detection_results.xlsx');

还有一个容易被忽略的细节:matlab代码里处理时间时要注意坐标对齐。如果用的是每隔30秒滑窗,窗的“时间戳”是取窗口终点还是中心,要在报告里说清楚,否则读者换数据复现时会对不上时间轴。

私心说一句,交付代码时最好把关键滤波器参数用变量配置写清楚,放在脚本头部,不要散落在代码中间。这样评审或者未来的你回过头来调参,能省下大量时间。做信号处理项目最怕“睡一觉起来忘了参数为什么这么设”,把决策过程写成注释,项目管理上这一点比任何优化技巧都值钱。

这个项目做到最后,你会发现自己掌握的不只是“用加速度计测心率”这一招,而是一整套“从微弱振动信号里提取生理周期信息”的方法论。把这套滤波、频谱估计、时域峰值检测、信号质量评估的流程吃透,后面再做步数检测、姿态识别、睡眠监测,基本都是同一套思路的延伸。如果你在复现时卡在哪一步,优先检查的永远是两件事:信号的频段到底对不对,以及滤波设计是否真的发挥了作用。这两点捋顺,结果大概率就稳了。

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

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

立即咨询