简介:这份资源围绕三维视线估计展开,面向计算机视觉与深度学习方向的学习者和开发者,解决从眼睛或人脸图像中推导视线方向的问题。其核心思路是用深度卷积神经网络对虹膜与瞳孔像素进行分类,提取特征并跟踪三维眼球的注视状态,同时兼顾眼睛状态与头部姿态对视线的影响,适合做人眼交互、注意力分析或相关课题复现。压缩包共37个文件,约14.37MB,包含11个Python脚本、6个pyc、4个xml、3个params、3个json,以及tflite、npy、mp4、avi等模型与演示文件,覆盖人脸检测、对齐、头部姿态、虹膜定位与注视分割等模块,并附参考链接与说明文档。目前已有1287人学习下载。读者可据此了解三维视线估计的完整工程结构,参考模型权重与推理脚本,结合演示视频快速验证效果,并在此基础上做二次开发或实验对比。
1. 三维视线估计落地:从虹膜分割到激光射线可视化
你盯着屏幕,摄像头捕捉到你的脸,然后一条激光射线从你眼球射出,打在屏幕上——这不是科幻,是 Laser-Eye 这个项目干的事。三维视线估计的目标是从眼睛或人脸图片中推导出视线方向,方向跟眼睛状态和头部姿态都有关。这个资源包用深度卷积网络对虹膜和瞳孔像素做分类,提取特征后跟踪三维眼球的注视状态,最终用激光射线把视线方向画出来。适合做视线追踪、人机交互、注意力分析的从业者,也适合想跑通三维视线估计全流程的深度学习工程师。代码结构清晰,权重文件齐全,能直接复现。
2. 拆开 Laser-Eye:模块分工与数据流
2.1 从人脸检测到虹膜定位的流水线
整个项目的数据流是串行的:输入图像先经过人脸检测,再做人脸对齐,然后估计头部姿态,接着定位虹膜,最后计算视线方向并渲染激光射线。每个环节都有独立的模块,方便替换和调试。
face_detector.py负责人脸检测,用的是 OpenCV 的 DNN 模块加载预训练模型。face_alignment.py做人脸关键点对齐,输出 106 个关键点,这个格式在2d106det-symbol.json和2d106det-0000.params里定义。head_pose.py根据关键点计算头部姿态角,包括俯仰、偏航、滚转。iris_localization.py是核心,用iris-symbol.json和iris-0000.params做虹膜分割,输出虹膜和瞳孔的像素级分类。gaze_segmentation.py把分割结果转成视线向量。gaze_laser.py负责把视线向量投射到三维空间,生成激光射线效果。
generate_anchor.py生成锚点,用于虹膜定位网络的训练或推理。model_update.py可能是模型更新脚本,但实际用到的概率不高。object_points.npy存的是三维人脸模型的关键点坐标,用于头部姿态估计。16and32-symbol.json和16and32-0000.params是另一个网络,可能是用于虹膜关键点检测的轻量模型。iris_landmark.tflite是 TensorFlow Lite 模型,适合移动端部署。
test.py是入口脚本,draw_lemon.py可能是画柠檬的演示脚本,asset目录下有logo.webp和flame.mp4,output.avi是输出视频。Readme.md和LICENSE是文档和协议。
2.2 权重文件与模型选型理由
项目提供了多个权重文件,每个都有明确用途。2d106det是人脸关键点检测模型,106 个点覆盖了眉毛、眼睛、鼻子、嘴巴和脸部轮廓。iris是虹膜分割模型,输出虹膜和瞳孔的像素级掩码。16and32是虹膜关键点检测模型,16 和 32 可能指输入分辨率或特征图大小。iris_landmark.tflite是移动端优化版本。
为什么用分割而不是直接回归视线向量?因为分割能提供像素级监督,对虹膜和瞳孔的边界更敏感,尤其在眼睛半闭或戴眼镜时,回归容易受遮挡影响。分割后计算质心,再结合头部姿态,视线方向的鲁棒性更高。这是常见做法,也是这个项目能跑通的关键。
提示:权重文件必须和 JSON 文件配对使用,缺一不可。
-symbol.json定义网络结构,-0000.params存参数。
3. 跑通 demo:环境配置与推理步骤
3.1 环境依赖与安装
项目依赖 MXNet 和 OpenCV。MXNet 用于加载-symbol.json和-0000.params,OpenCV 用于图像处理和 DNN 推理。Python 版本建议 3.6 到 3.8,MXNet 用 1.6 或 1.7。TensorFlow Lite 只在用iris_landmark.tflite时需要。
pip install mxnet==1.7.0 pip install opencv-python==4.5.5 pip install numpy==1.19.5 pip install tflite-runtime # 可选,仅当使用 tflite 模型MXNet 的 GPU 版本可以换成mxnet-cu102,但 CPU 版本足够跑通 demo。OpenCV 必须包含 DNN 模块,opencv-python默认包含。numpy 版本不要太高,1.20 以上可能和 MXNet 有兼容问题。
3.2 运行 test.py 的完整流程
test.py是主入口,默认读取摄像头或视频文件。先看代码里的参数:
# test.py 关键参数 cap = cv2.VideoCapture(0) # 0 表示摄像头,改成视频路径可读文件 face_detector = FaceDetector() # 人脸检测器 face_alignment = FaceAlignment() # 人脸对齐 head_pose = HeadPose() # 头部姿态 iris_localization = IrisLocalization() # 虹膜定位 gaze_laser = GazeLaser() # 激光渲染 while True: ret, frame = cap.read() if not ret: break faces = face_detector.detect(frame) for face in faces: landmarks = face_alignment.align(frame, face) pose = head_pose.estimate(landmarks) iris_mask = iris_localization.segment(frame, face) gaze_vector = gaze_segmentation.compute(iris_mask, pose) frame = gaze_laser.draw(frame, gaze_vector) cv2.imshow('Gaze', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break逻辑说明:先检测人脸,再对齐关键点,然后估计头部姿态,接着分割虹膜,计算视线向量,最后画激光射线。参数说明:cv2.VideoCapture(0)的 0 是摄像头索引,换成'video.mp4'可读文件。face_detector.detect返回人脸框列表,face_alignment.align返回 106 个关键点。head_pose.estimate返回三个角度。iris_localization.segment返回二值掩码。gaze_segmentation.compute返回三维向量。gaze_laser.draw在原图上画射线。
运行命令:
python test.py如果摄像头打不开,检查cv2.VideoCapture的参数,或者用ls /dev/video*确认设备号。如果报错找不到权重文件,检查weights目录是否在项目根目录下,且文件名大小写一致。
3.3 用 draw_lemon.py 做可视化调试
draw_lemon.py是一个独立的可视化脚本,用来画柠檬形状的视线指示器。它不依赖摄像头,直接读取图片或生成合成图像。适合调试虹膜分割和视线计算。
# draw_lemon.py 关键片段 import cv2 import numpy as np from service.iris_localization import IrisLocalization iris = IrisLocalization() img = cv2.imread('asset/logo.webp') mask = iris.segment(img, face_box=None) # 不传人脸框,直接分割 lemon = draw_lemon(mask) # 画柠檬形状 cv2.imshow('Lemon', lemon) cv2.waitKey(0)逻辑说明:IrisLocalization.segment可以接受face_box=None,此时会对整张图做虹膜分割。draw_lemon根据掩码画一个柠檬形状的轮廓,用来直观检查分割效果。参数说明:img是输入图像,mask是二值掩码,lemon是叠加了柠檬轮廓的图像。
运行:
python draw_lemon.py如果报错ModuleNotFoundError: No module named 'service',检查当前工作目录是否在项目根目录下。service是一个包,里面有__init__.py,必须从根目录运行。
注意:
draw_lemon.py里的face_box=None只适合调试,实际推理必须传人脸框,否则虹膜定位会受背景干扰。
4. 避坑与排查:权重加载、姿态角与性能问题
4.1 权重加载失败:JSON 和 params 不匹配
现象:运行test.py时报错mxnet.base.MXNetError: [11:23:45] src/nnvm/legacy_json_util.cc:209: Loading symbol saved by previous version...或者Check failed: header == 0x00000000。
原因:-symbol.json和-0000.params版本不匹配,或者文件损坏。常见于从不同来源下载的权重混用。
解决:确保2d106det-symbol.json和2d106det-0000.params来自同一个压缩包。iris-symbol.json和iris-0000.params同理。不要单独替换其中一个文件。如果文件损坏,重新解压Laser-Eye-master.zip。
4.2 头部姿态角异常:object_points.npy 坐标系不匹配
现象:头部姿态估计输出的角度跳变严重,或者视线方向明显偏离。
原因:object_points.npy里的三维关键点坐标系和face_alignment.py输出的二维关键点顺序不一致。常见于关键点顺序被修改或替换。
解决:检查face_alignment.py里关键点的索引顺序,确保和object_points.npy的行顺序对应。通常 106 个关键点里,眼睛、鼻子、嘴巴的索引是固定的。不要随意调整face_alignment.py的输出顺序。如果必须改,同步修改object_points.npy。
4.3 虹膜分割掩码全黑:输入尺寸或归一化错误
现象:iris_localization.segment返回的掩码全是 0,或者只有零星几个像素。
原因:输入图像的尺寸或归一化方式不对。虹膜分割网络对输入尺寸敏感,通常要求 256x256 或 128x128。如果直接传原始帧,网络可能无法正确响应。
解决:在iris_localization.py里找到预处理部分,确认cv2.resize的目标尺寸和mean、std参数。常见做法是缩放到 256x256,减去均值 127.5,除以 128。不要跳过预处理。
4.4 帧率过低:MXNet 推理未启用 GPU 或未做批处理
现象:test.py跑起来只有 2 到 3 帧,摄像头画面卡顿。
原因:MXNet 默认用 CPU 推理,且每个模块单独调用,没有批处理。人脸检测、对齐、虹膜分割串行执行,耗时叠加。
解决:安装mxnet-cu102并设置mxnet.context.Context('gpu', 0)。在face_detector.py、face_alignment.py、iris_localization.py里把ctx改成 GPU。如果 GPU 不可用,降低输入分辨率,比如把摄像头帧缩放到 640x480 再处理。不要同时开多个高分辨率模型。
4.5 激光射线方向反了:视线向量符号错误
现象:激光射线从眼睛射出,但方向指向人脸内部或反方向。
原因:gaze_segmentation.compute返回的向量符号和gaze_laser.draw的坐标系不一致。常见于 y 轴方向定义不同。
解决:在gaze_laser.py里检查draw函数的向量加法。如果射线方向反了,把gaze_vector取反,或者调整head_pose的旋转矩阵。不要直接改gaze_segmentation,先确认坐标系定义。
5. 进阶技巧:用 tflite 模型做移动端部署与精度验证
iris_landmark.tflite是 TensorFlow Lite 模型,适合在移动端或嵌入式设备上跑虹膜关键点检测。和 MXNet 版本相比,tflite 模型体积小,推理速度快,但精度略低。我一般会先用 MXNet 版本验证算法流程,再用 tflite 版本做端侧部署。
加载 tflite 模型的代码:
import tflite_runtime.interpreter as tflite import numpy as np import cv2 interpreter = tflite.Interpreter(model_path='weights/iris_landmark.tflite') interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() img = cv2.imread('asset/logo.webp') img = cv2.resize(img, (input_details[0]['shape'][1], input_details[0]['shape'][2])) img = img.astype(np.float32) / 255.0 img = np.expand_dims(img, axis=0) interpreter.set_tensor(input_details[0]['index'], img) interpreter.invoke() output = interpreter.get_tensor(output_details[0]['index']) print(output.shape) # 通常是 (1, 106, 2) 或 (1, 32, 32)逻辑说明:tflite.Interpreter加载模型,allocate_tensors分配内存,get_input_details和get_output_details获取输入输出张量信息。输入图像缩放到模型要求的尺寸,归一化到 0 到 1,增加 batch 维度。invoke执行推理,get_tensor取输出。参数说明:input_details[0]['shape']是模型输入形状,通常是[1, 192, 192, 3]或[1, 256, 256, 3]。output_details[0]['index']是输出张量索引。
精度验证方法:用同一张图片分别跑 MXNet 版本和 tflite 版本,比较虹膜关键点的欧氏距离。如果距离小于 2 个像素,说明 tflite 版本可用。如果大于 5 个像素,检查预处理是否一致。常见坑是 tflite 版本要求 RGB 输入,而 OpenCV 默认 BGR,需要cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。
移动端部署时,把iris_landmark.tflite和face_detector的轻量版本一起打包。人脸检测可以用 OpenCV 的 DNN 模块加载res10_300x300_ssd_iter_140000.caffemodel,或者用 MediaPipe 的人脸检测。虹膜关键点用 tflite,视线计算用 numpy 实现,不依赖 MXNet。这样整个流程可以在 Android 或 iOS 上跑。
提示:tflite 模型的输入尺寸和归一化参数必须和训练时一致,否则输出会完全错误。不要凭经验猜,用
interpreter.get_input_details()打印出来看。
从那以后我每次部署新模型前,都强制走一遍「打印输入输出形状 → 用同一张图对比 MXNet 和 tflite 输出 → 确认预处理一致」的流程。这个习惯帮我省了很多返工时间。希望帮到你。
本文还有配套的精品资源,点击获取