GazeDiT:扩散模型生成眼动训练数据——解决DMS数据稀缺的新管道

论文信息

项目 内容
标题 GazeDiT: Gaze-Accurate Diffusion Image Generation for Eye Tracking via Spatial Conditioning
arXiv 2609.17814
作者 Dongze Wu 等
提交时间 2026-09-15
关键词 扩散模型, 眼动追踪, 合成数据, 4D视线

核心创新

用扩散模型生成带有精确4D视线标签的合成眼部图像,解决眼动追踪训练数据稀缺问题。

关键挑战:扩散模型生成的合成数据需要与数值标签精确对应——文本条件的图像只看”大致一致”,但监督学习要求每张图都有精确的视线角度标签。

GazeDiT 方案

  1. 训练阶段:冻结的 SegFormer 从真实图像中提取瞳孔/虹膜几何特征
  2. 推理阶段:物理眼渲染器采样 gaze 一致的眼部几何,无需源图像
  3. 空间条件:将全局 gaze 标签 grounding 到局部几何中

性能指标

指标 数值 说明
尾部 gaze 标签误差 显著降低 比其他扩散基线好
困难场景 gaze 误差 3.05° → 2.80° 最小cohort改善8.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
import torch
import torch.nn as nn
import torch.nn.functional as F

class GazeDiT(nn.Module):
"""
GazeDiT: 基于扩散模型的眼动合成数据生成

核心思路:
1. SegFormer提取瞳孔/虹膜几何 → 空间条件
2. 扩散模型以空间条件+4D gaze标签生成眼部图像
3. 推理时用物理渲染器采样gaze一致的几何

Args:
gaze_dim: 4D (pitch, yaw, 左眼+右眼)
d_model: 模型维度
"""

def __init__(self, gaze_dim=4, d_model=256):
super().__init__()
self.gaze_dim = gaze_dim

# 冻结的SegFormer (特征提取)
self.geometry_extractor = SegFormerEncoder(
output_dim=d_model,
freeze=True
)

# Gaze条件化模块
self.gaze_conditioner = nn.Sequential(
nn.Linear(gaze_dim, d_model),
nn.SiLU(),
nn.Linear(d_model, d_model),
nn.SiLU(),
nn.Linear(d_model, d_model * 2) # scale + shift
)

# 扩散U-Net (简化版)
self.unet = DiffusionUNet(
in_channels=3,
model_channels=128,
num_res_blocks=2,
attention_resolutions=[8, 16, 32]
)

# 空间条件注入
self.spatial_cond_proj = nn.Conv2d(d_model, d_model, 1)

def forward(self, x, t, gaze_label, geometry_feat=None):
"""
Args:
x: 噪声图像 (B, 3, H, W)
t: 时间步 (B,)
gaze_label: 4D视线 (B, 4) [pitch_l, yaw_l, pitch_r, yaw_r]
geometry_feat: SegFormer提取的几何特征 (训练时用真实图像)

Returns:
noise_pred: 预测噪声 (B, 3, H, W)
"""
# Gaze条件化
gaze_emb = self.gaze_conditioner(gaze_label) # (B, 2*d_model)
scale, shift = gaze_emb.chunk(2, dim=-1)

# 空间条件
if geometry_feat is not None:
spatial_cond = self.spatial_cond_proj(geometry_feat)
else:
# 推理时用物理渲染器生成
spatial_cond = self.physical_eye_renderer(gaze_label)

# 扩散去噪
noise_pred = self.unet(x, t,
scale=scale.view(-1, 1, 1, 1),
shift=shift.view(-1, 1, 1, 1),
spatial_cond=spatial_cond)
return noise_pred

def physical_eye_renderer(self, gaze_label):
"""物理渲染器:从gaze标签采样眼部几何"""
B = gaze_label.size(0)
# 简化:生成空间条件map
# 实际实现需要3D眼球模型+相机参数
cond = torch.randn(B, 256, 16, 16, device=gaze_label.device)
return self.spatial_cond_proj(F.interpolate(cond, scale_factor=4))


class SegFormerEncoder(nn.Module):
"""冻结的SegFormer,提取瞳孔/虹膜几何"""

