rPPG信号质量评估:机器学习驱动的车载驾驶员远程脉搏监测——应对运动伪影与动态光照

论文信息

项目 内容
标题 Machine learning-based remote PPG signal quality assessment for in-vehicle driver monitoring
期刊 Biomedical Signal Processing and Control, Vol. 119, Part B
发表 2026年2月21日
作者 Babac S, Vosters LPJ, Vullings R, Zinger S, van Gastel MJH
机构 Eindhoven University of Technology(荷兰)
链接 https://doi.org/10.1016/j.bspc.2026.109869(OA, CC-BY)
研究 埃因霍温理工大学+TU/e Smart Mobility

核心创新

  1. 车载rPPG信号质量机器学习评估:专门针对车内环境运动伪影和动态光照
  2. 质量分类而非直接心率:先评估信号可靠性,再决定是否信任rPPG输出
  3. 驾驶场景特定:不同于医疗/静止场景,针对振动、光照变化、头部运动优化
  4. 与现有DMS摄像头集成:无需额外传感器

问题定义

车载rPPG三大挑战

挑战 静止场景 车载场景 影响
运动伪影 极小 频繁(头部运动、振动) rPPG信号被淹没
光照变化 受控 隧道进出、树荫、阳光角度 信号基线漂移
实时性要求 可后处理 需实时质量判定 延迟<1s

信号质量评估的必要性

方法详解

信号质量评估管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# 示意代码,不可直接运行(质量特征提取与规则分类为本站简化示意,
# 非论文官方实现;论文实际使用 XGBoost 等机器学习分类器,未找到官方开源仓库)
import numpy as np
from scipy.signal import welch, butter, filtfilt
from dataclasses import dataclass

@dataclass
class rPPGSignal:
"""rPPG信号段"""
raw: np.ndarray # 原始rPPG信号
fps: int # 帧率
duration_sec: float # 时长

class SignalQualityAssessor:
"""
rPPG信号质量机器学习评估

论文方法:从rPPG信号中提取质量指标,
用机器学习分类器判定信号质量等级
"""

def __init__(self, fs: int = 30):
self.fs = fs

def extract_quality_features(self,
rppg: np.ndarray) -> dict:
"""
提取信号质量特征

Args:
rppg: [N] rPPG信号段

Returns:
features: dict of quality metrics
"""
N = len(rppg)

# 1. 频谱特征
freqs, psd = welch(rppg, fs=self.fs, nperseg=min(256, N))

# 心率频段(0.7-4.0 Hz = 42-240 BPM)
hr_mask = (freqs >= 0.7) & (freqs <= 4.0)
hr_power = np.sum(psd[hr_mask])
total_power = np.sum(psd) + 1e-8
spectral_ratio = hr_power / total_power

# 峰值频率(预期在心率范围内)
if np.any(psd[hr_mask] > 0):
peak_freq = freqs[hr_mask][np.argmax(psd[hr_mask])]
else:
peak_freq = 0

# 频谱集中度(心率峰附近能量集中度)
peak_idx = np.argmax(psd[hr_mask])
peak_width = self._peak_width(freqs[hr_mask],
psd[hr_mask], peak_idx)
spectral_concentration = 1.0 / (1.0 + peak_width)

# 2. 时域特征
# 信号幅度
amplitude = np.std(rppg)

# 零交叉率
zero_crossings = np.sum(np.diff(np.sign(rppg)) != 0)
zcr = zero_crossings / N

# 信噪比(SNR)
# 信号:心率频段功率;噪声:其余频段
noise_power = total_power - hr_power
snr = 10 * np.log10(hr_power / (noise_power + 1e-8))

# 3. 运动伪影指标
# 高频成分占比(>4Hz通常为运动伪影)
motion_mask = freqs > 4.0
motion_power = np.sum(psd[motion_mask])
motion_ratio = motion_power / total_power

# 4. 趋势特征
# 线性趋势(基线漂移)
t = np.arange(N)
trend_coef = np.polyfit(t, rppg, 1)[0]
trend_ratio = abs(trend_coef * N) / (np.std(rppg) + 1e-8)

# 5. 周期性
# 自相关峰值
autocorr = np.correlate(rppg - np.mean(rppg),
rppg - np.mean(rppg),
mode='full')
autocorr = autocorr[N-1:] / autocorr[N-1]

# 找第一个非零延迟峰值
periodicity = 0
for lag in range(int(0.5 * self.fs), int(3.0 * self.fs)):
if lag < len(autocorr):
if autocorr[lag] > periodicity:
periodicity = autocorr[lag]

return {
'spectral_ratio': spectral_ratio,
'peak_freq': peak_freq,
'spectral_concentration': spectral_concentration,
'amplitude': amplitude,
'zcr': zcr,
'snr': snr,
'motion_ratio': motion_ratio,
'trend_ratio': trend_ratio,
'periodicity': periodicity,
}

def classify_quality(self, features: dict) -> dict:
"""
机器学习分类信号质量

Returns:
{'quality': str, 'confidence': float, 'usable': bool}
"""
# 简化版规则分类器(论文实际用 XGBoost/SVM/CNN/LSTM 等)
score = 0

# 频谱比率 > 0.3 → 高质量
if features['spectral_ratio'] > 0.3:
score += 2
elif features['spectral_ratio'] > 0.15:
score += 1

# SNR > 3dB → 高质量
if features['snr'] > 3.0:
score += 2
elif features['snr'] > 0.0:
score += 1

# 运动伪影比 < 0.2 → 高质量
if features['motion_ratio'] < 0.2:
score += 2
elif features['motion_ratio'] < 0.4:
score += 1

# 周期性 > 0.5 → 高质量
if features['periodicity'] > 0.5:
score += 2
elif features['periodicity'] > 0.3:
score += 1

