散焦深度无标记眼动估计:单摄像头单帧视线方向推断

散焦深度无标记眼动估计:单摄像头单帧视线方向推断

论文信息

项目 内容
标题 Marker-free eye-gaze estimation using a single image and depth from defocus
arXiv 2609.09610
日期 2026-09-09
领域 cs.CV

核心创新

提出散焦深度(Depth from Defocus, DfD)+ 变分贝叶斯逻辑回归的单摄像头无标记眼动估计方案:

特性 规格
传感器 单个 2D 摄像头(如笔记本摄像头)
标记 不需要外部标记
输入 单帧图像
特征 8维(头部姿态6维 + 虹膜位移2维)
映射 变分贝叶斯多项逻辑回归

与现有方法对比

方法 传感器 标记 精度 部署
红外标记 IR摄像头+标记 ✅ 需要 实验室
几何模型 多摄像头 复杂
CNN回归 单摄像头 中高 需GPU
DfD+VB 单摄像头 CPU即可

方法详解

1. 散焦深度(Depth from Defocus)

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
import numpy as np
import cv2

class DepthFromDefocus:
"""
散焦深度估计

原理:
- 物体在不同距离时,成像模糊程度不同
- 近距离物体更清晰,远距离更模糊
- 通过模糊度估计深度 → 头部姿态

优势:
- 单摄像头,无需深度传感器
- 无需外部标记
- 计算量小(CPU可运行)
"""

def __init__(self, focal_length_mm=6, aperture_f=2.8,
sensor_width_mm=4.8):
self.focal_length = focal_length_mm
self.aperture = aperture_f
self.sensor_width = sensor_width_mm

def estimate_depth(self, image: np.ndarray,
focus_region: tuple = None) -> float:
"""
从模糊度估计深度

Args:
image: 输入图像
focus_region: (x, y, w, h) 对焦区域

Returns:
depth_mm: 估计深度(毫米)
"""
if focus_region:
x, y, w, h = focus_region
region = image[y:y+h, x:x+w]
else:
region = image

# 计算模糊度(高频能量比)
gray = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY)

# Laplacian 方差(越高 = 越清晰 = 越近)
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()

# Tenengrad 梯度
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
tenengrad = np.mean(sobel_x**2 + sobel_y**2)

# 模糊度量
blur_metric = 1.0 / (laplacian_var + 1e-6)

# 从模糊度推断深度(标定后)
# 深度 ∝ 1/清晰度
depth_mm = self._blur_to_depth(blur_metric, tenengrad)

return depth_mm

def _blur_to_depth(self, blur: float, tenengrad: float) -> float:
"""模糊度到深度的映射(需标定)"""
# 简化模型:深度 = K / 清晰度
clarity = np.sqrt(tenengrad)
K = 500 # 标定常数
return K / (clarity + 1e-6)

def estimate_head_pose(self, image, face_landmarks=None):
"""
从散焦深度估计头部姿态

返回: (pitch, yaw, roll, tx, ty, tz)
"""
# 估计深度
depth = self.estimate_depth(image)

# 如果有面部关键点,用 PnP 估计姿态
if face_landmarks is not None:
# 使用 3D 面部模型 + 2D 关键点
rvec, tvec = self._solve_pnp(face_landmarks, depth)
return rvec, tvec

# 无关键点时,用深度推断距离
return np.array([0, 0, 0]), np.array([0, 0, depth])


class IrisDisplacement:
"""虹膜位移估计"""

def __init__(self):
self.face_detector = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_eye.xml'
)

def estimate_iris_displacement(self, image, face_box):
"""
估计虹膜位移(相对于眼眶中心)

Returns:
(dx, dy): 虹膜在眼眶中的位移
"""
x, y, w, h = face_box
roi = image[y:y+h, x:x+w]

# 检测眼睛
eyes = self.face_detector.detectMultiScale(
roi, 1.1, 5, minSize=(30, 30)
)

if len(eyes) < 2:
return 0.0, 0.0

displacements = []
for (ex, ey, ew, eh) in eyes[:2]:
# 眼眶中心
eye_center_x = ex + ew / 2
eye_center_y = ey + eh / 2

# 虹膜中心(灰度最小值)
eye_roi = roi[ey:ey+eh, ex:ex+ew]
gray = cv2.cvtColor(eye_roi, cv2.COLOR_BGR2GRAY)

# 找最暗区域(虹膜)
_, thresh = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)

if contours:
iris = max(contours, key=cv2.contourArea)
M = cv2.moments(iris)
if M['m00'] > 0:
iris_x = M['m10'] / M['m00']
iris_y = M['m01'] / M['m00']

