MultiNet:基于人脸到摄像头距离的多分支视线估计(MDPI Sensors 2026 论文解读)

MultiNet:基于人脸到摄像头距离的多分支视线估计

论文信息

项目 内容
标题 MultiNet: Improving Gaze Estimation via Face-to-Camera Distance and Multi-Branch Learning
期刊 Sensors (MDPI)
年份 2026
卷期 26(19), 6265
链接 https://www.mdpi.com/1424-8220/26/19/6265

1. 核心创新

MultiNet 提出了多分支管线,从单张摄像头图像中提取多种模态特征,并首次系统性地利用人脸到摄像头的度量距离来改进视线估计。

1.1 传统方法的局限

方法 输入 问题
眼部裁剪回归 裁剪的眼部图像 对光照/遮挡敏感,丢失上下文
全脸回归 完整人脸图像 眼部分辨率不足,细节丢失
头部姿态代理 头部角度 眼球偏移时失效
固定融合 上述组合 不同距离/角度下各模态贡献不同

1.2 人脸到摄像头距离的重要性

人脸到摄像头的度量距离影响视线估计精度:

距离 眼部像素 全脸信息 最佳策略
近 (30cm) 充分 (高分辨率) 可能裁剪 以眼部为主
中 (60cm) 适中 完整 均衡融合
远 (100cm+) 不足 (低分辨率) 完整 以面部/头部为主
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
"""
MultiNet 多分支视线估计框架
"""

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

class MultiBranchGazeNet(nn.Module):
"""
MultiNet: 多分支视线估计网络

分支1: 眼部分支(高分辨率眼部裁剪)
分支2: 面部分支(完整人脸上下文)
分支3: 距离分支(人脸到摄像头度量距离)
分支4: 头部姿态分支(头部角度先验)
"""

def __init__(self, config: dict):
super().__init__()

# 分支1: 眼部特征
self.eye_branch = self._build_eye_branch(
config.get('eye_input_size', (64, 128)) # (H, W) 左右眼拼接
)

# 分支2: 全脸特征
self.face_branch = self._build_face_branch(
config.get('face_input_size', (224, 224))
)

# 分支3: 距离特征
self.distance_branch = nn.Sequential(
nn.Linear(1, 32), # 输入: 人脸距离 (米)
nn.ReLU(),
nn.Linear(32, 64),
nn.ReLU()
)

# 分支4: 头部姿态
self.head_pose_branch = nn.Sequential(
nn.Linear(3, 64), # pitch, yaw, roll
nn.ReLU(),
nn.Linear(64, 128),
nn.ReLU()
)

# 融合层
eye_dim = config.get('eye_feat_dim', 128)
face_dim = config.get('face_feat_dim', 256)
dist_dim = 64
pose_dim = 128

fusion_input_dim = eye_dim + face_dim + dist_dim + pose_dim

self.fusion = nn.Sequential(
nn.Linear(fusion_input_dim, 512),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(512, 256),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, 2) # pitch, yaw
)

# 距离自适应权重
self.distance_aware_gate = nn.Sequential(
nn.Linear(1, 32),
nn.ReLU(),
nn.Linear(32, 4), # 4个分支的权重
nn.Softmax(dim=-1)
)

