摄像头心率变异性估计:vPPG技术在DMS中的落地分析

Nature Scientific Reports 2026 论文解读与座舱DMS应用

论文信息

  • 标题: Estimating heart rate variability using facial video photoplethysmography: a pilot validation study
  • 作者: Leszek Pstras, Tymoteusz Okupnik, Beata Ponikowska, Bartlomiej Paleczny
  • 机构: Polish Academy of Sciences / Wroclaw Medical University
  • 期刊: Scientific Reports (Nature)
  • 发布日期: 2026年10月10日
  • 链接: DOI:10.1038/s41598-026-70292-0

核心发现

使用智能手机摄像头进行面部视频光电容积脉搏波(vPPG)估计心率变异性(HRV),与同步ECG参考值对比:

HRV指标 中位绝对误差 相对误差 RMSE Pearson相关
SDNN 3.0 ms 7.1% 4.5 ms r=0.98
lnRMSSD 0.20 5.6% 0.31 r=0.88

关键结论: vPPG技术在年轻白人群体中估计HRV指标总体准确性良好,但存在轻微系统性高估(特别是在低值区域)。

vPPG/rPPG 技术原理

从面部视频提取心率信号

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
import numpy as np
import torch
import torch.nn as nn
from typing import Tuple

class vPPGPipeline:
"""
Video Photoplethysmography (vPPG) 信号提取管道

核心原理:
1. 面部皮肤血液体积随心跳周期性变化
2. 血液吸收绿光较多,摄像头可捕捉肤色微变化
3. 通过信号处理提取脉冲信号 → 心率/HRV

信号处理链:
视频帧 → 面部检测 → ROI提取 → 像素平均 →
去趋势 → 带通滤波 → 脉冲信号 → 峰值检测 → HR/HRV
"""

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

def extract_ppg_signal(self, frames: np.ndarray,
roi_mask: np.ndarray = None) -> np.ndarray:
"""
从视频帧序列中提取PPG信号

Args:
frames: (T, H, W, 3) RGB视频帧
roi_mask: (H, W) 面部ROI掩码

Returns:
ppg_signal: (T,) 归一化PPG信号
"""
T = len(frames)

if roi_mask is None:
# 默认使用整帧
roi_mask = np.ones(frames.shape[1:])

# 提取ROI内像素
roi_pixels = frames[:, roi_mask > 0] # (T, N, 3)

# 绿色通道平均(血液吸收峰值)
green_avg = roi_pixels[:, :, 1].mean(axis=1) # (T,)

# 归一化
green_norm = (green_avg - green_avg.mean()) / (green_avg.std() + 1e-8)

# 去趋势
from scipy.signal import detrend
green_detrend = detrend(green_norm)

# 带通滤波 (0.7-4.0 Hz, 即42-240 BPM)
from scipy.signal import butter, filtfilt
nyquist = self.fps / 2
low, high = 0.7 / nyquist, min(4.0 / nyquist, 0.99)
b, a = butter(3, [low, high], btype='band')
ppg_signal = filtfilt(b, a, green_detrend)

return ppg_signal

def estimate_heart_rate(self, ppg_signal: np.ndarray) -> dict:
"""
从PPG信号估计心率

Returns:
{'heart_rate_bpm': float, 'confidence': float}
"""
# FFT频谱分析
n = len(ppg_signal)
freqs = np.fft.rfftfreq(n, d=1.0/self.fps)
spectrum = np.abs(np.fft.rfft(ppg_signal))

# 限制在合理心率范围 (40-200 BPM)
valid = (freqs >= 40/60) & (freqs <= 200/60)
if not valid.any():
return {'heart_rate_bpm': 0, 'confidence': 0}

peak_idx = np.argmax(spectrum[valid])
hr_freq = freqs[valid][peak_idx]
hr_bpm = hr_freq * 60

# 置信度:峰值与均值比
confidence = spectrum[valid][peak_idx] / (spectrum[valid].mean() + 1e-8)

