GazeDiT:面向视线追踪的扩散模型合成数据生成——用AI生成训练数据

论文信息

项目 内容
标题 GazeDiT: Gaze-Accurate Diffusion Image Generation for Eye Tracking via Spatial Conditioning
arXiv 2609.17814
提交时间 2026-09-15
作者 Dongze Wu
领域 Computer Vision and Pattern Recognition (cs.CV)
核心 扩散模型生成精确标注4D视线的人眼图像

核心创新

GazeDiT解决了合成训练数据生成中的关键问题:如何确保扩散模型生成的图像与其数值标签(4D binocular gaze)之间有精确的对应关系。

一句话总结:通过物理眼渲染器+空间条件化,让扩散模型生成”看着正确方向”的人眼图像,可直接用于训练视线估计器。

问题背景

合成数据的标签精度困境

方法 标签精度 多样性 问题
3D渲染 ✅ 精确 ❌ 逼真度差 域差距大
文本条件扩散 ❌ 粗略 ✅ 高 “看着左上方”无法对应精确角度
图像条件扩散 ⚠️ 中等 ✅ 高 需要源图像,无法自由生成
GazeDiT ✅ 精确 ✅ 高 物理渲染+扩散模型结合

4D Binocular Gaze

视线追踪使用4D标签:左眼(pitch_left, yaw_left) + 右眼(pitch_right, yaw_right)。这4个数值通过微妙的眼球几何变化表现,但文本描述无法精确控制。

GazeDiT方法

架构

空间条件化

GazeDiT的核心设计是将全局4D gaze标签通过物理眼渲染器grounding到局部瞳孔/虹膜几何:

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

class PhysicalEyeRenderer:
"""
物理眼渲染器

功能:给定4D gaze标签,生成对应的瞳孔/虹膜几何

原理:
1. 3D眼球模型(球体+虹膜圆盘)
2. gaze方向 → 眼球旋转 → 瞳孔在2D投影中的位置和形状
3. 变化解剖学参数(角膜曲率、瞳孔大小、眼睑开度)
4. 变化相机参数(视角、距离)

这实现了"无需源图像的多样合成"
"""

def __init__(self, eye_radius=12e-3, iris_radius=5e-3,
pupil_radius_range=(1e-3, 4e-3)):
self.eye_radius = eye_radius # 眼球半径 ~12mm
self.iris_radius = iris_radius # 虹膜半径 ~5mm
self.pupil_range = pupil_radius_range

def gaze_to_eyeball_rotation(self, pitch: float, yaw: float) -> np.ndarray:
"""
将gaze (pitch, yaw)转换为眼球3D旋转矩阵

Args:
pitch: 俯仰角 (rad)
yaw: 偏航角 (rad)

Returns:
R: (3, 3) 旋转矩阵
"""
# Pitch绕X轴,Yaw绕Y轴
Rx = np.array([
[1, 0, 0],
[0, np.cos(pitch), -np.sin(pitch)],
[0, np.sin(pitch), np.cos(pitch)]
])
Ry = np.array([
[np.cos(yaw), 0, np.sin(yaw)],
[0, 1, 0],
[-np.sin(yaw), 0, np.cos(yaw)]
])
return Rx @ Ry

def project_pupil(self, R: np.ndarray, camera_dist: float = 0.5,
focal: float = 800, cx: float = 320,
cy: float = 240) -> Tuple[np.ndarray, float]:
"""
投影瞳孔到2D图像平面

Args:
R: 眼球旋转矩阵
camera_dist: 相机距离(m)
focal: 焦距(px)
cx, cy: 主点

Returns:
pupil_center_2d: (2,) 瞳孔中心2D坐标
pupil_radius_2d: 投影后瞳孔半径
"""
# 瞳孔在眼球坐标系中的位置(前方)
pupil_3d = np.array([0, 0, self.eye_radius])

# 旋转
pupil_rotated = R @ pupil_3d

# 平移到相机坐标系
pupil_camera = pupil_rotated + np.array([0, 0, -camera_dist])

# 透视投影
if pupil_camera[2] >= 0:
return np.array([cx, cy]), 0 # 瞳孔在相机后方

