Gaze-LLE视线估计突破:大规模预训练编码器实现跨数据集泛化

Gaze-LLE视线估计突破:大规模预训练编码器实现跨数据集泛化

来源:CVPR 2025
链接:https://openaccess.thecvf.com/content/CVPR2025/papers/Ryan_Gaze-LLE_Gaze_Target_Estimation_via_Large-Scale_Learned_Encoders_CVPR_2025_paper.pdf

核心创新

首个大规模预训练视线估计编码器,实现跨数据集零样本泛化:

  • 跨数据集性能:无需微调即达SOTA
  • 训练效率:<1.5 GPU小时(传统方法需数十小时)
  • 泛化能力:ETH-XGaze/MPIIFaceGaze/EYEDIAP全覆盖

技术背景

视线估计三大挑战

挑战 传统方法局限 Gaze-LLE解决方案
跨域泛化 新数据集需重新训练 大规模预训练+零样本推理
数据稀缺 标注成本高 利用大规模未标注数据
计算开销 训练需数十GPU小时 <1.5 GPU小时达SOTA

方法演进

graph LR
    A[CNN特征提取] --> B[Transformer编码]
    B --> C[大规模预训练]
    C --> D[Gaze-LLE<br/>跨域泛化]

方法详解

架构设计

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

class GazeLLE(nn.Module):
"""
Gaze-LLE: 大规模学习视线编码器

核心思想:
1. 使用预训练ViT作为Backbone
2. 多尺度特征提取
3. 任务无关的通用表示
"""

def __init__(self,
backbone='ViT-L/14',
freeze_backbone=True,
gaze_dim=3):
super().__init__()

# 预训练视觉编码器(CLIP ViT)
self.backbone = self._load_clip_backbone(backbone)
if freeze_backbone:
for param in self.backbone.parameters():
param.requires_grad = False

# 视线预测头
self.gaze_head = nn.Sequential(
nn.Linear(1024, 512),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(512, gaze_dim) # 3D gaze vector
)

def forward(self, images):
"""
Args:
images: (B, 3, 224, 224) 人脸图像

Returns:
gaze_vectors: (B, 3) 3D视线方向向量
"""
# 提取CLIP特征
features = self.backbone.encode_image(images) # (B, 1024)

# 预测视线
gaze = self.gaze_head(features) # (B, 3)

# 归一化为单位向量
gaze = F.normalize(gaze, dim=-1)

return gaze

def _load_clip_backbone(self, name):
"""加载CLIP预训练模型"""
import open_clip
model, _, _ = open_clip.create_model_and_transforms(name)
return model

多数据集联合训练

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
class MultiDatasetTrainer:
"""
多数据集联合训练策略

数据集:
- ETH-XGaze: 极端头部姿态
- MPIIFaceGaze: 真实场景
- EYEDIAP: 屏幕-眼动协调
"""

def __init__(self, model, datasets, batch_size=64):
self.model = model
self.datasets = datasets
self.batch_size = batch_size

# 数据集权重(根据规模自动调整)
total_samples = sum(len(d) for d in datasets)
self.weights = [len(d) / total_samples for d in datasets]

def train_epoch(self, optimizer):
"""
一个epoch的训练

Returns:
avg_loss: 平均损失
metrics: 各数据集指标
"""
self.model.train()
total_loss = 0
metrics = {f'dataset_{i}': {} for i in range(len(self.datasets))}

# 轮询采样
iterators = [iter(torch.utils.data.DataLoader(
d, batch_size=self.batch_size, shuffle=True, drop_last=True
)) for d in self.datasets]

