车辆运动学预测视觉负荷指数:无需眼动仪的驾驶员分心检测新范式

论文: Cross-Source Prediction of a Visual Load Index from Vehicle Kinematic Features: A Driver-Independent Validation Study
期刊: Sensors, Vol. 26, Issue 17, Article 5521
发表日期: 2026年8月30日
链接: https://www.mdpi.com/1424-8220/26/17/5521


论文信息

项目 内容
标题 Cross-Source Prediction of a Visual Load Index from Vehicle Kinematic Features: A Driver-Independent Validation Study
期刊 Sensors (MDPI), 2026, 26(17), 5521
DOI 10.3390/s26175521
关键词 visual load index, vehicle kinematics, driver monitoring, cross-source prediction, eye tracking

核心创新

本文提出了一种跨源预测框架:使用眼动追踪数据构建视觉负荷指数(VLI)代理标签,但在实际推理时仅依赖车辆运动学特征(方向盘角度、车速、加速度等)预测驾驶员的视觉负荷,无需部署眼动仪。这是DMS领域首次系统验证车辆运动学作为分心检测互补传感通道的可行性。

核心问题: 量产车中持续眼动追踪部署困难,且眼动派生代理标签存在同源循环性和道路类型混淆问题。

方法详解

1. 问题定义

传统DMS分心检测依赖眼动追踪(视线偏离道路),但存在两个根本问题:

  • 同源循环性: 使用眼动特征训练的模型,推理时仍需眼动输入
  • 道路类型混淆: 弯道驾驶时自然视线偏移被误判为分心

2. 跨源框架架构

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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
"""
跨源视觉负荷指数(VLI)预测框架
论文:Sensors 2026, 26(17), 5521
核心思路:训练用眼动数据,推理用车辆运动学
"""

import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import GroupKFold
from sklearn.metrics import mean_squared_error, r2_score
from scipy.signal import savgol_filter

class VisualLoadIndexPredictor:
"""
跨源VLI预测器

训练阶段:使用眼动特征计算VLI代理标签
推理阶段:仅使用车辆运动学特征预测VLI
"""

def __init__(self, config: dict):
super().__init__()
# 车辆运动学特征
self.kinematic_features = config.get('kinematic_features', [
'steering_angle', # 方向盘转角
'steering_velocity', # 方向盘角速度
'vehicle_speed', # 车速
'lateral_accel', # 横向加速度
'longitudinal_accel', # 纵向加速度
'yaw_rate', # 横摆角速度
'lane_distance', # 车道偏离距离
'time_since_departure' # 出发后时长
])

# VLI计算参数
self.gaze_threshold = config.get('gaze_threshold', 0.3) # 视线偏离阈值
self.window_sec = config.get('window_sec', 10) # 滑动窗口
self.fps = config.get('fps', 30) # 采样率

# 模型
self.model = None
self.architectures = ['GBDT', 'RandomForest', 'XGBoost', 'Linear', 'MLP']

def compute_vli_proxy(self, gaze_data: np.ndarray) -> np.ndarray:
"""
从眼动数据计算VLI代理标签

Args:
gaze_data: 眼动数据, shape=(N, 4)
columns: [gaze_x, gaze_y, pupil_diameter, blink_rate]

Returns:
vli: 视觉负荷指数, shape=(N-window,)
0.0 = 低负荷(视线在路上)
1.0 = 高负荷(视线偏离道路)

公式(论文Section 2.2):
VLI(t) = mean(|gaze_offset(t)| > threshold) over window
+ α * (1 - gaze_stability(t))
+ β * blink_rate_anomaly(t)
"""
window = int(self.window_sec * self.fps)
vli_values = []

for i in range(len(gaze_data) - window):
w = gaze_data[i:i+window]

# 视线偏离度
gaze_offset = np.sqrt(w[:, 0]**2 + w[:, 1]**2)
off_road_ratio = np.mean(gaze_offset > self.gaze_threshold)

# 注视稳定性(分散度)
gaze_stability = 1.0 - np.std(gaze_offset) / (np.mean(gaze_offset) + 1e-6)

# 眨眼异常
blink_anomaly = np.abs(w[:, 3] - np.median(w[:, 3])) / (np.median(w[:, 3]) + 1e-6)
blink_score = np.mean(blink_anomaly)

# 加权融合
alpha = 0.3
beta = 0.2
vli = off_road_ratio + alpha * (1 - gaze_stability) + beta * blink_score
vli = np.clip(vli, 0.0, 1.0)

vli_values.append(vli)

return np.array(vli_values)

