AttentiveGaze:不确定性感知的视线估计如何让DMS知道”自己看不准”

论文信息

项目 内容
标题 AttentiveGaze: an uncertainty-aware multimodal feature fusion for robust gaze estimation
期刊 Multimedia Tools and Applications
时间 2026年10月
作者 Choksy, Patel, Chowdhury, Pachade, Puar (Akeso Eyecare + EyelignAI)
来源 bioengineer.org (2026-10-04)

核心创新

AttentiveGaze提出三个关键创新:

  1. 自适应多模态融合 — 眼部纹理、全脸上下文、头部姿态三个模态通过可学习门控+跨模态注意力动态加权
  2. 不确定性输出 — 每个视线预测同时输出方差,告知系统”我有多大把握”
  3. 实时紧凑架构 — 设计为实时运行,适配车载DMS延迟要求

一句话总结: DMS不仅要看得准,还要知道自己什么时候看不准——不确定性输出让DMS能”知错就改”。

为什么不确定性对DMS至关重要

当前DMS的致命缺陷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 当前DMS的隐含假设问题
current_dms_problem = {
"假设": "模型输出的视线方向总是可信的",
"现实": "光照突变/遮挡/极端头部姿态时模型悄悄退化",
"后果": "DMS误认为驾驶员在看前方(实际偏离)→ 漏报分心",
"风险": "比没有DMS更危险——制造虚假安全感"
}

# AttentiveGaze的解决方案
attentive_gaze_solution = {
"核心": "模型输出(gaze_x, gaze_y, uncertainty)",
"策略": "uncertainty高时→降级到保守策略",
"示例": "驾驶员戴墨镜→uncertainty↑→触发物理备份(方向盘操作监控)"
}

不确定性在DMS中的应用场景

场景 当前DMS AttentiveGaze DMS
正常驾驶 视线准确 ✅ 视线准确+低不确定性
戴墨镜 视线可能错误,无提醒 ⚠️ 视线不确定→降级策略
强光逆光 悄悄退化 ⚠️ 不确定性↑→切换红外
极端头转 悄悄退化 ⚠️ 不确定性↑→保守判断
手挡眼睛 悄悄退化 ⚠️ 不确定性↑→行为备份

方法详解

1. 整体架构

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
import torch
import torch.nn as nn

class AttentiveEyeEncoder(nn.Module):
"""
AttentiveGaze眼部特征编码器

创新:空间注意力+通道注意力双重加权

作用:在眼镜反光/遮挡时自动降权受损区域
"""

def __init__(self, in_channels=3, d_model=128):
super().__init__()