# 趋势比 < 0.5 → 稳定
if features['trend_ratio'] < 0.5:
score += 1

# 分类
if score >= 7:
quality = 'Excellent'
confidence = 0.9
usable = True
elif score >= 5:
quality = 'Good'
confidence = 0.75
usable = True
elif score >= 3:
quality = 'Fair'
confidence = 0.5
usable = False # 标记不可靠
else:
quality = 'Poor'
confidence = 0.2
usable = False

return {
'quality': quality,
'confidence': confidence,
'usable': usable,
'score': score,
}

def _peak_width(self, freqs, psd, peak_idx,
threshold=0.5):
"""计算峰宽"""
peak_val = psd[peak_idx]
half_max = peak_val * threshold

# 左边界
left = peak_idx
while left > 0 and psd[left] > half_max:
left -= 1

# 右边界
right = peak_idx
while right < len(psd) - 1 and psd[right] > half_max:
right += 1

return freqs[right] - freqs[left]


# 测试
if __name__ == "__main__":
assessor = SignalQualityAssessor(fs=30)

# 模拟高质量rPPG(清晰脉搏)
t = np.arange(900) / 30 # 30s at 30fps
clean_rppg = 0.5 * np.sin(2 * np.pi * 1.2 * t) # 72 BPM
clean_rppg += 0.05 * np.random.randn(900)

features_clean = assessor.extract_quality_features(clean_rppg)
result_clean = assessor.classify_quality(features_clean)
print("高质量信号:")
print(f" 质量: {result_clean['quality']}")
print(f" 可用: {result_clean['usable']}")
print(f" SNR: {features_clean['snr']:.1f}dB")
print(f" 周期性: {features_clean['periodicity']:.2f}")

# 模拟低质量rPPG(大量运动伪影)
noisy_rppg = 0.1 * np.sin(2 * np.pi * 1.2 * t)
noisy_rppg += 0.5 * np.random.randn(900) # 强噪声
noisy_rppg += 0.3 * np.sin(2 * np.pi * 8 * t) # 运动伪影

features_noisy = assessor.extract_quality_features(noisy_rppg)
result_noisy = assessor.classify_quality(features_noisy)
print("\n低质量信号:")
print(f" 质量: {result_noisy['quality']}")
print(f" 可用: {result_noisy['usable']}")
print(f" SNR: {features_noisy['snr']:.1f}dB")
print(f" 运动比: {features_noisy['motion_ratio']:.2f}")

实验结果

以下数字来自论文摘要(可在 DOI 页 核对):

  • 研究对象:首个系统性评估 ML/DL 方法用于车载 rPPG 信号质量分类的研究
  • 对比方法:XGBoost、SVM、CNN、LSTM,跨时域/频域多种输入表示
  • 关键发现:峰值中心化幅度谱(peak-centered magnitude spectrum)显著提升可靠/不可靠 rPPG 信号段的区分能力
  • 最优模型:XGBoost 使用排名前五的频域特征,AUC 0.86,灵敏度表现良好,优于现有 SOTA PPG 质量方法,且计算成本(FLOPS 口径)远低于后者
  • 数据规模:31 名驾驶员、多种真实驾驶条件验证

注:分场景 SNR 分布与逐分类器的准确率/精确率/召回率/F1 细分数字,请以论文正文为准,此处仅摘录摘要明确给出的数字。

分类器对比(定性)

  • 摘要未给出各分类器逐项准确率数字,但从结论可确认的定性排序是:XGBoost + 峰值中心化幅度谱 为最优组合,传统方法(SVM 等)与深度方法(CNN/LSTM 等)均在其对比之列
  • 工程含义:车载 rPPG 质量评估不需要重量级深度模型——低计算成本的频域特征+梯度提升树即可达到优于 SOTA 的质量判别,这对量产 DMS 的算力预算非常友好

IMS开发启示

1. rPPG质量评估在IMS中的位置

2. 与MS-rPPG的协同

组件 MS-rPPG (#14) 质量评估 (本论文) 协同
目标 多光谱rPPG提取 信号质量判定 提取+验证
输入 RGB+NIR视频 rPPG信号 管道
输出 心率估计 质量等级 可信心率
场景 驾驶舱多光照 运动/光照干扰 鲁棒性

3. 完整rPPG管道

步骤 方法 来源
1. 采集 DMS RGB-IR摄像头 现有硬件
2. ROI提取 面部检测+额头/脸颊ROI MediaPipe
3. rPPG提取 MS-rPPG多光谱Mamba 论文#14
4. 质量评估 ML质量分类 本论文
5. 心率估计 高质量段→Welch峰值 标准
6. 健康监测 心率变异性+趋势 上层应用
7. 数据增强 rePPG relighting 论文#21

4. 部署参数

论文摘要未提供逐项部署参数(窗口长度、模型大小、推理延迟、目标处理器等均无),以下仅为定性结论:

  • 论文强调最优 XGBoost 模型的计算成本以 FLOPS 计远低于 SOTA PPG 质量方法,面向未来 DMS 的计算高效部署
  • 具体的窗口/频率/内存/延迟参数请以论文正文为准,此处不做具体声明

总结

rPPG信号质量评估是车载心率监测从实验走向量产的关键缺失环节:

  1. 先评估质量再输出:避免不可靠心率误导驾驶决策
  2. 频域特征+XGBoost达AUC 0.86:峰值中心化幅度谱是关键,计算成本远低于SOTA PPG质量方法
  3. 车载特定场景验证:31名驾驶员、多种真实驾驶条件
  4. 与MS-rPPG管道无缝衔接:提取→质量评估→可信心率输出
  5. 轻量ML优于深度模型:对边缘部署与量产DMS算力预算友好(具体模型大小/延迟原文未提供)