国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV识别面部表情

 二维码 46
Python      OpenCV
聚焦Python和OpenCV‍全文档‍

准备

您应该从官方OpenCV资料库下载Haar级联文件,或将它们从计算机的安装目录复制到项目存储库。

构思App

面部和面部表情的可靠识别对于人工智能(AI)来说是一项艰巨的任务,而人类却能够轻松地执行这些任务。 为了使我们的任务可行,我们将限于以下有限的情感表达:

  • 中性
  • 高兴
  • 悲伤
  • 惊喜
  • 生气
  • 厌恶

当今最先进的模型范围广泛,从适用于卷积神经网络(CNN)的3D变形人脸模型到深度学习算法。 当然,这些方法比我们的方法复杂得多。

但是,MLP是帮助改变机器学习领域的经典算法,因此出于教育目的,我们将坚持使用与OpenCV捆绑在一起的一组算法。

为了实现这样一个应用程序,我们需要解决以下两个挑战:

  • 面部识别::我们将使用Viola和Jones流行的Haar级联分类器,为此OpenCV提供了完整的预训练示例。 我们将利用面部叶栅和眼睛叶栅来可靠地检测并对齐各个框架中的面部区域。
  • 面部表情识别:我们将训练一个MLP,以识别出每张检测到的面孔中前面列出的六种不同的情感表达。 这种方法的成功关键取决于我们组装的训练集,以及我们选择应用于该集中每个样本的预处理。

    为了提高自记录训练集的质量,我们将确保使用仿射变换对齐所有数据样本,并通过应用PCA减少特征空间的维数。 所得表示有时也称为本征面。

我们将在单个端到端应用程序中结合前面提到的算法,该应用程序在视频直播流的每个捕获帧中为检测到的面部和相应的面部表情标签添加注释。 最终结果可能类似于以下屏幕快照,捕获了我的示例在代码首次运行时的反应:

最终的应用程序将包含主要脚本,该脚本将端对端的流程集成到一起(即从面部检测到面部表情识别),以及一些实用程序功能来提供帮助。

最终产品包含以下部件:

  • chapter8.py:这是本章的主要脚本和入口点,我们将在数据收集和演示中使用它。 它将具有以下布局:
    • chapter8.FacialExpressionRecognizerLayout:这是基于wx_gui.BaseLayout的自定义布局,该布局将检测每个视频帧中的人脸并通过使用预先训练的模型来预测相应的类标签。
    • chapter8.DataCollectorLayout:这是基于wx_gui.BaseLayout的自定义布局,该布局将收集图像帧,在其中检测人脸,使用用户选择的面部表情标签分配标签,并将帧保存到数据中。
  • wx_gui.py:这是我们在“滤镜”中开发的wxpython GUI文件。
  • detectors.FaceDetector:这是一类,将包含所有基于Haar级联的面部检测代码。 它将具有以下两种方法:
    • detect_face:此方法检测灰度图像中的脸部。 可以选择性将图像缩小以提高可靠性。 成功检测后,该方法将返回提取的头部区域。
    • align_head:此方法使用仿射变换对提取的头部区域进行预处理,以使生成的脸部居中并直立。
  • params/:这是一个目录,其中包含我们使用的默认Haar级联。
  • data/:我们将在此处编写所有代码来存储和处理我们的自定义数据。 该代码分为以下文件:
  • store.py:这是一个文件,我们在其中放置了所有辅助函数,以将数据写入磁盘并将数据从磁盘加载到计算机内存中。
  • process.py:这是一个文件,其中包含在保存之前对数据进行预处理的所有代码。 它还将包含用于从原始数据构造要素的代码。

面部识别

OpenCV预先安装了一系列用于通用对象检测的复杂分类器。 一旦知道了所要查找的内容,它们都具有非常相似的API,并且易于使用。 也许最广为人知的检测器是用于面部检测的基于Haar的特征检测器的级联,由Paul Viola和Michael Jones在2001年的论文《使用简单特征的增强级联进行快速物体检测》中首次提出。

基于Haar的特征检测器是一种机器学习算法,对很多正负标签样本进行训练。 我们将在应用程序中执行OpenCV附带的预训练分类器。 但是首先,让我们仔细看看分类器是如何工作的。

基于Haar的级联分类器

每本有关OpenCV的书都应至少提及Viola-Jones面部检测器。 该级联分类器于2001年发明,它最终允许实时面部检测和面部识别,从而扰乱了计算机视觉领域。

