在本教程中,我们将看到如何使用OpenCV和Dlib在Python中创建和启动人脸检测算法。我们还将添加一些功能来同时检测多个面孔上的眼睛和嘴巴。本文将介绍人脸检测的最基本实现,包括级联分类器、HOG窗口和深度学习CNNs。
我们将使用以下方法介绍人脸检测:
- 使用OpenCV的Haar Cascade Classifiers级联分类器
- 使用Dlib的方向梯度直方图
- 使用Dlib的卷积神经网络
这篇文章最初发表在我的个人博客上。
本文介绍
我们将使用OpenCV,一个用于计算机视觉的开源库,用C/C++编写,有C++、Python和Java接口。它支持Windows、Linux、MacOS、iOS和Android。我们的一些工作还需要使用Dlib,这是一个包含机器学习算法和用于创建复杂软件的工具的现代C++工具包。
需求
第一步是安装OpenCV和Dlib。运行以下命令:
pip install opencv-python
pip install dlib
根据你的版本,将在此处安装该文件:
/usr/local/lib/python3.7/site-packages/cv2
如果您在使用Dlib时遇到一些问题,请查看本文。
导入和模型路径
我们将创建一个新的Jupyter Notebook/ python文件,并从以下内容开始:
import cv2
import matplotlib.pyplot as plt
import dlib
from imutils import face_utilsfont = cv2.FONT_HERSHEY_SIMPLEX
1.Cascade Classifiers级联分类器
我们将首先探讨Cascade Classifiers级联分类器。
1.1理论
级联分类器(CascadeClassifier)是集成学习的一种特殊情况,是增强(boost)分类器的级联。它通常依赖于Adaboost分类器(以及其他模型,如Real Adaboost、Gentle Adaboost或Logitboost)。
级联分类器(CascadeClassifier)针对包含我们要检测的对象的数百幅图像样本和不包含这些图像的其他图像进行训练。
我们如何发现图片中是否有一张脸存在呢?有一种叫做Viola-Jones object detection framework的算法,它包含了实时人脸检测所需的所有步骤:
- Haar特征选择,Haar小波的特征
- 创建积分图像
- Adaboost训练
- 级联分类器
最初论文发表于2001年。
1.1.a Haar特征选择
我们在最常见的人类面孔上发现了一些共同的特征:
- 与上脸颊相比,眼睛是黑色区域
- 与眼睛相比,鼻梁区域明亮
- 眼睛,嘴,鼻子的特定位置…
这些特征称为HAAR特征。特征提取过程如下:
在这个例子中,第一个特征测量了眼睛区域和上脸颊区域之间的强度差异。特征值的计算方法很简单,将黑色区域的像素相加,然后减去白色区域的像素。
然后,我们把这个矩形作为卷积核应用到整个图像上。为了详尽,我们应该应用每个内核的所有可能的维度和位置。一个简单的24*24的图像通常会产生超过16万个特征,每个特征由像素值的和/减组成。这在计算上是不可能实现实时人脸检测的。那么,我们如何加快这个过程呢?
- 一旦好区域被矩形标识出来,在图像的完全不同的区域上运行该窗口是没有用的。这可以通过Adaboost实现。
- 使用积分图像原理计算矩形的特征,这样更快。我们将在下一节中讨论这个问题。
- 有几种类型的矩形可以用于Haar特征提取。根据原文:
- 双矩形特征是两个矩形区域内像素之和的差值,主要用于检测边缘(A,B)
- 三矩形特征计算的是由一个中心矩形内的和减去两个外矩形内的和,主要用于检测直线(C,D)。
- 四矩形特征计算矩形对角对之间的差值(E)
既然已经选择了这些特性,我们将它们应用于使用Adaboost分类的训练图像集,该分类器结合一组弱分类器来创建一个准确的集成模型。有200个特征(而不是最初的16万个),准确率达到95%。本文的作者选择了6000个特征。
1.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个数组引用计算了矩形内的值。
人们应该意识到矩形在实际中是非常简单的特征,但对于人脸检测来说已经足够了。当涉及到复杂的问题时,可操纵过滤器往往更灵活。
1.1.c 使用Adaboost学习分类函数
给定一组带标记的训练图像(正面或负面),Adaboost用于:
由于16万个特征中大部分都是不相关的,所以我们围绕弱学习算法建立了一个增强模型,该算法的目的是选择单个矩形特征,将最好的正、负样本进行分割。
1.1d 级联分类器(CascadeClassifier)
尽管上面描述的过程非常有效,但仍然存在一个主要问题。在图像中,大部分图像是非人脸区域。对图像的每个区域给予同等的重要性是没有意义的,因为我们应该主要关注最有可能包含图像的区域。Viola和Jones在使用级联分类器减少计算时间的同时提高了检测率。
关键思想是拒绝不包含人脸的子窗口,同时识别包含人脸的区域。由于任务是正确识别人脸,我们想要最小化假阴性率。即包含人脸且未被标识的子窗口。
- 每个子窗口都应用了一系列分类器。这些分类器是简单的决策树:
- 如果第一个分类器是正的,我们继续看第二个分类器
- 如果第二个分类器是正的,我们继续看第三个分类器
...
在某个时候,任何负面结果都会导致子窗口被拒绝,因为它可能包含人脸。初始分类器以较低的计算成本消除了大多数负面示例,下面的分类器消除了额外的负面示例,但需要更多的计算工作。
使用Adaboost分类器进行训练,并调整阈值以最小化错误率。在训练该模型时,变量如下:
幸运的是,在OpenCV中,整个模型已经经过了人脸检测的预训练。
如果您想了解更多关于增强技术的知识,我邀请您查看我在Adaboost上的文章。
1.2输入
下一步就是找到预先训练好的重量。我们将使用默认的预训练模型来检测人脸、眼睛和嘴巴。根据你的Python版本,文件应该放在这里的某个地方:
/usr/local/lib/python3.7/site-packages/cv2/data
一旦确定,我们将通过以下方式声明级联分类器:
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)
1.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)
下面是detectMultiScale函数最常见的参数列表:
ScaleFactor:参数,指定在每个图像缩放时图像大小减少了多少。
MinNeighbors:参数,指定每个候选矩形应该保留多少个邻居。
MinSize:最小可能的对象大小。小于该值的对象将被忽略。
MaxSize:最大可能的对象大小。大于该值的对象将被忽略。
最后,显示结果:
plt.figure(figsize=(12,8))
plt.imshow(gray, cmap='gray')
plt.show()
人脸检测在我们的测试图像很有效。我们现在就开始实验吧!
1.4 实时人脸检测
让我们继续讨论实时面部检测的Python实现。第一步是启动摄像头并拍摄视频。然后,我们将图像转换为灰度图像。这用于缩小输入图像的维数。事实上,我们采用了一个简单的线性变换,而不是每像素三个点来描述红、绿、蓝:
这在OpenCV中是默认实现的。
video_capture = cv2.VideoCapture(0)while True:
# Capture frame-by-frame
ret, frame = video_capture.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
现在,我们将使用上面定义的facecascade变量,它包含一个预先训练的算法,并将其应用于灰度图像。
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)
然后,计算人脸总数,显示整体图像:
cv2.putText(frame,'Number of Faces : ' + str(len(faces)),(40, 40), font, 1,(255,0,0),2)
# Display the resulting frame
cv2.imshow('Video', frame)
当我们想通过按q停止摄像机时,执行退出选项:
if cv2.waitKey(1) & 0xFF == ord('q'):
break
最后,当所有操作完成后,释放捕获并销毁所有窗口。在Mac上关闭windows存在一些问题,这可能需要稍后从活动管理器中删除Python。
video_capture.release()
cv2.destroyAllWindows()
今天我先写这一部分,明天继续更新,相信今天的内容也足够伙伴们好好消化一下的,好的,我们明天继续。