GazeCapsNet:面向边缘部署的轻量级视线估计框架

论文信息


核心创新

GazeCapsNet 提出了一种基于胶囊网络的轻量级视线估计框架,通过 Self-Attention Routing(SAR)机制替代传统迭代路由,实现了:

  1. 单次推理 - 20ms/frame,比 GazeCaps(25ms)降低 20%
  2. 轻量模型 - 11.7M 参数,适合边缘部署
  3. 端到端预测 - 无需人脸关键点检测,直接输出 3D 视线向量
  4. 遮挡鲁棒 - 通过 SAR 动态聚焦关键区域,应对光照/遮挡变化

方法详解

1. 问题定义

视线估计的核心任务是预测 3D 视线向量 $\mathbf{g} = (g_x, g_y, g_z)$,传统方法存在三大瓶颈:

问题类型 传统方法 GazeCapsNet 解决方案
计算开销 CNN + 迭代路由,35-50ms SAR 单次推理,20ms
空间层次丢失 CNN pooling 丢失空间关系 CapsNet 保留空间层次
依赖预处理 需人脸关键点检测 端到端,直接输入原图

2. 核心架构

graph LR
    A[输入图像] --> B[SCRFD人脸检测]
    B --> C[MobileNet v2 + ResNet-18]
    C --> D[Primary Capsules]
    D --> E[Self-Attention Routing]
    E --> F[Gaze Regression Head]
    F --> G[3D视线向量]

三阶段设计:

  1. 人脸检测: SCRFD(Single-Stage Efficient Real-Time Face Detector)

    • 输入:RGB 图像
    • 输出:人脸边界框
    • 耗时:2-3ms
  2. 特征提取: MobileNet v2 + ResNet-18 混合架构

    • MobileNet v2:轻量级特征提取(倒残差结构)
    • ResNet-18:增强空间层次建模
    • 输出:多尺度特征图
  3. 视线回归: CapsNet + SAR + Regression Head

    • Primary Capsules:捕获低层特征
    • SAR:单次注意力分配,替代迭代路由
    • Regression Head:输出 3D 视线向量

3. Self-Attention Routing(核心创新)

传统 CapsNet 的迭代路由(Dynamic Routing、EM Routing)需要 3-6 次迭代:

1
2
3
4
5
6
7
8
# ❌ 传统迭代路由(高计算开销)
for iteration in range(3): # 3次迭代
# 计算耦合系数
c_ij = softmax(b_ij)
# 更新高层胶囊
s_j = sum(c_ij * u_i)
# 更新路由参数
b_ij += u_i * s_j

SAR 单次推理:

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
# ✅ Self-Attention Routing(单次推理)
class SelfAttentionRouting(nn.Module):
"""
Self-Attention Routing for Capsule Networks

核心思想:
- 用注意力机制替代迭代路由
- 单次计算耦合系数
- 动态聚焦关键特征

论文 Section 3.2 实现
"""

def __init__(self, input_caps: int, output_caps: int, caps_dim: int):
super().__init__()
self.input_caps = input_caps # 输入胶囊数
self.output_caps = output_caps # 输出胶囊数
self.caps_dim = caps_dim # 胶囊维度

# 注意力权重矩阵
self.attention = nn.Linear(caps_dim, output_caps)

def forward(self, u: torch.Tensor) -> torch.Tensor:
"""
前向传播

Args:
u: 输入胶囊向量, shape=(B, input_caps, caps_dim)

Returns:
s: 输出胶囊向量, shape=(B, output_caps, caps_dim)
"""
# 计算注意力权重(单次)
attn_weights = self.attention(u) # (B, input_caps, output_caps)
attn_weights = F.softmax(attn_weights, dim=-1) # 归一化

# 加权聚合(无需迭代)
s = torch.einsum('bi o,bid->bod', attn_weights, u) # (B, output_caps, caps_dim)

# 非线性激活(squash)
s = self.squash(s)

return s

def squash(self, s: torch.Tensor) -> torch.Tensor:
"""
胶囊非线性激活函数

公式:v_j = ||s_j||^2 / (1 + ||s_j||^2) * s_j / ||s_j||
"""
norm = torch.norm(s, dim=-1, keepdim=True)
squash_factor = norm ** 2 / (1 + norm ** 2)
unit_vector = s / (norm + 1e-8)
return squash_factor * unit_vector


# 实际测试
if __name__ == "__main__":
# 模拟输入
model = SelfAttentionRouting(input_caps=32, output_caps=8, caps_dim=16)

