目标
在本教程中, 将学习
- Haar级联对象检测的工作原理
- 将使用基于Haar Feature的Cascade分类器了解人脸检测和眼睛检测的基础知识
- 将使用cv::CascadeClassifier类来检测视频流中的对象。具体地将使用以下函数:
-
cv::CascadeClassifier::load来加载
.xml
分类器文件,它可以是Haar或LBP分类器 - cv::CascadeClassifier::detectMultiScale来执行检测
-
cv::CascadeClassifier::load来加载
理论
使用基于Haar特征的级联分类器的对象检测是Paul Viola和Michael Jones于2001年在其论文“Rapid Object Detection using a Boosted Cascade of Simple Features”提出的一种有效的对象检测方法。这是一种基于机器学习的方法,其中从许多正负图像中训练级联函数。然后用于检测其他图像中的对象。
本文中将利用上述方法进行人脸检测。最初,该算法需要大量正图像(面部图像)和负图像(无面部图像)来训练分类器。 然后,需要从中提取特征。为此,使用下图所示的Haar特征, 它们就像卷积核一样,每个特征都是通过从黑色矩形中的像素总和中减去白色矩形中的像素总和而获得的单个值。
每个内核的所有可能大小和位置都能够计算得到许多特征。(试想一下它产生多少计算?即使是一个24x24
的窗口也会产生超过160000个特征)。对于每个特征计算,需要找到白色和黑色矩形下的像素总和。为了解决这个问题,引入了整体图像,无论图像有多大,它都会将给定像素的计算减少到仅涉及四个像素的操作,这使得计算变得更快。
但是在计算的所有这些特征中,大多数都不相关。例如,考虑下图。第一行显示了两个良好的特征。选择的第一个特征似乎着眼于眼睛区域,该区域通常比鼻子和脸颊区域更暗的属性。选择的第二个特征依赖于眼睛比鼻梁更黑的属性。但是,将相同的窗口应用于脸颊或其他任何地方都是无关紧要的。那么,如何从16万多个特征中选择最佳的特征呢,这是由Adaboost实现的。
Adaboost算法的基本原理:
先通过对N个训练样本的学习得到第一个弱分类器;
将分错的样本和其他的新数据一起构成一个新的N个的训练样本,通过对这个样本的学习得到第二个弱分类器;
将1和2都分错了的样本加上其他的新样本构成另一个新的N个的训练样本,通过对这个样本的学习得到第三个弱分类器;
最终经过提升的强分类器,即某个数据被分为哪一类要由各分类器权值决定;
为此,将所有特征应用于所有训练图像。对于每个特征,会找到最佳的阈值,该阈值会将人脸分为正面和负面。显然,会出现错误或分类错误。选择错误率最低的特征,这意味着它们是对人脸和非人脸图像进行最准确分类的特征 (此过程并非如此简单,参考adaboost算法。在开始时,每个图像的权重均相等。在每次分类后,错误分类的图像的权重都会增加。然后执行相同的过程将计算新的错误率,计算新的权重。继续进行此过程,直到达到所需的精度或错误率或找到所需的特征数量为止)。
最终分类器是这些弱分类器的加权和。之所以称为弱分类,是因为仅凭它自己并不能对图像进行正确分类,而是与其他分类一起形成强分类器。该论文说,甚至200个特征都可以提供95%的准确度检测,论文最终设置具有大约6000个特征。
因此,现在拍摄一张照片,取每个24x24
窗口,向其应用6000个特征, 检查图片中是否有脸。该方法是不是效率低下又费时?作者对此提出了一个更好的解决方案。
在图像中,大多数图像是非面部区域。因此,最好有一种简单的方法来检查窗口是否不是面部区域。如果该区域不是人脸区域,就不要再次对其进行处理。相反,应将重点放在可能有脸的区域,将花费更多时间检查可能的面部区域。类似于RPN网络,先获取到候选区域。
为此,引入了级联分类器(Cascade of Classifiers)的概念。不是将所有6000个特征应用到一个窗口中,而是将这些特征分组到不同的分类器阶段,并一一应用 (通常前几个阶段将包含很少的特征)。如果窗口在第一阶段失败,则将其丢弃,不考虑它的其余特征。如果通过,则应用特征的第二阶段并继续该过程,能够经过所有阶段的窗口那就证明是一个面部区域。
作者的检测器具有6000多个特征,具有38个阶段,在前五个阶段具有1、10、25、25和50个特
征(上图中的两个特征实际上是从Adaboost获得的最佳两个特征)。根据作者的说法,每个子窗口平均评估了6000多个特征中的10个特征。因此,这是Viola-Jones人脸检测工作原理的简单直观说明。
OpenCV中的Haar级联检测器
OpenCV提供了一种训练方法或预先训练的模型,可以使用cv::CascadeClassifier::load
方法读取预训练模型。预训练的模型位于OpenCV的data文件夹中,或在github找到。
以下代码示例将使用预训练的Haar级联模型来检测图像中的面部和眼睛。
- 首先,创建一个
cv::CascadeClassifier
并使用cv::CascadeClassifier::load
方法加载必要的XML文件。 - 然后,使用
cv::CascadeClassifier::detectMultiScale
方法完成检测,该方法返回检测到的脸部或眼睛的边界矩形
本教程的代码如下所示,也可以从这里下载:
from __future__ import print_function
import cv2
import numpy as np
import argparse
def detectAndDisplay(frame):
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame_gray = cv2.equalizeHist(frame_gray) # 均衡灰度图像的直方图
# detect faces
faces = face_cascade.detectMultiScale(frame_gray)
for (x, y, w, h) in faces:
center = (x+w//2, y+h//2)
frame = cv2.ellipse(
frame, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4) # 绘制椭圆
faceROI = frame_gray[y:y+h, x:x+w]
# In each face, detect eyes
eyes = eyes_cascade.detectMultiScale(faceROI)
for (x2, y2, w2, h2) in eyes:
eye_center = (x + x2 + w2//2, y + y2 + h2//2)
radius = int(round((w2+h2)*0.25))
frame = cv2.circle(frame, eye_center, radius, (255, 0, 0), 4) # 画圆
cv2.imshow('Capture - Face detection', frame)
parser = argparse.ArgumentParser(description='Code for Cascade Classifier tutorial.')
parser.add_argument(
'--face_cascade', help='Path to face cascade', default='./haarcascades/haarcascade_frontalface_alt.xml')
parser.add_argument(
'--eye_cascade', help='Path to eye cascade', default='./harrcascades/haarcascade_eye_tree_eyeglasses.xml')
parser.add_argument('--camera', help='Camera divide number.', type=int, default=0)
args = parser.parse_args()
face_cascade_name = args.face_cascade
eyes_cascade_name = args.eyes_cascade
face_cascade = cv2.CascadeClassifier()
eyes_cascade = cv2.CascadeClassifier()
#-- 1. Load the cascades
if not face_cascade.load(cv2.samples.findFile(face_cascade_name)):
print('--(!)Error loading face cascade')
exit(0)
if not eyes_cascade.load(cv2.samples.findFile(eyes_cascade_name)):
print('--(!)Error loading eyes cascade')
exit(0)
camera_device = args.camera
#-- 2. Read the video stream
cap = cvw.VideoCapture(camera_device)
if not cap.isOpened:
print('--(!)Error opening video capture')
exit(0)
while True:
ret, frame = cap.read()
if frame is None:
print('--(!) No captured frame -- Break!')
break
detectAndDisplay(frame)
if cv.waitKey(10) == 27:
break
结果
- 以下是运行上面的代码并使用作为输入内置网络摄像头的视频流的结果:
确保程序会找到文件
haarcascade_frontalface_alt.xml
和haarcascade_eye_tree_eyeglasses.xml
的路径,位于opencv/data/ haarcascades
中
- 以下是使用文件
lbpcascade_frontalface.xml
(经过LBP训练)进行人脸检测的结果。对于眼睛,继续使用本教程中使用的文件
附加资源
-
https://docs.opencv.org/4.5.5/db/d28/tutorial_cascade_classifier.html
-
An interesting interview regarding Face Detection by Adam Harvey