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线,改用电池供电,纹波就消失了。嗯,这就是典型的接地回路问题。

处理工频干扰,常用的方法有:

  1. 陷波滤波器:在50Hz处挖一个坑,把干扰滤掉。但要注意,陷波器会带来相位失真,可能影响脉搏波的特征点检测。
  2. 自适应陷波:频率自适应,可以跟踪电网频率的微小波动。我比较推荐这种方法,因为电网频率并不是严格稳定的50Hz,会有±0.1Hz左右的漂移。
  3. 同步采样:如果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的发光效率会随温度变化
  • 人体自身的血管舒缩运动
  • 皮肤血流量的缓慢变化

你想想看,如果基线在缓慢上升,而你用固定阈值去检测脉搏波的波峰,那结果肯定不准。基线漂到上面时,波峰可能被误判为波谷;基线漂到下面时,波谷可能被漏检。

处理基线漂移,我个人的习惯是:

  1. 高通滤波器:截止频率设在0.5Hz左右,可以滤掉大部分基线漂移。但要注意,心率很低时(比如40bpm,对应0.67Hz),高通滤波器可能会衰减有用的脉搏波成分。
  2. 多项式拟合:用低阶多项式拟合基线,然后从原始信号中减去。这种方法适合离线处理,实时性要求不高时可以用。
  3. 形态学滤波:用开运算和闭运算估计基线,效果不错,但计算量稍大。

我的建议:在嵌入式平台上,我推荐用一阶高通滤波器,系数简单,计算量小。截止频率可以设为0.5Hz,对于大多数成人来说够用了。但如果要做新生儿血氧监测,心率可能到200bpm以上,这时候截止频率可以适当提高。

4.5 四种噪声的对比总结

最后,咱们用一张表来总结这四种噪声,方便你对照设计滤波器:

噪声类型 频率范围 幅度特征 主要抑制方法
运动伪影 0.1Hz ~ 10Hz+ 幅度大,突发性强 加速度计辅助自适应滤波
环境光干扰 直流 ~ 低频 直流偏置为主,可能有突变 硬件抑制 + 软件背景减法
50Hz工频干扰 50Hz(及谐波) 幅度稳定,周期性 陷波滤波器 / 自适应陷波
基线漂移 0.1Hz ~ 0.5Hz 缓慢变化,幅度不定 高通滤波器 / 多项式拟合

嗯,这四种噪声,你搞定了,PPG信号处理就算入门了。下一章咱们会讲具体的滤波器设计,到时候我会给出完整的C代码实现。记住,没有万能的滤波器,只有针对性的组合方案。我在实际项目中,通常是把这几种方法串起来用:先做硬件抗混叠滤波,再做软件高通去基线,然后陷波去工频,最后用自适应滤波处理运动伪影。

每一步都有坑,但踩过了,你就成长了。