开发一个完整的眼动追踪应用-Python版

描述

 

目前有几个方案,在下面:

1.电极式眼动追踪:这种技术通过在眼球周围放置电极来测量眼睛的运动。它可以提供非常高的准确性和分辨率,但需要接触眼球,因此不太适合长时间使用或需要无接触测量的应用场景。2.红外线眼动追踪:这种技术使用红外线摄像机来观察眼睛的位置和运动。由于它不需要接触眼球,因此非常适合长时间使用和需要无接触测量的应用场景。它的准确性和分辨率通常比电极式眼动追踪低。3.磁共振眼动追踪:这种技术使用磁共振成像来测量眼球的位置和运动。它可以提供非常高的空间分辨率,但时间分辨率较低,因此不太适合研究快速眼动的过程。4.可穿戴式眼动追踪:这种技术使用小型传感器或摄像头,可以放置在眼镜或头盔上,可以随身携带,适用于移动应用场景。但是由于可穿戴设备的尺寸和重量限制,其准确性和分辨率通常较低。5.视网膜追踪:这种技术利用视网膜图像来跟踪眼球的位置和运动。它可以提供非常高的准确性和分辨率,但只能在特定的实验条件下使用,例如黑暗环境下观察单一的点光源。

以我目前念的这个书,我还是用视觉方案来实现:

1.特征提取:选择适当的特征来描述眼睛的形状、颜色、纹理等信息。例如,可以使用Haar级联检测器来提取眼睛的轮廓特征,或者使用颜色分布模型来提取眼球的颜色特征。这步主要是传统的2.目标检测:使用机器学习或计算机视觉技术来检测眼睛的位置和方向。因为直接目标检测是识别不准的,现实太复杂了。可以使用级联分类器或支持向量机(SVM)来识别眼睛的位置和方向,或者使用卷积神经网络(CNN)来分类眼动类型。3.跟踪和估计:根据检测结果,使用跟踪和估计算法来跟踪眼睛的位置和运动轨迹。在捕捉的基础上开始进行跟踪,持续的来捕获。使用卡尔曼滤波器或粒子滤波器来估计眼睛的位置和速度,或者使用光流算法来估计眼球的运动轨迹。4.数据分析:根据眼动追踪的结果,进行数据分析和可视化。可以计算注视点的位置、持续时间和注视次数等统计信息,或者使用热力图和轨迹图来可视化眼动数据。这个也是这次要写的一个点。

我们在这里主要是直接给出ROI的区域减少算力。

传感器

在预处理步骤中,使用了高斯平滑和边缘检测来增强图像特征

传感器

在特征提取步骤中,使用了霍夫圆变换来检测圆形区域

传感器

参数

传感器

在目标检测步骤中,找到最大的圆形区域作为眼球,并在图像中标记出来

太简单了家人们!

但是这个程序太简单了,就是一个找特征啥的,有点傻。这次换个库:

Dlib是一个C++编写的机器学习库,提供了用于人脸检测、关键点检测、姿态估计等任务的算法,其中也包括用于眼动追踪的算法。Dlib同样也提供了Python接口,可以在Python中使用Dlib的算法实现眼动追踪。

dlib提供一个方法可将人脸图片数据映射到128维度的空间向量,如果两张图片来源于同一个人,那么两个图片所映射的空间向量距离就很近,否则就会很远。因此,可以通过提取图片并映射到128维空间向量再度量它们的欧氏距离(Euclidean distance)是否足够小来判定是否为同一个人。我不要人我就要眼睛。

传感器

上次觉得CMake碍眼,卸载了

传感器

稍等不知道多久,反正我吃了个橘子

 python.exe -m pip install --upgrade pip
更新一下

传感器

安装成功了

传感器

传感器

这个就是里面的dat文件,68个关键点

先找脸,再找眼,合理!

传感器

简单的转下颜色,然后直接找

dlib 库提供了两个用于人脸检测的功能。

