ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenCV眼球追踪实战:人脸检测、瞳孔定位与视线映射全解析

OpenCV眼球追踪实战:人脸检测、瞳孔定位与视线映射全解析 简介基于Python3与OpenCV实现的眼球追踪项目面向计算机视觉与Python开发者提供了一套从摄像头捕获到眼球运动追踪的完整实时方案。项目完整覆盖视频流捕获、灰度与滤波预处理、Haar级联眼睛检测、瞳孔定位及连续帧追踪等核心模块压缩包共62个文件含Python脚本、预训练xml分类器、测试图片与界面资源整体仅393KB。目前已有1045人学习源码结构清晰附有可运行的示例脚本便于读者理解实时追踪原理并可快速移植到人机交互、生物识别等实际应用中。各功能模块按职责拆分示例脚本完整展示从摄像头调用、眼睛检测到瞳孔框定的流程并带有界面交互代码方便调整关键参数。对于希望快速上手视觉追踪的开发者可借助预训练模型与测试图片直接复现基础流程也适合作为二次开发的基础框架。1. 眼球追踪项目到底是什么一个文件包解决的不只是“看哪里”一个基于python3和OpenCV的“眼球追踪.zip”拿到手的第一反应可能是这玩意儿是不是就是检测人脸然后框个眼睛真跑起来你会发现它解决的是一整条链路——从摄像头抓到一帧画面开始到定位人脸、抠出眼睛区域、找到瞳孔中心再映射到屏幕坐标最后驱动鼠标或生成热力图。它能用的地方不止是“看哪里”电商做眼动热力图分析用户注意力医学康复追踪眼球震颤无障碍场景用眼睛代替鼠标甚至游戏里做视线瞄准。适合的人群也很明确有python3基础、想用OpenCV做图像处理项目、但不想从零研究人脸关键点和瞳孔分割算法的人。这个zip包的价值就是把散落在OpenCV官方教程和论文里的方法串成一个能跑的demo你改改参数就能接到自己的摄像头或视频流上。但我得先泼盆冷水开箱即用是运气不是常态环境版本不匹配、光照一换个个都是坑接下来的内容就是把这些坑逐一填平。2. 环境搭建与项目文件解读python3和OpenCV的版本匹配是第一个坑2.1 从零搭一个能跑眼球追踪的python3环境眼球追踪在OpenCV里本质上依赖三个能力人脸检测、特征点定位、图像处理。这三个能力在不同OpenCV版本里的API变化很大尤其是4.x之后很多检测函数挪到了cv2.face模块而这个模块在官方pip包中并不附带。常见的做法是用OpenCV自带的Haar级联分类器它不受cv2.face影响这也是很多眼球追踪项目能直接跑的原因。我的建议是你的环境里python3用3.7到3.9之间最稳OpenCV用4.5.x或4.6.x这两个版本对Haar级联和cv2.CascadeClassifier的支持最全网上现成的代码也基本按这个版本写的。# 创建虚拟环境避免系统python3被污染 python3 -m venv eye_tracking_env source eye_tracking_env/bin/activate # 安装OpenCV和numpynumpy是图像数组操作的底层依赖 pip install opencv-python4.6.0.66 pip install numpy1.21.6opencv-python这个包名对应的是官方预编译的OpenCV只包含主模块不包含cv2.face。如果你的解压项目代码里出现from cv2.face import ...那需要额外装opencv-contrib-python。但绝大多数眼球追踪项目用不到贡献模块能不用就别用因为contrib包经常和主包产生API冲突尤其是cv2.face在4.5之后官方已经不再更新维护与其折腾它不如直接用Haar级联。装完后可以用一行命令验证python3 -c import cv2; print(cv2.__version__)能输出版本号就说明OpenCV基础环境通了。2.2 解压后先看这五个文件代码结构与传统方案对照拿到zip包别急着双击运行先解压看目录结构。一个设计完整的眼球追踪项目通常包含这几个文件主程序eye_tracker.py或者main.py、人脸检测器配置文件haarcascade_frontalface_default.xml、眼睛检测器haarcascade_eye.xml、标定脚本calibration.py、以及一个存放输出结果的output目录。有些项目还会带一个requirements.txt那就省事多了直接pip install -r requirements.txt就能把依赖装齐。我一般会先把eye_tracker.py从头到尾读一遍重点看这几个关键点摄像头初始化用的cv2.VideoCapture(0)还是cv2.VideoCapture(1)这决定了你调用的是笔记本内置摄像头还是外接USB摄像头人脸检测是否写在一个独立函数里这决定后续改成视频文件还是图片输入时改动成本。还有一点值得注意很多项目里眼睛检测是嵌套在人脸检测之后的也就是先检测人脸再在人脸区域内部检测眼睛。这样做的好处是缩小了搜索范围速度和准确率都会提升这也是眼球追踪项目区别于单纯人脸检测的核心设计之一。import cv2 # 初始化摄像头0代表第一个摄像头设备 cap cv2.VideoCapture(0) # 加载人脸和眼睛的Haar级联分类器 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) eye_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_eye.xml ) # 读取一帧测试 ret, frame cap.read() if not ret: print(无法读取摄像头画面检查设备编号或权限) exit() # 转为灰度图Haar级联在灰度图上运行更稳定 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 检测人脸 faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) print(f检测到 {len(faces)} 张人脸) cap.release()这段代码的scaleFactor1.1表示每次搜索窗口缩放10%值越小检测越精细但越慢值太大容易漏检minNeighbors5表示一个候选框周围至少要存在5个其他候选框才保留这个值越大误检越少但越容易漏检真实目标。这两个参数直接决定了检测框是否稳定后面的避坑章节会具体展开。3. 核心算法拆解从人脸检测到瞳孔定位的完整链路3.1 人脸检测用OpenCV的Haar级联还是DNN眼球追踪的第一步是找到脸在哪否则后续的瞳孔定位就是无源之水。OpenCV里最传统的人脸检测方式是Haar级联它本质上是训练好的一个分类器通过滑动窗口对灰度图像中每个位置判断是否是目标。优点是不需要额外下载模型文件OpenCV安装包里自带了haarcascade_frontalface_default.xml缺点是对侧脸、遮挡和暗光非常敏感稍微偏头就检测不到。另一种方案是OpenCV DNN模块它加载预训练的深度学习人脸检测模型比如Caffe或TensorFlow格式的SSD模型检测精度更高且能处理一定程度的侧脸但需要额外下载模型文件且推理速度比Haar级联慢不少。在眼球追踪场景里我们关心的是实时性瞳孔定位的精度反而受摄像头分辨率和光照影响更大所以大多数开源项目默认用Haar级联。我的建议是如果你用的是树莓派这类ARM设备老老实实用Haar级联如果你是PC且摄像头清晰度还行可以试试DNN但要做好FPS下降的心理准备。# 使用DNN检测人脸如果项目模型支持 net cv2.dnn.readNetFromCaffe(deploy.prototxt, res10_300x300_ssd_iter_140000.caffemodel) blob cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104, 177, 123)) net.setInput(blob) detections net.forward()上面这段代码是OpenCV DNN人脸检测的标准流程blobFromImage的参数里(104, 177, 123)是模型训练时的均值像素值不能随便改。deploy.prototxt和.caffemodel文件需要从模型发布页下载这不在zip包内如果你解压后没看到这两个文件说明项目作者用的就是Haar级联别强行套DNN。3.2 瞳孔定位阈值分割、轮廓检测与质心计算的参数陷阱检测到人脸并且框出眼睛区域后下一步是在眼睛区域内部找到瞳孔中心。传统方法是图像阈值分割加轮廓分析思路是瞳孔在近红外或良好光照下比周围虹膜暗得多先把灰度图二值化把暗的区域变成白色或黑色取决于阈值方向然后用cv2.findContours找轮廓最后用cv2.moments计算质心。这个路径看似简单实际参数非常敏感。核心代码和参数如下import cv2 import numpy as np def find_pupil(eye_gray): # 高斯模糊去噪kernel大小必须是奇数 blurred cv2.GaussianBlur(eye_gray, (5, 5), 0) # 用Otsu自适应阈值自动计算最佳二值化阈值 _, threshold cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # 找轮廓只取最外层轮廓 contours, _ cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 按面积排序取最大轮廓通常瞳孔面积最大 max_contour max(contours, keycv2.contourArea) # 面积太小可能是噪声过滤掉 if cv2.contourArea(max_contour) 20: return None # 用图像矩计算质心 M cv2.moments(max_contour) if M[m00] 0: return None cx int(M[m10] / M[m00]) cy int(M[m01] / M[m00]) return (cx, cy)这段代码里最关键的是阈值方向。cv2.THRESH_BINARY_INV的含义是灰度值大于阈值的像素变成0黑灰度值小于阈值的像素变成255白。瞳孔比周围暗所以用反阈值正好把瞳孔变成白色亮点。如果你发现检测出来的斑块不是瞳孔而是眼白甚至眉毛大概率是阈值方向选反了改成cv2.THRESH_BINARY试试。cv2.findContours在OpenCV 4.x里返回两个值在3.x里返回三个值这也是新手最常碰到的版本不兼容崩溃代码里写成contours, _是4.x的写法如果你用3.x跑会直接报ValueError: not enough values to unpack。3.3 视线估计从2D瞳孔到屏幕坐标的映射需要标定瞳孔中心找到了但眼球追踪的最终输出是人正在看屏幕的哪个位置。这一步需要建立摄像头图像坐标到屏幕坐标的映射关系靠的是标定。常见做法是让用户盯着屏幕上几个已知坐标的点通常是九宫格记录对应的瞳孔坐标然后用多项式拟合出映射函数。# 简单的线性映射假设瞳孔偏移与屏幕坐标偏移成正比 # 先采集标定点存为两个列表 # eye_samples: [[x1,y1], [x2,y2], ...] # screen_samples: [[sx1,sy1], [sx2,sy2], ...] import numpy as np def fit_linear_map(eye_samples, screen_samples): # 构造线性方程 Ax B screen_x用最小二乘法求解 eye_arr np.array(eye_samples, dtypenp.float32) screen_arr np.array(screen_samples, dtypenp.float32) # 对x和y分别做一次多项式拟合 coeff_x np.polyfit(eye_arr[:, 0], screen_arr[:, 0], 1) coeff_y np.polyfit(eye_arr[:, 1], screen_arr[:, 1], 1) def map_point(eye_point): sx np.polyval(coeff_x, eye_point[0]) sy np.polyval(coeff_y, eye_point[1]) return (sx, sy) return map_pointnp.polyfit(x, y, 1)用最小二乘法拟合一条直线如果你采集的样本点覆盖范围够广线性映射已经能满足基本交互。但要注意瞳孔坐标会随着头部距离屏幕的远近而变化同一个视线方向在不同距离下映射的瞳孔偏移量不同。所以严格意义上你得固定头部位置或用深度信息做补偿。这个zip包里如果没写标定脚本那你只能自己加一个否则视线坐标会非常漂。4. 跑通最小示例用摄像头实时追踪眼球的完整代码与参数说明4.1 实时视频流处理框架读帧、检测、绘制、显示把前面的人脸检测、眼睛检测、瞳孔定位串进一个实时循环里就是一个能看到效果的最小程序。我给出的框架是标准的生产级写法读帧、缩小处理、检测、绘制、退出检测。注意每一帧都要先缩放因为摄像头默认分辨率往往是640x480甚至更高全分辨率跑Haar级联速度会非常慢而缩放并不会显著影响检测精度但能明显提升FPS。import cv2 import numpy as np # 加载级联分类器 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) eye_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_eye.xml ) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) def find_pupil(eye_gray): blurred cv2.GaussianBlur(eye_gray, (5, 5), 0) _, threshold cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) contours, _ cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None max_contour max(contours, keycv2.contourArea) if cv2.contourArea(max_contour) 20: return None M cv2.moments(max_contour) if M[m00] 0: return None return (int(M[m10] / M[m00]), int(M[m01] / M[m00])) while True: ret, frame cap.read() if not ret: break # 缩小帧到320宽度减少检测耗时 scale 320 / frame.shape[1] small cv2.resize(frame, (320, int(frame.shape[0] * scale))) gray cv2.cvtColor(small, cv2.COLOR_BGR2GRAY) # 检测人脸 faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) for (fx, fy, fw, fh) in faces: # 在原图坐标上画人脸框 cv2.rectangle(small, (fx, fy), (fxfw, fyfh), (0, 255, 0), 2) # 人脸区域也是眼睛搜索区域 face_roi gray[fy:fyfh, fx:fxfw] # 在人脸区域检测眼睛 eyes eye_cascade.detectMultiScale(face_roi, scaleFactor1.1, minNeighbors3) for (ex, ey, ew, eh) in eyes: # 眼睛坐标转换到原图 eye_roi face_roi[ey:eyeh, ex:exew] pupil find_pupil(eye_roi) if pupil: # 绘制瞳孔中心坐标要加回偏移量 px fx ex pupil[0] py fy ey pupil[1] cv2.circle(small, (px, py), 3, (0, 0, 255), -1) # 显示结果 cv2.imshow(Eye Tracking, small) # 按q退出 if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码最值得注意的地方是坐标转换。small是缩放后的帧face_cascade检测到的人脸框坐标是基于small的而eye_cascade检测到的眼睛框坐标是基于人脸ROI的。所以最终瞳孔坐标必须逐级加回偏移量人脸框左上角坐标 眼睛框坐标 瞳孔在眼睛ROI内的坐标。漏了这一步你画出来的瞳孔位置会完全错位。4.2 三个必调参数检测频率、阈值、平滑窗口实时跑起来后你会发现三个参数决定了体验第一个是cv2.waitKey(1)里的数字它控制每帧之间的等待毫秒数。设为1是尽量快的帧率如果CPU跟不上可以改成5或10但注意这样会产生明显延迟。第二个是瞳孔检测里的最小面积阈值20这个值取决于摄像头分辨率和眼睛在画面中的大小。如果眼睛区域大约80x40像素瞳孔半径大约10像素面积约300平方像素那20作为下限非常安全但如果摄像头离人远眼睛区域只有40x20瞳孔面积可能连100都不到此时要调小到10甚至5否则瞳孔会被过滤掉。第三个是Haar级联的minNeighbors人脸用5眼睛用3是我觉得比较稳的起点。人脸区域大特征稳定5能抑制误检眼睛区域小特征弱3能保证尽量不漏检。# 平滑瞳孔位置避免画面抖动 trajectory [] def smooth_point(pupil, window_size5): trajectory.append(pupil) if len(trajectory) window_size: trajectory.pop(0) avg_x int(sum(p[0] for p in trajectory) / len(trajectory)) avg_y int(sum(p[1] for p in trajectory) / len(trajectory)) return (avg_x, avg_y)这个简单的滑动平均是成本最低的平滑方案window_size5大概对应在30FPS下用150毫秒的历史数据做平均。如果你发现光标移动太粘滞就把窗口减到3如果太跳就加到8。注意滑动平均会引入滞后以及对突然的眼跳反应迟钝这是实时交互中需要接受的折中。5. 避坑指南眼球追踪项目最常见的5个翻车现场5.1 OpenCV安装后import报错版本与lib路径问题现象pip install opencv-python很顺利但一运行import cv2就报错提示ImportError: libGL.so.1: cannot open shared object file或者ModuleNotFoundError: No module named cv2。原因前者是OpenCV在Linux下依赖系统图形库缺libGL多发生在最小化安装的Ubuntu或CentOS容器里后者是虚拟环境装错了位置或者在系统python里装了但在虚拟环境里运行脚本。还有一个版本坑python3.11以上有些旧版OpenCV轮子不提供对应预编译包导致pip自动下载源码编译失败。解决先sudo apt-get install libgl1 libglib2.0-0补图形库再确认pip list | grep opencv看安装位置在虚拟环境里重新pip install opencv-python4.6.0.66。如果python版本太高建议换到3.9或3.10这不是OpenCV老而是某些依赖库比如numpy的轮子兼容性问题。5.2 眼睛区域检测框乱跳Haar级联的scaleFactor和minNeighbors现象程序运行后人脸框稳定但眼睛框一帧一个大小有时候框到眉毛有时候框到整个鼻子区域瞳孔中心也跟着乱跳。原因Haar级联的眼睛检测器训练样本主要是正面睁眼的清晰图片对轻微偏头和光照变化极为敏感。scaleFactor1.1意味着每一层缩放10%识别窗口数量多但噪声也多minNeighbors太小会让大量误检框保留下来。解决把人脸区域先做个ROI裁剪再单独对ROI做直方图均衡化增强对比度后再检测眼睛。同时把scaleFactor从1.1调到1.15降低搜索窗口数量minNeighbors从3提高到5。如果还跳可以限制眼睛检测框的宽高比因为真实眼睛框的宽高比通常在1.5到3之间超出范围的直接丢弃。# 增加对眼睛检测结果的几何约束过滤明显不合理的框 for (ex, ey, ew, eh) in eyes: if ew 0 or eh 0: continue ratio ew / eh if ratio 1.5 or ratio 3.5: continue # 然后才进入瞳孔检测这个几何过滤在偏头时会把部分真实眼睛框也过滤掉但换来的稳定性要高得多。我个人的习惯是宁可漏检一帧也不让错误框带偏平滑算法。5.3 瞳孔变成黑色块或消失光照与二值化阈值的关系现象室内灯光下瞳孔检测正常一到窗边或开台灯后瞳孔检测不是全黑就是消失程序一直返回None。原因Otsu阈值是全局自动计算的它假设光照均匀。当眼睛区域一半亮一半暗时Otsu算出来的阈值可能把暗眼白也归为前景阈值分割结果变成一个不规则的黑色块或者当整个眼睛区域被强光打亮瞳孔和虹膜对比度不足阈值分割找不出清晰的轮廓。解决先做高斯模糊这个我前面写了但还不够。更好的方式是对眼睛ROI做自适应阈值处理用cv2.adaptiveThreshold代替全局阈值。它根据每个像素周围邻域的亮度决定阈值能抵抗光照渐变。代价是参数多了两个blockSize邻域大小和C常数偏差。blockSize11适合30x60像素的眼睛区域C5表示阈值比邻域均值低5个灰度级因为瞳孔比周围暗设置负数反而会抑制瞳孔。adaptive_thresh cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 5 )如果用了自适应阈值还是不稳定优先排查摄像头曝光设置。很多USB摄像头默认自动曝光在场景亮度变化时会自动调节导致眼睛区域忽明忽暗。可以用cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0)关闭自动曝光再手动cap.set(cv2.CAP_PROP_EXPOSURE, -5)设一个固定曝光值。5.4 摄像头延迟高导致画面卡顿帧处理与显示分离现象运行程序后画面像幻灯片一样移动鼠标或转头时感觉有半秒以上的偏移FPS显示不到10。原因detectMultiScale是个吃CPU的操作在640x480的帧上做一次大约要40到100毫秒。如果每个循环里还做了缩放、二值化、轮廓查找整帧处理时间轻松超过150毫秒。出现卡顿的根本原因是主循环里串行做了太多事没有利用多核。解决用线程把摄像头读帧和处理帧分离。一个线程不停读摄像头存入队列主线程从队列取最新帧进行处理。这样即使处理慢画面也不会卡在读帧上。但注意队列不能无限增长否则延迟会越来越大应该只保留最新一帧。import threading import queue frame_queue queue.Queue(maxsize1) def read_camera(cap, frame_queue): while True: ret, frame cap.read() if ret: # 如果队列满了丢弃旧帧保证总是最新帧 if frame_queue.full(): try: frame_queue.get_nowait() except queue.Empty: pass frame_queue.put(frame)主循环里每次处理前先检查队列是否有新帧没有就跳过本次循环这样帧率会稳定很多。注意Python的GIL会让多线程在CPU密集型任务上提升有限但读帧和图像处理之间有不少I/O等待用线程解耦还是有明显改善。5.5 屏幕坐标映射不准标定过程与头部运动补偿现象标定的时候光标能跟随眼睛走但一换坐姿或稍微歪头光标就大幅度漂移完全对不上实际注视位置。原因前面用np.polyfit拟合的映射函数只对应固定的头部位置和姿态。当头部前后移动瞳孔在摄像头图像上的偏移量与屏幕坐标之间的关系改变了旧映射自然失效。另外很多项目只在屏幕上显示几个标定点但没有要求用户固定下巴或保持姿态采集样本的分布也不均匀导致拟合出的函数在标定点之外区域误差很大。解决第一标定时要求用户头部放在一个支架或靠背上尽量减少前后移动第二采集的标定点要覆盖屏幕边缘和四角至少16个点而不是9个第三在运行中增加简单的人脸框大小判断——当人脸框面积相对于标定时变化超过20%暂停映射输出并提示用户调整距离。这部分逻辑可以写进主循环里不需要额外的传感器。6. 进阶技巧把眼球追踪做成一个可用的交互设备6.1 用卡尔曼滤波平滑视线点的原理与简单实现滑动平均虽然简单但对突变不敏感而且会给视线轨迹引入恒定的滞后。如果你想把眼球追踪输出给鼠标控制或游戏瞄准更专业的做法是卡尔曼滤波。它的核心思想是用上一帧的状态位置和速度预测当前位置再用当前观测值修正预测得到一个既平滑又跟手的输出。OpenCV自带卡尔曼滤波封装不用自己实现矩阵运算。# 初始化4维状态[x, y, dx, dy]dx和dy是速度 kf cv2.KalmanFilter(4, 2) kf.transitionMatrix np.array([ [1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1] ], dtypenp.float32) # 位置 前位置 速度 kf.measurementMatrix np.array([ [1, 0, 0, 0], [0, 1, 0, 0] ], dtypenp.float32) # 观测值直接映射到前两维 kf.processNoiseCov np.eye(4, dtypenp.float32) * 0.01 kf.measurementNoiseCov np.eye(2, dtypenp.float32) * 0.5 def kalman_update(observed): prediction kf.predict() corrected kf.correct(observed) return (int(corrected[0]), int(corrected[1]))卡尔曼滤波的两个参数很关键processNoiseCov表示你对运动模型的信任程度调大它会让滤波更相信观测值响应更快但噪声更大调小则更信任预测更平滑但延迟更明显。measurementNoiseCov表示对观测值噪声的估计瞳孔检测误差大就调大。我常用的起点是process取0.01measurement取0.5实测在30FPS下能兼顾稳定和跟手。6.2 验证方法离线视频回放与视线落点热力图实时调试最难的是复现同一次头部运动来对比效果所以我建议把原始视频录下来离线跑同一份代码这才能公平对照参数改动前后的差异。先把录制视频的脚本写好处理时把cv2.VideoCapture(0)换成cv2.VideoCapture(recorded.mp4)同时把瞳孔坐标和对应时间戳写入CSV文件。然后拿真实屏幕坐标做对比计算平均欧氏距离误差。除了数字指标视觉验证也很直观。把所有视线落点画到一张空白图片上落点密集的地方用高亮色稀疏的地方用冷色生成热力图。如果热力图中心集中在屏幕中央区域周围几乎没有任何点说明你的映射可能有系统性偏差——大多数人在自由观看时视线应该散布在屏幕各处而不只是中间。看到这种图就先回头检查标定数据而不是继续调滤波参数。我自己的习惯是每次改完参数都把结果数据存档记录当时的摄像头型号、分辨率、光照条件和参数值。眼球追踪项目的变量实在太多了不记录这些第二天你可能就忘了上次那个“能跑”的版本是怎么调出来的。希望这篇拆解能让你少走点弯路把zip包真正变成你能改出花来的工具。本文还有配套的精品资源点击获取
返回列表