gaze

编程入门 行业动态 更新时间:2024-10-05 19:14:35

<a href=https://www.elefans.com/category/jswz/34/1762477.html style=gaze"/>

gaze

眼球追踪

摘要:

本文首先指出现有的OpenCV框架无法直接对眼眸进行识别,然后通过设置阈值的方式将视频中的眼眸位置与背景分离,利用AI对眼部图像进行分析后得到眼眸位置坐标,从而根据眼眸和眼眶的相对位置实现人眼注意力的识别。最后进行实验证明程序的有效性,并展示实验结果。

关键词:人眼注意力;图像识别;面部特征提取

1.引言:

图 1:shape_predictor_68_face_landmark人脸识别检测器所能识别的68个点


眼睛在人类的交流中起到了重要的作用,眼眸指向的方向表明了人注意力的焦点所在。现代的人工智能技术能够分析RGB图像中的人脸要素,并且将其识别出来。比如在在开源的计算机视觉软件库OpenCV中,可以识别人脸在图像中的位置。而通过人脸识别检测器数据库和Open CV的结合,就可以得到一张图像中人脸上68个特征点的位置信息。

通过对开源机器学习工具包dlib中shape_predictor的调用,可以得到这68个点在一张图像上的像素位置。

通过连续调用函数,可以逐帧地对视频使用图像分析技术,从而能够实时地通过网络摄像头分析屏幕前人面部的这68个点在视频中的像素位置。

然而,shape_predictor_68_face_landmark能够识别眼眶和眼皮的位置,却不能识别眼眸的位置。这意味着该人脸识别检测器数据库能够很好的对眼睛的张开/闭合状态进行很好的分析,却无法有效地通过提取眼眸位置来识别人眼的注意力方向。

人眼的注意力识别有广泛的实际用途。比如增强人机交互体验[1],比如分析广告对客户的吸引力,比如帮助渐冻症患者操作计算机。

本文通过对输入图像的裁剪、处理、设置阈值、AI分析,有效地确定了眼眸位置,从而能够实时地识别人眼注意力。

2.实现过程

首先将输入图像转变为灰度图像,减少后续工作的数据量。

然后基于shape_predictor_68_face_landmark人脸识别检测器,获得眼部特征点的坐标。其中左眼的特征点为36-41,右眼的特征点为42-47。

成功获得眼部周围区域特征点的坐标后,利用36特征点、39特征点到图像左上角的横向距离,以及37特征点、40特征点到左上角的纵向距离,就可以截取出左眼的图像。同理,利用42特征点、45特征点到图像左上角的横向距离,以及43特征点、48特征点到图像左上角的纵向距离,就可以截取出右眼的图像。

为了进一步降低AI算法需要处理的数据量,使用高斯滤波器(Gaussian Blur)对图像进行平滑处理,在降低信息量的同时尽可能地保留图像的总体灰度分布特征。

图 2: 用高斯滤波器处理过后的右眼图像

对处理后的右眼图像设置合适的阈值,将其从灰度图像转变成黑白图像,然后对图像进行黑白反转。

图 3:根据阈值转化为黑白图并反转颜色后的图像

接着通过OpenCV中的boundingRect获得包含白色区域的最小矩形。其中心点就是眼眸的位置。

图 4:标记眼眸位置

通过眼眸位置相对于眼周特征点的位置,可以识别出人眼的注意力方向。

考虑到人在往左看时,头可能微微向左转,导致左眼区域拍摄的图像发生变形,增加误判率,故用右眼的眼眸位置判断是否注意左方。同理,用左眼的眼眸位置判断是否注意右方。

判断过程:

若右眼眸与内眼角距离<直视时右眼眸与内眼角距离×0.9:判断注意力往左

若左眼眸与内眼角距离<直视时左眼眸与内眼角距离×0.9:判断注意力向右

其他:判断注意力向正前方

其中,直视时的眼眸和内眼角距离可以在校准时取得。

右眼眸与内眼角的距离通过右眼眸和图像左上角的横向距离和42特征点和左上角的横向距离相减得到。

左眼眸与内眼角的距离通过39特征点和左上角的横向距离与左眼眸和图像左上角的横向距离相减得到。

3.自动阈值调整

