光电容积脉搏波描记法(PPG)是穿戴设备测量心率的标配技术,它依赖LED光源照射皮肤、光电探测器接收反射光强的变化来推算血容量搏动。静止状态下这套方案非常可靠,但一旦用户开始跑步、骑车或者做力量训练,肌肉收缩、肢体摆动、皮肤与传感器之间的相对位移,都会让光路发生剧烈变化。这些变化叠加在真实的脉搏波上,形成幅度远大于生理信号的干扰,也就是常说的运动伪影(Motion Artifact)。如果不做任何处理,设备输出的心率值可能从实际的150次每分钟跳到90甚至200以上,完全失去参考价值。

运动伪影的频谱其实很有特点。正常人的心率大约在0.5Hz到4Hz之间,而跑步时手臂摆动的频率通常在1.5Hz到3Hz,恰好与心率频带重叠。传统带通滤波器只能去掉明显的高频抖动和低频漂移,对同频段的伪影几乎无能为力。这时候就需要引入额外的参考信息——加速度计。加速度计能够直接测量三维方向上的加速度变化,其信号与肢体运动高度相关。如果把加速度计数据当作噪声参考输入,再结合自适应滤波器,系统就能实时估计并抵消PPG信号中的运动成分。
运动伪影的产生机制与频谱特征
PPG传感器通常佩戴在手腕、上臂或者耳垂,测量部位会随着身体运动发生周期性移动。以手腕为例,跑步时手腕前后摆动,皮肤表面与传感器之间的接触压力不断变化,导致LED照射深度和探测器接收到的散射光强度出现低频波动。这种波动不是心脏搏动引起的,但频率却可能落入心率范围。更麻烦的是,不同人的摆臂习惯、步频、步幅都不一样,伪影的具体频率和幅度具有很强的个体差异。
从频域来看,静止时PPG信号在心率基频及其谐波上有明显峰值,而运动伪影则呈现宽带特性,同时叠加了与步频相关的尖峰。如果用户步频恰好是心率的两倍或一半,传统锁相或谱峰追踪算法就可能把步频误判为心率。加速度计信号虽然也受到同样的运动影响,但它不包含任何脉搏信息,因此可以作为理想的参考噪声源。关键在于如何设计一个滤波器,使得输出信号中与加速度计相关的成分被抑制,而保留真正的心搏信号。
实际采集到的原始PPG数据往往混有基线漂移和工频干扰,预处理阶段需要先做去趋势处理和50Hz陷波。去趋势可以使用一阶差分或者高通滤波器,截止频率设在0.3Hz左右;陷波器则用于消除电源线耦合噪声。完成这些基础清理后,再进入加速度计融合环节。需要注意的是,加速度计三轴数据并不都需要参与运算,一般选取与皮肤垂直方向(Z轴)变化最剧烈的那一轴作为主参考,另外两轴可以作为辅助特征用于运动状态分类。
基于最小均方算法的自适应滤波实现
最小均方(LMS)算法结构简单、计算量小,非常适合在低功耗穿戴设备的MCU上运行。它的基本思路是:将加速度计信号作为参考输入x(n),PPG信号作为期望信号d(n),通过一个有限冲激响应滤波器产生输出y(n),目标是让误差e(n)=d(n)-y(n)的均方值最小。误差e(n)就是去除运动伪影后的干净脉搏信号。滤波器系数通过迭代更新,步长μ控制收敛速度和稳态误差之间的平衡。
下面给出一个基于Python的简化实现,演示如何利用numpy完成信号生成、LMS滤波和结果对比。代码中模拟了一段带有运动伪影的PPG信号,加速度计信号由正弦波叠加噪声产生,两者存在一定相关性。
import numpy as np
import matplotlib.pyplot as plt
# 生成模拟信号
fs = 50 # 采样率50Hz
t = np.arange(0, 10, 1/fs)
heart_rate = 1.8 # 1.8Hz对应108bpm
ppg_clean = 1.0 * np.sin(2 * np.pi * heart_rate * t)
# 运动伪影:步频2.2Hz叠加低频漂移
motion = 1.5 * np.sin(2 * np.pi * 2.2 * t) + 0.5 * np.sin(2 * np.pi * 0.4 * t)
accel_ref = motion + 0.1 * np.random.randn(len(t))
ppg_noisy = ppg_clean + motion + 0.2 * np.random.randn(len(t))
# LMS自适应滤波
order = 16 # 滤波器阶数
mu = 0.005 # 步长
w = np.zeros(order) # 初始权重
y = np.zeros(len(t))
e = np.zeros(len(t))
for n in range(order, len(t)):
x = accel_ref[n-order:n][::-1] # 参考输入向量
y[n] = np.dot(w, x)
e[n] = ppg_noisy[n] - y[n]
w = w + 2 * mu * e[n] * x
# 输出结果,e即为滤波后信号
plt.figure(figsize=(10,6))
plt.subplot(3,1,1)
plt.plot(t, ppg_clean, label='Clean PPG')
plt.legend()
plt.subplot(3,1,2)
plt.plot(t, ppg_noisy, label='Noisy PPG')
plt.legend()
plt.subplot(3,1,3)
plt.plot(t, e, label='Filtered (LMS output)')
plt.legend()
plt.xlabel('Time (s)')
plt.tight_layout()
plt.show()
这段代码展示了最基础的LMS流程,但实际产品中需要处理几个关键问题。首先,加速度计和PPG的采样率可能不同,比如加速度计100Hz而PPG只有25Hz,必须通过插值或抽取对齐时间轴。其次,运动伪影与加速度计信号并不是简单的线性关系,当用户做变速跑或改变动作模式时,单一固定步长的LMS可能收敛过慢或发散。工程上常用变步长LMS或归一化LMS(NLMS)来改善稳定性,NLMS的步长会根据输入信号功率自动调整。
另一个容易被忽略的细节是延迟。自适应滤波器本质上是因果系统,输出信号会有order/2个采样点的群延迟,这意味着在连续心率监测中需要做相位补偿。对于实时显示心率值的场景,几十毫秒的延迟也许可以接受,但如果要用于心率变异性分析,就必须校正相位,否则频域指标会完全失真。一种简单的补偿方法是在滤波后通过一个全通滤波器或者直接丢弃前order个样本后做时间移位。
加速度计数据融合的进阶方案与工程优化
LMS虽然经典,但在复杂运动环境下效果有限。当用户进行间歇性高强度训练时,运动伪影的统计特性变化很快,自适应算法可能来不及收敛。为此,很多商用设备采用了频域方法,例如联合独立成分分析(ICA)或主成分分析(PCA)。ICA假设PPG信号和运动伪影在统计上相互独立,通过寻找线性变换使得输出分量之间的互信息最小。加速度计的三轴信号可以直接作为ICA的多个输入通道,与PPG构成一个多通道观测矩阵,分离出的独立成分中,与加速度计相关性低且具有明显脉搏周期性的那个分量就是干净PPG。
还有一种性价比很高的方案是运动状态分类加动态滤波。先利用加速度计数据计算短时能量、谱质心、过零率等特征,通过一个轻量级决策树或阈值逻辑判断当前运动强度(静止、走路、跑步、剧烈运动)。不同运动级别采用不同的滤波器参数:静止时关闭自适应滤波,直接使用带通滤波结果;走路时设置较小的μ和较低的阶数;跑步时提高μ并增加阶数;剧烈运动时可能切换为频域方法或者融合多通道PPG传感器数据。这种分级策略既能保证信号质量,又能节省计算资源。
在多传感器融合方面,一些高端运动手表会在表壳周围布置多个绿光LED和多个光电探测器,利用空间分集来抑制局部运动伪影。当某个探测器受到的压力变化特别大时,可以通过加权平均或者选择信噪比最高的通道来获得更稳定的PPG信号。加速度计在这里的角色不仅仅是提供参考噪声,还可以辅助判断传感器与皮肤的贴合状态,比如通过加速度突变检测到手表发生滑动时,立即触发重新校准流程。
代码层面,工程实现还需要考虑定点化。穿戴设备的MCU往往没有浮点运算单元,浮点乘加会消耗大量功耗。将LMS的权重、输入和步长转换为Q15或Q31定点格式,并用查表法替代部分乘法,可以把单次滤波的时钟周期数降低一个数量级。同时,缓冲区管理、环形队列和DMA传输也需要精心设计,避免数据丢失和内存碎片。以下是一个定点LMS的伪代码片段,展示如何将浮点运算映射到16位整数。
#define ORDER 16
#define SHIFT 15 // Q15定点
int16_t w[ORDER] = {0};
int16_t x_buf[ORDER] = {0}; // 环形缓冲区
int16_t lms_filter(int16_t d, int16_t x) {
// 更新缓冲区
for (int i = ORDER-1; i > 0; i--) {
x_buf[i] = x_buf[i-1];
}
x_buf[0] = x;
int32_t y = 0;
for (int i = 0; i < ORDER; i++) {
y += (int32_t)w[i] * x_buf[i];
}
int16_t y_q = (int16_t)(y >> SHIFT);
int16_t e = d - y_q;
// 更新权重,mu固定为2的幂次方便移位
int32_t mu_e = (int32_t)e * 8; // mu = 8/32768 ≈ 0.000244
for (int i = 0; i < ORDER; i++) {
int32_t update = (mu_e * x_buf[i]) >> SHIFT;
w[i] += (int16_t)update;
}
return e; // 滤波后的干净信号
}
这段C语言风格的代码展示了如何把LMS算法压缩到16位定点运算。SHIFT等于15意味着Q15格式,权重和输入都限制在[-1,1)范围内,实际使用时需要提前对PPG和加速度计信号做归一化。update计算中的右移操作替代了浮点除法,显著降低计算量。值得注意的是,定点实现必须防止累加过程中的溢出,int32_t中间变量是必不可少的。在极端运动条件下,如果输入信号幅度过大导致连续溢出,可以动态调整SHIFT值或者使用饱和运算保护。
除了算法本身的优化,硬件层面的配合也至关重要。PPG采集电路的前端模拟滤波需要精心选择截止频率,避免有用信号在进入ADC之前就被过度衰减。有些设计会在模拟前端加入一个同步检测器,利用加速度计信号控制可编程增益放大器的增益,运动剧烈时自动降低增益防止ADC饱和,运动平缓时提高增益以增强微弱脉搏信号。这种硬件级的数据融合思路能够从源头上减少伪影,让后端的数字滤波压力小很多。
最终评估一套心率监测系统在运动状态下的性能,需要依赖标准化的测试协议。常用的指标包括平均绝对误差、误差标准差、以及与胸带式心电图机的一致性系数。测试场景应该覆盖步行、慢跑、快跑、上下楼梯、骑行和力量训练等多种模式,每种模式至少采集5分钟数据。通过对比滤波前后这些指标的变化,可以定量验证加速度计融合算法的有效性。实践中,一套良好的融合方案通常能把运动状态下的心率误差从±15bpm降低到±3bpm以内,满足日常健康和运动指导的精度要求。