CCGF:追踪丢失期间的因果视线恢复——驾驶监控盲区填补

论文信息

项目 内容
标题 Context-Aware Causal Gaze Forecasting for Human-Vehicle Interaction During In-Cabin Tracking Dropouts
arXiv 2609.12374
提交时间 2026-09-11
评估机构 Pith (T0 AI Review)
代码/数据 作者声明将发布数据集、评估协议和因果基线

核心问题

DMS的致命盲区

仪表盘-mounted视线追踪器在大角度头部旋转时经常丢失驾驶员视线追踪。这些时刻包括:

场景 发生频率 安全相关性
肩部检查(shoulder check) 占驾驶时间8.5% 🔴 极高(变道/汇合)
后视镜扫视 频繁 🟡 中等
交叉口扫描 高频 🔴 极高

矛盾点:追踪器丢失的时刻,恰恰是驾驶员注意力信息最有价值的时刻。

现有方法的局限

CCGF方法详解

架构

CCGF(Causal Context-Gated Forecaster)由三个核心组件构成:

数学公式

历史编码器输出 $\mathbf{h}_t$,场景编码器输出 $\mathbf{s}_t$,门控权重 $\alpha_t$:

$$\mathbf{f}_t = \alpha_t \cdot \mathbf{h}_t + (1 - \alpha_t) \cdot \mathbf{s}_t$$

$$\hat{p}_t = \text{softmax}(\mathbf{W}_d \cdot \mathbf{f}_t + \mathbf{b}_d)$$

$$\hat{g}t = \sum{i,j} (i, j) \cdot \hat{p}_t(i, j) \quad \text{(质心)}$$

其中 $\hat{p}_t \in \mathbb{R}^{24 \times 32}$ 是空间概率分布,$\hat{g}_t$ 是预测的视线位置(像素坐标)。

代码复现

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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import torch
import torch.nn as nn
from typing import Tuple

class CausalContextGatedForecaster(nn.Module):
"""
CCGF: 因果上下文门控预测器

论文: arXiv:2609.12374
核心功能: 在追踪器丢失期间,仅用过去数据预测驾驶员视线

架构:
1. 历史LSTM: 编码60帧预丢失视线+头姿
2. 场景LSTM: 编码DINOv3场景特征
3. 可靠性门控: 动态融合历史和场景
4. 空间概率解码: 24×32分布 → 质心
"""

def __init__(
self,
gaze_dim: int = 2, # 视线 (x, y) 像素坐标
head_dim: int = 6, # 头姿 (pitch, yaw, roll + velocity)
scene_dim: int = 768, # DINOv3特征维度
hidden_dim: int = 256,
grid_h: int = 24,
grid_w: int = 32,
history_len: int = 60
):
super().__init__()
self.grid_h = grid_h
self.grid_w = grid_w
self.history_len = history_len

# 历史编码器: 2层LSTM
self.history_lstm = nn.LSTM(
input_size=gaze_dim + head_dim,
hidden_size=hidden_dim,
num_layers=2,
batch_first=True,
dropout=0.1
)

# 场景编码器: 1层LSTM
self.scene_lstm = nn.LSTM(
input_size=scene_dim,
hidden_size=hidden_dim,
num_layers=1,
batch_first=True
)

# 可靠性门控
# 输入: 当前时间步、历史隐状态、场景隐状态
self.gate = nn.Sequential(
nn.Linear(hidden_dim * 2 + 1, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, 1),
nn.Sigmoid()
)

# 空间概率解码器
self.decoder = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim * 2),
nn.GELU(),
nn.Linear(hidden_dim * 2, grid_h * grid_w)
)

# 位置编码
self.register_buffer(
'grid_coords',
self._create_grid_coords(grid_h, grid_w)
)

def _create_grid_coords(self, h: int, w: int) -> torch.Tensor:
"""创建空间网格坐标"""
ys = torch.linspace(0, 1, h)
xs = torch.linspace(0, 1, w)
grid = torch.stack(torch.meshgrid(ys, xs, indexing='ij'))
return grid.reshape(2, -1).T # (h*w, 2)