分类器基于类似Haar的特征(类似于Haar基函数),该特征汇总图像小区域中的像素强度,并捕获相邻图像区域之间的差异。

以下屏幕截图显示了四个矩形功能。 可视化用于计算在某个位置应用的特征的值。 您应该将深灰色矩形中的所有像素值相加,然后从白色矩形中的所有像素值之和中减去该值:

在上一个屏幕截图中,第一行显示了边缘特征的两个示例(即,您可以使用它们检测边缘),可以是垂直方向(左上)或以45º角方向(右上)定向。 底行显示线要素(左下方)和中心环绕特征(右下方)。

在所有可能的位置应用这些滤镜可以使算法捕获人脸的某些细节,例如眼睛区域通常比脸颊周围的区域更暗的事实。

因此,常见的Haar特征将在明亮的矩形(代表脸颊区域)上方有一个黑色的矩形(代表眼睛区域)。 Viola和Jones将这一特征与一系列旋转的且稍微复杂一点的小波相结合,得出了一个强大的人脸特征描述符。 另外,这些家伙还想出了一种有效的方法来计算这些特征,从而首次实现了实时检测人脸的功能。

最终分类器是较小的较弱分类器的加权总和,每个较弱的分类器均基于先前描述的单个功能。 最难的部分是弄清楚哪些特征组合有助于检测不同类型的对象。 幸运的是,OpenCV包含此类分类器的集合。

预训练级联分类器

更好的是,这种方法不仅适用于面部,而且适用于眼睛,嘴巴,全身,公司徽标; 你给它命名。 下表显示了许多预训练的分类器,这些分类器可在数据文件夹的OpenCV安装路径下找到:

级联分类器XML文件名
面部识别(默认)haarcascade_frontalface_default.xml
面部识别(快速 Haar)haarcascade_frontalface_alt2.xml
眼睛识别haarcascade_eye.xml
嘴巴识别haarcascade_mcs_mouth.xml
鼻子识别haarcascade_mcs_nose.xml
全身识别haarcascade_fullbody.xml

本文,我们将使用haarcascade_frontalface_default.xml和haarcascade_eye.xml。

如果您戴着眼镜,请确保使用haarcascade_eye_tree_eyeglasses.xml进行眼睛检测。

使用预先训练的级联分类器

可以使用以下代码加载级联分类器并将其应用于图像(灰度),我们首先读取图像,然后将其转换为灰度,最后使用级联分类器检测所有面部:

import cv2
gray_img = cv2.cvtColor(cv2.imread('example.png'), cv2.COLOR_RGB2GRAY)
cascade_clf = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = cascade_clf.detectMultiScale(gray_img,scaleFactor=1.1,minNeighbors=3,
flags=cv2.CASCADE_SCALE_IMAGE)

在前面的代码中,detectMultiScale函数带有许多选项:

  • minFeatureSize是要考虑的最小脸部尺寸,例如20 x 20像素。
  • searchScaleFactor是我们重新缩放图像的数值。 例如,值1.1会逐渐将输入图像的尺寸减小10%,从而更有可能找到具有较大值的脸部(图像)。
  • minNeighbors是每个候选矩形必须保留的邻居数。 通常,我们选择3或5。
  • flags是一个用于调整算法的选项对象,例如,查找所有面孔还是仅查找最大面孔(cv2.cv.CASCADE_FIND_BIGGEST_OBJECT)。

如果检测成功,该函数将返回包含检测到的面部区域的坐标的边界框(面部)列表,如下所示:

for (x, y, w, h) in faces:
 # draw bounding box on frame
 cv2.rectangle(frame, (x, y), (x + w, y + h), (100, 255, 0),thickness=2)

在前面的代码中,我们遍历返回的面,并向每个面添加一个厚度为2个像素的矩形轮廓。

如果您的预先训练的面部层叠没有检测到任何东西,通常的原因通常是找不到预先训练的层叠文件的路径。 在这种情况下,CascadeClassifier将静默失败。 因此,始终最好通过检查casc.empty()来检查返回的分类器casc = cv2.CascadeClassifier(filename)是否为空。

如果在Lenna.png图片上运行代码,则应该得到以下内容:

在上一个屏幕截图的左侧,您可以看到原始图像,右侧是传递到OpenCV的图像以及检测到的脸部的矩形轮廓。