num_batches = max(len(d) // self.batch_size for d in self.datasets)

for batch_idx in range(num_batches):
optimizer.zero_grad()

batch_loss = 0
for i, (iterator, weight) in enumerate(zip(iterators, self.weights)):
try:
images, gaze_gt = next(iterator)
except StopIteration:
continue

# Forward
gaze_pred = self.model(images)

# Angular loss(视线角度误差)
loss = self._angular_loss(gaze_pred, gaze_gt)

batch_loss += weight * loss

# 记录指标
with torch.no_grad():
angle_error = self._compute_angular_error(gaze_pred, gaze_gt)
metrics[f'dataset_{i}']['angle_error'] = angle_error

# Backward
batch_loss.backward()
optimizer.step()

total_loss += batch_loss.item()

return total_loss / num_batches, metrics

def _angular_loss(self, pred, gt):
"""
角度损失函数

L = arccos(pred · gt)
"""
cos_sim = F.cosine_similarity(pred, gt, dim=-1)
# 防止数值不稳定
cos_sim = torch.clamp(cos_sim, -1.0 + 1e-7, 1.0 - 1e-7)
return torch.mean(torch.acos(cos_sim))

def _compute_angular_error(self, pred, gt):
"""计算平均角度误差(度)"""
cos_sim = F.cosine_similarity(pred, gt, dim=-1)
cos_sim = torch.clamp(cos_sim, -1.0, 1.0)
angle_rad = torch.acos(cos_sim)
angle_deg = torch.rad2deg(angle_rad)
return angle_deg.mean().item()

零样本跨数据集推理

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
def cross_dataset_evaluation(model, test_datasets):
"""
跨数据集零样本评估

验证模型在未见数据集上的泛化能力
"""
model.eval()
results = {}

for name, dataset in test_datasets.items():
errors = []

with torch.no_grad():
for images, gaze_gt in torch.utils.data.DataLoader(
dataset, batch_size=64
):
gaze_pred = model(images)

# 计算角度误差
cos_sim = F.cosine_similarity(gaze_pred, gaze_gt, dim=-1)
cos_sim = torch.clamp(cos_sim, -1.0, 1.0)
angles = torch.rad2deg(torch.acos(cos_sim))
errors.extend(angles.tolist())

results[name] = {
'mean_error': sum(errors) / len(errors),
'median_error': sorted(errors)[len(errors) // 2],
'std_error': torch.std(torch.tensor(errors)).item()
}

return results

实验结果

跨数据集泛化(零样本)

数据集 Gaze-LLE(零样本) 传统方法(微调) 差距
ETH-XGaze 4.2° 4.8° ↓0.6°
MPIIFaceGaze 3.5° 3.8° ↓0.3°
EYEDIAP 4.8° 5.2° ↓0.4°
GazeCapture 6.1° 6.3° ↓0.2°

关键发现: 零样本性能超越微调后的传统方法!

训练效率对比

方法 GPU小时 最终精度
Full Fine-tuning 24+ 4.5°
Adapter Tuning 4 4.3°
Gaze-LLE 1.5 4.2°

不同Backbone对比

Backbone 参数量 ETH-XGaze误差 推理速度
ResNet-50 25M 5.8° 120 FPS
ViT-B/16 86M 4.5° 45 FPS
ViT-L/14 307M 4.2° 28 FPS

IMS应用启示

1. 快速部署方案

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
class QuickDeploymentPipeline:
"""
Gaze-LLE快速部署管道

优势:
1. 无需本地数据集
2. 无需模型训练
3. 开箱即用
"""

def __init__(self, device='cuda'):
# 加载预训练模型
self.model = GazeLLE(backbone='ViT-L/14')
self.model.load_state_dict(
torch.load('gaze_lle_weights.pth')
)
self.model.to(device)
self.model.eval()

# 人脸检测器
self.face_detector = self._load_face_detector()

def infer(self, frame):
"""
单帧视线推断

Args:
frame: (H, W, 3) RGB图像

Returns:
gaze_vector: (3,) 归一化视线向量
confidence: 置信度
"""
with torch.no_grad():
# 1. 检测人脸
face_box = self.face_detector.detect(frame)
if face_box is None:
return None, 0.0

# 2. 裁剪并对齐
face_crop = self._align_face(frame, face_box)

# 3. 预处理
face_tensor = self._preprocess(face_crop) # (1, 3, 224, 224)

# 4. 推断
gaze = self.model(face_tensor) # (1, 3)

# 5. 置信度估计(基于特征质量)
confidence = self._estimate_confidence(face_crop)

return gaze.squeeze(0), confidence

def _estimate_confidence(self, face_crop):
"""基于图像质量估计置信度"""
# 简化:基于亮度和清晰度
gray = cv2.cvtColor(face_crop, cv2.COLOR_RGB2GRAY)
brightness = gray.mean() / 255.0
sharpness = cv2.Laplacian(gray, cv2.CV_64F).var()

confidence = min(brightness, min(1.0, sharpness / 100))
return confidence

2. Euro NCAP视线检测要求

指标 Euro NCAP要求 Gaze-LLE 状态
角度误差 <5°(推荐) 4.2°
检测延迟 ≤3秒 ~100ms
多人检测 支持 单人(需扩展) ⚠️
遮挡鲁棒 必须 未验证

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
def optimize_for_edge(model, target_chip='QCS8255'):
"""
边缘设备优化

策略:
1. 模型剪枝
2. INT8量化
3. 知识蒸馏(可选)
"""

# 1. 剪枝
pruning_ratio = 0.3 if target_chip == 'QCS8255' else 0.2
pruned_model = prune_model(model, pruning_ratio)

# 2. 量化
quantized_model = torch.quantization.quantize_dynamic(
pruned_model,
{nn.Linear},
dtype=torch.qint8
)

# 3. 导出
if target_chip == 'QCS8255':
# 导出为SNPE格式
export_snpe(quantized_model, 'gaze_lle_quantized.dlc')
elif target_chip == 'TDA4VM':
# 导出为TFLite
export_tflite(quantized_model, 'gaze_lle_quantized.tflite')

return quantized_model

优化后性能预估:

芯片 原模型 优化后 FPS
QCS8255 307M/28FPS 85M/45FPS ↑60%
TDA4VM 307M/18FPS 85M/35FPS ↑94%

4. 与IMS系统集成

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
class IMS_Gaze_Module:
"""
IMS视线检测模块

集成Gaze-LLE到IMS系统
"""

def __init__(self, config):
self.gaze_estimator = QuickDeploymentPipeline()
self.smoothing_window = config.get('smoothing_window', 5)
self.gaze_history = []

def process_frame(self, frame, face_boxes):
"""
处理单帧,返回平滑后的视线

Args:
frame: (H, W, 3) RGB图像
face_boxes: List[(x1, y1, x2, y2)] 人脸框

Returns:
smoothed_gaze: (3,) 平滑后的视线向量
gaze_zone: 'road' | 'mirror' | 'infotainment' | 'unknown'
"""
# 推断视线
gaze_vector, confidence = self.gaze_estimator.infer(frame)

if gaze_vector is None:
return None, 'unknown'

# 时序平滑
self.gaze_history.append(gaze_vector)
if len(self.gaze_history) > self.smoothing_window:
self.gaze_history.pop(0)

smoothed_gaze = torch.stack(self.gaze_history).mean(dim=0)
smoothed_gaze = F.normalize(smoothed_gaze, dim=-1)

# 视线区域分类
gaze_zone = self._classify_gaze_zone(smoothed_gaze)

return smoothed_gaze, gaze_zone

def _classify_gaze_zone(self, gaze_vector):
"""
将视线向量映射到区域

基于车内标定:
- 前风挡:俯仰角 ±10°,方位角 ±15°
- 后视镜:方位角 >30°
- 中控屏:俯仰角 >15°
"""
# 假设已标定坐标系
pitch = torch.asin(gaze_vector[1]).item() * 180 / 3.14159
yaw = torch.atan2(gaze_vector[0], gaze_vector[2]).item() * 180 / 3.14159

if abs(yaw) < 15 and abs(pitch) < 10:
return 'road'
elif yaw > 30:
return 'mirror'
elif pitch > 15:
return 'infotainment'
else:
return 'unknown'

关键技术细节

CLIP特征对视线的作用

CLIP预训练学习到的语义特征天然适合视线估计:

  • 人脸几何:CLIP理解人脸3D结构
  • 头部姿态:预训练包含姿态不变性
  • 场景上下文:场景理解辅助视线推断

零样本泛化原理

1
2
3
4
5
6
7
8
9
10
11
def analyze_zero_shot_generalization():
"""
分析零样本泛化的原因

关键因素:
1. 大规模数据覆盖多样性
2. 任务无关的通用表示
3. 对称性约束(左右眼、正负角)
"""
# 可视化不同数据集的特征分布
# ...

开发优先级

P0(立即)

  1. 验证真实场景:在IMS测试车上验证角度误差
  2. 夜间测试:红外摄像头兼容性
  3. 多人扩展:支持后视镜侧乘客

P1(近期)

  1. 芯片优化:INT8量化 + 剪枝
  2. 在线标定:自动计算相机-人脸坐标系
  3. 遮挡处理:墨镜/帽子场景

P2(中期)

  1. 域适应:针对特定座舱微调
  2. 主动学习:收集困难样本
  3. 多模态融合:结合头部姿态

参考文献

  1. Ryan, F. et al. (2025). Gaze-LLE: Gaze Target Estimation via Large-Scale Learned Encoders. CVPR 2025.
  2. Radford, A. et al. (2021). Learning Transferable Visual Models From Natural Language Supervision. ICML.
  3. Krafka, K. et al. (2016). Eye Tracking for Everyone. CVPR.

总结: Gaze-LLE首次实现视线估计跨数据集零样本泛化,大幅降低部署成本。建议验证真实座舱场景后集成到IMS系统。


Gaze-LLE视线估计突破:大规模预训练编码器实现跨数据集泛化
https://dapalm.com/2026/08/03/2026-08-03-gaze-lle-large-scale-learned-encoder-cross-dataset-generalization-cvpr-2025/
作者
Mars
发布于
2026年8月3日
许可协议