开源眼动追踪:GazeTracking(下:实现)

2022-06-15 16:00:19 浏览数 (1)

上篇文章我们看到了效果,这篇文章来回答怎么实现的,也学一下通用的CV编程。

文件不多,工作量不大

init是包的入口,使用点号绝对路径导入

在上层目录里面串联着要使用的所有库

在看一眼效果,俩鼻孔真好看

库使用了精确的除法:

代码语言:javascript复制
from __future__ import division

导入Python未来支持的语言特征division(精确除法),当我们没有在程序中导入该特征时,"/"操作符执行的是截断除法(Truncating Division),当我们导入精确除法之后,"/"执行的是精确除法,如下所示:

代码语言:javascript复制
>>> 3/4
0

>>> from __future__ import division
>>> 3/4
0.75

导入精确除法后,若要执行截断除法,可以使用"//"操作符:

接下来读pupil的源码

一定要找到依赖库最少相对独立的文件,这样的实现很重要,大家都依赖。

接着看结构,只有一个类

init方法

事实上初始化的方法会确定很多东西,保证我们的程序按照一个可控的情况取运行。

最后的函数在下面有实现。

把虹膜抠出来

先说使用的装饰器:

一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法。

而使用@staticmethod或@classmethod,就可以不需要实例化,直接以类名.方法名()来调用。

这有利于组织代码,把某些应该属于某个类的函数给放到那个类里去,同时有利于 命名空间的整洁。

既然@staticmethod和@classmethod都可以直接类名.方法名()来调用,那他们有什么区别呢?从它们的使用上来看:

  • @staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
  • @classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。 如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。 而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。

好处就是避免乱拉屎的情况。

CV是,先搞一个小的像素点,接着双边滤波一下,那么什么是双边略波?

双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。均值滤波、中值滤波和高斯滤波,都属于各向同性滤波,它们对待噪声和图像的边缘信息都采取一样的态度,结果,噪声被磨平的同时,图像中具有重要地位的边缘、纹理和细节也同时被抹平了,这是我们所不希望看到的。具有简单、非迭代、局部的特点。双边滤波器的好处是可以做边缘保存(edge preserving),一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显,为了解决这个问题,人们陆续提出了一些算法来把图像边缘和噪声区别对待,比如双边滤波和导向滤波。

变得清晰了一些,然后腐蚀一下,变得大一点,二值化,最后把值返回。

检测虹膜并且找到对应得位置。

函数一开始没有说得,这里是寻找轮廓,以树形结构输出信息,储存所有点得信息。

输出是:

Python3里返回三个值:image,contours,hierarchy

image:可能是跟输入contour类似的一张二值图;

contours:list结构,列表中每个元素代表一个边沿信息。每个元素是(x,1,2)的三维向量,x表示该条边沿里共有多少个像素点,第三维的那个“2”表示每个点的横、纵坐标;

注意:如果输入选择cv2.CHAIN_APPROX_SIMPLE,则contours中一个list元素所包含的x点之间应该用直线连接起来,这个可以用cv2.drawContours()函数观察一下效果。

hierarchy:返回类型是(x,4)的二维ndarray。x和contours里的x是一样的意思。如果输入选择cv2.RETR_TREE,则以树形结构组织输出,hierarchy的四列分别对应下一个轮廓编号、上一个轮廓编号、父轮廓编号、子轮廓编号,该值为负数表示没有对应项。

使用了:

这样得处理办法,提取了我要的东西

下面是排序了一下,按照轮廓的面积排布。

排序是从小到大,现在又取了最后两个,两个最大的眼孔。

接下来看这个

就是几个函数而已

接着初始化

左右眼要同时满足才算矫正

阈值是左右眼都有,求和除以长度,有点面密度的味道

虹膜占多大

这个图清晰吧

先取中间的RIO区域,把图像抽取出来,X完以后就是一个面积,之后-不为黑色位置,不就是白色圈。最后一比

开始使用遍历的方法来找到二值化的阈值是多少

先计算一下平均的虹膜大小,创建一个数据容器,按照5的step计算20次。

这里注意,二值化的函数返回值有两个,需要[1],读取的是第二个参数的内容,是一张图。

接着计算这图的比值,将内容放到字典里面。

最后把20次的内容,做计算,事实上,使用的内置的min函数:

语法:min(iterable, *[, key, default]) 参数:iterable是可迭代的对象,key指定寻找最小值的关键字,若key不指定则采用默认位置,与内置函数sorted类似 返回值:返回值是iterable中寻找到的最小值

我们要的是瞳孔的位置,所以比值要小,这里取小。

如图所示

这里就是可以一个眼睛一个眼睛的校准,存到字典里面

校准20次就可以了

最后一个

上面那么多内容都是给它做准备

初始化

先用dlib找到人脸,接着是把要寻找的特征文件准备好,下面一个函数把姿态点计算出来。

@property的作用: 广泛用于类的定义中,把方法变成属性,保证对参数进行必要的检查,减少程序运行时出错的可能性。

是否定位的函数

在我们上面分析的代码里面

漏了一个函数,补全:

读这个

初始化的内容

内置的工具函数,下划线为了访问限制

这个函数比较复杂

把里面的点解出来,放一个数组里面,转换数据类型,放在新的数组里面

写挺明白的了

转换过的点在这里

把眼睛割出来

X[:,0]是numpy中数组的一种写法,表示对一个二维数组,取该二维数组第一维中的所有数据,第二维中取第0个数据,直观来说,X[:,0]就是取所有行的第0个数据, X[:,1] 就是取所有行的第1个数据。

然后

大概就这样把

前面4个取点,后面两个算东西,接着算比值

如图

写不动了。。。我之后出个补篇,把屁股擦了。

0 人点赞