# 随机输入胶囊
u = torch.randn(1, 32, 16)

# 单次推理
s = model(u)

print(f"输入形状: {u.shape}")
print(f"输出形状: {s.shape}")
print(f"推理耗时: 测试实际耗时")

4. 损失函数

视线估计采用角度误差损失

$$
L = \frac{1}{N} \sum_{i=1}^{N} \arccos\left(\frac{\mathbf{g}_i \cdot \mathbf{g}_i^}{||\mathbf{g}_i|| \cdot ||\mathbf{g}_i^||}\right)
$$

其中:

  • $\mathbf{g}_i$:预测视线向量
  • $\mathbf{g}_i^*$:真实视线向量
  • $N$:样本数量

代码复现

完整模型实现

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
"""
论文:GazeCapsNet: A Lightweight Gaze Estimation Framework
作者:PMC11860563
期刊:Sensors 2025
链接:https://pmc.ncbi.nlm.nih.gov/articles/PMC11860563/

核心方法:Capsule Networks + Self-Attention Routing + MobileNet v2
"""

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision.models import mobilenet_v2, resnet18


class GazeCapsNet(nn.Module):
"""
GazeCapsNet 完整模型

论文 Section 3 架构:
- Face Detection: SCRFD(此处简化为固定输入)
- Feature Extraction: MobileNet v2 + ResNet-18
- Capsule Network: Primary Capsules + SAR
- Gaze Regression: 3D向量输出
"""

def __init__(self, config: dict = None):
super().__init__()
config = config or {}

# 特征提取器(MobileNet v2 + ResNet-18)
self.mobilenet = mobilenet_v2(pretrained=True).features
self.resnet = resnet18(pretrained=True)
self.resnet.fc = nn.Identity() # 移除分类头

# 特征融合层
self.fusion = nn.Conv2d(1280 + 512, 256, kernel_size=1)

# Primary Capsules
self.primary_caps = nn.Conv2d(256, 32 * 8, kernel_size=3, stride=2)

# Self-Attention Routing
self.sar = SelfAttentionRouting(input_caps=32, output_caps=8, caps_dim=8)

# 视线回归头
self.gaze_head = nn.Linear(8 * 8, 3) # 输出3D视线向量

def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
前向传播

Args:
x: 输入人脸图像, shape=(B, 3, 224, 224)

Returns:
gaze: 3D视线向量, shape=(B, 3)
"""
# 特征提取
mb_features = self.mobilenet(x) # (B, 1280, 7, 7)
res_features = self.resnet(x) # (B, 512)

# ResNet特征reshape
res_features_2d = res_features.view(-1, 512, 1, 1).expand(-1, 512, 7, 7)

# 特征融合
fused = torch.cat([mb_features, res_features_2d], dim=1) # (B, 1792, 7, 7)
fused = self.fusion(fused) # (B, 256, 7, 7)

# Primary Capsules
caps = self.primary_caps(fused) # (B, 256, 3, 3)
caps = caps.view(-1, 32, 8) # reshape为胶囊格式

# Self-Attention Routing
s = self.sar(caps) # (B, 8, 8)

# 视线回归
s_flat = s.view(-1, 64)
gaze = self.gaze_head(s_flat) # (B, 3)

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

return gaze

def get_model_size(self) -> dict:
"""统计模型参数"""
total_params = sum(p.numel() for p in self.parameters())
trainable_params = sum(p.numel() for p in self.parameters() if p.requires_grad)

return {
"total_params": total_params,
"trainable_params": trainable_params,
"size_mb": total_params * 4 / (1024 ** 2) # 4 bytes per param
}


class SelfAttentionRouting(nn.Module):
"""Self-Attention Routing(见上文)"""

def __init__(self, input_caps: int, output_caps: int, caps_dim: int):
super().__init__()
self.input_caps = input_caps
self.output_caps = output_caps
self.caps_dim = caps_dim
self.attention = nn.Linear(caps_dim, output_caps)

def forward(self, u: torch.Tensor) -> torch.Tensor:
attn_weights = self.attention(u)
attn_weights = F.softmax(attn_weights, dim=-1)
s = torch.einsum('bio,bid->bod', attn_weights, u)
s = self.squash(s)
return s

def squash(self, s: torch.Tensor) -> torch.Tensor:
norm = torch.norm(s, dim=-1, keepdim=True)
squash_factor = norm ** 2 / (1 + norm ** 2)
unit_vector = s / (norm + 1e-8)
return squash_factor * unit_vector


# 实际测试
if __name__ == "__main__":
# 创建模型
model = GazeCapsNet()

# 统计参数
stats = model.get_model_size()
print(f"模型参数: {stats['total_params'] / 1e6:.2f}M")
print(f"模型大小: {stats['size_mb']:.2f}MB")

# 模拟输入
x = torch.randn(1, 3, 224, 224)

# 前向推理
gaze = model(x)
print(f"输出视线向量: {gaze}")
print(f"视线角度: arctan(gaze)")

# 测试推理速度
import time
start = time.time()
for _ in range(100):
gaze = model(x)
elapsed = (time.time() - start) / 100
print(f"平均推理耗时: {elapsed * 1000:.2f}ms")

实验结果

性能对比表

模型 ETH-XGaze MAE Gaze360 MAE MPIIGaze MAE 参数量 推理耗时
FullFace 4.5° 11.2° 5.1° 196.6M 50ms
RT-GENE 4.8° 10.9° 5.3° 45.2M 40ms
GazeTR-Pure 4.1° 10.5° 4.9° 58.1M 45ms
GazeCaps 4.2° 10.7° 5.0° 35.2M 25ms
GazeCapsNet(本论文) 3.6° 9.5° 4.3° 11.7M 20ms

关键指标:

  • MAE(Mean Angular Error)降低 15%(对比 GazeCaps)
  • 参数量减少 67%(对比 GazeCaps)
  • 推理速度提升 20%

遮挡/光照鲁棒性测试

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
# 遮挡测试代码
def test_occlusion_robustness(model, test_image):
"""
测试遮挡场景下的视线估计

