这是一篇人脸检测基本实现最好懂的实践指南了!

点击上方关注,All in AI中国

这是一篇人脸检测基本实现最好懂的实践指南了!

在本教程中,我们将了解如何使用OpenCV和Dlib在Python中创建和启动面部检测算法。我们还将添加一些功能,以同时检测多个脸部的眼睛和嘴巴。本文将介绍人脸检测的最基本实现,包括级联分类器、HOG和深度学习CNN。

我们将使用以下内容覆盖面部检测:

  • 使用OpenCV的Haar级联分类器
  • 使用Dlib的定向梯度直方图
  • 使用Dlib的卷积神经网络

本文最初发布在我的个人博客上:https://maelfabien.github.io/tutorials/face-detection/#

可以在此处找到本文的Github存储库(以及我博客中的所有其他存储库):

https://github.com/maelfabien/Machine_Learning_Tutorials

介绍

我们将使用OpenCV,一个用C / C ++编写的计算机视觉开源库,它有C ++、Python和Java接口。支持Windows、Linux、MacOS、iOS和Android。我们的一些工作还需要使用Dlib,这是一个现代C ++工具包,包含用于创建复杂软件的机器学习算法和工具。

要求

第一步是安装OpenCV和Dlib。运行以下命令:

这是一篇人脸检测基本实现最好懂的实践指南了!

根据您的版本,该文件将安装在此处:

这是一篇人脸检测基本实现最好懂的实践指南了!

进口和模型路径

我们将创建一个新的Jupyter笔记本/ python文件,从以下开始:

这是一篇人脸检测基本实现最好懂的实践指南了!

I.级联分类器

我们将首先探索级联分类器。

I.1.理论

级联分类器,或者是类似于类似哈尔特征的级联增强分类器,是集成学习的一种特殊情况,称为增强。它通常依赖于Adaboost分类器(以及其他模型,如Real Adaboost、Gentle Adaboost或Logitboost)。

级联分类器在包含我们想要检测的对象的几百个图像样本图像以及不包含这些图像的其他图像上进行训练。

我们如何检测面部是否存在?有一种名为Viola-Jones对象检测框架的算法,包括实时面部检测所需的所有步骤:

  • Haar特征选择,源自Haar小波的特征
  • 创建积分图像
  • Adaboost训练
  • 级联分类器

原始论文于2001年出版。英文版传送门:https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf

I.1.a.哈尔特征选择

我们在最常见的人脸上发现了一些共同特征:

  • 一个黑眼圈区域与上颊相比
  • 与眼睛相比,明亮的鼻梁区域
  • 眼睛,嘴巴,鼻子的一些特定位置......

这些特征称为哈尔特征。特征提取过程如下所示:

这是一篇人脸检测基本实现最好懂的实践指南了!

Haar Features

在该示例中,第一特征测量的是眼睛区域和横跨上脸颊的区域之间的强度差异。特征值的计算方法很简单,将黑色区域的像素相加,然后减去白色区域的像素。

这是一篇人脸检测基本实现最好懂的实践指南了!

然后,我们将这个矩形作为卷积内核应用于整个图像上。为了详尽无遗,我们应该应用每个内核的所有可能的维度和位置。简单的24 * 24图像通常会产生超过160'000个特征,每个特征由像素值的和/减法组成。实时面部检测在计算上是不可能的。那么,我们如何加快这个过程呢?

  • 一旦通过矩形识别好区域,在完全不同的图像区域上运行窗口是没有用的,这可以通过Adaboost实现。
  • 使用积分图像原理计算矩形特征,这种方法会更快。我们将在下一节中介绍这一点。

这是一篇人脸检测基本实现最好懂的实践指南了!

有几种类型的矩形可用于Haar特征提取。根据原始论文:

  • 两个矩形特征是两个矩形区域内像素之和的差异,主要用于检测边缘(a,b)
  • 三矩形特征计算从中心矩形中的和减去两个外部矩形内的和,主要用于检测直线(c,d)
  • 四矩形特征计算矩形对角线对之间的差异(e)

这是一篇人脸检测基本实现最好懂的实践指南了!

矩形哈尔

