4、PPG信号噪声分析:运动伪影、环境光干扰、50Hz工频干扰、基线漂移
做血氧饱和度算法,说白了就是跟噪声做斗争。PPG信号本身很微弱,幅度也就几十到几百毫伏,随便来点干扰就能把信号淹没掉。我刚开始做这个方向时,天真地以为只要把传感器贴好就能拿到干净波形,结果被现实狠狠教育了一顿。
今天咱们就来拆解一下,PPG信号里最常见的四种噪声。搞懂了它们,你才能知道滤波器该怎么设计,算法该怎么写。
4.1 运动伪影——最头疼的噪声
运动伪影,英文叫Motion Artifact,简称MA。这是所有可穿戴设备工程师的噩梦。你想想看,用户戴着血氧仪在走路、跑步、甚至只是手指动了一下,传感器和皮肤之间的相对位置就会发生变化。
为什么会这样?因为PPG是靠光穿透组织来测量的。一旦传感器和皮肤之间有位移,光路就变了,接收到的光强会剧烈波动。这种波动的幅度,有时候比真正的脉搏波还大好几倍。
关键特征:
- 频率范围宽,从0.1Hz到10Hz以上都可能出现
- 与脉搏波在频域上有重叠,单纯用带通滤波器很难分离
- 幅度不稳定,有时突然出现一个尖峰
我在项目中遇到过最极端的情况:受试者只是打了个喷嚏,PPG波形直接飞出了ADC的量程。嗯,这时候你算出来的血氧饱和度,肯定是错的。
我的经验:运动伪影的抑制,不能只靠硬件。加速度计辅助的自适应滤波是个好路子。我习惯用三轴加速度计的数据作为参考,通过LMS或RLS算法自适应地消除运动分量。当然,这需要额外的硬件成本和算力开销。
4.2 环境光干扰——你以为遮住了就没事?
环境光干扰,说白了就是太阳光、日光灯、LED灯这些外部光源,直接或者间接地照到了光电探测器上。PPG传感器通常用红光和红外光LED作为光源,但环境光里也包含这些波段的光。
你可能会说:「传感器不是有遮光罩吗?」嗯,理论上是这样。但实际使用中,尤其是户外场景,环境光的强度可能比LED的反射光强好几个数量级。这时候,探测器接收到的信号里,环境光成分占了绝大部分。
环境光干扰的特点:
- 通常是直流分量或缓慢变化的成分
- 但如果环境光本身在变化(比如有人在旁边开关灯),就会产生突变
- 50Hz/60Hz的工频成分也会通过环境光耦合进来
我曾经在做一个户外血氧监测项目时,发现白天和晚上的血氧值差异很大。排查了半天,最后发现是环境光导致探测器饱和了。ADC采到的数值一直卡在满量程附近,脉搏波完全被淹没了。
避坑指南:硬件上一定要做环境光抑制电路。我建议用差分采样或者动态偏置的方法,把环境光产生的直流分量抵消掉。软件上,可以在信号采集前先测一次环境光背景,然后做减法。但要注意,环境光会变化,所以这个背景值需要定期更新。
4.3 50Hz工频干扰——无处不在的幽灵
50Hz工频干扰,搞嵌入式的人都不陌生。市电电网的频率,在中国是50Hz,在一些国家是60Hz。这个频率刚好落在PPG信号的频带内——正常心率对应的频率是0.5Hz到3Hz左右,但工频干扰的谐波可能会混进来。
为什么PPG信号里会有工频干扰?原因很多:
- 传感器和人体之间的电容耦合
- 电源线的电磁辐射
- 接地回路引入的噪声
- 甚至你的USB供电线都会带来干扰
我记得有一次调试一个血氧仪原型机,插着USB线供电时,波形上总有明显的50Hz纹波。拔掉USB线,改用电池供电,纹波就消失了。嗯,这就是典型的接地回路问题。
处理工频干扰,常用的方法有:
- 陷波滤波器:在50Hz处挖一个坑,把干扰滤掉。但要注意,陷波器会带来相位失真,可能影响脉搏波的特征点检测。
- 自适应陷波:频率自适应,可以跟踪电网频率的微小波动。我比较推荐这种方法,因为电网频率并不是严格稳定的50Hz,会有±0.1Hz左右的漂移。
- 同步采样:如果ADC的采样时钟和电网同步,可以在工频的过零点采样,避开干扰最大的时刻。但这需要硬件支持。
代码示例:一个简单的IIR陷波滤波器实现
// 50Hz陷波滤波器,采样率Fs=200Hz
// 使用双线性变换法设计
float notch_filter(float input) {
static float x1 = 0, x2 = 0;
static float y1 = 0, y2 = 0;
float w0 = 2 * 3.14159 * 50.0 / 200.0; // 归一化频率
float alpha = 0.95; // 陷波宽度控制
float b0 = 1.0;
float b1 = -2.0 * cos(w0);
float b2 = 1.0;
float a0 = 1.0;
float a1 = -2.0 * alpha * cos(w0);
float a2 = alpha * alpha;
float output = (b0 * input + b1 * x1 + b2 * x2
- a1 * y1 - a2 * y2) / a0;
x2 = x1;
x1 = input;
y2 = y1;
y1 = output;
return output;
}
4.4 基线漂移——低频的慢性杀手
基线漂移,就是PPG信号的整体水平线在缓慢地上下移动。频率通常在0.1Hz到0.5Hz之间,比呼吸频率还低一些。
产生原因主要有:
- 呼吸引起的胸腔起伏,改变了光路长度
- 传感器的温度漂移,LED的发光效率会随温度变化
- 人体自身的血管舒缩运动
- 皮肤血流量的缓慢变化
你想想看,如果基线在缓慢上升,而你用固定阈值去检测脉搏波的波峰,那结果肯定不准。基线漂到上面时,波峰可能被误判为波谷;基线漂到下面时,波谷可能被漏检。
处理基线漂移,我个人的习惯是:
- 高通滤波器:截止频率设在0.5Hz左右,可以滤掉大部分基线漂移。但要注意,心率很低时(比如40bpm,对应0.67Hz),高通滤波器可能会衰减有用的脉搏波成分。
- 多项式拟合:用低阶多项式拟合基线,然后从原始信号中减去。这种方法适合离线处理,实时性要求不高时可以用。
- 形态学滤波:用开运算和闭运算估计基线,效果不错,但计算量稍大。
我的建议:在嵌入式平台上,我推荐用一阶高通滤波器,系数简单,计算量小。截止频率可以设为0.5Hz,对于大多数成人来说够用了。但如果要做新生儿血氧监测,心率可能到200bpm以上,这时候截止频率可以适当提高。
4.5 四种噪声的对比总结
最后,咱们用一张表来总结这四种噪声,方便你对照设计滤波器:
| 噪声类型 | 频率范围 | 幅度特征 | 主要抑制方法 |
|---|---|---|---|
| 运动伪影 | 0.1Hz ~ 10Hz+ | 幅度大,突发性强 | 加速度计辅助自适应滤波 |
| 环境光干扰 | 直流 ~ 低频 | 直流偏置为主,可能有突变 | 硬件抑制 + 软件背景减法 |
| 50Hz工频干扰 | 50Hz(及谐波) | 幅度稳定,周期性 | 陷波滤波器 / 自适应陷波 |
| 基线漂移 | 0.1Hz ~ 0.5Hz | 缓慢变化,幅度不定 | 高通滤波器 / 多项式拟合 |
嗯,这四种噪声,你搞定了,PPG信号处理就算入门了。下一章咱们会讲具体的滤波器设计,到时候我会给出完整的C代码实现。记住,没有万能的滤波器,只有针对性的组合方案。我在实际项目中,通常是把这几种方法串起来用:先做硬件抗混叠滤波,再做软件高通去基线,然后陷波去工频,最后用自适应滤波处理运动伪影。
每一步都有坑,但踩过了,你就成长了。