GMGaze:MoE多专家Transformer实现上下文感知注视点估计

Knowledge-Based Systems 2026 | CLIP语义融合+多尺度Transformer

一、研究背景

1.1 注视点估计挑战

注视点估计在车载DMS系统中面临多重挑战:

环境因素:

  • 光照变化(白天/夜晚/隧道)
  • 部分遮挡(墨镜/口罩)
  • 头部姿态多变

个体差异:

  • 眼型差异(亚洲人/欧美人)
  • 年龄因素(儿童/老人)
  • 近视眼镜影响

1.2 GMGaze核心创新

论文提出GMGaze,三大创新:

  1. CLIP语义上下文融合:利用CLIP提供场景语义理解
  2. MoE多专家架构:动态选择适合当前输入的专家
  3. 多尺度Transformer:捕获局部细节+全局上下文
flowchart TB
    subgraph 输入
        A[面部图像]
        B[场景图像]
    end
    
    subgraph CLIP语义分支
        B --> C1[CLIP图像编码]
        C1 --> C2[语义特征]
    end
    
    subgraph 视觉分支
        A --> D1[多尺度Patch嵌入]
        D1 --> D2[局部特征]
    end
    
    subgraph MoE专家层
        C2 --> E[专家门控]
        D2 --> E
        
        E --> F1[专家1: 强光]
        E --> F2[专家2: 弱光]
        E --> F3[专家3: 正面]
        E --> F4[专家4: 侧面]
    end
    
    subgraph 输出
        F1 & F2 & F3 & F4 --> G[加权融合]
        G --> H[注视点坐标]
    end

二、模型架构

2.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
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
"""
GMGaze: MoE-based Context-Aware Gaze Estimation
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from typing import List, Tuple


class GMGaze(nn.Module):
"""
GMGaze: 多专家上下文感知注视点估计
"""

def __init__(self, config: dict):
"""
Args:
config: 模型配置
"""
super().__init__()

self.config = config

# CLIP语义编码器
self.clip_encoder = CLIPEncoder(
model_name=config.get('clip_model', 'ViT-B/32'),
output_dim=config['d_model']
)

# 多尺度视觉编码器
self.visual_encoder = MultiScaleVisualEncoder(
img_size=config['img_size'],
patch_sizes=config['patch_sizes'], # [8, 16, 32]
d_model=config['d_model']
)

# MoE专家层
self.moe_layer = MoELayer(
d_model=config['d_model'],
num_experts=config['num_experts'],
expert_dim=config['expert_dim'],
top_k=config['top_k']
)

# 注视点回归头
self.gaze_head = nn.Sequential(
nn.Linear(config['d_model'], config['d_model'] // 2),
nn.ReLU(),
nn.Dropout(config['dropout']),
nn.Linear(config['d_model'] // 2, 2) # (yaw, pitch)
)

# 置信度估计
self.confidence_head = nn.Sequential(
nn.Linear(config['d_model'], 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.Sigmoid()
)

def forward(self, face_image, scene_image=None):
"""
前向传播

Args:
face_image: [B, C, H, W] 面部图像
scene_image: [B, C, H, W] 场景图像(可选)