现在已经选择了这些特征,我们使用Adaboost分类将它们应用于训练图像集,该分类结合了一组弱分类器来创建精确的集合模型。具有200个功能(最初不是160'000),实现了95%的精度。该论文的作者选择了6,000个特征。

I.1.b.积分图像

用卷积内核样式计算矩形特征可能很长很长。出于这个原因,作者Viola和Jones提出了一种图像的中间表示:积分图像。积分图像的作用是允许仅使用四个值简单地计算任何矩形和。我们可以看看它是如何工作的!

假设我们想要确定一个给定像素上坐标(x,y)的矩形特征。然后,像素的积分图像在给定像素的上方和左侧的像素之和中。

这是一篇人脸检测基本实现最好懂的实践指南了!

其中ii(x,y)是积分图像,i(x,y)是原始图像。

当您计算整个积分图像时,有一种形式是重复,只需要在原始图像上递归一次。实际上,我们可以定义以下两组递归:

这是一篇人脸检测基本实现最好懂的实践指南了!

其中s(x,y)是累积行和,并且s(x-1)= 0,ii(-1,y)= 0。

这有什么用?考虑一个我们想要估算像素总和的区域D。我们还定义了3个其他区域:A、B和C。

  • 点1处的积分图像的值是矩形A中的像素的总和
  • 第2点的值是ΔA+ B.
  • 第3点的值是ΔA+ C.
  • 第4点的值是A + B + C + D.

因此,区域D中的像素之和可以简单地计算为:4 + 1-(2 + 3)。

在一次传递中,我们仅使用4个数组引用计算了矩形内的值。

这是一篇人脸检测基本实现最好懂的实践指南了!

人们应该简单地意识到矩形在实践中是非常简单的特征,但足以用于面部检测。当涉及复杂问题时,可操纵滤波器往往更灵活。

这是一篇人脸检测基本实现最好懂的实践指南了!

可操纵的过滤器

I.1c.使用Adaboost学习分类函数

给定一组标记的训练图像(正面或负面),Adaboost用于:

  • 选择一小组特征
  • 并训练分类器

由于160'000中的大多数特征应该是非常不相关的,因此我们构建增强模型的弱学习算法建立了一个增强模型。该算法的目的是选择单个矩形特征,将最好的正、负样本进行分割。

I.1.d.级联分类器

虽然上述过程非常有效,但仍存在一个重大问题。在图像中,大部分图像是非面部区域。对每个区域都基于同等的重要性是没有意义的,因为我们应该主要关注最有可能包含图片的区域。Viola和Jones在使用级联分类器减少计算时间的同时提高了检测率。

关键的想法是在识别区域时拒绝不包含人脸的子窗口。由于任务是正确识别面部,我们希望最小化假阴性率,即包含面部并且尚未被识别的子窗口。

一系列分类器应用于每个子窗口。这些分类器是简单的决策树:

  • 如果第一个分类器是正数,我们继续到第二个
  • 如果第二个分类器是正数,我们继续第三个
  • ...

在某些时候任何负面结果都会导致子窗口被拒绝为可能包含面部。初始分类器以较低的计算成本消除了大多数否定示例,并且以下分类器消除了额外的负面示例,但需要更多的计算工作量。

这是一篇人脸检测基本实现最好懂的实践指南了!

使用Adaboost训练分类器并调整阈值以最小化错误率。在训练这样的模型时,变量如下:

  • 分类器级数
  • 每个阶段的特征数量
  • 每个阶段的阀值

幸运的是,在OpenCV中,整个模型已经过预先训练,可用于人脸检测。

I.2.输入

下一步只是找到预先训练的权重。我们将使用默认的预训练模型来检测面部、眼睛和嘴巴。根据您的Python版本,文件应放在这里的某个地方:

/usr/local/lib/python3.7/site-packages/cv2/data

一旦确定,我们将以这种方式声明Cascade分类器:

cascPath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_frontalface_default.xml"
eyePath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_eye.xml"
smilePath = "/usr/local/lib/python3.7/site-packages/cv2/data/haarcascade_smile.xml"
faceCascade = cv2.CascadeClassifier(cascPath)
eyeCascade = cv2.CascadeClassifier(eyePath)
smileCascade = cv2.CascadeClassifier(smilePath)

I.3.检测图像上的脸部

在实现实时人脸检测算法之前,我们先在图像上尝试一个简单的版本。我们可以从加载测试图像开始:

# Load the image
gray = cv2.imread('face_detect_test.jpeg', 0)
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()

这是一篇人脸检测基本实现最好懂的实践指南了!

测试图像

然后,我们检测到脸部并在其周围添加一个矩形:

# Detect faces
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
flags=cv2.CASCADE_SCALE_IMAGE
)
# For each face
for (x, y, w, h) in faces: 
 # Draw rectangle around the face
 cv2.rectangle(gray, (x, y), (x+w, y+h), (255, 255, 255), 3)

以下是detect Multi Scale函数最常见参数的列表:

  • scaleFactor:指定在每个图像比例下图像大小减少多少的参数。
  • minNeighbors:参数指定每个候选矩形应保留多少个邻居。
  • minSize:最小可能的对象大小。小于该值的对象将被忽略。
  • maxSize:最大可能的对象大小。大于该值的对象将被忽略。

最后,显示结果:

plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()

这是一篇人脸检测基本实现最好懂的实践指南了!