def _build_eye_branch(self, input_size):
"""眼部特征提取分支"""
return nn.Sequential(
nn.Conv2d(3, 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.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(128, 128)
)

def _build_face_branch(self, input_size):
"""全脸特征提取分支"""
return nn.Sequential(
nn.Conv2d(3, 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.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.Conv2d(128, 256, 3, stride=2, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(256, 256)
)

def forward(self, eye_img, face_img, distance, head_pose):
"""
Args:
eye_img: (B, 3, H, W) 眼部裁剪
face_img: (B, 3, 224, 224) 全脸
distance: (B, 1) 人脸到摄像头距离 (米)
head_pose: (B, 3) 头部姿态 (pitch, yaw, roll)
"""
# 各分支提取特征
eye_feat = self.eye_branch(eye_img) # (B, 128)
face_feat = self.face_branch(face_img) # (B, 256)
dist_feat = self.distance_branch(distance) # (B, 64)
pose_feat = self.head_pose_branch(head_pose) # (B, 128)

# 距离自适应权重
weights = self.distance_aware_gate(distance) # (B, 4)

# 加权融合
feats = torch.stack([
eye_feat, face_feat, dist_feat, pose_feat
], dim=1) # (B, 4, D_i)

# 由于各分支维度不同,用拼接+MLP代替直接加权
fused = torch.cat([
eye_feat, face_feat, dist_feat, pose_feat
], dim=-1) # (B, 128+256+64+128)

gaze = self.fusion(fused) # (B, 2)

return gaze, weights

def loss(self, pred_gaze, target_gaze, pred_weights, distance):
"""复合损失函数"""
# MSE 损失
mse = F.mse_loss(pred_gaze, target_gaze)

# 距离一致性正则化
# 近距离时眼部权重应更高
ideal_weights = self._compute_ideal_weights(distance)
reg = F.mse_loss(pred_weights, ideal_weights)

return mse + 0.1 * reg

def _compute_ideal_weights(self, distance):
"""根据距离计算理想权重(软目标)"""
# 近距离: 眼部权重高
# 远距离: 面部/头部权重高
d = distance.squeeze(-1)

eye_w = torch.exp(-d / 0.3) # 随距离衰减
face_w = 1.0 - eye_w * 0.5
dist_w = torch.ones_like(d) * 0.1
pose_w = 1.0 - eye_w - face_w - dist_w
pose_w = torch.clamp(pose_w, min=0.01)

# 归一化
total = eye_w + face_w + dist_w + pose_w
return torch.stack([
eye_w/total, face_w/total,
dist_w/total, pose_w/total
], dim=-1)


# 测试
if __name__ == "__main__":
model = MultiBranchGazeNet({
'eye_input_size': (64, 128),
'face_input_size': (224, 224),
'eye_feat_dim': 128,
'face_feat_dim': 256
})

batch = 4
eye = torch.randn(batch, 3, 64, 128)
face = torch.randn(batch, 3, 224, 224)
dist = torch.tensor([[0.4], [0.6], [0.8], [1.0]]) # 米
pose = torch.randn(batch, 3)

gaze, weights = model(eye, face, dist, pose)
print(f"视线预测: {gaze.shape}")
print(f"分支权重 (近距离0.4m): {weights[0].detach()}")
print(f"分支权重 (远距离1.0m): {weights[3].detach()}")

target = torch.randn(batch, 2)
loss = model.loss(gaze, target, weights, dist)
print(f"损失: {loss.item():.4f}")
print(f"参数量: {sum(p.numel() for p in model.parameters()):,}")

2. 人脸距离估计方法

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
"""
从单摄像头人脸图像估计度量距离
基于人脸关键点的几何方法
"""

import numpy as np
import cv2

class FaceDistanceEstimator:
"""
人脸到摄像头度量距离估计器

方法:
1. 检测人脸关键点
2. 计算双瞳间距(像素)
3. 利用已知平均瞳距(~6.3cm)和摄像头焦距
4. 三角测量计算距离
"""

def __init__(self, focal_length_px: float = 800.0,
avg_ipd_cm: float = 6.3):
"""
Args:
focal_length_px: 摄像头焦距(像素)
avg_ipd_cm: 平均瞳距(厘米),成人约6.3cm
"""
self.f = focal_length_px
self.ipd_real = avg_ipd_cm # cm

def estimate_distance(self, left_eye_center: tuple,
right_eye_center: tuple) -> float:
"""
估计人脸到摄像头距离

Args:
left_eye_center: (x, y) 左眼中心像素坐标
right_eye_center: (x, y) 右眼中心像素坐标

Returns:
distance_m: 估计距离(米)
"""
# 像素瞳距
ipd_px = np.sqrt(
(right_eye_center[0] - left_eye_center[0])**2 +
(right_eye_center[1] - left_eye_center[1])**2
)

if ipd_px < 5: # 太小,可能检测错误
return -1.0

# 透视投影: 距离 = (focal * real_size) / (pixel_size)
distance_cm = (self.f * self.ipd_real) / ipd_px
distance_m = distance_cm / 100.0

return distance_m

def estimate_from_landmarks(self, landmarks: np.ndarray) -> dict:
"""
从完整人脸关键点估计距离和姿态

Args:
landmarks: (68, 2) 或 (468, 2) 人脸关键点

Returns:
{'distance_m': float, 'head_pose': (pitch, yaw, roll)}
"""
# 简化版:使用关键点 36-41 (左眼) 和 42-47 (右眼)
if landmarks.shape[0] >= 48:
left_eye = np.mean(landmarks[36:42], axis=0)
right_eye = np.mean(landmarks[42:48], axis=0)
else:
# 假设已裁剪
left_eye = landmarks[0]
right_eye = landmarks[1]

distance = self.estimate_distance(left_eye, right_eye)

# 头部姿态估计(简化)
# 使用solvePnP需要3D模型点
pitch = 0.0 # placeholder
yaw = 0.0
roll = np.arctan2(
right_eye[1] - left_eye[1],
right_eye[0] - left_eye[0]
) * 180 / np.pi

return {
'distance_m': distance,
'head_pose': (pitch, yaw, roll),
'ipd_px': np.linalg.norm(right_eye - left_eye)
}


# 测试
if __name__ == "__main__":
estimator = FaceDistanceEstimator(focal_length_px=800, avg_ipd_cm=6.3)

# 模拟: 不同距离下的瞳距
for dist_m in [0.3, 0.5, 0.7, 1.0, 1.5]:
# 距离 → 像素瞳距 = focal * real_size / distance
ipd_px = 800 * 6.3 / (dist_m * 100)
left = (320 - ipd_px/2, 240)
right = (320 + ipd_px/2, 240)

est_dist = estimator.estimate_distance(left, right)
print(f"真实距离: {dist_m:.1f}m, 像素瞳距: {ipd_px:.1f}px, "
f"估计距离: {est_dist:.2f}m")

3. 对 IMS 开发的启示

3.1 人脸距离在 DMS 中的应用

应用 描述 价值
自适应视线模型 不同距离使用不同分支权重 远距离时避免眼部特征不可靠
座舱 ergonomic 检测驾驶员是否太近/太远 安全驾驶建议
乘员分类 距离+体型 → 成人/儿童判断 OMS 辅助
3D 视线落点 距离信息辅助 2D→3D 视线映射 提高视线精度

3.2 开发建议

优先级 建议
🔴 P0 在 DMS pipeline 中加入人脸距离估计
🔴 P0 根据距离动态调整眼部/面部分支权重
🟡 P1 用距离信息改进 PERCLOS 计算(眼睑开度归一化)
🟢 P2 多摄像头三角测量提高距离精度

4. 参考


MultiNet:基于人脸到摄像头距离的多分支视线估计(MDPI Sensors 2026 论文解读)
https://dapalm.com/2026/10/06/2026-10-06-003-multinet-gaze-estimation-face-distance/
作者
Mars
发布于
2026年10月6日
许可协议