def extract_kinematic_features(self, can_data: np.ndarray) -> np.ndarray:
"""
从CAN总线数据提取运动学特征

Args:
can_data: CAN总线数据, shape=(N, 8)
columns: [steering_angle, speed, accel_x, accel_y,
yaw_rate, lane_dist, brake, throttle]

Returns:
features: 运动学特征矩阵, shape=(N, M)
"""
features = np.zeros((len(can_data), 12))

# 基础特征
features[:, 0] = can_data[:, 0] # 方向盘转角
features[:, 1] = can_data[:, 1] # 车速
features[:, 2] = can_data[:, 2] # 横向加速度
features[:, 3] = can_data[:, 3] # 纵向加速度
features[:, 4] = can_data[:, 4] # 横摆角速度
features[:, 5] = can_data[:, 5] # 车道偏离

# 派生特征
features[:, 6] = np.gradient(can_data[:, 0]) # 方向盘角速度
features[:, 7] = np.gradient(can_data[:, 1]) # 加速度变化率
features[:, 8] = savgol_filter(can_data[:, 2], 11, 2) # 平滑横向加速
features[:, 9] = can_data[:, 0] * can_data[:, 1] # 转向×速度交互
features[:, 10] = np.abs(can_data[:, 4]) * can_data[:, 1] # 横摆×速度
features[:, 11] = np.gradient(can_data[:, 5]) # 车道偏离速率

return features

def train_cross_source(self,
can_data: np.ndarray,
gaze_data: np.ndarray,
driver_ids: np.ndarray,
architecture: str = 'GBDT'):
"""
跨源训练:用眼动数据生成标签,用车载数据训练模型

关键:GroupKFold按驾驶员分组,确保驾驶员独立性

Args:
can_data: CAN总线数据
gaze_data: 同步眼动数据
driver_ids: 驾驶员ID(用于GroupKFold)
architecture: 模型架构
"""
# Step 1: 计算VLI代理标签
vli_labels = self.compute_vli_proxy(gaze_data)

# Step 2: 对齐时间窗口
window = int(self.window_sec * self.fps)
kinematic_features = self.extract_kinematic_features(can_data[:-window])

# Step 3: 按驾驶员分组交叉验证
gkf = GroupKFold(n_splits=5)

results = []
for train_idx, test_idx in gkf.split(kinematic_features, vli_labels,
groups=driver_ids[:-window]):
X_train, X_test = kinematic_features[train_idx], kinematic_features[test_idx]
y_train, y_test = vli_labels[train_idx], vli_labels[test_idx]

# 选择架构
if architecture == 'GBDT':
model = GradientBoostingRegressor(
n_estimators=200, max_depth=5, learning_rate=0.05,
subsample=0.8, random_state=42
)
elif architecture == 'XGBoost':
import xgboost as xgb
model = xgb.XGBRegressor(
n_estimators=200, max_depth=5, learning_rate=0.05,
subsample=0.8, random_state=42
)
elif architecture == 'RandomForest':
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor(
n_estimators=200, max_depth=8, random_state=42
)
elif architecture == 'Linear':
from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)
elif architecture == 'MLP':
from sklearn.neural_network import MLPRegressor
model = MLPRegressor(
hidden_layer_sizes=(64, 32), max_iter=500, random_state=42
)

model.fit(X_train, y_train)
y_pred = model.predict(X_test)

rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)

results.append({
'architecture': architecture,
'rmse': rmse,
'r2': r2,
'n_train': len(train_idx),
'n_test': len(test_idx)
})

self.model = model
return results

def predict_realtime(self, can_data: np.ndarray) -> float:
"""
实时推理:仅需CAN总线数据,无需眼动仪

Args:
can_data: 最近10秒CAN数据, shape=(300, 8) (30fps)

Returns:
vli_pred: 预测视觉负荷指数 [0, 1]
"""
features = self.extract_kinematic_features(can_data)
latest_feature = features[-1:].reshape(1, -1)
vli_pred = self.model.predict(latest_feature)[0]
return float(np.clip(vli_pred, 0.0, 1.0))


# ============ 实际测试 ============
if __name__ == "__main__":
import time

np.random.seed(42)

# 模拟数据:10分钟驾驶,30fps
n_samples = 10 * 60 * 30 # 18000

# CAN总线数据模拟
can_data = np.zeros((n_samples, 8))
can_data[:, 0] = np.cumsum(np.random.normal(0, 0.5, n_samples)) # 方向盘
can_data[:, 1] = 60 + 20 * np.sin(np.linspace(0, 10, n_samples)) # 车速
can_data[:, 2] = np.random.normal(0, 0.1, n_samples) # 横向加速度
can_data[:, 3] = np.random.normal(0, 0.05, n_samples) # 纵向加速度
can_data[:, 4] = np.random.normal(0, 0.02, n_samples) # 横摆角速度
can_data[:, 5] = np.random.normal(0, 0.3, n_samples) # 车道偏离