人脸检测在我们的测试图像上运行良好。让我们现在开始进入最振奋的部分吧!

I.4.实时人脸检测

让我们继续进行实时面部检测的Python实现。第一步是启动摄像头,并捕获视频。然后,我们将图像转换为灰度图像。这用于减小输入图像的尺寸。实际上,我们应用简单的线性变换,而不是描述红色、绿色、蓝色的每个像素3个点,而不是:

这是一篇人脸检测基本实现最好懂的实践指南了!

这在OpenCV中默认实现。

video_capture = cv2.VideoCapture(0)
while True:
 # Capture frame-by-frame
 ret, frame = video_capture.read()
 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

现在,我们将使用上面定义的face Cascade变量,它包含一个预先训练过的算法,并将其应用于灰度图像。

faces = faceCascade.detectMultiScale(
 gray,
 scaleFactor=1.1,
 minNeighbors=5,
 minSize=(30, 30),
 flags=cv2.CASCADE_SCALE_IMAGE
 )

对于检测到的每个脸部,我们将在脸部周围绘制一个矩形:

for (x, y, w, h) in faces:
 if w > 250 :
 cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 3)
 roi_gray = gray[y:y+h, x:x+w]
 roi_color = frame[y:y+h, x:x+w]

对于检测到的每个嘴,在它周围画一个矩形:

smile = smileCascade.detectMultiScale(
 roi_gray,
 scaleFactor= 1.16,
 minNeighbors=35,
 minSize=(25, 25),
 flags=cv2.CASCADE_SCALE_IMAGE
 )
 for (sx, sy, sw, sh) in smile:
 cv2.rectangle(roi_color, (sh, sy), (sx+sw, sy+sh), (255, 0, 0), 2)
 cv2.putText(frame,'Smile',(x + sx,y + sy), 1, 1, (0, 255, 0), 1)

对于检测到的每只眼睛,在其周围绘制一个矩形:

eyes = eyeCascade.detectMultiScale(roi_gray)
 for (ex,ey,ew,eh) in eyes:
 cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
 cv2.putText(frame,'Eye',(x + ex,y + ey), 1, 1, (0, 255, 0), 1)

然后,计算面部总数,并显示整体图像:

if cv2.waitKey(1) & 0xFF == ord('q'):
 break

当我们想通过按q来停止相机时,执行退出选项:

video_capture.release()
cv2.destroyAllWindows()

最后,当一切都完成后,释放捕获并销毁所有窗口。在Mac上关闭Windows可能存在一些问题,可能需要稍后从活动管理器中kill Python。

I.5.总结

这是一篇人脸检测基本实现最好懂的实践指南了!

I.6.结果

这里做了一个人脸检测算法的快速演示。

这是一篇人脸检测基本实现最好懂的实践指南了!

II.Dlib中的定向梯度直方图(HOG)

Dlib提供了第二种最常用的人脸检测工具,它使用了一种名为直方图梯度(HOG)的概念。这是Dalal和Triggs的原始论文的实现。

II.1. 理论

HOG背后的想法是将特征提取到矢量中,并将其提供给分类算法,例如支持向量机,例如,它将评估一个人脸(或您训练它实际识别的任何对象)是否存在于某个区域中。

提取的特征是图像的梯度方向(定向梯度)的分布(直方图)。梯度通常在边缘和角落周围较大,并允许我们检测这些区域。

在原始论文中,对人体检测过程进行了实现,检测链如下:

这是一篇人脸检测基本实现最好懂的实践指南了!

II.1.a.预处理

首先,输入图像必须具有相同的尺寸(裁剪和重新缩放图像)。我们将应用的色块要求宽高比为1:2,因此输入图像的尺寸可能为64x128或100x200。

II.1.b.计算梯度图像

第一步是通过应用以下内核计算图像的水平和垂直梯度:

这是一篇人脸检测基本实现最好懂的实践指南了!

用于计算梯度的内核

图像的梯度通常会删除非必要信息。

我们在上面考虑的图像的梯度可以在Python中以这种方式找到:

gray = cv2.imread('images/face_detect_test.jpeg', 0)
im = np.float32(gray) / 255.0
# Calculate gradient 
gx = cv2.Sobel(im, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(im, cv2.CV_32F, 0, 1, ksize=1)
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

并绘制图片:

plt.figure(figsize=(12,8))
plt.imshow(mag)
plt.show()

这是一篇人脸检测基本实现最好懂的实践指南了!

我们之前没有对图像进行预先处理。

II.1.c.计算HOG

然后将图像分成8x8单元,以提供一个紧凑的表示,使我们的HOG对噪声更加鲁棒。然后,我们为每个单元格计算一个HOG。

为了估计区域内的梯度方向,我们只需在每个区域内的64个梯度方向值(8x8)及其大小(另外64个值)之间构建直方图。直方图的类别对应于梯度的角度,从0到180°。总共9类:0°、20°、40°...... 160°。

上面的代码给了我们2个信息:

  • 梯度的方向
  • 梯度的大小

当我们构建HOG时,有3个子案例:

  • 角度小于160°,且不介于两类之间。在这种情况下,角度将添加到HOG的右侧类别中
  • 角度小于160°,恰好在2级之间。在这种情况下,我们考虑对2个最近的类进行相等的贡献,并将大小分成2个部分

这是一篇人脸检测基本实现最好懂的实践指南了!

  • 角度大于160°。在这种情况下,我们认为像素的贡献比例为160°和0°。

这是一篇人脸检测基本实现最好懂的实践指南了!

对于每个8x8单元,HOG看起来像这样:

这是一篇人脸检测基本实现最好懂的实践指南了!

Hog

II.1.d. 区间归一化

最后,可以使用16x16块对图像进行归一化,使其不受光照的影响。这可以通过将大小为8x8的HOG的每个值除以包含它的16x16块的HOG的L2范数来实现,这实际上是长度为9 * 4 = 36的简单向量。

II.2.检测图像上的脸部

实施非常简单:

face_detect = dlib.get_frontal_face_detector()
rects = face_detect(gray, 1)
for (i, rect) in enumerate(rects):
(x, y, w, h) = face_utils.rect_to_bb(rect)
 cv2.rectangle(gray, (x, y), (x + w, y + h), (255, 255, 255), 3)
 
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()

这是一篇人脸检测基本实现最好懂的实践指南了!

II.3.实时人脸检测

如前所述,该算法非常容易实现。我们还通过仅检测面部来实现更轻的版本。 Dlib也很容易检测面部关键点,但这是另一个话题。

这是一篇人脸检测基本实现最好懂的实践指南了!

III.Dlib中的卷积神经网络

最后一种方法基于卷积神经网络(CNN)。它还实现了一篇关于最大边距目标检测(MMOD)的论文,以增强结果。

III.1.一点理论

卷积神经网络(CNN)是前馈神经网络,主要用于计算机视觉。它们提供自动图像预处理以及密集的神经网络部分。 CNN是用于处理具有网格状拓扑的数据的特殊类型的神经网络。 CNN的架构受到动物视觉皮层的启发。

在以前的方法中,很大一部分工作是选择滤镜以创建特征,以便尽可能多地从图像中提取信息。随着深度学习和更高计算能力的提高,这项工作现在可以实现自动化。 CNN的名称来自于我们将初始图像输入与一组过滤器进行卷积的事实。要选择的参数仍然是要应用的过滤器数量以及过滤器的尺寸。过滤器的尺寸称为步幅长度。步幅的典型值介于2和5之间。

这是一篇人脸检测基本实现最好懂的实践指南了!

在这种特定情况下,CNN的输出是二进制分类,如果有面部,则取值1,否则取0。

III.2.检测图像上的脸部

一些元素在实现中发生了变化。

第一步是在这里下载预先训练的模型。将权重移动到您的文件夹,并定义dnnDaceDetector:

dnnFaceDetector = dlib.cnn_face_detection_model_v1("mmod_human_face_detector.dat")

然后,与我们迄今为止所做的完全相同:

rects = dnnFaceDetector(gray, 1)
for (i, rect) in enumerate(rects):
 x1 = rect.rect.left()
 y1 = rect.rect.top()
 x2 = rect.rect.right()
 y2 = rect.rect.bottom()
 # Rectangle around the face
 cv2.rectangle(gray, (x1, y1), (x2, y2), (255, 255, 255), 3)
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()

这是一篇人脸检测基本实现最好懂的实践指南了!

III.3.实时人脸检测

最后,我们将实现CNN人脸检测的实时版本:

这是一篇人脸检测基本实现最好懂的实践指南了!

IV.哪一个选择?

这是一个棘手的问题,但我们只会通过两个重要指标:

  • 计算时间
  • 准确性

在速度方面,HoG似乎是最快的算法,其次是Haar Cascade分类器和CNN。

但是,Dlib中的CNN往往是最准确的算法。 HoG表现相当不错,但有一些识别小面孔方面的问题。 HaarCascade分类器的性能与HoG一样好。

我个人主要在我的个人项目中使用HoG,因为它可以快速的进行实时人脸检测。

V. 来源 :

  • HOG
  • DLIB
  • Viola-Jones Paper
  • Face Detection 1
  • Face Detection 2
  • Face Detection 3
  • DetectMultiScale
  • Viola-Jones

原文作者:Maël Fabien

这是一篇人脸检测基本实现最好懂的实践指南了!

编译出品

相关推荐