用来从灰度图得到黑白图的阈值会随着环境光的变化发生改变。在测试版本中采用手动滑块的方式调整阈值,但在完成品中实现了自动化的阈值调整。

用OpenCV中的contourArea得到当前黑白图中白色部分的面积大小。

经过试验,当白色部分面积大小在30到60之间时,能够较好地提取出眼眸的图像,识别较为准确。

故而,如果白色部分面积小于30,则不断提高阈值。

若白色部分面积大于60,则降低阈值。

由此,通过对黑白图中白色面积的监测,得以动态地调整阈值,以应对环境光变化对图像造成的影响。

4.实验过程

在被试面前横向放置3块屏幕,要求被试轮流注意左、中、右三块屏幕上的内容。同时中部放置摄像头,实时采集视频数据。以此来测试程序是否能够有效地识别被试的人眼注意力。

然后,让被试随意地注视三块屏幕中的任意一块,观察程序是否能够马上作出反应,并准确识别新的注意力方向。

图 5:实验环境

在实验中,程序能够较好地对人眼注意力的方向进行分类,并且面对人眼注意力的快速变化能够及时作出反应。

当人注意力在右边的屏幕时,左眼眼眸相对于眼眶向右移动,程序成功识别这一位移,并将注意力判断为向右。

当人注意力向左边屏幕时,右眼眼眸相对于眼眶向左移动,程序成功识别这一位移,并将注意力判断为向左。

当人的注意力在中间屏幕时,眼眸靠近眼眶的中部,程序判断为直视前方。

6.实验结论

综上,通过对输入图像的灰度化、模糊化,自动动态调整阈值进行黑白化处理,然后通过AI技术自动识别黑白图上白色部分(眼眸)的中心点,成功得到了眼眸的位置坐标。并且基于眼眸和眼眶的相对位置,实时判断人眼的注意力方向,达到了课题目标。

7.展望与优化

本文采用的阈值图像处理法的优点在于:

简单明了,能够有效提取眼眸的位置信息。

对设备要求低,只需要一个摄像头就可以实现人眼注意力识别。

延迟短,能够及时对人眼的变化作出反应。

能够自动调整阈值,适应环境光变化。

该方法的缺点在于:

视角单一,如果人脸对摄像头偏转角度过大,可能无法识别。

对距离敏感,当人脸距离摄像头较远时可能会表现不佳。

可以实现的优化有:

多摄像头多角度进行观测,降低脸部偏转对于程序分析的影响。

增加距离判断算法

8.引用

[1]Carlos H. Morimoto, Marcio R.M. Mimica,Eye gaze tracking techniques for interactive applications,Computer Vision and Image Understanding,Volume 98, Issue 1,2005,Pages 4-24,ISSN 1077-3142,.1016/j.cviu.2004.07.010.

9.附录

实验程序代码:

import cv2

import dlib

import numpy as np

cap = cv2.VideoCapture(0)

detector = dlib.get_frontal_face_detector()

predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

left = [36,37,38,39,40,41]

right = [42,43,44,45,46,47]

def midpoint(p1, p2):

    return int((p1.x+p2.x)/2), int((p1.y+p2.y)/2)

ret, frame = cap.read()

frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)

frame = cv2.flip(frame, 1)

thresh = frame.copy()

cv2.namedWindow('frame')

kernel = np.ones((9,9), np.uint8)

def nothing(x):

    pass

cv2.createTrackbar('threshold', 'frame', 0, 255, nothing)

cv2.createTrackbar('sample', 'frame', 0, 1, nothing)

leftmid, rightmid = None, None

athresh = 0

def autothresh(lefteye_blur, righteye_blur):

    global athresh

    _, thresholdleft = cv2.threshold(lefteye_blur, athresh, 255, cv2.THRESH_BINARY_INV)

    _, thresholdright = cv2.threshold(righteye_blur, athresh, 255, cv2.THRESH_BINARY_INV)

    leftcontours = cv2.findContours(thresholdleft, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

    rightcontours = cv2.findContours(thresholdright, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

    leftcontours = sorted(leftcontours, key = lambda x:cv2.contourArea(x), reverse=True)

    rightcontours = sorted(rightcontours, key = lambda x:cv2.contourArea(x), reverse=True)

    if leftcontours:

        if cv2.contourArea(leftcontours[0]) <= 30:

            athresh += 5

        if cv2.contourArea(leftcontours[0]) >= 60:

            athresh -= 5

    else:

        athresh += 10

    return athresh

   

while True:

    ret, frame = cap.read()

    frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)

    frame = cv2.flip(frame, 1)

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    faces = detector(gray, 1)

    for face in faces:

        landmarks = predictor(gray, face)

        lefteyecord = [landmarks.part(36).x-5, landmarks.part(37).y-5, landmarks.part(39).x+5, landmarks.part(40).y+5]

        righteyecord = [landmarks.part(42).x-5, landmarks.part(43).y-5, landmarks.part(45).x+5, landmarks.part(46).y+5]

        lefteye = gray[lefteyecord[1]:lefteyecord[3], lefteyecord[0]:lefteyecord[2]]

        righteye = gray[righteyecord[1]:righteyecord[3], righteyecord[0]:righteyecord[2]]

        lefteye_blur = cv2.GaussianBlur(lefteye, (7,7), 0)

        righteye_blur = cv2.GaussianBlur(righteye, (7,7), 0)

        # slidethresh = cv2.getTrackbarPos('threshold', 'frame')

        slidethresh = autothresh(lefteye_blur, righteye_blur)

        _, thresholdleft = cv2.threshold(lefteye_blur, slidethresh, 255, cv2.THRESH_BINARY_INV)

        _, thresholdright = cv2.threshold(righteye_blur, slidethresh, 255, cv2.THRESH_BINARY_INV)

        cv2.imshow('thresholdright', thresholdright)

        leftcontours = cv2.findContours(thresholdleft, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

        leftcontours = sorted(leftcontours, key = lambda x:cv2.contourArea(x), reverse=True)

        rightcontours = cv2.findContours(thresholdright, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]

        rightcontours = sorted(rightcontours, key = lambda x:cv2.contourArea(x), reverse=True)

       

        leftpupilx, leftpupily, rightpupilx, rightpupily = None, None, None, None

        for element in leftcontours:

            (x, y, w, h) = cv2.boundingRect(element)

            leftpupilx, leftpupily = lefteyecord[0]+x+int(w/2), lefteyecord[1]+y+int(h/2)

            cv2.circle(frame, (lefteyecord[0]+x+int(w/2), lefteyecord[1]+y+int(h/2)),2, (0,0,140), 2)

            break

        for element in rightcontours:

            (x, y, w, h) = cv2.boundingRect(element)

            rightpupilx, rightpupily = righteyecord[2]+x+int(w/2), righteyecord[3]+y+int(h/2)

            cv2.circle(frame, (righteyecord[0]+x+int(w/2), righteyecord[1]+y+int(h/2)),2, (0,0,140), 2)

            break

        if cv2.getTrackbarPos('sample', 'frame'):

            if leftpupilx and rightpupilx:

                leftmid, rightmid = (lefteyecord[2] - leftpupilx)/(lefteyecord[2]-lefteyecord[0]), (rightpupilx-righteyecord[0])/(righteyecord[2]-righteyecord[0])

        leftdelta = (lefteyecord[2] - lefteyecord[0]) // 8

        rightdelta = (righteyecord[2] - righteyecord[0]) // 8

        if (rightmid or leftmid) and (rightpupilx != None or leftpupilx != None):

            if rightpupilx != None and (rightpupilx - righteyecord[0])/(righteyecord[2]-righteyecord[0]) <= rightmid*0.9:

                print('You are looking left')

            elif leftpupilx != None and (lefteyecord[2] - leftpupilx)/(lefteyecord[2]-lefteyecord[0]) <= leftmid*0.9:

                print('You are looking right')

            else:

                print('You are looking middle')

       

    cv2.imshow('frame', frame)

    key = cv2.waitKey(1)

    if key==27:

        break

cap.release()

cv2.destroyAllWindows()

注:

校准时需直视前方,拖动sample滑块到1的位置,开始采样。将sample滑块放回0的位置,结束采样。

实验中的摄像头顺时针旋转了90度,故有

frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)

用于调整图像方向。若摄像头未旋转需要删去这一行。

frame = cv2.flip(frame, 1)用于镜面翻转图像,使得左右的方向更自然。

   


   

   

   

更多推荐

gaze

本文发布于:2024-02-14 07:20:57,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1762341.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:gaze

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!