滤波背景:采样率、BPM、频域、噪声和基线漂移
先不看代码,先理解呼吸信号为什么适合带通滤波,采样率和 BPM 如何映射到频率,以及实时滤波为什么天然会有延迟。
背景问题
RespiraScope 里的滤波不是为了让曲线“看起来更顺”。它的目标是把原始传感器信号变成一个更适合识别呼吸周期的波形。原始数据里通常混有三类东西:真正的呼吸起伏、低频基线漂移、高频噪声或体动尖峰。
如果直接在原始数据上找波峰波谷,噪声会制造伪峰,基线漂移会让阈值失效,咳嗽或体动会让 BPM 瞬间乱跳。所以在峰谷识别之前,需要先把“呼吸可能出现的频段”保留下来,把明显不属于呼吸的部分压掉。
背景概念
采样率决定每秒有多少个点。项目默认 sampling_rate = 50,也就是每秒 50 个采样点。BPM 是每分钟呼吸次数。频率 Hz 是每秒周期数,所以换算关系是:
Hz = BPM / 60
6 BPM = 0.10 Hz
40 BPM = 0.67 Hz
这就是项目默认把 low_bpm = 6、high_bpm = 40 转成 0.10 Hz 到 0.67 Hz 的原因。对工程验证和普通呼吸监测演示来说,这个频段能覆盖慢呼吸、正常呼吸、浅快呼吸和多数异常演示场景。
带通滤波的意思是只保留一个频率区间。低于区间的慢变化被削弱,高于区间的快速抖动也被削弱。呼吸信号天然适合这种方法,因为呼吸周期通常有明确的生理频率范围。
设计方案对比
| 方案 | 能解决什么 | 主要问题 | 项目取舍 |
|---|---|---|---|
| 不滤波,直接找峰 | 实现最简单 | 噪声、漂移、体动都会制造伪峰 | 只适合干净仿真数据 |
| 移动平均 | 压掉高频噪声,计算便宜 | 对基线漂移帮助有限,窗口选择影响大 | 前端回看可做轻量视觉平滑 |
| 低通滤波 | 压高频噪声 | 保留了基线漂移,峰谷阈值仍会受影响 | 不够完整 |
| 带通滤波 | 同时压低频漂移和高频噪声 | 参数要和采样率、BPM 匹配 | 当前核心方案 |
| 小波或更复杂模型 | 能处理非平稳信号 | 参数、解释和维护成本高 | 当前项目不需要 |
RespiraScope 不是做复杂生理建模,而是做工程验证和可解释实时监测。所以项目选择 Butterworth 带通滤波加 Gaussian 平滑。这个组合足够成熟、容易解释,也能用 SciPy 稳定实现。
当前方案
项目的核心滤波参数在 FilterConfig:
| 参数 | 默认值 | 作用 |
|---|---|---|
sampling_rate | 50 | 每秒采样点数,影响频率换算和峰间距 |
low_bpm | 6 | 呼吸频率下界,会转成 0.10 Hz |
high_bpm | 40 | 呼吸频率上界,会转成 0.67 Hz |
order | 1 | Butterworth 滤波器阶数 |
gaussian_sigma | 1 | 高斯平滑强度 |
restore_baseline | true | 滤波后把曲线恢复到原始中位数附近 |
normalize_filter_config() 会把 BPM 转成 Hz:
config.lowpass_cutoff = config.low_bpm / 60
config.highpass_cutoff = config.high_bpm / 60
字段名 lowpass_cutoff 和 highpass_cutoff 沿用了旧接口,实际进入 butterworth_filter() 时被当作带通区间的低频和高频边界。
为什么实时滤波会延迟
实时滤波只能用已经到来的数据。它不能知道未来波形会怎么走,所以对峰谷位置的判断天然更保守。sosfilt 是因果滤波,适合流式处理,但会产生相位延迟:曲线形状被平滑后,峰值可能看起来稍晚。
离线滤波不同。Record End 后,系统已经拿到了完整片段,sosfiltfilt 可以先正向滤波再反向滤波,抵消相位延迟。这就是项目为什么区分实时和离线:实时页面优先低延迟,记录复盘优先质量。
代价和边界
带通滤波依赖参数。low_bpm 太高会削弱慢呼吸,high_bpm 太低会削弱快呼吸,gaussian_sigma 太大会抹平真实峰谷。RespiraScope 的默认值适合常见演示和工程验证,但不是所有患者、所有传感器、所有体位的通用参数。
滤波也不能“修复”坏数据。传感器断流、线缆松动、体动强干扰和饱和信号,需要状态提示和质量事件,而不是靠滤波硬压。项目里 SignalQualityAnalyzer 正是为这些情况提供额外信号。
关键函数表
| 函数或类 | 来源 | 在滤波背景中的意义 |
|---|---|---|
np.median() | NumPy | 用原始中位数恢复基线,使滤波曲线回到可读值域 |
np.round() | NumPy | 输出保留 1 位小数,减少前端数据抖动 |
signal.butter() | SciPy | 设计 Butterworth 带通滤波器 |
signal.sosfilt() | SciPy | 实时因果滤波,低延迟但有相位延迟 |
signal.sosfiltfilt() | SciPy | 离线双向滤波,抵消相位延迟 |
gaussian_filter1d() | SciPy | 对滤波后波形做一维高斯平滑 |
读源码建议
先读 http/schemas.py 的 FilterConfig,理解每个参数的范围和默认值。再读 normalize_filter_config(),确认 BPM 到 Hz 的转换。最后读 filter_strategies.py,重点看 butterworth_filter() 和 _filter_common(),你会看到项目里的滤波链路其实就是“带通滤波、恢复基线、高斯平滑、四舍五入”四步。