轻量级几何 3D 视线跟踪:无需训练的消费者级硬件方案

轻量级几何 3D 视线跟踪:无需训练的消费者级硬件方案

论文信息


核心创新

首个无需训练的几何 3D 视线跟踪框架

  • 消费者级硬件: 普通红外摄像头即可实现
  • 无训练需求: 纯几何方法,无需深度学习训练
  • 高精度: 角度误差 <2°
  • 实时性: 30fps+ 处理速度
  • 双目立体三角测量: 重建 3D 视线向量

方法详解

1. 整体流程

graph LR
    A[红外图像] --> B[眼部检测]
    B --> C[瞳孔定位]
    C --> D[眼睑分割]
    D --> E[角膜反射检测]
    E --> F[3D三角测量]
    F --> G[视线向量]
    G --> H[落点估计]

2. 几何核心算法

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
"""
轻量级几何 3D 视线跟踪算法

核心方法:
1. 瞳孔中心定位
2. 角膜反射检测
3. 眼球模型拟合
4. 双目立体三角测量
"""

import numpy as np
from typing import Tuple, Dict

def detect_pupil_center(
eye_image: np.ndarray,
threshold: float = 0.3
) -> Tuple[float, float]:
"""
检测瞳孔中心

Args:
eye_image: 眼部红外图像, shape=(H, W)
threshold: 瞳孔分割阈值

Returns:
pupil_center: 瞳孔中心坐标

几何方法:
1. 阈值分割(瞳孔暗区)
2. 形态学处理
3. 椭圆拟合
"""
# 1. 阈值分割(瞳孔区域)
binary = eye_image < (threshold * np.max(eye_image))

# 2. 形态学处理
from scipy.ndimage import binary_closing, binary_opening
binary = binary_closing(binary, iterations=2)
binary = binary_opening(binary, iterations=1)

# 3. 找最大连通区域(瞳孔)
from scipy.ndimage import label
labeled, num_features = label(binary)

if num_features > 0:
# 最大区域
region_sizes = np.bincount(labeled.ravel())[1:]
largest_region = np.argmax(region_sizes) + 1

# 区域坐标
y_coords, x_coords = np.where(labeled == largest_region)

# 椭圆拟合
pupil_center = (np.mean(x_coords), np.mean(y_coords))
else:
pupil_center = (eye_image.shape[1] / 2, eye_image.shape[0] / 2)

return pupil_center

def detect_corneal_reflection(
eye_image: np.ndarray,
glint_threshold: float = 0.9
) -> Tuple[float, float]:
"""
检测角膜反射(红外光斑)

Args:
eye_image: 眼部红外图像
glint_threshold: 反射阈值

Returns:
glint_center: 反射光斑中心

几何方法:
红外光源在角膜上形成高亮光斑
用于估计眼球 3D 位置
"""
# 高亮区域检测
binary = eye_image > (glint_threshold * np.max(eye_image))

# 找最大亮点
from scipy.ndimage import label
labeled, num_features = label(binary)

if num_features > 0:
region_sizes = np.bincount(labeled.ravel())[1:]
largest_region = np.argmax(region_sizes) + 1

y_coords, x_coords = np.where(labeled == largest_region)
glint_center = (np.mean(x_coords), np.mean(y_coords))
else:
glint_center = (eye_image.shape[1] / 2, eye_image.shape[0] / 2)

return glint_center

def estimate_eye_ball_center(
pupil_center: Tuple[float, float],
glint_center: Tuple[float, float],
camera_matrix: np.ndarray,
light_position: np.ndarray
) -> np.ndarray:
"""
估计眼球中心

Args:
pupil_center: 瞳孔中心(像素)
glint_center: 反射光斑中心(像素)
camera_matrix: 相机内参矩阵
light_position: 红外光源位置(世界坐标)

Returns:
eye_ball_center: 眼球中心(世界坐标)

几何方法:
1. 光斑位置 + 光源位置 → 角膜表面法向量
2. 瞳孔位置 + 角膜位置 → 眼球中心
"""
# 反投影到 3D 射线
glint_pixel = np.array([glint_center[0], glint_center[1], 1.0])
glint_ray = np.linalg.inv(camera_matrix) @ glint_pixel
glint_ray = glint_ray / np.linalg.norm(glint_ray)