测试条件:
- 眼镜遮挡:覆盖眼睛区域 30%
- 光照变化:低光照(<50 lux)、高光照(>500 lux)
- 头部姿态:±30° 旋转

论文 Figure 3 定性结果
"""
results = {}

# 原始图像
gaze_normal = model(test_image)
results['normal'] = gaze_normal

# 眼镜遮挡(模拟)
occluded_image = add_glasses(test_image) # 添加眼镜遮挡
gaze_occluded = model(occluded_image)
error_occluded = angular_error(gaze_occluded, gaze_normal)
results['occluded'] = error_occluded

# 低光照(亮度衰减)
dark_image = adjust_brightness(test_image, factor=0.3)
gaze_dark = model(dark_image)
error_dark = angular_error(gaze_dark, gaze_normal)
results['dark'] = error_dark

# 头部旋转(±30°)
rotated_image = rotate_head(test_image, angle=30)
gaze_rotated = model(rotated_image)
error_rotated = angular_error(gaze_rotated, gaze_normal)
results['rotated'] = error_rotated

return results

# 测试结果(论文 Table 4)
"""
遮挡场景性能:
- 正常条件:MAE 3.6°
- 眼镜遮挡:MAE 4.2° (+0.6°)
- 低光照:MAE 4.8° (+1.2°)
- 头部旋转±30°:MAE 4.5° (+0.9°)
"""

IMS应用启示

1. DMS 视线落点检测

场景对应:

  • Euro NCAP DSM 分心检测(D-01/D-02/D-05)
  • 需实时判断视线是否偏离道路

落地方案:

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
class DMSGazeMonitor:
"""
DMS 视线监控模块

基于 GazeCapsNet 实现视线落点检测

功能:
- 视线偏离道路检测(Euro NCAP D-05)
- 手机使用检测(视线看向手机)
- 仪表盘交互检测(视线看向仪表盘)
"""

def __init__(self):
self.gaze_model = GazeCapsNet()
self.gaze_model.eval()

# 视线落点阈值(Euro NCAP 定义)
self.road_region = {
'xmin': -30, 'xmax': 30, # 前方道路区域(度)
'ymin': -15, 'ymax': 15 # 垂直范围
}

def detect_distraction(self, face_image: np.ndarray) -> dict:
"""
检测视线分心

Args:
face_image: 人脸图像 (H, W, 3)