了解FaceDetector类

本文中所有相关的面部检测代码都可以在检测器模块中的FaceDetector类中找到。 实例化后,此类将加载预处理所需的两个不同的层叠分类器,即face_cascade分类器和eye_cascade分类器,如下所示:

import cv2
import numpy as np
class FaceDetector:
 def __init__(self, *,
	 face_cascade='params/haarcascade_frontalface_default.xml',
	 eye_cascade='params/haarcascade_lefteye_2splits.xml',
	 scale_factor=4):

因为我们的预处理需要一个有效的面部层叠,所以我们确保可以加载该文件。 如果没有,我们将抛出ValueError异常,因此程序将终止并通知用户出了什么问题,如以下代码块所示:

# load pre-trained cascades
 self.face_clf = cv2.CascadeClassifier(face_cascade)
 if self.face_clf.empty():
	 raise ValueError(f'Could not load face cascade
	 "{face_cascade}"')

我们也对眼睛分类器执行相同的操作,如下所示:

self.eye_clf = cv2.CascadeClassifier(eye_cascade)
 if self.eye_clf.empty():
	 raise ValueError(
		 f'Could not load eye cascade "{eye_cascade}"')

人脸检测在低分辨率的灰度图像上效果最好。 这就是为什么我们还要存储比例因子(scale_factor)的原因,以便在必要时可以对输入图像的缩小版本进行操作,如下所示:

self.scale_factor = scale_factor

现在我们已经设置了类的初始化,让我们看一下检测人脸的算法。

在灰度图像中检测人脸

现在,我们将在上一节中学到的东西放到一种方法中,该方法将拍摄一张图像并返回该图像中最大的面孔。 我们返回最大的面孔来简化操作,因为我们知道在我们的应用程序中,只有一个用户坐在网络摄像头前。 作为挑战,您可以尝试扩展此功能以使用多张面孔!

我们调用检测最大脸的方法(detect_face)。 让我们逐步进行一下:

  1. 与上一节一样,首先,我们将RGB图像参数转换为灰度,并通过运行以下代码通过scale_factor对其进行缩放:
    def detect_face(self, rgb_img, *, outline=True):
     frameCasc = cv2.cvtColor(cv2.resize(rgb_img, (0, 0),
    	 fx=1.0 /
    	 self.scale_factor,
    	 fy=1.0 /
    	 self.scale_factor),
    	 cv2.COLOR_RGB2GRAY)
  1. 然后,我们检测灰度图像中的面部,如下所示:
    faces = self.face_clf.detectMultiScale(
     frameCasc,
     scaleFactor=1.1,
     minNeighbors=3,
     flags=cv2.CASCADE_SCALE_IMAGE) * self.scale_factor
  1. 如果将outline = True关键字参数传递给detect_face,我们将遍历检测到的面部并概述轮廓。 OpenCV向我们返回左上角位置的x,y坐标以及头部的w,h宽度和高度。 因此,为了构造轮廓,我们只需计算轮廓的底部和右侧坐标,然后调用cv2.rectangle函数,如下所示:
    for (x, y, w, h) in faces:
     if outline:
    	 cv2.rectangle(rgb_img, (x, y), (x + w, y + h),(100, 255, 0), thickness=2)
  1. 我们从原始RGB图像中裁剪出头部。 如果我们要对头部进行更多处理(例如,识别面部表情),这将很方便。 运行以下代码:
    head = cv2.cvtColor(rgb_img[y:y + h, x:x + w],cv2.COLOR_RGB2GRAY)
  1. 我们返回以下四元组:
    • 一个布尔值,用于检查检测是否成功。
    • 添加了面部轮廓的原始图像(如果需要)。
    • 根据需要使用的头部裁剪图像。
    • 原始图像中头部位置的坐标。
  1. 如果成功,我们将返回以下内容:
    return True, rgb_img, head, (x, y)

    在失败的情况下,我们返回未找到头部,对于未确定的任何事物,返回None,如下所示:

    return False, rgb_img, None, (None, None)

现在,让我们看看检测到面孔后会发生什么,以使其准备好用于机器学习算法。

预处理检测到的面部

采集数据

组合训练数据集

运行应用

实现数据收集器UI

存储数据

面部表情识别

处理数据集

了解MLP

制作面部表情识别MLP


阅读完整文档

文章分类: 编程碎语PythonOpenCV