DistillGaze:从DINOv3到256K设备端模型,无需真实标注的视线追踪新范式

ECCV 2026论文解读

“Rapidly Deploying On-Device Eye Tracking by Distilling Visual Foundation Models”, ECCV 2026

论文信息

项目 内容
标题 Rapidly Deploying On-Device Eye Tracking by Distilling Visual Foundation Models
会议 ECCV 2026
关键词 knowledge distillation, eye tracking, visual foundation model, synthetic-to-real, on-device deployment
核心 DINOv3 → 86M teacher → 256K student, 无需真实视线标注

核心创新

两步蒸馏框架:将视觉基础模型(DINOv3, 86M参数)的知识转移到仅256K参数的设备端模型,完全不使用真实视线标注数据。

关键指标

指标 基线(256K) DINOv3直接(86M) DistillGaze(256K) 改善
中位角度误差 3.48° >5° 1.44° -58.6%
尾部误差(E90U90) 14.84° — 8.45° -43.1%
模型大小 256K 86M 256K 不变
真实标注需求 需要 — 不需要 突破

为什么DINOv3直接用不行?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""
问题:视觉基础模型(VFM)如DINOv3携带了视线信号
但不是可用形式

t-SNE分析发现:
- DINOv3对近眼图像的嵌入 → 按身份(identity)聚类
- 视线方向信号被身份信号压制
- gaze signal确实编码在嵌入中,但需"重塑"

实验证据:
- DINOv3 ViT-B (86M) 冻结+线性探测 → 角度误差 >5°
- 而256K纯合成训练模型 → 3.48°
- 336×参数量但更差!

结论:VFM携带信号但不是可用形式
需要域内适配而非直接使用
"""

方法:两步蒸馏

Stage 1: VFM自蒸馏

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
"""
Stage 1: VFM自蒸馏优化

输入:
1. 合成眼图像(Blender渲染,有像素级gaze标注)
2. 未标注真实IR眼图像(设备使用中被动收集)

方法:
1. 用合成标签监督DINOv3学习gaze回归
2. 同时用真实未标注图像做自蒸馏(teacher-student)
3. 伪标签:teacher对真实图像生成伪gaze标签

关键:
- 合成图像有完美gaze标注但存在synthetic-to-real gap
- 真实图像无标注但分布正确
- 自蒸馏 + 伪标签 = 弥合gap

输出:86M gaze-specialized teacher
"""

import torch
import torch.nn as nn
import torch.nn.functional as F

class VFMOptimization(nn.Module):
"""Stage 1: DINOv3域适配"""

def __init__(self, dino_backbone: nn.Module, embed_dim: int = 768):
super().__init__()
self.dino = dino_backbone # 冻结DINOv3 ViT-B
self.regressor = nn.Linear(embed_dim, 2) # yaw, pitch
self.ema_regressor = nn.Linear(embed_dim, 2) # EMA副本

def forward(self, synthetic_img, real_img=None):
"""
Args:
synthetic_img: Blender渲染眼图像(有gaze标签)
real_img: 真实IR眼图像(无标注)
Returns:
gaze_pred: 合成图像gaze预测
pseudo_gaze: 真实图像伪标签(可选)
"""
# 合成图像监督
feat_synth = self.dino(synthetic_img)
gaze_pred = self.regressor(feat_synth)

if real_img is not None:
feat_real = self.dino(real_img)
pseudo_gaze = self.ema_regressor(feat_real)
return gaze_pred, pseudo_gaze

return gaze_pred

def consistency_loss(self, pred, pseudo, mask):
"""一致性损失:伪标签与teacher一致"""
return F.mse_loss(pred, pseudo.detach()) * mask

Stage 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
"""
Stage 2: 86M Teacher → 256K Student

关键设计:
1. Student架构:轻量CNN/MLP,仅256K参数
2. 增强策略:模拟设备相机退化(模糊、噪声、光照)
3. 知识蒸馏:KL散度 + 特征匹配
4. 无需真实gaze标签

部署:仅Student上设备,Teacher训练后丢弃
"""

class OnDeviceStudent(nn.Module):
"""256K参数设备端gaze估计器"""

def __init__(self):
super().__init__()
# 轻量CNN特征提取
self.features = nn.Sequential(
nn.Conv2d(1, 16, 3, stride=2, padding=1), # IR灰度输入
nn.BatchNorm2d(16), nn.ReLU(),
nn.Conv2d(16, 32, 3, stride=2, padding=1),
nn.BatchNorm2d(32), nn.ReLU(),
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64), nn.ReLU(),
nn.AdaptiveAvgPool2d(1),
)
# gaze回归头
self.regressor = nn.Linear(64, 2) # yaw, pitch

def forward(self, x):
feat = self.features(x)
gaze = self.regressor(feat.squeeze(-1).squeeze(-1))
return gaze # (B, 2): yaw, pitch


def distillation_loss(student_pred, teacher_pred, true_gaze=None):
"""
知识蒸馏损失