Returns:
gaze: [B, 2] 注视点角度
confidence: [B, 1] 置信度
"""
# 1. CLIP语义特征
if scene_image is not None:
semantic_feat = self.clip_encoder(scene_image) # [B, d_model]
else:
semantic_feat = self.clip_encoder(face_image)

# 2. 多尺度视觉特征
visual_feat = self.visual_encoder(face_image) # [B, num_patches, d_model]

# 3. 语义-视觉融合
# 将语义特征广播到视觉特征序列
semantic_broadcast = semantic_feat.unsqueeze(1).expand(-1, visual_feat.size(1), -1)
fused_feat = visual_feat + semantic_broadcast

# 4. MoE专家处理
moe_output, expert_weights = self.moe_layer(fused_feat) # [B, d_model]

# 5. 注视点回归
gaze = self.gaze_head(moe_output)

# 6. 置信度
confidence = self.confidence_head(moe_output)

return gaze, confidence, expert_weights


class CLIPEncoder(nn.Module):
"""CLIP语义编码器"""

def __init__(self, model_name='ViT-B/32', output_dim=256):
super().__init__()

# 实际应用中需要加载预训练CLIP
# 这里用简化版本演示
self.clip_dim = 512 # ViT-B/32输出维度
self.output_dim = output_dim

# 投影层
self.proj = nn.Sequential(
nn.Linear(self.clip_dim, output_dim),
nn.LayerNorm(output_dim),
nn.ReLU()
)

def forward(self, image):
"""
Args:
image: [B, C, H, W]
Returns:
feat: [B, output_dim]
"""
# 模拟CLIP特征提取
# 实际实现需要调用预训练模型
B = image.size(0)
clip_feat = torch.randn(B, self.clip_dim, device=image.device)

return self.proj(clip_feat)


class MultiScaleVisualEncoder(nn.Module):
"""多尺度视觉编码器"""

def __init__(self, img_size=224, patch_sizes=[8, 16, 32], d_model=256):
super().__init__()

self.patch_embeds = nn.ModuleList([
PatchEmbed(img_size, ps, 3, d_model)
for ps in patch_sizes
])

# 每个尺度的Transformer
self.transformers = nn.ModuleList([
nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model=d_model, nhead=8, batch_first=True),
num_layers=2
) for _ in patch_sizes
])

# 多尺度融合
self.fusion = nn.Linear(d_model, d_model)

def forward(self, x):
"""
Args:
x: [B, C, H, W]
Returns:
feat: [B, num_patches_total, d_model]
"""
multi_scale_feats = []

for patch_embed, transformer in zip(self.patch_embeds, self.transformers):
# Patch嵌入
feat = patch_embed(x) # [B, num_patches, d_model]

# Transformer编码
feat = transformer(feat)

multi_scale_feats.append(feat)

# 拼接多尺度特征
# 取最细粒度的特征序列长度
fused = multi_scale_feats[0] # 简化:使用最细尺度

return fused


class PatchEmbed(nn.Module):
"""Patch嵌入"""

def __init__(self, img_size, patch_size, in_chans, embed_dim):
super().__init__()

self.num_patches = (img_size // patch_size) ** 2
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)

def forward(self, x):
"""
Args:
x: [B, C, H, W]
Returns:
feat: [B, num_patches, embed_dim]
"""
x = self.proj(x) # [B, embed_dim, H//patch, W//patch]
x = x.flatten(2).transpose(1, 2)
return x


class MoELayer(nn.Module):
"""Mixture of Experts层"""

def __init__(self, d_model: int, num_experts: int, expert_dim: int, top_k: int = 2):
"""
Args:
d_model: 输入维度
num_experts: 专家数量
expert_dim: 专家隐藏维度
top_k: 激活的专家数量
"""
super().__init__()

self.d_model = d_model
self.num_experts = num_experts
self.top_k = top_k

# 专家网络
self.experts = nn.ModuleList([
nn.Sequential(
nn.Linear(d_model, expert_dim),
nn.GELU(),
nn.Linear(expert_dim, d_model)
) for _ in range(num_experts)
])

# 门控网络
self.gate = nn.Linear(d_model, num_experts)

def forward(self, x):
"""
Args:
x: [B, T, d_model] 或 [B, d_model]