dx = iris_x - ew / 2
dy = iris_y - eh / 2
displacements.append((dx, dy))

if displacements:
return np.mean(displacements, axis=0)
return 0.0, 0.0


class VariationalBayesGaze:
"""
变分贝叶斯多项逻辑回归

8维特征 → 注视点 (x, y)

特征向量:
[pitch, yaw, roll, tx, ty, tz, iris_dx, iris_dy]
"""

def __init__(self, n_features=8, n_classes_x=15, n_classes_y=15):
self.n_features = n_features
self.n_classes_x = n_classes_x # X方向离散网格
self.n_classes_y = n_classes_y # Y方向离散网格

# 权重(变分贝叶斯学习)
self.W_x = np.random.randn(n_features, n_classes_x) * 0.01
self.W_y = np.random.randn(n_features, n_classes_y) * 0.01

# 变分参数
self.alpha = np.ones(n_features) # 精度先验

def predict(self, features: np.ndarray) -> tuple:
"""
预测注视点

Args:
features: 8维特征向量

Returns:
(gaze_x, gaze_y): 注视点坐标
"""
# Softmax 分类
logits_x = features @ self.W_x
logits_y = features @ self.W_y

probs_x = np.exp(logits_x) / np.sum(np.exp(logits_x))
probs_y = np.exp(logits_y) / np.sum(np.exp(logits_y))

# 期望值
gaze_x = np.sum(probs_x * np.arange(self.n_classes_x)) / self.n_classes_x
gaze_y = np.sum(probs_y * np.arange(self.n_classes_y)) / self.n_classes_y

return gaze_x, gaze_y

def train(self, features: np.ndarray, gaze_x: np.ndarray,
gaze_y: np.ndarray, n_epochs=100):
"""变分贝叶斯训练"""
for epoch in range(n_epochs):
# 变分更新(简化)
grad_x = features.T @ (self._softmax(features @ self.W_x) -
self._onehot(gaze_x, self.n_classes_x))
grad_y = features.T @ (self._softmax(features @ self.W_y) -
self._onehot(gaze_y, self.n_classes_y))

self.W_x += 0.01 * grad_x
self.W_y += 0.01 * grad_y

def _softmax(self, x):
return np.exp(x) / np.sum(np.exp(x), axis=-1, keepdims=True)

def _onehot(self, idx, n):
oh = np.zeros(n)
oh[int(idx * n)] = 1
return oh


# 完整流水线
if __name__ == "__main__":
# 1. 散焦深度
dfd = DepthFromDefocus()

# 2. 虹膜位移
iris = IrisDisplacement()

# 3. 变分贝叶斯映射
vbg = VariationalBayesGaze(n_features=8)

# 模拟流水线
image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
face_box = (200, 150, 100, 100)

depth = dfd.estimate_depth(image, (200, 150, 100, 100))
print(f"估计深度: {depth:.1f}mm")

dx, dy = iris.estimate_iris_displacement(image, face_box)
print(f"虹膜位移: dx={dx:.2f}, dy={dy:.2f}")

features = np.array([0, 0, 0, 0, 0, depth, dx, dy])
gaze_x, gaze_y = vbg.predict(features)
print(f"注视点: ({gaze_x:.3f}, {gaze_y:.3f})")

IMS 应用启示

1. 低成本 DMS 方案

方案 传感器 成本 精度 适用
IR+标记 IR摄像头 $15 量产
CNN回归 RGB摄像头 $10 中高 主流
DfD+VB RGB摄像头 $8 低端

2. 单摄像头优势

优势 说明
硬件成本最低 仅 1 个 RGB 摄像头
无需标记 不需要 IR 补光/标记点
CPU 可运行 无需 NPU
单帧推理 无需多帧积分

3. 部署限制

限制 说明 缓解方案
精度中等 ~3-5° vs IR 1-2° 多帧平均
距离敏感 散焦与距离非线性 需标定
光照敏感 RGB 无 IR CLAHE 增强
墨镜失效 虹膜不可见 IR 备选

总结

DfD+VB 方案为低成本 DMS 提供了可行路径:

  1. 单摄像头 CPU 可运行:最低成本的 DMS 方案
  2. 散焦深度是创新点:从模糊度推断头部距离
  3. 8维特征足够:头部姿态6维 + 虹膜位移2维
  4. 变分贝叶斯比 CNN 轻量:无需 GPU
  5. 适合入门级车型:精度中等但成本极低

散焦深度无标记眼动估计:单摄像头单帧视线方向推断
https://dapalm.com/2026/09/11/2026-09-11-depth-from-defocus-marker-free-gaze-estimation-single-camera-ims/
作者
Mars
发布于
2026年9月11日
许可协议