• 发文
  • 评论
  • 微博
  • 空间
  • 微信

使用网络摄像头进行眼睛注视估计

磐创AI 2022-06-24 11:25 发文

让我们看看下面的情况,你坐在图书馆里,你刚刚看到最漂亮的女人坐在图书馆的另一边。哎呀,她发现你在盯着她看。她估计你的目光在盯着她,而你通过理解她的目光指向你,注意到被她抓个正着。

眼睛凝视:一个人的眼睛聚焦的点

就像我们惊人的大脑毫不费力地完成许多任务一样,这是一个很难“教”计算机的问题,因为我们需要执行几项艰巨的任务:

· 人脸识别

· 眼睛识别和瞳孔定位

· 确定头部和眼睛的 3D 定位

商业凝视跟踪器有各种形状和大小。从眼镜到屏幕的基础解决方案。但是,尽管这些产品精度很高,但它们使用的是专有软件和硬件,而且非常昂贵。

让我们开始构建我们的视线跟踪器

为了使这篇博客的篇幅保持合理,我们将构建一个基本的注视跟踪形式。有几个粗略的估计。而且我们不会确定确切的注视点,而是确定注视方向。

凝视是相对于镜头的,而我坐在镜头下

人脸识别和瞳孔定位

对于这项任务,我们将使用MediaPipe(https://google.github.io/mediapipe/solutions/face_mesh.html),这是一个由 Google 开发的惊人的深度学习框架,它将实时为我们提供 468 个 2D 人脸地标,而使用很少的资源。

让我们看一些代码:

import mediapipe as mp

import cv2

import gaze

mp_face_mesh = mp.solutions.face_mesh # initialize the face mesh model


# camera stream:

cap = cv2.VideoCapture(1)

with mp_face_mesh.FaceMesh(

       max_num_faces=1,                            # number of faces to track in each frame

       refine_landmarks=True,                      # includes iris landmarks in the face mesh model

       min_detection_confidence=0.5,

       min_tracking_confidence=0.5) as face_mesh:

   while cap.isOpened():

       success, image = cap.read()

       if not success:                            # no frame input

           print("Ignoring empty camera frame.")

           continue

       # To improve performance, optionally mark the image as not writeable to

       # pass by reference.

       image.flags.writeable = False

       image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # frame to RGB for the face-mesh model

       results = face_mesh.process(image)

       image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

       if results.multi_face_landmarks:

           gaze.gaze(image, results.multi_face_landmarks[0])

       cv2.imshow('output window', image)

       if cv2.waitKey(2) & 0xFF == 27:          

           break

cap.release()

这里没什么特别的,在第 27 行,我们将从 mediapipe 框架获得的当前帧和面部标志点传递给我们的 gaze  函数,这就是所有乐趣所在。

2D转3D?

视线追踪是一个 3D 问题,但我们在标题中说我们只使用了一个简单的网络摄像头,这怎么可能呢?

我们将使用一些魔法(线性代数)来实现它。

首先,让我们了解一下我们的相机是如何“看到”这个世界的。

来自 OpenCV 文档的图像

看屏幕时看到的 2D 图像用蓝色表示,3D 世界用世界坐标系表示。它们之间有什么联系?我们如何从 2D 图像映射 3D 世界,或者至少得到一个粗略的估计?

让我们弄清楚吧!

我们都一样

我们人类比我们想象的更相似,我们可以采用人脸的通用 3D 模型,这将是对大多数人口的 3D 比例的一个很好的估计。

让我们使用这样的模型来定义一个 3D 坐标系,我们将鼻尖设置为我们的坐标系的原点,相对于它我们将再定义 5 个点,如下所示:

def gaze(frame, points):

   '''

   2D image points.

   relative takes mediapipe points that normelized to [-1, 1] and returns image points

   at (x,y) format

   '''

   image_points = np.array([

       relative(points.landmark[4], frame.shape),    # Nose tip

       relative(points.landmark[152], frame.shape),  # Chin

       relative(points.landmark[263], frame.shape),  # Left eye left corner

       relative(points.landmark[33], frame.shape),   # Right eye right corner

       relative(points.landmark[287], frame.shape),  # Left Mouth corner

       relative(points.landmark[57], frame.shape)    # Right mouth corner

   ], dtype="double")

   # 3D model points.

   model_points = np.array([

       (0.0, 0.0, 0.0),       # Nose tip

       (0, -63.6, -12.5),     # Chin

       (-43.3, 32.7, -26),    # Left eye left corner

       (43.3, 32.7, -26),     # Right eye right corner

       (-28.9, -28.9, -24.1), # Left Mouth corner

       (28.9, -28.9, -24.1)   # Right mouth corner

   ])

   '''

   3D model eye points

   The center of the eye ball

   '''

   Eye_ball_center_right = np.array([[-29.05],[32.7],[-39.5]])

   Eye_ball_center_left = np.array([[29.05],[32.7],[-39.5]])

现在我们有 6 个从 mediapipe 获得的 2D 点,以及我们定义的世界坐标系中的相应 3D 点。我们的目标是了解这些点的 3D 位置的变化,并通过使用我们的 2D 图像来做到这一点。我们该怎么做?

针孔相机模型救援

针孔相机模型是一种数学模型,它描述了 3D 世界中的点之间的关系以及它们在 2D 图像平面上的投影。从这个模型中,我们将得出以下方程:

使用这个等式,我们可以获得将 3D 点投影到图像 2D 图像平面的变换。但是我们能解决吗?好吧,至少不是通过简单的代数工具,但你不用担心,这就是 OpenCV 使用 solvePnP 函数的地方,请查看链接以获得更深入的解释:

我们将获取我们的 6 个图像点和相应的 3D 模型点,并将它们传递给 solvepnp 函数。作为回报,我们将获得一个旋转和平移向量,从而得到一个变换,这将帮助我们将一个点从 3D 世界点投影到 2D 平面。

  '''

   camera matrix estimation

   '''

   focal_length = frame.shape[1]

   center = (frame.shape[1] / 2, frame.shape[0] / 2)

   camera_matrix = np.array(

       [[focal_length, 0, center[0]],

        [0, focal_length, center[1]],

        [0, 0, 1]], dtype="double"

   )


   dist_coeffs = np.zeros((4, 1))  # Assuming no lens distortion

   (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix,
                                                                 dist_coeffs, flags=cv2.cv2.SOLVEPNP_ITERATIVE)

使用我们的新转换,我们可以从 3D 空间中取出一个点并将其投影到 2D 图像平面。因此,我们将了解这个 3D 点在空间中指向的位置。这就是点 (0,0,150) 的样子。

2D从3D

现在我们将获取瞳孔 2D 图像坐标并将它们投影到我们的 3D 模型坐标。与我们在头部姿势估计部分所做的正好相反。

# project image point to world point

_ ,transformation, _ = cv2.estimateAffine3D(image_points1, model_points) # image cord to world cord tramsformation

pupil_world_cord =  transformation @ np.array([[left_pupil[0],left_pupil[1],0,1]]).T # Transformation * pupil image point vector

如代码片段所示,我们将使用 OpenCV 的估计 Affline3D 函数。此函数使用我们讨论的针孔相机模型的相同原理。它采用两组 3D 点并返回第一组和第二组之间的转换。但是等等,我们的图像点是二维的,这怎么可能?

好吧,我们将获取图像点 (x,y) 并将它们作为 (x,y,0) 传递,因此将获得图像坐标到模型坐标之间的转换。使用这种方法,我们可以从我们从 mediapipe 获取的 2D 图像点获取瞳孔 3D 模型点。

注意:这不是一个非常准确的估计

我没有告诉你,但是如果你看上面的第二个代码片段,你可以看到我们有眼睛中心模型点(3D),我们刚刚使用 estimateAffline3D 获取了瞳孔3D模型点。

现在要找到注视方向,我们需要解决这个线平面相交问题,如上图所述。我们试图找到的点用 S 表示。让我们将点投影到 2D 平面中。

  # project pupil image point into world point

   pupil_world_cord =  transformation @ np.array([[left_pupil[0],left_pupil[1],0,1]]).T
   

   # 3D gaze point (10 is arbitrary value denoting gaze distance)

   S = Eye_ball_center_left + (pupil_world_cord - Eye_ball_center_left) * 10
   

   # Project a 3D gaze point onto the image plane.

   (eye_pupil2D, jacobian) = cv2.projectPoints((int(S[0]), int(S[1]), int(S[2])), rotation_vector,
                                                   translation_vector, camera_matrix, dist_coeffs)

   # Draw gaze line into screen

   p1  = (int(left_pupil[0]), int(left_pupil[1]))

   p2 = (int(eye_pupil2D[0][0][0]) , int(eye_pupil2D[0][0][1]))

   cv2.line(frame, p1, p2, (0, 0, 255), 2)

注意:在第 5 行中,我们使用“魔术”数 10,这是因为我们不知道拍摄对象与相机的距离。所以图中用 t 表示的瞳孔到相机的距离是未知的

完了吗?

还没有。现在我们需要考虑头部运动,这样,们的视线追踪器就能适应头部的运动。让我们从一开始就使用我们的头部姿势估计。

瞳孔的 2D 位置由点 p 表示,点 g 是注视 + 头部旋转投影,点h是头部姿势投影。现在为了获得干净的注视信息,我们从向量A中构造向量B。

# Project a 3D gaze direction onto the image plane.

(eye_pupil2D, _) = cv2.projectPoints((int(S[0]), int(S[1]), int(S[2])), rotation_vector,
                                               translation_vector, camera_matrix, dist_coeffs)

# project 3D head pose into the image plane

(head_pose, _) = cv2.projectPoints((int(pupil_world_cord[0]), int(pupil_world_cord[1]), int(40)), rotation_vector,
                                               translation_vector, camera_matrix, dist_coeffs)

# correct gaze for head rotation

gaze = left_pupil + (eye_pupil2D[0][0] - left_pupil) - (head_pose[0][0] - left_pupil)

在第 5 行中,我们使用了“魔术”数 40,原因与我们在上面的代码片段中使用 10 的原因相同。

结束

我们已经完成了,至少现在是这样。你可以在 Github 页面上看到完整的代码,并在你的机器上运行它

但是我们真的完成了吗?

我们可以改变一些东西来提高准确性:

正确校准相机,不要使用估计。

使用双眼,计算两个位置之间的平均值。(我们只用了左眼)

我们正在使用estimateAffine3D 方法将2d 瞳孔位置投影到3d 空间中,但这不是一个准确的估计。我们可以使用眼睛结构和眼窝中的瞳孔位置来推断瞳孔的 3d 位置。

我们完全忽略了拍摄对象与相机的距离。正因为如此,我们只得到了一个注视方向而不是一个注视点。它可能是最重要的部分,但也是最复杂的部分。

通过一些工作,你可以实施你的解决方案,并将其用于你的特定需求。

声明:本文为OFweek维科号作者发布,不代表OFweek维科号立场。如有侵权或其他问题,请及时联系我们举报。
2
评论

评论

    相关阅读

    暂无数据

    磐创AI

    人工智能前沿技术分享。...

    举报文章问题

    ×
    • 营销广告
    • 重复、旧闻
    • 格式问题
    • 低俗
    • 标题夸张
    • 与事实不符
    • 疑似抄袭
    • 我有话要说
    确定 取消

    举报评论问题

    ×
    • 淫秽色情
    • 营销广告
    • 恶意攻击谩骂
    • 我要吐槽
    确定 取消

    用户登录×

    请输入用户名/手机/邮箱

    请输入密码