# 眼动数据模拟(训练时使用)
gaze_data = np.zeros((n_samples, 4))
gaze_data[:, 0] = np.random.normal(0, 0.2, n_samples) # gaze_x
gaze_data[:, 1] = np.random.normal(0, 0.15, n_samples) # gaze_y
gaze_data[:, 2] = 4.0 + np.random.normal(0, 0.3, n_samples) # 瞳孔直径
gaze_data[:, 3] = 15 + np.random.poisson(1, n_samples) # 眨眼率

# 驾驶员ID(10名驾驶员)
driver_ids = np.repeat(np.arange(10), n_samples // 10)

# 训练
predictor = VisualLoadIndexPredictor({'fps': 30, 'window_sec': 10})

print("=" * 60)
print("跨源VLI预测实验")
print("=" * 60)

for arch in ['GBDT', 'RandomForest', 'Linear']:
results = predictor.train_cross_source(can_data, gaze_data,
driver_ids, architecture=arch)
avg_rmse = np.mean([r['rmse'] for r in results])
avg_r2 = np.mean([r['r2'] for r in results])
print(f"\n{arch}:")
print(f" RMSE: {avg_rmse:.4f} (±{np.std([r['rmse'] for r in results]):.4f})")
print(f" R²: {avg_r2:.4f} (±{np.std([r['r2'] for r in results]):.4f})")

# 实时预测测试
test_can = can_data[:300] # 10秒数据
vli = predictor.predict_realtime(test_can)
print(f"\n实时VLI预测: {vli:.3f}")
print(f" VLI > 0.5: {'⚠️ 高视觉负荷' if vli > 0.5 else '✅ 正常'}")

实验结果

5种架构对比

架构 RMSE R² 推理延迟 备注
GBDT 0.142 0.31 <1ms 最佳整体性能
XGBoost 0.145 0.29 <1ms 与GBDT接近
RandomForest 0.148 0.27 <1ms 略低于GBDT
Linear (Ridge) 0.155 0.22 <0.1ms 简单但有效
MLP 0.150 0.25 2ms 非线性但易过拟合

关键发现

  1. 驾驶员独立泛化: GroupKFold确保训练/测试不重叠驾驶员,R²=0.31说明车辆运动学可解释约31%的视觉负荷方差
  2. 未来状态预测收敛: 预测20秒后的VLI趋于持续性基线,说明短期预测有效
  3. 方向一致性: 5种架构方向一致,支持车辆运动学作为多源DMS互补通道

IMS开发启示

1. 部署优势

特性 传统眼动DMS 车辆运动学VLI
硬件需求 IR摄像头+红外补光 CAN总线数据(零额外硬件)
隐私 面部图像 无图像,纯数值
成本 $15-30/车 ~$0
鲁棒性 受光照/眼镜影响 不受光照影响
局限 需要面部可见 仅能间接估计

2. 多源融合架构建议

3. 落地优先级

步骤 内容 工时 依赖
1 CAN数据采集器(方向盘+车速+加速度) 2天 车辆CAN矩阵
2 VLI离线训练管道(Python+scikit-learn) 3天 标注眼动数据
3 GBDT模型量化与嵌入式部署 2天 Snapdragon 8295
4 多源融合决策层集成 3天 现有DMS框架
5 Euro NCAP D-02/D-03场景验证 2天 测试车辆

4. Euro NCAP场景适配

Euro NCAP场景 VLI预期响应 验证方法
D-02 手持电话 VLI ↑(方向盘微修正减少) 模拟+实车
D-03 打字操作 VLI ↑↑(横向加速度波动增加) 模拟+实车
D-05 视线偏离>3s VLI ↑(取决于驾驶场景) 模拟
D-07 调整设备 VLI ↑(短时波动) 实车

5. 局限性与改进方向

论文局限:

  • VLI未通过独立主观/生理标准外部验证
  • 仅验证了跨源预测能力,非地面真值
  • 20秒后预测趋于基线

改进建议:

  • 融合方向盘握力传感器
  • 引路面类型分类器(弯道/直道/高速/城市)
  • 结合时间序列注意力机制(Transformer)提升长期预测

总结

这篇论文为IMS提供了零额外硬件成本的DMS互补方案。车辆运动学特征虽然不能完全替代视觉DMS,但作为多源融合的一个通道具有显著价值:

  1. 成本优势: 利用现有CAN数据,零硬件增量
  2. 隐私优势: 无图像数据,完全GDPR合规
  3. 鲁棒性: 不受光照、眼镜、口罩影响
  4. 融合价值: 与视觉DMS互补,提升整体检测准确率

建议IMS团队优先在现有测试车辆上部署VLI离线分析模块,积累数据后评估融合收益。