return {
'heart_rate_bpm': float(hr_bpm),
'confidence': float(min(confidence / 5.0, 1.0))
}

def estimate_hrv(self, ppg_signal: np.ndarray) -> dict:
"""
从PPG信号估计心率变异性

时域指标:
- SDNN: RR间期标准差
- RMSSD: 相邻RR间期差值均方根

Returns:
{'sdnn_ms': float, 'rmssd_ms': float, 'ln_rmssd': float}
"""
# 峰值检测
from scipy.signal import find_peaks

peaks, _ = find_peaks(ppg_signal, distance=self.fps*0.4)

if len(peaks) < 3:
return {'sdnn_ms': 0, 'rmssd_ms': 0, 'ln_rmssd': 0}

# RR间期
rr_intervals = np.diff(peaks) / self.fps * 1000 # ms

# SDNN
sdnn = np.std(rr_intervals, ddof=1)

# RMSSD
rr_diff = np.diff(rr_intervals)
rmssd = np.sqrt(np.mean(rr_diff**2))

return {
'sdnn_ms': float(sdnn),
'rmssd_ms': float(rmssd),
'ln_rmssd': float(np.log(rmssd + 1e-6)),
'rr_intervals': rr_intervals.tolist(),
'n_beats': len(peaks)
}


# 测试
if __name__ == "__main__":
pipeline = vPPGPipeline(fps=30)

# 模拟视频帧序列 (60秒)
T = 30 * 60 # 1800帧
frames = np.random.randint(100, 200, (T, 64, 64, 3)).astype(np.float32)

# 注入心率信号(72 BPM = 1.2 Hz)
t = np.arange(T) / 30
pulse = 2 * np.sin(2 * np.pi * 1.2 * t)
frames[:, 20:44, 20:44, 1] += pulse * 5 # 绿色通道注入脉冲

# 提取PPG信号
ppg = pipeline.extract_ppg_signal(frames)

# 估计心率
hr_result = pipeline.estimate_heart_rate(ppg)
print(f"估计心率: {hr_result['heart_rate_bpm']:.1f} BPM")
print(f"置信度: {hr_result['confidence']:.2f}")

# 估计HRV
hrv_result = pipeline.estimate_hrv(ppg)
print(f"SDNN: {hrv_result['sdnn_ms']:.1f} ms")
print(f"RMSSD: {hrv_result['rmssd_ms']:.1f} ms")
print(f"ln(RMSSD): {hrv_result['ln_rmssd']:.3f}")
print(f"检测到 {hrv_result['n_beats']} 个心跳")

深度学习增强的rPPG

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
class DeepRPPGModel(nn.Module):
"""
深度学习增强的rPPG模型

超越传统信号处理方法,使用时空网络学习面部肤色变化模式

架构:
1. 面部时空特征提取(3D-CNN + LSTM)
2. 脉冲信号回归
3. 心率/HRV估计
"""

def __init__(self, seq_len: int = 150): # 5秒@30fps
super().__init__()

# 空间特征提取(每个帧的面部特征)
self.spatial_encoder = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1))
)

# 时序建模
self.temporal_model = nn.LSTM(
input_size=64,
hidden_size=128,
num_layers=2,
batch_first=True,
bidirectional=False
)

# 脉冲信号回归头
self.pulse_head = nn.Sequential(
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 1)
)

# 心率估计头
self.hr_head = nn.Sequential(
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.ReLU() # 心率>0
)

def forward(self, frames: torch.Tensor) -> dict:
"""
Args:
frames: (B, T, C, H, W) 面部视频序列
Returns:
{'pulse': (B, T), 'heart_rate': (B,)}
"""
B, T, C, H, W = frames.shape

# 空间编码
frames_flat = frames.view(B * T, C, H, W)
spatial_feat = self.spatial_encoder(frames_flat)
spatial_feat = spatial_feat.view(B, T, -1)

