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://www.sciencedirect.com/science/article/pii/S1746809426004234
研究 埃因霍温理工大学+TU/e Smart Mobility

核心创新

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

问题定义

车载rPPG三大挑战

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

信号质量评估的必要性

flowchart TD
    A[rPPG原始信号] --> B{信号质量评估}
    B -->|高质量| C[信任心率输出]
    B -->|低质量| D[标记不可靠]
    D --> E[切换到备用估计]
    D --> F[降低置信度]
    D --> G[请求用户配合]
    C --> H[正常健康监测]
    E --> I[IMU/ECG融合]

方法详解

信号质量评估管道

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
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}
"""
# 简化版规则分类器(实际用Random Forest/SVM)
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}")

实验结果

车载场景信号质量分布

场景 信号质量 SNR(dB) 可用心率估计
城市道路白天 Good 4.2
高速公路白天 Excellent 6.8
隧道进出 Poor→Good 1.5→5.2 短暂不可用
树荫间歇 Fair 2.8 ⚠️ 降级
夜间红外 Good 5.5
颠簸路面 Poor 0.8
驾驶员转头 Poor -2.1

质量分类器性能

分类器 准确率 精确率 召回率 F1
规则阈值 78.5% 76.2% 80.1% 78.1%
SVM 85.3% 83.7% 87.2% 85.4%
Random Forest 88.7% 87.1% 90.3 88.7%
轻量CNN 87.2% 85.8% 88.9% 87.3%

IMS开发启示

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

flowchart TD
    A[DMS摄像头] --> B[面部检测+ROI提取]
    B --> C[rPPG信号提取]
    C --> D[信号质量评估]
    D -->|Excellent/Good| E[心率输出→健康监测]
    D -->|Fair| F[降级输出→趋势估计]
    D -->|Poor| G[标记不可靠→暂停输出]
    G --> H[切换到ECG/PPG备份]

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. 部署参数

参数
评估窗口 10秒(300帧@30fps)
更新频率 1Hz
分类器 Random Forest(9特征)
模型大小 ~50KB
推理延迟 <5ms
处理器 QCS8255 NPU

总结

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

  1. 先评估质量再输出:避免不可靠心率误导驾驶决策
  2. 9维特征+RF分类器达到88.7%准确率:频谱+时域+运动+周期性
  3. 车载特定场景验证:隧道、树荫、颠簸、夜间全覆盖
  4. 与MS-rPPG管道无缝衔接:提取→质量评估→可信心率输出
  5. 50KB模型+5ms推理:边缘部署友好

https://dapalm.com/2026/09/22/2026-09-22-03-rppg-signal-quality-ml-in-vehicle-ims/
作者
Mars
发布于
2026年9月22日
许可协议