Returns:
result: {'distraction_type': 'road'|'phone'|'dashboard',
'gaze_vector': (g_x, g_y, g_z),
'is_distracted': bool}
"""
# 视线估计
gaze = self.gaze_model(face_image)

# 计算视线角度
gaze_horizontal = np.arctan2(gaze[0], gaze[2]) * 180 / np.pi
gaze_vertical = np.arctan2(gaze[1], gaze[2]) * 180 / np.pi

# 判断视线落点
if self._is_in_road_region(gaze_horizontal, gaze_vertical):
return {'distraction_type': 'road', 'is_distracted': False}
elif gaze_horizontal < -45: # 左侧(手机区域)
return {'distraction_type': 'phone', 'is_distracted': True}
elif gaze_horizontal > 45: # 右侧(仪表盘区域)
return {'distraction_type': 'dashboard', 'is_distracted': True}
else:
return {'distraction_type': 'unknown', 'is_distracted': True}

def _is_in_road_region(self, h_angle: float, v_angle: float) -> bool:
"""判断视线是否在道路区域"""
return (self.road_region['xmin'] <= h_angle <= self.road_region['xmax'] and
self.road_region['ymin'] <= v_angle <= self.road_region['ymax'])

2. 边缘部署优化

部署平台:

  • Qualcomm QCS8255(Hexagon NPU,26 TOPS)
  • TI TDA4VM(C7x DSP,8 TOPS)

优化方案:

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
# ONNX 导出 + INT8 量化
def deploy_to_edge(model):
"""
边缘部署流程

步骤:
1. 导出 ONNX
2. INT8 量化
3. NPU 编译
"""

# 导出 ONNX
torch.onnx.export(
model,
torch.randn(1, 3, 224, 224),
"gazecapsnet.onnx",
input_names=['input'],
output_names=['gaze'],
dynamic_axes={'input': {0: 'batch'}}
)

# ONNX Simplifier
import onnx_simplifier
onnx_simplifier.simplify("gazecapsnet.onnx")

# INT8 量化(高通 Hexagon)
# Qualcomm QNN SDK
# qnn-onnx-converter --input gazecapsnet.onnx --output gazecapsnet_qnn.bin

# TI TDA4 编译
# TI Edge AI SDK
# edgeai-compiler --model gazecapsnet.onnx --target TDA4

return "gazecapsnet_quantized.bin"


# 部署后性能预估
"""
性能预估(QCS8255):
- 原始模型:11.7M 参数,20ms/frame(GPU)
- INT8 量化:~3M 参数,8-12ms/frame(NPU)
- 内存占用:<15MB
- 功耗:<1W
"""

3. Euro NCAP DSM 测试场景

场景映射表:

Euro NCAP 场景 GazeCapsNet 应用 预期性能
D-01:视线偏离道路 >3s 视线角度判断 MAE 3.6°,检测精度 95%
D-02:手机使用 视线落点判断(看向手机) 角度误差 4.2°(遮挡情况)
D-05:视线偏离道路 ≥3s(单次) 滑动窗口统计 时序稳定性高
F-01:PERCLOS ≥30% 结合闭眼检测 可与疲劳检测模块集成

4. 数据合成启示

GazeCapsNet 的 SAR 机制可用于:

  • 数据合成中的注意力引导
  • 合成图像的关键区域增强
  • 遮挡场景的鲁棒性训练

开发优先级

模块 优先级 开发周期 技术难点
视线估计模型移植 🔴 P0 2周 SAR 实现 + 量化
视线落点判断 🟡 P1 1周 道路区域定义
Euro NCAP 场景验证 🟡 P1 2周 测试场景搭建
边缘部署优化 🔴 P0 3周 NPU 编译

总结

GazeCapsNet 通过 Capsule Networks + Self-Attention Routing 实现了轻量级、高精度的视线估计:

  • 精度: MAE 降低 15%,遮挡鲁棒性强
  • 速度: 20ms/frame,适合实时 DMS
  • 部署: 11.7M 参数,可量化至 3M(INT8)

IMS 开发启示:

  1. 替代现有基于关键点的视线估计
  2. 实现视线落点检测(Euro NCAP DSM)
  3. 集成到疲劳/分心检测流程
  4. 边缘部署优化(高通/TI 平台)

参考文献

  1. 论文原文:https://pmc.ncbi.nlm.nih.gov/articles/PMC11860563/
  2. Euro NCAP DSM Protocol:https://www.euroncap.com/protocols/
  3. ETH-XGaze Dataset:https://ait.ethz.ch/projects/2020/ETH-XGaze/
  4. Gaze360 Dataset:https://gaze360.csail.mit.edu/

GazeCapsNet:面向边缘部署的轻量级视线估计框架
https://dapalm.com/2026/07/06/2026-07-06-gaze-capsnet-lightweight-zh/
作者
Mars
发布于
2026年7月6日
许可协议