# 时序建模
temporal_feat, _ = self.temporal_model(spatial_feat)

# 脉冲信号
pulse = self.pulse_head(temporal_feat).squeeze(-1) # (B, T)

# 心率
hr_feat = temporal_feat.mean(dim=1)
heart_rate = self.hr_head(hr_feat).squeeze(-1) # (B,)

return {'pulse': pulse, 'heart_rate': heart_rate}

在DMS中的应用价值

认知负荷与压力评估

HRV指标 正常范围 认知负荷/压力表现 DMS应用
SDNN 30-150 ms 降低 → 压力/疲劳 驾驶员状态评估
RMSSD 20-100 ms 降低 → 自主神经失衡 疲劳早期预警
LF/HF比 0.5-3.0 升高 → 交感激活 压力检测

座舱部署方案

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
class CabinRPPGSystem:
"""
座舱rPPG系统

部署方案:
- 传感器:DMS红外摄像头(已有)
- 处理:边缘NPU(QCS8255 Hexagon)
- 模型:轻量3D-CNN + LSTM
- 推理:实时(<10ms/帧)

优势:
- 零新增硬件成本(复用DMS摄像头)
- 非接触式(无需佩戴设备)
- 实时连续监测

挑战:
- 光照变化(红外/可见光切换)
- 运动伪迹(头部运动)
- 肤色差异(深色皮肤SNR低)
"""

def __init__(self):
self.pipeline = vPPGPipeline(fps=30)
self.baseline_hrv = None
self.window_sec = 60 # 1分钟窗口

def process_frame_batch(self, frames: np.ndarray) -> dict:
"""
处理一批帧,返回心率/HRV评估

Args:
frames: (T, H, W, 3) 视频帧
Returns:
状态评估结果
"""
# 提取PPG信号
ppg = self.pipeline.extract_ppg_signal(frames)

# 心率估计
hr = self.pipeline.estimate_heart_rate(ppg)

# HRV估计(需要至少1分钟数据)
hrv = self.pipeline.estimate_hrv(ppg)

# 状态评估
assessment = self.assess_driver_state(hr, hrv)

return {
'heart_rate': hr['heart_rate_bpm'],
'hr_confidence': hr['confidence'],
'sdnn': hrv['sdnn_ms'],
'rmssd': hrv['rmssd_ms'],
**assessment
}

def assess_driver_state(self, hr: dict, hrv: dict) -> dict:
"""评估驾驶员状态"""
if hr['confidence'] < 0.3:
return {'state': 'unknown', 'confidence': 0}

hr_bpm = hr['heart_rate_bpm']
sdnn = hrv.get('sdnn_ms', 0)
rmssd = hrv.get('rmssd_ms', 0)

# 疲劳检测:HRV降低 + 心率降低
if sdnn < 20 and hr_bpm < 60:
return {'state': 'fatigued', 'confidence': 0.7,
'indicators': {'sdnn': sdnn, 'hr': hr_bpm}}

# 压力检测:HRV降低 + 心率升高
if sdnn < 25 and hr_bpm > 90:
return {'state': 'stressed', 'confidence': 0.6,
'indicators': {'sdnn': sdnn, 'hr': hr_bpm}}

# 正常
return {'state': 'normal', 'confidence': 0.8,
'indicators': {'sdnn': sdnn, 'hr': hr_bpm}}


# 测试
if __name__ == "__main__":
system = CabinRPPGSystem()

# 模拟正常驾驶
T = 1800
normal_frames = np.random.randint(100, 200, (T, 64, 64, 3)).astype(np.float32)
t = np.arange(T) / 30
normal_frames[:, 20:44, 20:44, 1] += 3 * np.sin(2*np.pi*1.0*t) # 60 BPM
result = system.process_frame_batch(normal_frames)
print("正常驾驶:", result)

