今天寫一個(gè)相對(duì)完善的眼動(dòng)項(xiàng)目
基于磁場(chǎng)定位的眼動(dòng)追蹤技術(shù)
眼動(dòng)追蹤傳感器選型
眼動(dòng)追蹤:梯度法精確定位眼中心(論文)
開源眼動(dòng)-前導(dǎo)篇
開眼眼動(dòng)-分析引擎
眼動(dòng)追蹤中的坐標(biāo)+追蹤原理
開源眼動(dòng)追蹤:GazeTracking(上:效果)
開源眼動(dòng)追蹤:GazeTracking(下:實(shí)現(xiàn))
EyeLoop基于Python的眼動(dòng)儀(超棒)
目前有幾個(gè)方案,在下面:
以我目前念的這個(gè)書,我還是用視覺方案來實(shí)現(xiàn):
我們?cè)谶@里主要是直接給出ROI的區(qū)域減少算力。

在預(yù)處理步驟中,使用了高斯平滑和邊緣檢測(cè)來增強(qiáng)圖像特征

在特征提取步驟中,使用了霍夫圓變換來檢測(cè)圓形區(qū)域

參數(shù)

在目標(biāo)檢測(cè)步驟中,找到最大的圓形區(qū)域作為眼球,并在圖像中標(biāo)記出來
太簡單了家人們!
但是這個(gè)程序太簡單了,就是一個(gè)找特征啥的,有點(diǎn)傻。這次換個(gè)庫:
dlib提供一個(gè)方法可將人臉圖片數(shù)據(jù)映射到128維度的空間向量,如果兩張圖片來源于同一個(gè)人,那么兩個(gè)圖片所映射的空間向量距離就很近,否則就會(huì)很遠(yuǎn)。因此,可以通過提取圖片并映射到128維空間向量再度量它們的歐氏距離(Euclidean distance)是否足夠小來判定是否為同一個(gè)人。我不要人我就要眼睛。

上次覺得CMake礙眼,卸載了

稍等不知道多久,反正我吃了個(gè)橘子
python.exe -m pip install --upgrade pip
安裝成功了


這個(gè)就是里面的dat文件,68個(gè)關(guān)鍵點(diǎn)
先找臉,再找眼,合理!

簡單的轉(zhuǎn)下顏色,然后直接找
dlib 庫提供了兩個(gè)用于人臉檢測(cè)的功能。
第一個(gè)是HOG+線性SVM人臉檢測(cè)器,另一個(gè)是深度學(xué)習(xí)MMOD CNN人臉檢測(cè)器。
反正就是找一下面部的區(qū)域:

這些代碼是再找眼部的位置

在范圍之內(nèi)就可以了

有極值坐標(biāo)嘎嘎算

接著進(jìn)一步給出瞳孔的坐標(biāo)

我考你,你遇到這種情況怎么寫?

先給外圈的大圓套上

再處理瞳孔
多數(shù)情況下,我們是要實(shí)時(shí)的檢測(cè)的:

來一段從攝像頭捕獲的代碼

祖?zhèn)鞔a不能丟
更實(shí)用性的是兩個(gè)攝像頭來捕捉眼動(dòng):

這個(gè)寫的比較呆逼,不過我在后面會(huì)有進(jìn)行封裝
現(xiàn)在是彈出兩個(gè)框來輸出圖像,趕緊不好看捏!我們來讓他并排排列!
使用OpenCV中的cv2.hconcat()函數(shù)將兩個(gè)視頻幀水平合并在一起,并使用cv2.imshow()函數(shù)將合并后的視頻幀顯示出來。

就很簡單,其實(shí)這里就變成一個(gè)合并橫向排列的視頻組,但是在處理流程上面有問題,應(yīng)該先單一處理,最后合并結(jié)果。

封裝好啦!(有點(diǎn)傻逼哦~)

繼續(xù)封裝,注意視頻流
工程問題的話,上面的代碼還是太慢了,讓我來加一點(diǎn)多線程的魔法!
設(shè)計(jì)兩個(gè)線程分別處理左右眼視頻幀的讀取和合并:

隊(duì)列無疑是最合適的數(shù)據(jù)結(jié)構(gòu)