# 角膜反射几何
# 光源 → 角膜 → 相机
# 简化:假设角膜半径 R = 7.8mm
R_cornea = 7.8 # mm

# 角膜中心估计(简化)
# 实际需要解非线性方程
eye_ball_center = np.array([0, 0, 500]) # mm,相机坐标系

return eye_ball_center

def triangulate_gaze_vector(
left_eye_center: np.ndarray,
right_eye_center: np.ndarray,
left_pupil: Tuple[float, float],
right_pupil: Tuple[float, float],
camera_matrix: np.ndarray
) -> np.ndarray:
"""
双目立体三角测量重建 3D 视线向量

Args:
left_eye_center: 左眼球中心(世界坐标)
right_eye_center: 右眼球中心(世界坐标)
left_pupil: 左瞳孔中心(像素)
right_pupil: 右瞳孔中心(像素)
camera_matrix: 相机内参矩阵

Returns:
gaze_vector: 3D 视线向量

几何方法:
1. 双目视线交点(注视点)
2. 眼球中心 → 注视点 = 视线向量
"""
# 反投影瞳孔到 3D 射线
left_pupil_pixel = np.array([left_pupil[0], left_pupil[1], 1.0])
left_ray = np.linalg.inv(camera_matrix) @ left_pupil_pixel
left_ray = left_ray / np.linalg.norm(left_ray)

right_pupil_pixel = np.array([right_pupil[0], right_pupil[1], 1.0])
right_ray = np.linalg.inv(camera_matrix) @ right_pupil_pixel
right_ray = right_ray / np.linalg.norm(right_ray)

# 双目三角测量(简化)
# 假设双目基线距离 60mm
baseline = 60.0 # mm

# 视线交点估计
# 实际需要解非线性方程
gaze_point = np.array([0, 0, 1000]) # mm,前方1m

# 视线向量(从双眼中心到注视点)
eye_center_avg = (left_eye_center + right_eye_center) / 2
gaze_vector = gaze_point - eye_center_avg
gaze_vector = gaze_vector / np.linalg.norm(gaze_vector)

return gaze_vector

# 实际测试代码
if __name__ == "__main__":
# 模拟红外眼部图像
H, W = 120, 160
eye_image = np.random.rand(H, W) * 0.3

# 添加瞳孔(暗区)
cv2 = __import__('cv2')
cv2.circle(eye_image, (80, 60), 15, 0.1, -1)

# 添加光斑(亮区)
cv2.circle(eye_image, (75, 55), 5, 1.0, -1)

# 检测瞳孔和光斑
pupil_center = detect_pupil_center(eye_image)
glint_center = detect_corneal_reflection(eye_image)

print("="*60)
print("轻量级几何 3D 视线跟踪测试")
print("="*60)
print(f"瞳孔中心: {pupil_center}")
print(f"光斑中心: {glint_center}")

# 模拟相机内参
camera_matrix = np.array([
[500, 0, 80],
[0, 500, 60],
[0, 0, 1]
])

# 估计眼球中心
eye_ball_center = estimate_eye_ball_center(
pupil_center, glint_center, camera_matrix, np.array([0, 0, 0])
)
print(f"眼球中心: {eye_ball_center}")

与深度学习方法对比

特性 几何方法(本文) 深度学习方法
训练需求 ❌ 无需训练 ✅ 需大规模数据集
硬件要求 低(CPU即可) 高(需GPU)
实时性 高(>60fps) 中(30-60fps)
精度 高(<2°) 高(<1°)
泛化性 强(几何约束) 弱(依赖数据)
部署难度 高(需模型优化)