pupil_2d_x = focal * pupil_camera[0] / (-pupil_camera[2]) + cx
pupil_2d_y = focal * pupil_camera[1] / (-pupil_camera[2]) + cy
pupil_center = np.array([pupil_2d_x, pupil_2d_y])

# 投影后瞳孔半径(简化:假设正圆投影)
pupil_radius_3d = 2e-3 # 2mm
pupil_radius_2d = focal * pupil_radius_3d / (-pupil_camera[2])

return pupil_center, pupil_radius_2d

def render_geometry_map(self, pitch_l: float, yaw_l: float,
pitch_r: float, yaw_r: float,
img_size=(256, 256)) -> np.ndarray:
"""
生成空间几何条件图(用于扩散模型条件化)

Args:
pitch_l, yaw_l: 左眼gaze
pitch_r, yaw_r: 右眼gaze
img_size: 输出图像大小

Returns:
geometry_map: (H, W, C) 几何条件图
"""
H, W = img_size
cx, cy = W // 4, H // 2 # 左眼中心

# 左眼
R_l = self.gaze_to_eyeball_rotation(pitch_l, yaw_l)
pupil_l, radius_l = self.project_pupil(R_l, cx=cx, cy=cy)

# 右眼
cx_r = 3 * W // 4
R_r = self.gaze_to_eyeball_rotation(pitch_r, yaw_r)
pupil_r, radius_r = self.project_pupil(R_r, cx=cx_r, cy=cy)

# 创建几何条件图
# 通道1: 左眼瞳孔位置+半径
# 通道2: 右眼瞳孔位置+半径
# 通道3: 虹膜mask
geom_map = np.zeros((H, W, 3), dtype=np.float32)

# 绘制左眼瞳孔
Y, X = np.ogrid[:H, :W]
dist_l = np.sqrt((Y - pupil_l[1])**2 + (X - pupil_l[0])**2)
geom_map[:, :, 0] = (dist_l < radius_l).astype(np.float32)
geom_map[:, :, 1] = (dist_l < radius_l * 2.5).astype(np.float32) # 虹膜

# 绘制右眼瞳孔
dist_r = np.sqrt((Y - pupil_r[1])**2 + (X - pupil_r[0])**2)
geom_map[:, :, 0] += (dist_r < radius_r).astype(np.float32) * 0.5
geom_map[:, :, 2] = (dist_r < radius_r * 2.5).astype(np.float32) # 虹膜

return geom_map

def sample_diverse_geometry(self, pitch_l, yaw_l, pitch_r, yaw_r,
n_samples=10):
"""
通过变化解剖学参数生成多样化几何

这实现了"无需源图像的多样合成"

Args:
n_samples: 采样数量

Returns:
geometries: list of geometry maps
"""
geometries = []
for i in range(n_samples):
# 随机变化解剖学参数
pupil_size = np.random.uniform(*self.pupil_range)
eye_openness = np.random.uniform(0.6, 1.0)
camera_angle = np.random.uniform(-5, 5) # degrees

# 生成几何
geom = self.render_geometry_map(
pitch_l, yaw_l, pitch_r, yaw_r
)
geometries.append(geom)

return geometries


class GazeDiTConditioning(nn.Module):
"""
GazeDiT条件化模块

将4D gaze标签和几何条件图融合为扩散模型的条件信号
"""

def __init__(self, gaze_dim=4, geom_channels=3,
hidden_dim=256):
super().__init__()

# 4D gaze嵌入
self.gaze_embed = nn.Sequential(
nn.Linear(gaze_dim, 64),
nn.GELU(),
nn.Linear(64, 128)
)

# 几何条件图编码(简化版,实际用SegFormer)
self.geom_encoder = nn.Sequential(
nn.Conv2d(geom_channels, 32, 3, stride=2, padding=1),
nn.GELU(),
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.GELU(),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.GELU(),
nn.AdaptiveAvgPool2d(1)
)

# 融合
self.fusion = nn.Sequential(
nn.Linear(128 + 128, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, hidden_dim)
)

