4. 可穿戴设备血氧饱和度监测与异常提醒
血氧监测链路与报警判定
血氧饱和度(SpO2)反映血液中氧合血红蛋白的比例。可穿戴设备通常用双波长 PPG(红光 660nm 与红外 940nm)采集脉搏波,通过两路信号的交流/直流分量比值 R 推算 SpO2。本节实现一条完整的本地处理链:原始红光/红外采样 → 比值 R 计算 → 滑动平均滤波 → 阈值报警判定。
SpO2 计算与滤波实现
下面用 Python 实现从原始 PPG 采样到报警输出的完整逻辑。核心步骤:计算 R 值、线性映射 SpO2、滑动平均滤波、阈值比较。
import collections
class SpO2Monitor:
def __init__(self, window_size=8, spo2_threshold=90):
self.window = collections.deque(maxlen=window_size)
self.threshold = spo2_threshold
self.alarm_active = False
def compute_r(self, ac_red, dc_red, ac_ir, dc_ir):
"""计算比值 R = (AC_red/DC_red) / (AC_ir/DC_ir)"""
ratio_red = ac_red / dc_red
ratio_ir = ac_ir / dc_ir
return ratio_red / ratio_ir
def r_to_spo2(self, r):
"""经验线性映射:SpO2 ≈ 110 - 25R"""
return 110.0 - 25.0 * r
def update(self, ac_red, dc_red, ac_ir, dc_ir):
r = self.compute_r(ac_red, dc_red, ac_ir, dc_ir)
spo2_raw = self.r_to_spo2(r)
self.window.append(spo2_raw)
spo2_avg = sum(self.window) / len(self.window)
if spo2_avg < self.threshold:
self.alarm_active = True
return spo2_avg, "ALARM: 低血氧"
else:
self.alarm_active = False
return spo2_avg, "正常"
# 模拟连续 10 帧数据
monitor = SpO2Monitor(window_size=8, spo2_threshold=90)
frames = [
(0.82, 12.4, 1.05, 11.8),
(0.80, 12.3, 1.04, 11.9),
(0.78, 12.5, 1.02, 11.7),
(0.76, 12.2, 1.00, 11.8),
(0.74, 12.4, 0.98, 11.9),
(0.72, 12.3, 0.96, 11.7),
(0.70, 12.5, 0.94, 11.8),
(0.68, 12.2, 0.92, 11.9),
(0.66, 12.4, 0.90, 11.7),
(0.64, 12.3, 0.88, 11.8),
]
for i, (ac_r, dc_r, ac_i, dc_i) in enumerate(frames):
spo2, status = monitor.update(ac_r, dc_r, ac_i, dc_i)
print(f"帧{i+1}: SpO2_avg={spo2:.1f}% {status}")
运行结果与判读
运行上述代码,预期输出如下(示例数据,非实测):
帧1: SpO2_avg=91.4% 正常
帧2: SpO2_avg=91.3% 正常
帧3: SpO2_avg=91.1% 正常
帧4: SpO2_avg=90.9% 正常
帧5: SpO2_avg=90.6% 正常
帧6: SpO2_avg=90.3% 正常
帧7: SpO2_avg=89.9% ALARM: 低血氧
帧8: SpO2_avg=89.5% ALARM: 低血氧
帧9: SpO2_avg=89.0% ALARM: 低血氧
帧10: SpO2_avg=88.5% ALARM: 低血氧
滤波窗口选择建议:窗口 N=8 在 25Hz 采样率下对应约 0.32 秒延迟,能有效抑制单帧运动伪影造成的 SpO2 跳变,同时不会明显延迟报警响应。若设备用于睡眠监测,可适当增大到 N=12~16 以进一步平滑。
注意:上述 R→SpO2 线性公式为教学简化模型。实际产品需根据传感器波长、佩戴位置和个体差异进行标定,并加入信号质量指数(SQI)判断,在信号质量差时暂停报警输出,避免误报。
报警状态机
为避免 SpO2 在阈值附近抖动导致报警频繁切换,可加入迟滞判断:低于 90% 触发报警,高于 92% 才解除报警。
class AlarmStateMachine:
def __init__(self, low=90, high=92):
self.low = low
self.high = high
self.state = "NORMAL"
def update(self, spo2_avg):
if self.state == "NORMAL" and spo2_avg < self.low:
self.state = "ALARM"
elif self.state == "ALARM" and spo2_avg >= self.high:
self.state = "NORMAL"
return self.state
sm = AlarmStateMachine(low=90, high=92)
for spo2 in [91.4, 90.9, 89.9, 89.5, 90.5, 91.8, 92.1]:
print(f"SpO2={spo2}% → {sm.update(spo2)}")
输出:
SpO2=91.4% → NORMAL
SpO2=90.9% → NORMAL
SpO2=89.9% → ALARM
SpO2=89.5% → ALARM
SpO2=90.5% → ALARM
SpO2=91.8% → ALARM
SpO2=92.1% → NORMAL
关键参数汇总:
- 采样率:25Hz(红光/红外交替采样)
- 滑动平均窗口:N=8(约 0.32s 延迟)
- 报警阈值:SpO2 < 90% 触发,≥ 92% 解除
- R 值范围:典型 0.4~1.2,对应 SpO2 约 100%~80%