# 基础CNN特征
self.backbone = nn.Sequential(
nn.Conv2d(in_channels, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.GELU(),
nn.MaxPool2d(2), # /2

nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.GELU(),
nn.MaxPool2d(2), # /4

nn.Conv2d(64, d_model, 3, padding=1),
nn.BatchNorm2d(d_model),
nn.GELU(),
nn.AdaptiveAvgPool2d((4, 4)) # 固定输出大小
)

# 空间注意力(哪些区域重要)
self.spatial_attn = nn.Sequential(
nn.Conv2d(d_model, d_model // 8, 1),
nn.GELU(),
nn.Conv2d(d_model // 8, 1, 1),
nn.Sigmoid()
)

# 通道注意力(哪些特征通道重要)
self.channel_attn = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(d_model, d_model // 8),
nn.GELU(),
nn.Linear(d_model // 8, d_model),
nn.Sigmoid()
)

# 展平
self.flatten = nn.Flatten()
self.proj = nn.Linear(d_model * 4 * 4, d_model)

def forward(self, eye_image):
"""
Args:
eye_image: (B, 3, H, W) 眼部裁剪图

Returns:
features: (B, d_model) 注意力增强的眼部特征
"""
# CNN特征
feat = self.backbone(eye_image) # (B, d, 4, 4)

# 空间注意力
spatial_w = self.spatial_attn(feat) # (B, 1, 4, 4)
feat = feat * spatial_w

# 通道注意力
channel_w = self.channel_attn(feat) # (B, d)
feat = feat * channel_w.unsqueeze(-1).unsqueeze(-1)

# 展平+投影
feat = self.flatten(feat) # (B, d*4*4)
feat = self.proj(feat) # (B, d_model)

return feat, spatial_w # 返回注意力图用于可视化


class CrossModalFusion(nn.Module):
"""
跨模态注意力融合

眼部、全脸、头姿三个模态互相查询
可学习门控决定最终融合权重
"""

def __init__(self, d_model=128, n_heads=4):
super().__init__()

# 跨模态注意力
self.eye_to_face = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
self.eye_to_head = nn.MultiheadAttention(d_model, n_heads, batch_first=True)

# 可学习门控
self.gate = nn.Sequential(
nn.Linear(d_model * 3, d_model),
nn.GELU(),
nn.Linear(d_model, 3),
nn.Softmax(dim=-1)
)

def forward(self, eye_feat, face_feat, head_feat):
"""
Returns:
fused: (B, d_model) 融合特征
weights: (B, 3) 各模态权重
"""
# 跨模态查询
eye_enhanced = eye_feat.unsqueeze(1)
face_enhanced = face_feat.unsqueeze(1)
head_enhanced = head_feat.unsqueeze(1)

# 眼部查询全脸
eye_from_face, _ = self.eye_to_face(
eye_enhanced, face_enhanced, face_enhanced
)

# 眼部查询头姿
eye_from_head, _ = self.eye_to_head(
eye_enhanced, head_enhanced, head_feat.unsqueeze(1)
)

# 门控融合
concat = torch.cat([
eye_feat,
eye_from_face.squeeze(1),
eye_from_head.squeeze(1)
], dim=-1)

weights = self.gate(concat) # (B, 3)

fused = (
weights[:, 0:1] * eye_feat +
weights[:, 1:2] * eye_from_face.squeeze(1) +
weights[:, 2:3] * eye_from_head.squeeze(1)
)

return fused, weights


class UncertaintyGazeHead(nn.Module):
"""
不确定性感知视线回归头

输出:视线方向(pitch, yaw) + 不确定性(variance)

基于Nix & Weigend的异方差回归
"""

def __init__(self, d_model=128, n_heads=4):
super().__init__()

# 多头嵌入
self.multi_head = nn.ModuleList([
nn.Linear(d_model, d_model // n_heads) for _ in range(n_heads)
])

# 视线回归
self.gaze_regressor = nn.Linear(d_model, 2) # pitch, yaw

# 不确定性回归
self.uncertainty_regressor = nn.Sequential(
nn.Linear(d_model, d_model // 2),
nn.GELU(),
nn.Linear(d_model // 2, 2), # 每个维度独立方差
nn.Softplus() # 确保正值
)

def forward(self, fused_feat):
"""
Returns:
gaze: (B, 2) pitch, yaw in radians
variance: (B, 2) per-dimension uncertainty
"""
# 多头嵌入
head_outputs = []
for head in self.multi_head:
head_outputs.append(head(fused_feat))
multi_embed = torch.cat(head_outputs, dim=-1)

# 视线
gaze = self.gaze_regressor(fused_feat)

# 不确定性
variance = self.uncertainty_regressor(fused_feat)

return gaze, variance


class AttentiveGaze(nn.Module):
"""AttentiveGaze完整模型"""

def __init__(self):
super().__init__()
self.eye_encoder = AttentiveEyeEncoder()
self.face_encoder = nn.Sequential(
nn.Conv2d(3, 32, 3, stride=2, padding=1),
nn.GELU(),
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.GELU(),
nn.AdaptiveAvgPool2d((4, 4)),
nn.Flatten(),
nn.Linear(64 * 4 * 4, 128)
)
self.head_pose_encoder = nn.Linear(6, 128) # 6DoF头姿
self.fusion = CrossModalFusion()
self.gaze_head = UncertaintyGazeHead()

def forward(self, eye_img, face_img, head_pose):
eye_feat, attn_map = self.eye_encoder(eye_img)
face_feat = self.face_encoder(face_img)
head_feat = self.head_pose_encoder(head_pose)

fused, weights = self.fusion(eye_feat, face_feat, head_feat)
gaze, variance = self.gaze_head(fused)

return {
'gaze': gaze, # (B, 2) 视线方向
'uncertainty': variance, # (B, 2) 不确定性
'fusion_weights': weights, # (B, 3) 模态权重
'attention_map': attn_map # (B, 1, H, W) 空间注意力
}


# 测试
if __name__ == "__main__":
model = AttentiveGaze()

# 模拟输入
B = 2
eye_img = torch.randn(B, 3, 64, 32) # 眼部裁剪 64x32
face_img = torch.randn(B, 3, 224, 224) # 全脸 224x224
head_pose = torch.randn(B, 6) # 6DoF头姿

output = model(eye_img, face_img, head_pose)

print("AttentiveGaze 输出:")
print(f" 视线方向: {output['gaze'].shape} (pitch, yaw)")
print(f" 不确定性: {output['uncertainty'].shape} (每维度方差)")
print(f" 融合权重: {output['fusion_weights'].shape} (眼/脸/头)")
print(f" 注意力图: {output['attention_map'].shape}")

print(f"\n示例输出:")
for i in range(B):
g = output['gaze'][i].detach().numpy()
u = output['uncertainty'][i].detach().numpy()
w = output['fusion_weights'][i].detach().numpy()
print(f" 样本{i}: gaze=({g[0]:.2f}°, {g[1]:.2f}°) "
f"unc=({u[0]:.3f}, {u[1]:.3f}) "
f"weights=(eye:{w[0]:.2f}, face:{w[1]:.2f}, head:{w[2]:.2f})")

3. 不确定性损失函数

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
class UncertaintyLoss(nn.Module):
"""
异方差不确定性损失(Nix & Weigend 1994)

L = (y - μ)² / (2σ²) + log(σ²) / 2

含义:
- 第一项:预测误差被不确定性归一化(高不确定性时宽容)
- 第二项:正则化,防止所有预测都设高不确定性

效果:
- 模型对确信的预测输出低不确定性
- 对不确定的预测输出高不确定性
- 模型不会通过简单提高不确定性来"作弊"
"""

def __init__(self, beta=1.0):
super().__init__()
self.beta = beta # 不确定性正则化强度

def forward(self, pred_gaze, pred_var, target_gaze):
"""
Args:
pred_gaze: (B, 2) 预测视线
pred_var: (B, 2) 预测方差(正数)
target_gaze: (B, 2) 真实视线
"""
# 异方差回归损失
diff = (target_gaze - pred_gaze) ** 2
loss = diff / (2 * pred_var + 1e-6) + \
self.beta * torch.log(pred_var + 1e-6) / 2
return loss.mean()


# 损失函数验证
criterion = UncertaintyLoss(beta=0.5)

# 场景1:高置信预测(小误差+低不确定性)
pred1 = torch.tensor([[0.1, 0.2]])
var1 = torch.tensor([[0.01, 0.01]])
target = torch.tensor([[0.12, 0.18]])

# 场景2:低置信预测(大误差+高不确定性)
pred2 = torch.tensor([[0.5, 0.6]])
var2 = torch.tensor([[0.5, 0.5]])

loss1 = criterion(pred1, var1, target)
loss2 = criterion(pred2, var2, target)
loss2_low_var = criterion(pred2, var1, target) # 大误差+低不确定性→高loss

print(f"高置信(小误差+低不确定性): loss={loss1:.4f}")
print(f"低置信(大误差+高不确定性): loss={loss2:.4f}")
print(f"作弊场景(大误差+低不确定性): loss={loss2_low_var:.4f} ← 惩罚!")
print(f"\n模型被激励:不确定时提高不确定性,但确定时必须降低")

4. 评估结果

数据集 角度误差 特点
MPIIFaceGaze 竞争性 笔记本摄像头,自然条件
EyeDiap 竞争性 Idiap研究所,RGB+深度
GazeCapture 竞争性 大规模移动设备
不确定性OOD检测 显著改善 检测遮挡/极端条件
实时性 ✅ 实时 紧凑架构

IMS开发启示

1. 不确定性DMS决策框架

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
class UncertaintyAwareDMS:
"""
不确定性感知DMS决策框架

核心原则:不确定性高时不做判断,降级到保守策略
"""

def __init__(self):
self.thresholds = {
'gaze_uncertainty_low': 0.05, # 自信
'gaze_uncertainty_high': 0.3, # 不自信
'gaze_off_road_threshold': 15.0, # 度
}

def evaluate(self, gaze, uncertainty):
"""
Args:
gaze: (pitch, yaw) in degrees
uncertainty: (var_pitch, var_yaw)

Returns:
decision: action + confidence
"""
mean_unc = np.mean(uncertainty)
gaze_off = abs(gaze[1]) > self.thresholds['gaze_off_road_threshold']

if mean_unc < self.thresholds['gaze_uncertainty_low']:
# 高置信:直接判断
if gaze_off:
return {'action': 'warn', 'level': 1, 'reason': 'gaze_off_road'}
else:
return {'action': 'normal', 'level': 0, 'reason': 'gaze_on_road'}

elif mean_unc < self.thresholds['gaze_uncertainty_high']:
# 中等置信:结合其他信号
return {'action': 'caution', 'level': 1, 'reason': 'uncertain_gaze'}

else:
# 低置信:降级策略
return {
'action': 'fallback',
'level': 2,
'reason': 'gaze_unreliable',
'fallback': 'use_steering_behavior' # 转向行为监控
}


# 测试不同场景
dms = UncertaintyAwareDMS()

scenarios = [
("正常驾驶", (2, 5), (0.02, 0.03)),
("看手机", (2, 25), (0.03, 0.05)),
("戴墨镜", (2, 20), (0.15, 0.20)),
("强光逆光", (1, 10), (0.35, 0.40)),
("手挡眼睛", (5, 15), (0.50, 0.60)),
]

print(f"{'场景':<12} {'视线':>12} {'不确定性':>12} {'决策':<20} {'原因'}")
print("-" * 75)
for name, gaze, unc in scenarios:
result = dms.evaluate(gaze, unc)
print(f"{name:<12} ({gaze[0]:>4.0f},{gaze[1]:>4.0f}°) ({unc[0]:>.2f},{unc[1]:>.2f}) {result['action']:<20} {result['reason']}")

2. 系统级架构

3. 测试场景

DMS-UNC-01 不确定性触发降级测试

前置条件:

  • AttentiveGaze已部署
  • 驾驶员正常坐姿
  • 环境光照500 lux

测试步骤:

  1. 正常驾驶30秒 → 记录基线不确定性
  2. 驾驶员戴墨镜 → 记录不确定性变化
  3. 系统应检测到不确定性↑ → 降级策略激活
  4. 验证降级策略(方向盘+车道监控)是否接管分心检测

判定条件:

场景 不确定性 系统响应 通过条件
正常 <0.05 主视线判断 分心检测正常
墨镜 0.15-0.30 降级+主视线 双重监控
手挡 >0.30 纯降级 降级策略有效

4. 开发优先级

优先级 行动 预期收益
🔴 P0 添加不确定性输出到现有视线模型 DMS自检能力
🔴 P0 实现降级策略(行为备份) 高置信失效时不失控
🟡 P1 采集遮挡/极端条件数据 不确定性校准
🟡 P1 实现跨模态注意力融合 多条件鲁棒性
🟢 P2 个性化不确定性阈值 个体差异适配

与其他DMS方案对比

方案 视线精度 不确定性输出 多模态融合 实时性
当前IMS DMS ~3-5° ❌ 无 固定权重 ✅
EyeTAG (BMVC 2026) ~2-3° ❌ 无 轨迹感知 ✅
AttentiveGaze ~3-4° ✅ 有 自适应门控 ✅
差分视线估计 ~2-3° ❌ 无 窄范围优化 ✅

结论

AttentiveGaze的核心贡献不是精度提升,而是让DMS知道自己看不准。这在安全系统中价值巨大:

  1. 不确定性是安全系统的必需品 — 不知道自己错了比错误本身更危险
  2. 自适应融合优于固定融合 — 不同条件信任不同模态
  3. 降级策略是关键 — 不确定性高时切换到行为备份
  4. 紧凑实时 — 不需要大模型也能实现

建议: 立即在现有DMS视线模型上添加不确定性输出头,实现最基本的”自检+降级”能力。


论文来源:Multimedia Tools and Applications (2026-10), Akeso Eyecare + EyelignAI