def forward(
self,
gaze_history: torch.Tensor, # (B, T, 2)
head_history: torch.Tensor, # (B, T, 6)
scene_features: torch.Tensor, # (B, T, 768)
dropout_progress: float = 0.0 # 丢失进度 0-1
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
前向传播

Args:
gaze_history: 过去T帧视线坐标 (B, T, 2)
head_history: 过去T帧头姿 (B, T, 6)
scene_features: 场景DINOv3特征 (B, T, 768)
dropout_progress: 当前在丢失期间的进度 [0, 1]

Returns:
predicted_gaze: (B, 2) 预测视线位置
prob_map: (B, 24, 32) 空间概率分布
"""
B = gaze_history.shape[0]

# 历史编码
hist_input = torch.cat([gaze_history, head_history], dim=-1)
_, (h_n, _) = self.history_lstm(hist_input)
hist_hidden = h_n[-1] # (B, hidden_dim) 最后一层

# 场景编码
_, (s_n, _) = self.scene_lstm(scene_features)
scene_hidden = s_n[-1] # (B, hidden_dim)

# 可靠性门控
progress = torch.tensor(dropout_progress,
device=gaze_history.device).expand(B, 1)
gate_input = torch.cat([hist_hidden, scene_hidden, progress], dim=-1)
alpha = self.gate(gate_input) # (B, 1)

# 融合
fused = alpha * hist_hidden + (1 - alpha) * scene_hidden

# 解码为空间概率分布
logits = self.decoder(fused) # (B, h*w)
prob_map = torch.softmax(logits, dim=-1) # (B, h*w)
prob_map = prob_map.reshape(B, self.grid_h, self.grid_w)

# 质心 = 预测视线位置
grid = self.grid_coords.to(gaze_history.device) # (h*w, 2)
prob_flat = prob_map.reshape(B, -1) # (B, h*w)
predicted_gaze = (prob_flat.unsqueeze(-1) * grid).sum(dim=1) # (B, 2)

return predicted_gaze, prob_map


class DropoutRecoverySystem:
"""
CCGF在线丢失恢复系统

集成到DMS中的完整pipeline:
1. 检测追踪器丢失
2. 启动CCGF预测
3. 追踪器恢复后平滑切换
"""

def __init__(self, history_len=60, max_dropout_len=120):
self.forecaster = CausalContextGatedForecaster(history_len=history_len)
self.history_len = history_len
self.max_dropout_len = max_dropout_len

# 历史缓冲区
self.gaze_buffer = []
self.head_buffer = []
self.scene_buffer = []

# 状态
self.is_dropout = False
self.dropout_frame = 0

def update(self, gaze, head_pose, scene_feat, tracker_status):
"""
每帧调用

Args:
gaze: (2,) 视线坐标, None if tracker lost
head_pose: (6,) 头姿
scene_feat: (768,) 场景特征
tracker_status: 'tracking' or 'lost'

Returns:
predicted_gaze: (2,) 视线坐标(真实或预测)
is_predicted: bool 是否为预测值
"""
if tracker_status == 'tracking':
# 正常追踪:更新缓冲区
self.gaze_buffer.append(gaze.copy())
self.head_buffer.append(head_pose.copy())
self.scene_buffer.append(scene_feat.copy())

# 保持窗口大小
if len(self.gaze_buffer) > self.history_len:
self.gaze_buffer.pop(0)
self.head_buffer.pop(0)
self.scene_buffer.pop(0)

self.is_dropout = False
self.dropout_frame = 0
return gaze, False

else:
# 追踪丢失:启动CCGF预测
if not self.is_dropout:
self.is_dropout = True
self.dropout_frame = 0

self.dropout_frame += 1

# 需要足够的历史
if len(self.gaze_buffer) < self.history_len:
return None, False

# 准备输入
gaze_hist = torch.tensor(
np.array(self.gaze_buffer)
).unsqueeze(0).float()
head_hist = torch.tensor(
np.array(self.head_buffer)
).unsqueeze(0).float()
scene_hist = torch.tensor(
np.array(self.scene_buffer)
).unsqueeze(0).float()

# 预测
progress = min(self.dropout_frame / self.max_dropout_len, 1.0)
with torch.no_grad():
pred_gaze, prob_map = self.forecaster(
gaze_hist, head_hist, scene_hist,
dropout_progress=progress
)

return pred_gaze[0].numpy(), True


import numpy as np

# 测试
if __name__ == "__main__":
print("=" * 60)
print("CCGF 因果视线恢复 - 测试")
print("=" * 60)

# 模型初始化
model = CausalContextGatedForecaster()

total_params = sum(p.numel() for p in model.parameters())
print(f"\n模型参数量: {total_params:,}")

# 模拟输入
B, T = 1, 60
gaze_hist = torch.randn(B, T, 2)
head_hist = torch.randn(B, T, 6)
scene_feat = torch.randn(B, T, 768)

# 预测
pred_gaze, prob_map = model(gaze_hist, head_hist, scene_feat, 0.3)

print(f"\n输入:")
print(f" 视线历史: {gaze_hist.shape}")
print(f" 头姿历史: {head_hist.shape}")
print(f" 场景特征: {scene_feat.shape}")
print(f"\n输出:")
print(f" 预测视线: {pred_gaze.shape} → {pred_gaze[0].detach().numpy()}")
print(f" 概率分布: {prob_map.shape}")
print(f" 概率和: {prob_map[0].sum().item():.4f}")

# 完整系统测试
print(f"\n{'='*60}")
print("在线丢失恢复系统测试")
print(f"{'='*60}")

system = DropoutRecoverySystem(history_len=60)

# 填充历史
for i in range(60):
gaze = np.array([0.5 + 0.01*i, 0.5])
head = np.random.randn(6)
scene = np.random.randn(768)
system.update(gaze, head, scene, 'tracking')

print(f"历史缓冲区已填充: {len(system.gaze_buffer)}帧")

# 模拟追踪丢失
print(f"\n模拟追踪丢失(5帧):")
print(f"{'帧':>4} | {'x':>8} | {'y':>8} | {'预测?':>6}")
print("-" * 36)

for i in range(5):
gaze = system.update(
None, np.random.randn(6), np.random.randn(768), 'lost'
)
if gaze[0] is not None:
x, y = gaze[0]
print(f"{i:4d} | {x:8.4f} | {y:8.4f} | {'是' if gaze[1] else '否':>6}")

实验结果

数据集

项目 数值
自然驾驶时长 10.5小时
驾驶员数量 10人
丢失事件数 2,047个
丢失时间占比 8.5%
评估方式 Leave-one-driver-out
参考标注 头戴式Pupil Labs Neon眼动仪

性能对比

方法 类型 中位误差(px) 中位误差(°) vs 历史基线
历史only LSTM 因果 263.9 ~15.9 —
CCGF (Frozen场景) 因果 210.8 12.9 −20%
CCGF (Live场景) 因果 175.7 10.5 −33%
非因果插值 非因果 ~180 ~10.8 —

关键发现:CCGF的因果方法甚至超越了一些使用未来数据的非因果方法。

门控行为分析

丢失阶段 α权重 含义
0-0.1s α≈0.8 主要依赖历史(近期历史可靠)
0.1-0.5s α≈0.5 历史和场景均衡
0.5-1.0s α≈0.3 主要依赖场景(历史衰减)
>1.0s α≈0.2 场景主导(历史几乎无用)

IMS开发启示

1. 填补8.5%监控盲区

场景 当前DMS CCGF集成后
肩部检查 视线丢失,无法判定分心 预测视线位置,持续监控
后视镜扫视 视线丢失 预测是否在看后视镜
交叉口扫描 视线丢失 判断是否扫描交通

2. 部署考量

因素 分析
场景特征来源 需要egocentric场景摄像头(前挡风玻璃方向),DMS系统通常已有
DINOv3推理 可用轻量版(ViT-S/B),边缘可部署
LSTM推理 极轻量,<1ms/帧
历史窗口 60帧@30fps = 2秒,合理
最大预测时长 实测>1秒仍有用,建议<2秒切换保守策略

3. 与EyeTAG的协同

EyeTAG解决”正常追踪时的抖动和偏差”,CCGF解决”追踪丢失时的盲区”:

组件 覆盖时段 解决问题
EyeTAG 追踪正常时(91.5%) 抖动、扫视偏差
CCGF 追踪丢失时(8.5%) 视线空白、监控盲区
组合 100% 完整覆盖

4. 安全分析应用

论文提供的Frozen场景条件(210.8px)可用作安全分析的下界:

1
2
3
4
5
安全场景:
- 追踪正常: 使用真实视线,精度高
- 追踪丢失 <0.5s: CCGF Live预测,精度中等
- 追踪丢失 0.5-1.0s: CCGF Frozen预测,精度下界
- 追踪丢失 >1.0s: 触发保守策略(要求驾驶员重新建立追踪)

总结

CCGF解决了一个被长期忽视的问题:DMS追踪器在关键时刻丢失时的视线空白。方法严格因果(只用过去数据),在线可用,且性能超越非因果方法。

核心数据:

  • 填补8.5%驾驶时间的视线监控盲区
  • 比历史基线减少33%误差
  • 2,047个真实丢失事件验证
  • 10人leave-one-driver-out严格评估

IMS落地建议: 与EyeTAG组合使用,形成”正常追踪+丢失恢复”的完整视线监控方案。优先在肩部检查和交叉口场景中验证。