L = α * KL(student, teacher) + β * MSE(student, true_gaze) [仅合成]
"""
# 软目标蒸馏
kl_loss = F.kl_div(
F.log_softmax(student_pred, dim=-1),
F.softmax(teacher_pred.detach(), dim=-1),
reduction='batchmean'
)

if true_gaze is not None:
hard_loss = F.mse_loss(student_pred, true_gaze)
return 0.5 * kl_loss + 0.5 * hard_loss

return kl_loss


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

# 模型参数量验证
student = OnDeviceStudent()
params = sum(p.numel() for p in student.parameters())
print(f"Student参数量: {params:,} ({params/1024:.0f}K)")
print(f"目标: 256K")

# 推理速度测试
dummy = torch.randn(1, 1, 64, 64)

# 前向
start = time.time()
for _ in range(1000):
_ = student(dummy)
elapsed = (time.time() - start) / 1000 * 1000
print(f"推理延迟: {elapsed:.2f}ms/帧")
print(f"帧率: {1000/elapsed:.0f}fps")

print("\n=== DistillGaze性能对比 ===")
print(f"{'方法':<25} {'参数量':<10} {'中位误差':<12} {'尾部误差':<12}")
print(f"{'-'*59}")
print(f"{'256K + 合成数据':<25} {'256K':<10} {'3.48°':<12} {'14.84°':<12}")
print(f"{'DINOv3 ViT-B (冻结+LP)':<25} {'86M':<10} {'>5°':<12} {'—':<12}")
print(f"{'DistillGaze (本文)':<25} {'256K':<10} {'1.44°':<12} {'8.45°':<12}")
print(f"\n改善: 中位误差 -58.6%, 尾部误差 -43.1%")
print(f"模型大小不变: 256K → 256K")

合成数据的作用

IMS开发启示

1. 对DMS视线估计的直接价值

维度 当前DMS DistillGaze方案
标注成本 需大量真实标注 ✅ 零真实标注
新设备适配 重新采集+标注 ✅ 仅更新合成参数
模型大小 1-10MB ✅ 256KB
精度 2-5° ✅ 1.44°
部署 需NPU ✅ CPU可运行

2. 合成数据→真实迁移的通用框架

DistillGaze的两步蒸馏不限于gaze:

  1. 合成数据+VFM→域适配teacher → 适用任何有合成数据的感知任务
  2. teacher→轻量student → 通用模型压缩
  3. 无需真实标注 → 新设备/新场景快速部署

3. 与IMS的部署路径

4. 与GazeDiT的互补

同周发布的GazeDiT(arXiv 2609.17814)用扩散模型生成gaze-accurate合成眼图像——可作为DistillGaze合成数据的生成器:

组件 作用 来源
GazeDiT 生成gaze-accurate合成眼图像 扩散模型
DistillGaze 用合成+未标注真实训练 两步蒸馏
组合 GazeDiT生成→DistillGaze训练 端到端无标注

5. 部署优先级

功能 模型 参数 延迟 精度
视线落点 DistillGaze Student 256K <1ms 1.44°
PERCLOS 简单EAR阈值 <1K <0.1ms —
头部姿态 MediaPipe ~5M ~5ms ~3°
乘员分类 YOLO27n 3.8M ~10ms 60+ mAP

局限性

  1. 合成质量依赖:Blender渲染质量影响最终精度
  2. DINOv3计算成本:Stage 1需86M模型训练(离线)
  3. 仅双眼IR:未验证RGB输入
  4. ECCV会议论文:尚未有大规模量产验证
  5. Project Aria基准:特定设备评测,跨设备泛化待验证

结论

DistillGaze解决了DMS视线估计的核心痛点:新设备适配无需标注周期。

对IMS的启示:

  1. 合成数据 + VFM蒸馏是DMS快速部署的新范式
  2. 256K模型可达1.44°精度——不需要大模型
  3. VFM携带信号但需域适配——DINOv3不是万能的
  4. 与GazeDiT组合可形成端到端无标注gaze管道

参考来源:

  • ECCV 2026 Poster: Rapidly Deploying On-Device Eye Tracking
  • DINOv3: Meta AI Visual Foundation Model
  • Project Aria: Meta crowdsourced eye tracking benchmark
  • GazeDiT: arXiv 2609.17814 (2026.09)