合并函數(shù)也是如此,因?yàn)閳D像這種處理的方式就適合隊(duì)列

這個(gè)是提前設(shè)置好的

標(biāo)準(zhǔn)的流程

這個(gè)就是線程的啟動(dòng)了,然后一個(gè)循環(huán)不停的合并

也可以加一個(gè)日志的功能,直接寫到最上面就行
現(xiàn)在的程序一點(diǎn)也不裝逼,如果可以加一些文字什么的,就更好啦!

要把視頻流封裝成一個(gè)類,然后里面也是多線程處理

在下面調(diào)用的時(shí)候,就是實(shí)例化代碼

這里就顯示一個(gè)左眼的FPS信息疊加
代碼中,putText函數(shù)用于將幀率信息添加到視頻幀的左上角。其中,cv2.FONT_HERSHEY_SIMPLEX指定了字體類型,1指定了字體大小,(255, 255, 255)指定了字體顏色,2指定了字體線寬。
一方面顯示是可視化,另一方面我們需要保存具體的眼動(dòng)數(shù)據(jù)來后處理。可以在程序中添加一個(gè)函數(shù)來提取圓形框的坐標(biāo)信息,并將它們保存到一個(gè)文件中。

這個(gè)是簡單版本
eye_data 是一個(gè)包含眼動(dòng)信息的列表,每個(gè)元素都是一個(gè)二元組,表示眼睛的坐標(biāo)。在循環(huán)中,將每個(gè)元素寫入文件中,每個(gè)坐標(biāo)之間用逗號(hào)分隔,每行結(jié)束后添加一個(gè)換行符。


現(xiàn)在就是一個(gè)較為完善的函數(shù)了
再讓我封裝一下:


這個(gè)是信息保存的函數(shù)
目前實(shí)現(xiàn)的功能挺多的了,現(xiàn)在來寫一個(gè)GUI吧!

就兩個(gè)線程就行

初始化是捕獲線程,在run函數(shù)里面進(jìn)行了顏色的轉(zhuǎn)換

使用定時(shí)器來更新幀率信息

這些都簡單

都比較簡單吧?
完整代碼我放在Github上面了。
我們拿到了保存的數(shù)據(jù),想重新把他們展示出來。假設(shè)眼動(dòng)數(shù)據(jù)文件是一個(gè)文本文件,每行包含兩個(gè)數(shù)字,分別代表左右眼的坐標(biāo)。

使用matplotlib庫中的plot函數(shù)來繪制左右眼的坐標(biāo)。下面是一個(gè)簡單的示例代碼,繪制左眼的x坐標(biāo)和y坐標(biāo):

來封裝一下

好啦!
再實(shí)現(xiàn)一個(gè)功能吧!
在播放的時(shí)候點(diǎn)按鼠標(biāo)就可以捕捉當(dāng)前播放的數(shù)據(jù)而且在圖片上面標(biāo)注時(shí)間戳。
程序應(yīng)該這樣寫:

讀取
后面的功能分開寫不好,這里合在一起寫。

先讀取視頻幀,然后就是獲取幀率,創(chuàng)建一個(gè)窗口

能看懂吧?

這個(gè)就是繪制標(biāo)記

許久不用Python,然后壞了,各種運(yùn)行出錯(cuò),VSCode都撲街了,哭死,不知道咋辦了。

解決不了

重新安裝就好啦!

嚶嚶嚶,沒看上
c:/Users/yunswj/AppData/Local/Programs/Python/Python310/python.exe -mpip install ipykernel -U --user --force-reinstall
pip install opencv-pythonhttps://cmake.org/download/import cv2import threadingclass 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 = Falseself.frame = Nonedef start(self):if self.status:return Noneself.status = Truethreading.Thread(target=self.update, args=()).start()def update(self):while self.status:_, self.frame = self.stream.read()def read(self):return self.framedef stop(self):self.status = Falsedef main():# 創(chuàng)建兩個(gè)VideoStream對(duì)象,用于捕獲左右眼視頻流left_cam = VideoStream(0)right_cam = VideoStream(1)# 開始捕獲視頻流left_cam.start()right_cam.start()# 創(chuàng)建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()# 關(guān)閉OpenCV窗口cv2.destroyAllWindows()if __name__ == "__main__":main()