# 模拟疲劳驾驶(低HRV + 低HR)
fatigued_frames = np.random.randint(100, 200, (T, 64, 64, 3)).astype(np.float32)
fatigued_frames[:, 20:44, 20:44, 1] += 2 * np.sin(2*np.pi*0.9*t) # 54 BPM
result = system.process_frame_batch(fatigued_frames)
print("疲劳驾驶:", result)

论文验证结果详解

实验设计

参数 值
受试者 35名健康志愿者(17女)
年龄 中位25岁(20-42岁)
录制时长 1分钟/人
设备 智能手机 + 三脚架 + LED灯
参考设备 1导联胸ECG
分析指标 SDNN, lnRMSSD
受试者姿势 坐位,头部相对静止

关键结果

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
# 论文结果复现
results = {
'SDNN': {
'median_abs_error_ms': 3.0,
'relative_error_pct': 7.1,
'rmse_ms': 4.5,
'rmse_relative_pct': 15.7,
'pearson_r': 0.98,
'p_value': '<0.001',
'systematic_bias': 'slight overestimation, especially at low values'
},
'lnRMSSD': {
'median_abs_error': 0.20,
'relative_error_pct': 5.6,
'rmse': 0.31,
'rmse_relative_pct': 9.9,
'pearson_r': 0.88,
'p_value': '<0.001',
'systematic_bias': 'slight overestimation'
}
}

print("=== vPPG HRV 验证结果 ===")
for metric, vals in results.items():
print(f"\n{metric}:")
print(f" 中位绝对误差: {vals['median_abs_error_ms' if 'ms' in vals else 'median_abs_error']} ({vals['relative_error_pct']}%)")
print(f" RMSE: {vals['rmse_ms' if 'ms' in vals else 'rmse']} ({vals['rmse_relative_pct']}%)")
print(f" Pearson r: {vals['pearson_r']} (p{vals['p_value']})")
print(f" 系统偏差: {vals['systematic_bias']}")

局限性

局限 对DMS影响 缓解方案
仅年轻白人受试者 肤色差异影响SNR 多肤色训练数据
头部相对静止 座舱中头部会移动 运动补偿算法
1分钟录制 实时DMS需要短窗口 滑动窗口估计
智能手机LED照明 座舱用红外补光 IR波段rPPG
系统性高估 可能低估疲劳程度 校准曲线修正

行业对比

rPPG方案 精度(SDNN误差) 实时性 座舱可行性
vPPG (本论文) 3.0ms (7.1%) 离线 ⚠️ 需适配
DeepPhy ~5ms 实时 ✅
TS-CAN ~4ms 实时 ✅
Physformer ~3.5ms 实时 ✅
Teladoc SoloVitals 未知 实时 医疗级

IMS开发启示

1. rPPG在DMS中的技术路线

阶段 目标 技术 周期
Phase 1 基础rPPG原型 绿通道+带通滤波 2月
Phase 2 运动补偿 面部关键点追踪+去伪迹 3月
Phase 3 深度学习增强 TS-CAN/Physformer 4月
Phase 4 HRV+认知负荷 HRV→认知分心映射 3月

2. 与SensAI的互补关系

vPPG/rPPG 是 Fraunhofer SensAI 多模态认知分心检测的关键模态之一。rPPG提供心率/HRV信号,补充了摄像头无法直接获取的生理指标。

3. 部署建议

配置 硬件 精度 成本
可见光rPPG DMS RGB摄像头 中 $0(复用)
红外rPPG DMS IR摄像头 中低 $0(复用)
多光谱rPPG 双光谱(IR+RGB) 高 $5(额外IR LED)

参考资源


总结: vPPG技术已接近ECG级HRV精度(r=0.98 for SDNN)。在DMS中,rPPG可以作为认知负荷和疲劳评估的重要补充模态——零硬件成本、非接触式、实时连续。主要挑战是运动伪迹和肤色差异,需要深度学习方法来提升鲁棒性。结合SensAI的多模态方案,rPPG+视觉+车辆数据的融合将显著提升认知分心检测的可靠性。