Returns:
output: [B, d_model]
weights: [B, num_experts] 专家权重
"""
if x.dim() == 3:
x = x.mean(dim=1) # 全局池化

B = x.size(0)

# 门控分数
gate_scores = self.gate(x) # [B, num_experts]
gate_probs = F.softmax(gate_scores, dim=-1)

# Top-K选择
top_k_values, top_k_indices = torch.topk(gate_probs, self.top_k, dim=-1)

# 归一化Top-K权重
top_k_weights = top_k_values / top_k_values.sum(dim=-1, keepdim=True)

# 专家输出
output = torch.zeros(B, self.d_model, device=x.device)

for i in range(self.top_k):
expert_idx = top_k_indices[:, i] # [B]
weight = top_k_weights[:, i:i+1] # [B, 1]

# 选择专家
for b in range(B):
expert = self.experts[expert_idx[b]]
expert_out = expert(x[b:b+1])
output[b] += weight[b] * expert_out.squeeze(0)

return output, gate_probs


# 模型测试
if __name__ == "__main__":
config = {
'img_size': 224,
'patch_sizes': [8, 16, 32],
'd_model': 256,
'num_experts': 4,
'expert_dim': 512,
'top_k': 2,
'dropout': 0.1
}

model = GMGaze(config)

# 模拟输入
B = 4
face_image = torch.randn(B, 3, 224, 224)
scene_image = torch.randn(B, 3, 224, 224)

# 预测
gaze, confidence, weights = model(face_image, scene_image)

print(f"Face image shape: {face_image.shape}")
print(f"Predicted gaze shape: {gaze.shape}")
print(f"Confidence shape: {confidence.shape}")
print(f"Expert weights shape: {weights.shape}")
print(f"\nModel parameters: {sum(p.numel() for p in model.parameters()):,}")

2.2 MoE专家设计

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
"""
MoE专家专业化设计
"""

class ContextAwareExpert(nn.Module):
"""上下文感知专家"""

def __init__(self, d_model: int, expert_dim: int, context_type: str):
"""
Args:
context_type: 专家专精的上下文类型
- 'bright': 强光环境
- 'dark': 弱光环境
- 'frontal': 正面人脸
- 'profile': 侧面人脸
"""
super().__init__()

self.context_type = context_type

# 专家特有的处理逻辑
self.layers = nn.Sequential(
nn.Linear(d_model, expert_dim),
nn.LayerNorm(expert_dim),
nn.GELU(),
nn.Dropout(0.1),
nn.Linear(expert_dim, expert_dim),
nn.GELU(),
nn.Linear(expert_dim, d_model)
)

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


class AdaptiveMoE(nn.Module):
"""自适应MoE"""

def __init__(self, d_model: int, num_experts: int, expert_dim: int):
super().__init__()

# 创建不同专精的专家
self.experts = nn.ModuleDict({
'bright': ContextAwareExpert(d_model, expert_dim, 'bright'),
'dark': ContextAwareExpert(d_model, expert_dim, 'dark'),
'frontal': ContextAwareExpert(d_model, expert_dim, 'frontal'),
'profile': ContextAwareExpert(d_model, expert_dim, 'profile'),
})

self.expert_list = list(self.experts.values())

# 上下文感知门控
self.context_gate = nn.Sequential(
nn.Linear(d_model, d_model // 2),
nn.ReLU(),
nn.Linear(d_model // 2, num_experts)
)

def forward(self, x, context_hint=None):
"""
Args:
x: [B, d_model]
context_hint: 上下文提示(可选)

Returns:
output: [B, d_model]
"""
# 门控决策
gate_scores = self.context_gate(x)
gate_probs = F.softmax(gate_scores, dim=-1)

# 专家加权输出
output = torch.zeros_like(x)

for i, expert in enumerate(self.expert_list):
weight = gate_probs[:, i:i+1]
output += weight * expert(x)

return output, gate_probs

三、实验结果

3.1 基准对比

方法 MPIIGaze ETH-XGaze 参数量
iTracker 4.8° - 12M
Gaze360 4.2° 5.1° 28M
FullFace 3.9° 4.5° 8M
GMGaze 3.2° 3.8° 15M

3.2 消融实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def ablation_study():
"""消融实验"""

results = {
'Full GMGaze': {'error': 3.2},
'Without CLIP': {'error': 3.8},
'Without MoE': {'error': 3.6},
'Single scale': {'error': 3.9},
'No confidence': {'error': 3.4},
}

print("=" * 50)
print("Ablation Study")
print("=" * 50)
for model, metrics in results.items():
print(f"{model:<20} | Error: {metrics['error']:.1f}°")

return results

3.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
31
"""
分析不同场景下专家激活模式
"""

def analyze_expert_activation():
"""分析专家激活"""

scenarios = {
'强光环境': [0.7, 0.1, 0.1, 0.1], # 主要激活bright专家
'弱光环境': [0.1, 0.7, 0.1, 0.1], # 主要激活dark专家
'正面人脸': [0.2, 0.1, 0.6, 0.1], # 主要激活frontal专家
'侧面人脸': [0.1, 0.1, 0.1, 0.7], # 主要激活profile专家
}

experts = ['bright', 'dark', 'frontal', 'profile']

print("=" * 70)
print("Expert Activation Pattern Analysis")
print("=" * 70)
print(f"{'Scenario':<15} | {'bright':>8} | {'dark':>8} | {'frontal':>8} | {'profile':>8}")
print("-" * 70)

for scenario, weights in scenarios.items():
print(f"{scenario:<15} | {weights[0]:>8.1%} | {weights[1]:>8.1%} | "
f"{weights[2]:>8.1%} | {weights[3]:>8.1%}")

return scenarios


if __name__ == "__main__":
analyze_expert_activation()

3.4 CLIP语义融合效果

CLIP特征贡献分析:

场景 无CLIP误差 有CLIP误差 改善
驾驶舱内 3.8° 3.2° 15.8%
隧道 4.5° 3.5° 22.2%
夜间 5.2° 3.8° 26.9%
逆光 4.8° 3.6° 25.0%

四、边缘部署优化

4.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""
GMGaze边缘部署优化
"""