def forward(self, gaze_4d: torch.Tensor,
geom_map: torch.Tensor) -> torch.Tensor:
"""
Args:
gaze_4d: (B, 4) 4D binocular gaze [pitch_l, yaw_l, pitch_r, yaw_r]
geom_map: (B, 3, H, W) 几何条件图

Returns:
conditioning: (B, hidden_dim) 扩散模型条件向量
"""
# gaze嵌入
gaze_feat = self.gaze_embed(gaze_4d) # (B, 128)

# 几何编码
geom_feat = self.geom_encoder(geom_map).flatten(1) # (B, 128)

# 融合
conditioning = self.fusion(
torch.cat([gaze_feat, geom_feat], dim=-1)
)

return conditioning


# 测试
if __name__ == "__main__":
print("=" * 60)
print("GazeDiT 物理眼渲染器 + 条件化模块 测试")
print("=" * 60)

# 物理渲染器
renderer = PhysicalEyeRenderer()

# 生成几何条件图
pitch_l, yaw_l = np.radians(-5), np.radians(10)
pitch_r, yaw_r = np.radians(-5), np.radians(10)

geom = renderer.render_geometry_map(pitch_l, yaw_l, pitch_r, yaw_r)
print(f"\n几何条件图: shape={geom.shape}, 非零比例={np.mean(geom > 0):.4f}")

# 多样化采样
geoms = renderer.sample_diverse_geometry(
pitch_l, yaw_l, pitch_r, yaw_r, n_samples=5
)
print(f"多样化采样: {len(geoms)}个几何条件图")

# 条件化模块
conditioning = GazeDiTConditioning()

gaze_4d = torch.tensor([[pitch_l, yaw_l, pitch_r, yaw_r]])
geom_tensor = torch.from_numpy(geom).permute(2, 0, 1).unsqueeze(0).float()

cond = conditioning(gaze_4d, geom_tensor)
print(f"\n条件化输出: shape={cond.shape}")
print(f"参数量: {sum(p.numel() for p in conditioning.parameters()):,}")

实验结果

方法 Tail Gaze Error (°) 备注
文本条件扩散基线 8.5+ gaze标签粗略
图像条件扩散基线 5.2 需要源图像
GazeDiT ~3.1 接近真实图像上的误差
冻结视线估计器(真实图像) ~2.9 上界

下游视线追踪器提升

数据组成 困难样本误差(°) 提升
仅真实数据 3.05 baseline
真实 + GazeDiT合成 2.80 −8.2%

IMS开发启示

1. 合成数据生成管道

步骤 工具 输出
1. 采样4D gaze 随机/均匀分布 (pitch_l, yaw_l, pitch_r, yaw_r)
2. 物理渲染 PhysicalEyeRenderer 空间几何条件图
3. 扩散生成 GazeDiT 合成人眼图像
4. 验证 冻结视线估计器 gaze error检查
5. 增强训练 合成+真实混合 提升困难样本精度

2. 与NVIDIA Isaac Sim的对比

维度 GazeDiT Isaac Sim
生成对象 人眼特写 完整座舱场景
标签精度 ✅ 4D gaze精确 ✅ 全标注
多样性 中等(眼区域) 高(全场景)
计算成本 低(单图像扩散) 高(全场景渲染)
适用场景 视线估计器训练 DMS/OMS全模块训练
互补性 ✅ 可嵌入Isaac Sim —

3. 优先级排序

任务 优先级 预期收益
集成GazeDiT生成困难样本 🟡 中 困难样本误差↓8%
与Isaac Sim管道集成 🟢 低 完整合成数据管道
领域自适应优化 🟡 中 缩小合成-真实域差

总结

GazeDiT代表了合成数据生成从”好看”到”标签精确”的转变。其核心创新是通过物理眼渲染器将全局gaze标签grounding到局部几何,使扩散模型生成的图像既有高逼真度又有精确标签。

对IMS的价值: 可用于生成困难视线样本(极端角度、遮挡、光照),提升视线估计器在边缘情况下的鲁棒性。与NVIDIA Isaac Sim形成互补——GazeDiT专注眼区域高精度生成,Isaac Sim覆盖全座舱场景。

代码开源状态: 论文未明确代码发布,但方法可用上述代码框架复现。

信息来源: arXiv:2609.17814