DMS 应用场景

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
def estimate_gaze_point(
gaze_vector: np.ndarray,
eye_position: np.ndarray,
screen_plane: np.ndarray # 屏幕平面方程
) -> np.ndarray:
"""
估计视线落点

Args:
gaze_vector: 视线向量
eye_position: 眼球位置
screen_plane: 屏幕平面方程 ax + by + cz + d = 0

Returns:
gaze_point: 视线在屏幕上的落点

DMS 应用:
判断驾驶员是否看向前方道路
"""
# 视线与屏幕平面求交
# parametric: P = eye + t * gaze
# plane: a*x + b*y + c*z + d = 0

# 代入参数方程
# a*(eye_x + t*gaze_x) + b*(eye_y + t*gaze_y) + c*(eye_z + t*gaze_z) + d = 0

a, b, c, d = screen_plane

# 解 t
numerator = -(a * eye_position[0] + b * eye_position[1] + c * eye_position[2] + d)
denominator = a * gaze_vector[0] + b * gaze_vector[1] + c * gaze_vector[2]

if abs(denominator) < 1e-6:
return None # 视线与平面平行

t = numerator / denominator

# 落点
gaze_point = eye_position + t * gaze_vector

return gaze_point

2. 分心检测

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 detect_distraction(
gaze_point: np.ndarray,
road_region: Tuple[float, float, float, float],
threshold_time: float = 3.0
) -> Tuple[bool, str]:
"""
检测分心

Args:
gaze_point: 视线落点
road_region: 道路区域(前方视野范围)
threshold_time: 分心判定阈值(秒)

Returns:
is_distraction: 是否分心
level: 分心等级

DMS 应用:
视线偏离道路区域 >3秒 → 分心警告
"""
x_min, y_min, x_max, y_max = road_region

# 判断视线是否在道路区域内
in_road = (
gaze_point[0] >= x_min and gaze_point[0] <= x_max and
gaze_point[1] >= y_min and gaze_point[1] <= y_max
)

# 分心判定(简化:单帧)
# 实际需要累积时间
is_distraction = not in_road

if is_distraction:
# 判断分心方向
if gaze_point[0] < x_min:
level = "left_distraction"
elif gaze_point[0] > x_max:
level = "right_distraction"
elif gaze_point[1] < y_min:
level = "up_distraction"
else:
level = "down_distraction"
else:
level = "normal"

return is_distraction, level

消费者级硬件配置

组件 参数 成本
红外摄像头 720p, 60fps $20-30
红外光源 850nm LED $5-10
计算平台 ARM Cortex-A53 $30-50
总成本 - $55-90

IMS 开发优先级

任务 几何方法支持 IMS 优先级 开发难度
视线落点估计 ✅ 核心 P0
分心检测 ✅ 可用 P0
疲劳检测 ⚠️ 需扩展 P1
多标定适配 ⚠️ 需扩展 P2

数据来源


IMS 开发启示

  1. 几何方法优势: 无需训练、低硬件要求、强泛化性,适合嵌入式部署
  2. 精度 vs 成本权衡: 几何方法精度略低于深度学习,但成本降低 >90%
  3. 红外光源必需: 角膜反射检测需要红外光源,DMS 系统需标配
  4. 双目优于单目: 双目立体测量精度显著高于单目,但硬件成本增加
  5. 实时性保障: 纯几何计算 CPU 即可 >60fps,无需 GPU

总结: 轻量级几何 3D 视线跟踪框架提供了无需训练、消费者级硬件即可实现的高精度方案。核心方法包括瞳孔定位、角膜反射检测、双目立体三角测量,角度误差 <2°。相比深度学习方法,无需训练、硬件成本低、实时性高、泛化性强,非常适合嵌入式 DMS 部署。IMS 开发应优先采用几何方法降低成本,在精度要求极高场景(如 L3 接管评估)可结合深度学习方法提升精度。


轻量级几何 3D 视线跟踪:无需训练的消费者级硬件方案
https://dapalm.com/2026/07/10/2026-07-10-lightweight-geometric-3d-gaze-tracking-training-free-consumer-hardware/
作者
Mars
发布于
2026年7月10日
许可协议