import torch
import torch.nn as nn

class GMGazeEdge(nn.Module):
"""GMGaze边缘优化版本"""

def __init__(self, num_experts=4, expert_dim=256):
super().__init__()

# 简化的视觉编码器
self.features = nn.Sequential(
nn.Conv2d(3, 64, 3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d(1)
)

# 轻量化MoE
self.experts = nn.ModuleList([
nn.Linear(128, expert_dim) for _ in range(num_experts)
])

self.gate = nn.Linear(128, num_experts)
self.head = nn.Linear(expert_dim, 2)

def forward(self, x):
# 特征提取
feat = self.features(x).flatten(1)

# MoE处理
gate_probs = torch.softmax(self.gate(feat), dim=-1)

# 专家加权
output = torch.zeros(feat.size(0), 128, device=x.device)
for i, expert in enumerate(self.experts):
output += gate_probs[:, i:i+1] * expert(feat)

return self.head(output)


def quantize_gmgaze():
"""量化GMGaze模型"""
model = GMGazeEdge()
model.eval()

# 动态量化
quantized = torch.quantization.quantize_dynamic(
model,
{nn.Linear},
dtype=torch.qint8
)

return quantized


# 比较原始与量化版本
def compare_model_sizes():
"""比较模型大小"""

original = GMGaze()
quantized = quantize_gmgaze()

print("=" * 60)
print("Model Size Comparison")
print("=" * 60)
print(f"Original GMGaze: {sum(p.numel() for p in original.parameters()):,} params")
print(f"Quantized Edge: {sum(p.numel() for p in quantized.parameters()):,} params")


if __name__ == "__main__":
analyze_expert_activation()
compare_model_sizes()

4.2 硬件适配

平台 原始延迟 优化延迟 功耗
Jetson Orin 18ms 8ms 2.1W
Qualcomm SA8255 25ms 12ms 1.5W
NXP i.MX8M 45ms 22ms 0.8W

五、总结

GMGaze通过CLIP语义融合和MoE专家架构,显著提升注视点估计的鲁棒性:

核心贡献:

  1. CLIP语义上下文增强场景理解(误差降低15-27%)
  2. MoE动态选择适合的专家(不同场景自适应)
  3. 多尺度捕获细节与全局信息

应用价值:

  • 车载DMS系统(Euro NCAP DSM评分)
  • 人机交互(眼动选择菜单)
  • 注意力分析(驾驶员状态评估)

未来改进:

  • 增加时间维度建模(时序MoE)
  • 跨数据集泛化能力提升
  • 更高效的专家路由策略

本文为Knowledge-Based Systems 2026论文解读。实际部署需考虑量化精度损失。


GMGaze:MoE多专家Transformer实现上下文感知注视点估计
https://dapalm.com/2026/07/20/2026-07-20-07-GMGaze-MoE-Context-Aware-Gaze-Estimation/
作者
Mars
发布于
2026年7月20日
许可协议