第一个是HOG+线性SVM人脸检测器,另一个是深度学习MMOD CNN人脸检测器。

反正就是找一下面部的区域:

传感器

这些代码是再找眼部的位置

传感器

在范围之内就可以了

传感器

有极值坐标嘎嘎算

传感器

接着进一步给出瞳孔的坐标

2.0~8.0mm之间瞳孔大小是指虹膜中央的一圆孔的直径,受光线、年龄、人种、屈光状态、目标远近和情绪等因素影响,正常范围在2.0~8.0mm之间。瞳孔在强光下缩小,在黑暗下扩大,这是人体的正常生理反应。瞳孔大小不一致或对光反应异常可能是脑部或眼部疾病的征兆。

传感器

我考你,你遇到这种情况怎么写?

传感器

先给外圈的大圆套上

传感器

再处理瞳孔

多数情况下,我们是要实时的检测的:

传感器

来一段从摄像头捕获的代码

传感器

祖传代码不能丢

更实用性的是两个摄像头来捕捉眼动:

传感器

这个写的比较呆逼,不过我在后面会有进行封装

现在是弹出两个框来输出图像,赶紧不好看捏!我们来让他并排排列!

使用OpenCV中的cv2.hconcat()函数将两个视频帧水平合并在一起,并使用cv2.imshow()函数将合并后的视频帧显示出来。

传感器

就很简单,其实这里就变成一个合并横向排列的视频组,但是在处理流程上面有问题,应该先单一处理,最后合并结果。

传感器

封装好啦!(有点傻逼哦~)

传感器

继续封装,注意视频流

工程问题的话,上面的代码还是太慢了,让我来加一点多线程的魔法!

设计两个线程分别处理左右眼视频帧的读取和合并:

传感器

队列无疑是最合适的数据结构

传感器

合并函数也是如此,因为图像这种处理的方式就适合队列

传感器

这个是提前设置好的

传感器

标准的流程

传感器

这个就是线程的启动了,然后一个循环不停的合并

传感器

也可以加一个日志的功能,直接写到最上面就行

现在的程序一点也不装逼,如果可以加一些文字什么的,就更好啦!

传感器

要把视频流封装成一个类,然后里面也是多线程处理

传感器

在下面调用的时候,就是实例化代码

传感器

这里就显示一个左眼的FPS信息叠加

代码中,putText函数用于将帧率信息添加到视频帧的左上角。其中,cv2.FONT_HERSHEY_SIMPLEX指定了字体类型,1指定了字体大小,(255, 255, 255)指定了字体颜色,2指定了字体线宽。

一方面显示是可视化,另一方面我们需要保存具体的眼动数据来后处理。可以在程序中添加一个函数来提取圆形框的坐标信息,并将它们保存到一个文件中。

传感器

这个是简单版本

eye_data 是一个包含眼动信息的列表,每个元素都是一个二元组,表示眼睛的坐标。在循环中,将每个元素写入文件中,每个坐标之间用逗号分隔,每行结束后添加一个换行符。

假设圆形框的半径为 r,圆心坐标为 (x, y),那么可以使用 OpenCV 中的 circle 函数来绘制圆形框。在绘制圆形框时,同时将圆心坐标和半径信息保存到一个列表中:

传感器

在每次绘制圆形框时,将圆心坐标和半径信息添加到 eye_data 列表中。最后,可以将 eye_data 中的信息保存到一个文本文件中:

传感器

现在就是一个较为完善的函数了

再让我封装一下:

传感器

在视频帧上绘制眼球圆形框,并返回圆形框的坐标信息

传感器

这个是信息保存的函数

目前实现的功能挺多的了,现在来写一个GUI吧!

传感器

就两个线程就行

传感器

 

初始化是捕获线程,在run函数里面进行了颜色的转换

传感器

使用定时器来更新帧率信息

传感器

这些都简单

传感器

都比较简单吧?