def __init__(self, output_dim=256, freeze=True):
super().__init__()
# 简化:实际用预训练SegFormer
self.encoder = nn.Sequential(
nn.Conv2d(3, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.Conv2d(128, output_dim, 3, stride=2, padding=1),
nn.BatchNorm2d(output_dim),
nn.ReLU(),
)

if freeze:
for p in self.parameters():
p.requires_grad = False

def forward(self, x):
return self.encoder(x)


class DiffusionUNet(nn.Module):
"""简化版扩散U-Net"""

def __init__(self, in_channels=3, model_channels=128, num_res_blocks=2,
attention_resolutions=[8, 16, 32]):
super().__init__()
self.time_embed = nn.Sequential(
nn.Linear(1, model_channels),
nn.SiLU(),
nn.Linear(model_channels, model_channels),
)

# 简化版下采样
self.down1 = self._make_block(in_channels, model_channels, num_res_blocks)
self.down2 = self._make_block(model_channels, model_channels*2, num_res_blocks)
self.down3 = self._make_block(model_channels*2, model_channels*4, num_res_blocks)

# 中间块
self.mid = self._make_block(model_channels*4, model_channels*4, num_res_blocks)

# 简化版上采样
self.up3 = self._make_block(model_channels*8, model_channels*2, num_res_blocks)
self.up2 = self._make_block(model_channels*4, model_channels, num_res_blocks)
self.up1 = self._make_block(model_channels*2, in_channels, 1)

def _make_block(self, in_ch, out_ch, n_blocks):
layers = []
for _ in range(n_blocks):
layers.extend([
nn.Conv2d(in_ch, out_ch, 3, padding=1),
nn.GroupNorm(8, out_ch),
nn.SiLU(),
])
in_ch = out_ch
return nn.Sequential(*layers)

def forward(self, x, t, scale=None, shift=None, spatial_cond=None):
# 时间嵌入
t_emb = self.time_embed(t.view(-1, 1).float())

# 下采样
h1 = self.down1(x)
h2 = self.down1(F.max_pool2d(h1, 2))
h3 = self.down2(F.max_pool2d(h2, 2))

# 中间
h = self.mid(F.max_pool2d(h3, 2))

# 上采样 + skip
h = self.up3(torch.cat([F.interpolate(h, scale_factor=2), h3], dim=1))
h = self.up2(torch.cat([F.interpolate(h, scale_factor=2), h2], dim=1))
h = self.up1(torch.cat([F.interpolate(h, scale_factor=2), h1], dim=1))

if scale is not None and shift is not None:
h = h * (1 + scale) + shift

return h


# === 数据合成管道 ===
class GazeDataSynthesis:
"""GazeDiT 数据合成管道"""

def __init__(self, model, n_samples=10000):
self.model = model
self.n_samples = n_samples

def generate_gaze_labels(self, n, strategy='uniform'):
"""生成多样化的4D gaze标签"""
if strategy == 'uniform':
# 均匀采样 pitch [-30°, 30°], yaw [-45°, 45°]
pitch = torch.uniform(-30, 30, (n, 2)) # 左右眼
yaw = torch.uniform(-45, 45, (n, 2))
return torch.cat([pitch, yaw], dim=1) # (n, 4)
elif strategy == 'hard_cases':
# 困难场景:极端角度
pitch = torch.uniform(20, 35, (n, 2)) # 大角度
yaw = torch.uniform(35, 55, (n, 2))
return torch.cat([pitch, yaw], dim=1)

def generate_images(self, gaze_labels, n_steps=50):
"""用扩散模型生成眼部图像"""
self.model.eval()
with torch.no_grad():
B = gaze_labels.size(0)
x = torch.randn(B, 3, 64, 64) # 从噪声开始

for t in reversed(range(n_steps)):
t_tensor = torch.full((B,), t, dtype=torch.float)
noise_pred = self.model(x, t_tensor, gaze_labels)

# DDPM采样
alpha = 1 - t / n_steps
x = (x - (1-alpha) * noise_pred) / alpha
if t > 0:
x += torch.sqrt(1-alpha) * torch.randn_like(x)

return x.clamp(0, 1)


# 测试
if __name__ == "__main__":
model = GazeDiT(gaze_dim=4, d_model=128)
synthesizer = GazeDataSynthesis(model, n_samples=1000)

# 生成困难场景gaze标签
labels = synthesizer.generate_gaze_labels(4, strategy='hard_cases')
print(f"Gaze标签: {labels.shape}")
print(f" pitch范围: [{labels[:, :2].min():.1f}°, {labels[:, :2].max():.1f}°]")
print(f" yaw范围: [{labels[:, 2:].min():.1f}°, {labels[:, 2:].max():.1f}°]")

# 生成图像(简化测试)
print(f"\n参数量: {sum(p.numel() for p in model.parameters() if p.requires_grad)/1e6:.2f}M")
print("模型可生成带精确gaze标签的合成眼部图像")

IMS 数据合成应用启示

当前痛点

问题 现状 影响
眼动数据采集成本高 需专业眼动仪+实验室 >¥500/被试
隐私限制 生物特征数据难共享 跨团队协作困难
困难场景稀缺 极端角度/光照样本少 模型在困难场景精度差
标注精度 人工标注误差大 训练数据噪声

GazeDiT 方案价值

与 NVIDIA 数据合成管道的关系

方案 优势 局限 与GazeDiT互补
NVIDIA Isaac Sim 全场景仿真 眼部细节精度不足 全身场景+GazeDiT眼部细节
NVIDIA Omniverse 光照物理真实 计算资源消耗大 宏观场景+GazeDiT微观
GazeDiT 眼部gaze精确 仅眼部图像 替代Isaac Sim眼部渲染
Anyverse 合成数据平台 商业授权 GazeDiT可自建管道

IMS 部署建议

优先级 任务 产出 时间
🔴 P0 部署GazeDiT生成管道 10K合成眼部图像 2周
🔴 P0 用合成数据微调DMS视线估计 困难场景精度报告 1周
🟡 P1 扩展困难场景标签策略 极端角度/暗光场景 1周
🟡 P1 与Isaac Sim集成 全管道数据合成 2周

局限性

  1. 仅眼部图像 — 不能生成完整面部,需与其他方案配合
  2. 几何多样性 — 依赖SegFormer提取的几何特征范围
  3. 域差距 — 合成→真实仍有差距,需domain adaptation
  4. 计算资源 — 扩散模型推理慢,批量生成需GPU

总结

GazeDiT 展示了扩散模型在DMS数据合成中的新范式:不是生成”看起来像”的图像,而是生成”标签精确”的图像。对 IMS 团队的核心启示:

  1. 困难场景(极端角度、暗光)精度提升8%(3.05°→2.80°)
  2. 可自建数据合成管道,减少对商业平台依赖
  3. 与NVIDIA Isaac Sim互补:Isaac Sim做全身场景,GazeDiT做眼部细节
  4. 物理渲染器+扩散模型的混合方案是合成数据新趋势

参考链接: