首页 / 资讯中心 / 文章详情

Laser-Eye-master三维视线估计:从人脸关键点到注视向量实战

Laser-Eye-master三维视线估计:从人脸关键点到注视向量实战 ★ FEATURED ARTICLE
简介这份资源围绕三维视线估计展开面向计算机视觉与深度学习方向的学习者和开发者解决从眼睛或人脸图像中推导视线方向的问题。其核心思路是用深度卷积神经网络对虹膜与瞳孔像素进行分类提取特征并跟踪三维眼球的注视状态同时兼顾眼睛状态与头部姿态对视线的影响适合作为视线估计入门与工程复现的参考。压缩包共37个文件约14.37MB以py脚本为主体配合pyc缓存、xml与json配置、params权重、tflite模型及npy数据文件另附mp4与avi演示视频、md说明和license覆盖人脸检测、对齐、头部姿态、虹膜定位与注视分割等模块。已有1287人学习下载。借助这些脚本与权重读者可快速跑通从人脸检测到三维注视估计的完整流程理解虹膜与瞳孔分类、关键点回归和模型加载等关键环节并对照演示视频验证效果。1. 从一张自拍到三维视线这套 Laser-Eye 资源到底能跑出什么你手头有一张普通摄像头拍的人脸照想算出这个人到底在看屏幕的哪个位置——不是「头朝向哪」而是「眼球真正注视哪」。这件事在学术上叫三维视线估计工业界落地场景包括驾驶疲劳监测、远程会议注意力分析、零售货架热区统计、无障碍眼控交互。难点在于视线方向由眼球自身旋转和头部姿态共同决定单看瞳孔位置会被头动带偏单看头部朝向又丢掉了眼球细节。Laser-Eye-master 这套资源给出的解法是先用 2D 人脸关键点定位出眼睛区域再对虹膜和瞳孔做像素级分割最后结合头部姿态解算出三维注视向量。它把 face_detector、face_alignment、head_pose、iris_localization、gaze_segmentation、gaze_laser 几个模块串成一条完整推理链权重文件2d106det、iris、16and32、iris_landmark.tflite都随包附带不需要你再去别处找模型。适合谁做计算机视觉落地、想快速验证视线估计可行性、又不打算从零训模型的工程师。下面按「资源结构 → 环境与推理 → 关键参数 → 避坑 → 进阶验证」拆开讲。2. 拆开 Laser-Eye-master模块职责与数据流2.1 目录里每个文件到底干什么拿到一个 zip 包我习惯先看目录结构再动手跑不然容易在无关文件上浪费时间。Laser-Eye-master 的顶层文件分四类入口脚本、核心模块、权重、辅助资源。文件/目录类型作用test.py入口主推理脚本读图/视频串起整条链路draw_lemon.py入口可视化调试脚本画柠檬状注视区域service/face_detector.py模块人脸检测输出人脸框service/face_alignment.py模块2D 关键点对齐定位眼睛service/head_pose.py模块头部姿态解算输出欧拉角service/iris_localization.py模块虹膜/瞳孔像素分类与定位service/gaze_segmentation.py模块注视区域分割service/gaze_laser.py模块三维视线向量生成service/generate_anchor.py模块生成锚点供关键点回归用service/model_update.py模块模型加载与更新逻辑weights/2d106det-*权重106 点人脸关键点模型weights/iris-*权重虹膜关键点模型weights/16and32-*权重辅助网络权重weights/iris_landmark.tflite权重轻量虹膜关键点模型object_points.npy数据三维人脸参考点供姿态解算asset/资源logo、flame.mp4 等演示素材output.avi输出默认推理结果视频关键点service/下每个模块职责单一test.py负责编排。这种结构的好处是你想替换某一环比如换更轻的人脸检测器时只动一个文件不影响其他模块。2.2 从人脸框到三维注视向量的完整链路整条数据流可以拆成五步每一步的输出是下一步的输入人脸检测face_detector.py输入原始帧输出人脸边界框。常见做法是用轻量检测网络保证实时性。关键点对齐face_alignment.py在框内回归 106 个关键点其中眼部周围点用于裁剪眼睛区域。这里会用到generate_anchor.py生成的锚点。头部姿态head_pose.py拿关键点与object_points.npy做 PnP 解算得到头部旋转矩阵转成 yaw/pitch/roll。虹膜定位iris_localization.py对眼睛区域做像素级分类区分虹膜、瞳孔、巩膜输出虹膜中心。视线合成gaze_laser.py把虹膜中心偏移量与头部姿态融合输出三维注视向量gaze_segmentation.py负责把注视落点映射到画面区域。提示第 3 步和第 4 步的融合方式是这套方案的核心。头部姿态提供「眼球在空间中的基准朝向」虹膜偏移提供「眼球相对头部的旋转量」两者缺一不可。2.3 为什么用「虹膜分割 头部姿态」而不是端到端回归很多视线估计方案直接用一个 CNN 从人脸图回归注视向量训练数据够多时效果不错但有两个硬伤一是泛化差换摄像头、换光照就崩二是可解释性弱出问题不知道是头动没算对还是眼球没定位准。Laser-Eye 走的是几何学习混合路线虹膜定位用分割网络保证像素级精度头部姿态用 PnP 保证几何一致性最后融合。这样每个环节都能单独验证——你可以只跑head_pose.py看欧拉角对不对也可以只跑iris_localization.py看虹膜中心准不准。对落地工程师来说可调试性比端到端高几个档次。3. 把环境跑起来依赖、权重加载与首帧推理3.1 环境准备与依赖安装这套代码基于 Python 深度学习推理框架从权重格式看2d106det 和 iris 是 MXNet 的 symbolparamsiris_landmark 是 TFLite。我一般会建独立虚拟环境避免和系统里的包打架。# 创建虚拟环境Python 版本建议 3.7-3.9 python -m venv laser_env source laser_env/bin/activate # Windows 用 laser_env\Scripts\activate # 基础依赖 pip install numpy opencv-python scipy # 推理框架MXNet 用于 2d106det 和 iris 模型 pip install mxnet1.9.1 # TFLite 推理iris_landmark pip install tflite-runtime # 如果 tflite-runtime 装不上用完整 tensorflow # pip install tensorflow2.8.0参数说明mxnet1.9.1是较稳定的 CPU/GPU 通用版本如果你有 CUDA 环境可以换mxnet-cu112。tflite-runtime比完整 TensorFlow 轻量得多适合只做推理的场景。opencv-python 用于图像读写和可视化。3.2 权重文件加载与路径校验权重都在weights/下但代码里加载路径可能是相对路径换工作目录就会报 file not found。我习惯在跑之前先确认一遍import os weight_dir weights required [ 2d106det-symbol.json, 2d106det-0000.params, iris-symbol.json, iris-0000.params, 16and32-symbol.json, 16and32-0000.params, iris_landmark.tflite ] for f in required: p os.path.join(weight_dir, f) if not os.path.exists(p): print(f[缺失] {p}) else: size_mb os.path.getsize(p) / 1024 / 1024 print(f[OK] {f} {size_mb:.1f} MB)逻辑说明MXNet 的模型加载需要 symbol网络结构和 params权重成对出现缺一个都会失败。16and32是辅助网络有些分支可能不加载它但建议先确认文件齐全。iris_landmark.tflite是独立文件用于轻量虹膜关键点推理。3.3 跑通第一帧test.py 的最小调用test.py是主入口通常支持图片或视频输入。我一般先用单张图验证链路再上视频。import cv2 from service.face_detector import FaceDetector from service.face_alignment import FaceAlignment from service.head_pose import HeadPose from service.iris_localization import IrisLocalization from service.gaze_laser import GazeLaser # 初始化各模块 detector FaceDetector() aligner FaceAlignment(weight_dirweights) pose HeadPose(ref_pointsobject_points.npy) iris IrisLocalization(weight_dirweights) gaze GazeLaser() # 读图 img cv2.imread(asset/test_face.jpg) if img is None: raise FileNotFoundError(检查图片路径) # 第一步人脸检测 boxes detector.detect(img) if len(boxes) 0: print(未检测到人脸检查图片质量或检测阈值) else: # 第二步关键点对齐 landmarks aligner.align(img, boxes[0]) # 第三步头部姿态 yaw, pitch, roll pose.solve(landmarks) # 第四步虹膜定位 iris_center iris.locate(img, landmarks) # 第五步视线向量 gaze_vec gaze.compute(iris_center, (yaw, pitch, roll)) print(f头部姿态 yaw{yaw:.1f} pitch{pitch:.1f} roll{roll:.1f}) print(f三维注视向量: {gaze_vec})逻辑说明每个模块独立初始化weight_dir和ref_points是关键参数。检测不到人脸时不要急着调模型先看图片本身是否过暗、人脸是否太小。头部姿态的欧拉角单位通常是度yaw 左右转、pitch 上下转、roll 歪头。注视向量是三维单位向量方向即注视方向。注意不同版本的模块初始化参数名可能不同如果报unexpected keyword argument直接打开对应.py文件看__init__签名不要猜。4. 关键参数怎么调检测阈值、姿态解算与虹膜分割4.1 人脸检测阈值与输入尺寸face_detector.py里通常有两个可调参数置信度阈值和输入分辨率。阈值太低会误检背景太高会漏检侧脸。# 常见做法是在 detector 初始化时传入 detector FaceDetector(score_thresh0.6, input_size(320, 320)) # 如果侧脸多降低阈值但配合 NMS detector FaceDetector(score_thresh0.4, nms_thresh0.3)参数说明score_thresh控制检测框置信度下限室内正脸场景 0.6 够用监控远距离可降到 0.4。input_size影响速度和精度320×320 适合实时640×640 适合离线高精度。nms_thresh是非极大值抑制阈值多人场景调低避免框重叠。4.2 头部姿态解算的参考点与相机内参head_pose.py用 PnP 解算依赖object_points.npy里的三维参考点和相机内参。参考点不对欧拉角会整体偏移。import numpy as np # 加载三维参考点 obj_pts np.load(object_points.npy) print(参考点形状:, obj_pts.shape) # 通常是 (N, 3) # 相机内参如果没有标定用图像尺寸估算 h, w img.shape[:2] focal w # 常见近似 camera_matrix np.array([ [focal, 0, w / 2], [0, focal, h / 2], [0, 0, 1] ], dtypenp.float64) dist_coeffs np.zeros((4, 1)) # 假设无畸变逻辑说明object_points.npy里的点顺序必须和face_alignment.py输出的关键点顺序一一对应否则 PnP 解出来是乱的。相机内参如果没做标定用焦距≈图像宽度的近似在多数场景够用但广角镜头会有偏差。dist_coeffs如果相机有畸变需要填实际值否则边缘区域姿态会偏。4.3 虹膜分割的像素分类与后处理iris_localization.py对眼睛区域做逐像素分类输出虹膜 mask。后处理决定最终虹膜中心精度。import cv2 import numpy as np # 假设 iris_mask 是网络输出的概率图 iris_mask iris.predict(eye_region) # shape: (H, W) # 二值化 binary (iris_mask 0.5).astype(np.uint8) # 形态学去噪 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 取最大连通域作为虹膜 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: largest max(contours, keycv2.contourArea) M cv2.moments(largest) cx M[m10] / M[m00] cy M[m01] / M[m00] print(f虹膜中心: ({cx:.1f}, {cy:.1f}))参数说明阈值 0.5 是分类边界光照差时可降到 0.4 提高召回。形态学开运算去掉小噪点核大小 3×3 适合小眼睛区域太大可能把虹膜本身腐蚀掉。取最大连通域是假设一只眼睛只有一个虹膜如果分割出多个区域说明模型在该帧不稳定需要检查输入质量。5. 避坑与排查这套代码最容易翻车的五个地方5.1 现象检测不到人脸但图片里明明有人原因face_detector.py的默认阈值偏高或者输入图片被 resize 后人脸占比过小。另一个常见原因是图片通道顺序搞反——OpenCV 读进来是 BGR有些检测网络期望 RGB。解决先把阈值降到 0.3 试一次确认cv2.imread后是否做了cv2.cvtColor(img, cv2.COLOR_BGR2RGB)打印图片尺寸如果人脸区域小于 40×40 像素检测器基本无能为力需要先裁剪放大。5.2 现象头部姿态欧拉角抖动严重帧间跳变原因关键点检测在连续帧上不稳定PnP 对关键点噪声敏感。另外如果object_points.npy的点顺序和实际关键点顺序有偏移解算结果会整体漂移。解决对欧拉角做滑动平均滤波窗口 5 帧左右检查face_alignment.py输出的关键点索引和object_points.npy的对应关系可以画点验证如果相机有畸变补上dist_coeffs。5.3 现象虹膜中心偏向眼角注视向量明显不准原因虹膜分割把巩膜或眼睑误分类成虹膜导致质心偏移。也可能是眼睛区域裁剪时包含了过多眉毛区域。解决在iris_localization.py后处理里加面积约束——虹膜面积应在眼睛区域面积的 10%~40% 之间超出范围丢弃该帧检查face_alignment.py裁剪眼睛时的边界框适当收紧上下边界把眉毛排除在外。5.4 现象MXNet 加载模型报Check failed: e 0或 shape mismatch原因symbol 和 params 版本不匹配或者 MXNet 版本与模型导出时的版本差异过大。16and32模型如果没被实际使用加载失败不影响主链路但代码里如果强制加载就会中断。解决确认2d106det-symbol.json和2d106det-0000.params是同一批导出的尝试换 MXNet 1.6~1.9 之间的版本如果16and32不是必需在代码里加 try-except 跳过。5.5 现象视频推理速度远低于实时output.avi 卡顿原因每帧都在做完整五步推理其中虹膜分割和关键点回归最耗时。如果输入分辨率是 1080p检测器输入没降采样速度会崩。解决把检测器输入固定到 320×320虹膜分割只在检测到人脸时执行如果只要注视方向不要可视化关掉draw_lemon.py里的绘制逻辑用cv2.VideoCapture时设置CAP_PROP_FPS和分辨率避免读原始 4K 流。6. 进阶验证用注视落点热区检验整条链路跑通单帧只是第一步真正验证这套资源能不能用要看连续视频里的注视落点是否稳定、是否符合直觉。我的做法是拿asset/flame.mp4或自己录一段「人看屏幕不同位置」的视频逐帧算注视向量再把向量投影到画面平面统计落点分布。import cv2 import numpy as np cap cv2.VideoCapture(asset/flame.mp4) heat np.zeros((480, 640), dtypenp.float32) frame_idx 0 while True: ret, frame cap.read() if not ret: break frame cv2.resize(frame, (640, 480)) boxes detector.detect(frame) if len(boxes) 0: landmarks aligner.align(frame, boxes[0]) yaw, pitch, roll pose.solve(landmarks) iris_center iris.locate(frame, landmarks) gaze_vec gaze.compute(iris_center, (yaw, pitch, roll)) # 把三维注视向量投影到画面取 x,y 分量映射到像素 gx int(320 gaze_vec[0] * 300) gy int(240 gaze_vec[1] * 300) if 0 gx 640 and 0 gy 480: heat[gy, gx] 1 frame_idx 1 cap.release() # 归一化并叠加到一张背景图上 heat_norm cv2.normalize(heat, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) heat_color cv2.applyColorMap(heat_norm, cv2.COLORMAP_JET) cv2.imwrite(gaze_heatmap.jpg, heat_color) print(f处理帧数: {frame_idx})逻辑说明gaze_vec是三维单位向量取 x、y 分量乘以缩放系数映射到画面坐标缩放系数 300 是经验值根据相机视场角调整。热区集中在某几个点说明视线估计稳定如果热区散成一片说明虹膜定位或头部姿态有噪声回到第 5 章排查。这个验证方法的好处是不需要标注数据靠肉眼就能判断链路是否可信。提示如果热区整体偏移先检查相机内参的焦距近似是否合理再检查object_points.npy的坐标系定义——有些参考点用毫米有些用归一化坐标混用会导致投影尺度错误。从那以后我每次拿到新的视线估计代码都强制先跑一遍热区验证再谈精度指标。这套 Laser-Eye-master 的价值不在于它有多新而在于每个环节都拆得开、改得动、验得了。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站