完整代码我放在Github上面了。

我们拿到了保存的数据,想重新把他们展示出来。假设眼动数据文件是一个文本文件,每行包含两个数字,分别代表左右眼的坐标。

传感器

使用matplotlib库中的plot函数来绘制左右眼的坐标。下面是一个简单的示例代码,绘制左眼的x坐标和y坐标:

传感器

来封装一下

传感器

好啦!

再实现一个功能吧!

在播放的时候点按鼠标就可以捕捉当前播放的数据而且在图片上面标注时间戳。

程序应该这样写:

1.读取眼动数据文本文件,将数据存储到一个列表中。2打开视频文件,并读取第一帧。3.在窗口上显示第一帧图像。4.进入循环,依次读取眼动数据列表中的每个数据。5.当用户按下鼠标时,记录当前的时间戳,并在图像上绘制一个圆形或者其他标记,标记当前时间戳。6.在窗口上显示标记后的图像。

传感器

读取

后面的功能分开写不好,这里合在一起写。

传感器

先读取视频帧,然后就是获取帧率,创建一个窗口

传感器

能看懂吧?

传感器

这个就是绘制标记

传感器

许久不用Python,然后坏了,各种运行出错,VSCode都扑街了,哭死,不知道咋办了。

传感器

解决不了

传感器

重新安装就好啦!

传感器

嘤嘤嘤,没看上

c:/Users/yunswj/AppData/Local/Programs/Python/Python310/python.exe -m 
pip install ipykernel -U --user --force-reinstall
pip install opencv-python
https://cmake.org/download/
import cv2
import threading




class VideoStream:
    def __init__(self, src=0, width=640, height=480, fps=30):
        self.stream = cv2.VideoCapture(src)
        self.stream.set(cv2.CAP_PROP_FRAME_WIDTH, width)
        self.stream.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
        self.stream.set(cv2.CAP_PROP_FPS, fps)
        self.width = int(self.stream.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.height = int(self.stream.get(cv2.CAP_PROP_FRAME_HEIGHT))
        self.fps = int(self.stream.get(cv2.CAP_PROP_FPS))
        self.status = False
        self.frame = None


    def start(self):
        if self.status:
            return None
        self.status = True
        threading.Thread(target=self.update, args=()).start()


    def update(self):
        while self.status:
            _, self.frame = self.stream.read()


    def read(self):
        return self.frame


    def stop(self):
        self.status = False




def main():
    # 创建两个VideoStream对象,用于捕获左右眼视频流
    left_cam = VideoStream(0)
    right_cam = VideoStream(1)


    # 开始捕获视频流
    left_cam.start()
    right_cam.start()


    # 创建OpenCV窗口用于显示视频流
    cv2.namedWindow("Video Stream", cv2.WINDOW_NORMAL)


    while True:
        # 读取左右眼视频流
        left_frame = left_cam.read()
        right_frame = right_cam.read()


        # 在视频流上添加帧率信息
        left_fps_text = f"FPS: {left_cam.fps}"
        right_fps_text = f"FPS: {right_cam.fps}"
        cv2.putText(
            left_frame,
            left_fps_text,
            (10, 30),
            cv2.FONT_HERSHEY_SIMPLEX,
            1,
            (255, 255, 255),
            2,
        )
        cv2.putText(
            right_frame,
            right_fps_text,
            (10, 30),
            cv2.FONT_HERSHEY_SIMPLEX,
            1,
            (255, 255, 255),
            2,
        )


        # 合并左右眼视频流并显示
        merged_frame = cv2.hconcat([left_frame, right_frame])
        cv2.imshow("Video Stream", merged_frame)


        # 按'q'键退出
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break


    # 停止视频流捕获
    left_cam.stop()
    right_cam.stop()


    # 关闭OpenCV窗口
    cv2.destroyAllWindows()




if __name__ == "__main__":
    main()


审核编辑 :李倩

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分