上一个教程: 光学流
下一个教程: 级联分类器训练
Original author | Ana Huamán |
---|---|
Compatibility | OpenCV >= 3.0 |
目标
在本教程中
- 我们将学习Haar级联物体检测的工作原理。
- 我们将看到使用基于Haar特征的级联分类器进行人脸检测和眼睛检测的基本情况。
- 我们将使用cv::CascadeClassifier类来检测视频流中的物体。特别是,我们将使用这些函数:
- cv::CascadeClassifier::load来加载一个.xml分类器文件。它可以是一个Haar或LBP分类器
- cv::CascadeClassifier::detectMultiScale来进行检测。
理论
使用基于Haar特征的级联分类器进行物体检测是一种有效的物体检测方法,由Paul Viola和Michael Jones于2001年在他们的论文《使用简单特征的提升级联的快速物体检测》中提出。这是一种基于机器学习的方法,从大量的正面和负面图像中训练出一个级联函数。然后用它来检测其他图像中的物体。
在这里,我们将进行人脸检测。最初,该算法需要大量的正面图像(人脸图像)和负面图像(没有人脸的图像)来训练分类器。然后,我们需要从中提取特征。为此,我们使用了下图中的Haar特征。它们就像我们的卷积核一样。每个特征都是一个单一的值,由白色矩形下的像素之和减去黑色矩形下的像素之和得到。
现在,每个核的所有可能的大小和位置都被用来计算大量的特征。(试想一下,这需要多少计算量?即使是一个24x24的窗口也会产生超过160000个特征)。对于每个特征的计算,我们需要找到白色和黑色矩形下的像素之和。为了解决这个问题,他们引入了积分图像。无论你的图像有多大,它都能将一个给定像素的计算减少到只涉及四个像素的操作。很好,不是吗?它使事情变得超级快。
但在我们计算的所有这些功能中,大部分都是不相关的。例如,考虑下面的图片。最上面一行显示了两个好的特征。所选的第一个特征似乎集中在眼睛的区域往往比鼻子和脸颊的区域更黑这一特性上。所选的第二个特征依靠的是眼睛比鼻梁深的属性。但同样的窗口适用于脸颊或任何其他地方是不相关的。那么,我们如何从16万多个特征中选择最佳特征呢?它是通过Adaboost实现的。
为此,我们在所有训练图像上应用每一个特征。对于每一个特征,它都会找到最佳的阈值,将人脸分类为正面和负面。很明显,会有错误或误分类的情况。我们选择错误率最低的特征,这意味着它们是最准确地对人脸和非人脸图像进行分类的特征。(这个过程并不像这样简单。每张图像在开始时都被赋予相同的权重。每次分类后,被错误分类的图像的权重会增加。然后再做同样的过程。新的错误率被计算出来。也是新的权重。这个过程一直持续到达到所需的准确率或错误率,或找到所需数量的特征)。
最后的分类器是这些弱分类器的加权和。它之所以被称为弱,是因为它单独不能对图像进行分类,但与其他分类器一起构成了一个强分类器。论文中说,即使是200个特征也能提供95%的检测准确率。他们的最终设置有大约6000个特征。(想象一下,从16万多个特征减少到6000个特征。这是一个很大的收获)。
因此,现在你采取了一个图像。采取每个24x24的窗口。对它应用6000个特征。检查它是否是人脸。哇,这不是有点低效和耗时吗?是的,它是。作者对此有一个很好的解决方案。
在一张图片中,大部分的图片都是非脸部区域。因此,有一个简单的方法来检查一个窗口是否是脸部区域是一个更好的主意。如果不是,就一次性丢弃它,不要再处理它。相反,把重点放在有可能存在人脸的区域。这样,我们花更多的时间来检查可能的人脸区域。
为此,他们引入了级联分类器的概念。与其在一个窗口上应用所有6000个特征,不如将这些特征归入不同阶段的分类器中,并逐一应用。(通常情况下,前几个阶段会包含非常少的特征)。如果一个窗口在第一阶段失败了,就把它丢弃。我们不考虑它身上剩余的特征。如果它通过了,就应用第二阶段的特征并继续这个过程。通过所有阶段的窗口就是一个面部区域。这个计划是怎样的!?
作者的检测器有6000多个特征,38个阶段,前五个阶段有1、10、25、25和50个特征。(上图中的两个特征实际上是作为Adaboost的最佳两个特征得到的)。根据作者的说法,平均每个子窗口对6000多个特征中的10个特征进行评估。
因此,这是对Viola-Jones人脸检测工作原理的一个简单直观的解释。阅读该论文以了解更多细节,或查看附加资源部分的参考文献。
OpenCV中的Haar-ascade检测
OpenCV提供了一个训练方法(见级联分类器训练)或预训练模型,可以用cv::CascadeClassifier::load方法读取。预训练的模型位于OpenCV安装中的数据文件夹中,或者可以在这里找到。
下面的代码例子将使用预训练的Haar级联模型来检测图像中的人脸和眼睛。首先,创建一个cv::CascadeClassifier,使用cv::CascadeClassifier::load方法加载必要的XML文件。之后,使用cv::CascadeClassifier::detectMultiScale方法进行检测,该方法返回检测到的人脸或眼睛的边界矩形。
这个教程的代码显示在下面几行。你也可以从这里下载
objectDetection.cpp
objectDetection.java
objectDetection.py
C++
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <iostream>
using namespace std;
using namespace cv;
void detectAndDisplay( Mat frame );
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
int main( int argc, const char** argv )
{
CommandLineParser parser(argc, argv,
"{help h||}"
"{face_cascade|data/haarcascades/haarcascade_frontalface_alt.xml|Path to face cascade.}"
"{eyes_cascade|data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|Path to eyes cascade.}"
"{camera|0|Camera device number.}");
parser.about( "\nThis program demonstrates using the cv::CascadeClassifier class to detect objects (Face + eyes) in a video stream.\n"
"You can use Haar or LBP features.\n\n" );
parser.printMessage();
String face_cascade_name = samples::findFile( parser.get<String>("face_cascade") );
String eyes_cascade_name = samples::findFile( parser.get<String>("eyes_cascade") );
//-- 1.加载级联
if( !face_cascade.load( face_cascade_name ) )
{
cout << "--(!)Error loading face cascade\n";
return -1;
};
if( !eyes_cascade.load( eyes_cascade_name ) )
{
cout << "--(!)Error loading eyes cascade\n";
return -1;
};
int camera_device = parser.get<int>("camera");
VideoCapture capture;
//-- 2.读取视频流
capture.open( camera_device );
if ( ! capture.isOpened() )
{
cout << "--(!)Error opening video capture\n";
return -1;
}
Mat frame;
while ( capture.read(frame) )
{
if( frame.empty() )
{
cout << "--(!) No captured frame -- Break!\n";
break;
}
//-- 3.对帧应用分类器
detectAndDisplay( frame );
if( waitKey(10) == 27 )
{
break; // escape
}
}
return 0;
}
void detectAndDisplay( Mat frame )
{
Mat frame_gray;
cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
equalizeHist( frame_gray, frame_gray );
//-- 检测面孔
std::vector<Rect> faces;
face_cascade.detectMultiScale( frame_gray, faces );
for ( size_t i = 0; i < faces.size(); i++ )
{
Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );
ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );
Mat faceROI = frame_gray( faces[i] );
//-- 在每个面孔中,检测眼睛
std::vector<Rect> eyes;
eyes_cascade.detectMultiScale( faceROI, eyes );
for ( size_t j = 0; j < eyes.size(); j++ )
{
Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );
int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 4 );
}
}
//-- 显示你得到的东西
imshow( "Capture - Face detection", frame );
}
Java
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.HighGui;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.videoio.VideoCapture;
class ObjectDetection {
public void detectAndDisplay(Mat frame, CascadeClassifier faceCascade, CascadeClassifier eyesCascade) {
Mat frameGray = new Mat();
Imgproc.cvtColor(frame, frameGray, Imgproc.COLOR_BGR2GRAY);
Imgproc.equalizeHist(frameGray, frameGray);
//-- 检测面孔
MatOfRect faces = new MatOfRect();
faceCascade.detectMultiScale(frameGray, faces);
List<Rect> listOfFaces = faces.toList();
for (Rect face : listOfFaces) {
Point center = new Point(face.x + face.width / 2, face.y + face.height / 2);
Imgproc.ellipse(frame, center, new Size(face.width / 2, face.height / 2), 0, 0, 360,
new Scalar(255, 0, 255));
Mat faceROI = frameGray.submat(face);
// -- 在每个脸部,检测眼睛
MatOfRect eyes = new MatOfRect();
eyesCascade.detectMultiScale(faceROI, eyes);
List<Rect> listOfEyes = eyes.toList();
for (Rect eye : listOfEyes) {
Point eyeCenter = new Point(face.x + eye.x + eye.width / 2, face.y + eye.y + eye.height / 2);
int radius = (int) Math.round((eye.width + eye.height) * 0.25);
Imgproc.circle(frame, eyeCenter, radius, new Scalar(255, 0, 0), 4);
}
}
//-- 显示你得到的东西
HighGui.imshow("Capture - Face detection", frame );
}
public void run(String[] args) {
String filenameFaceCascade = args.length > 2 ? args[0] : "../../data/haarcascades/haarcascade_frontalface_alt.xml";
String filenameEyesCascade = args.length > 2 ? args[1] : "../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml";
int cameraDevice = args.length > 2 ? Integer.parseInt(args[2]) : 0;
CascadeClassifier faceCascade = new CascadeClassifier();
CascadeClassifier eyesCascade = new CascadeClassifier();
if (!faceCascade.load(filenameFaceCascade)) {
System.err.println("--(!)Error loading face cascade: " + filenameFaceCascade);
System.exit(0);
}
if (!eyesCascade.load(filenameEyesCascade)) {
System.err.println("--(!)Error loading eyes cascade: " + filenameEyesCascade);
System.exit(0);
}
VideoCapture capture = new VideoCapture(cameraDevice);
if (!capture.isOpened()) {
System.err.println("--(!)Error opening video capture");
System.exit(0);
}
Mat frame = new Mat();
while (capture.read(frame)) {
if (frame.empty()) {
System.err.println("--(!) No captured frame -- Break!");
break;
}
//-- 3.将分类器应用于该帧
detectAndDisplay(frame, faceCascade, eyesCascade);
if (HighGui.waitKey(10) == 27) {
break;// escape
}
}
System.exit(0);
}
}
public class ObjectDetectionDemo {
public static void main(String[] args) {
// 加载本地OpenCV库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new ObjectDetection().run(args);
}
}
Python
from __future__ import print_function
import cv2 as cv
import argparse
def detectAndDisplay(frame):
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
frame_gray = cv.equalizeHist(frame_gray)
#--检测面孔
faces = face_cascade.detectMultiScale(frame_gray)
for (x,y,w,h) in faces:
center = (x + w//2, y + h//2)
frame = cv.ellipse(frame, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4)
faceROI = frame_gray[y:y+h,x:x+w]
#--在每个面部,检测眼睛
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 = cv.circle(frame, eye_center, radius, (255, 0, 0 ), 4)
cv.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='data/haarcascades/haarcascade_frontalface_alt.xml')
parser.add_argument('--eyes_cascade', help='Path to eyes cascade.', default='data/haarcascades/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 = cv.CascadeClassifier()
eyes_cascade = cv.CascadeClassifier()
#-- 1.加载级联
if not face_cascade.load(cv.samples.findFile(face_cascade_name)):
print('--(!)Error loading face cascade')
exit(0)
if not eyes_cascade.load(cv.samples.findFile(eyes_cascade_name)):
print('--(!)Error loading eyes cascade')
exit(0)
camera_device = args.camera
#-- 2. 读取视频流
cap = cv.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训练)进行脸部检测的结果。对于眼睛,我们继续使用教程中使用的文件。
其他资源
- Paul Viola 和 Michael J. Jones. 健全的实时人脸检测。国际计算机视觉杂志,57(2):137-154,2004(Paul Viola and Michael J. Jones. Robust real-time face detection. International Journal of Computer Vision, 57(2):137–154, 2004)。[267]
- Rainer Lienhart和Jochen Maydt. An extended set of haar-like features for rapid object detection. In Image Processing. 2002. Proceedings. 2002年国际会议,第一卷,第I-900页。IEEE, 2002. (Rainer Lienhart and Jochen Maydt. An extended set of haar-like features for rapid object detection. In Image Processing. 2002. Proceedings. 2002 International Conference on, volume 1, pages I–900. IEEE, 2002)[152]
- 关于人脸检测和跟踪的视频讲座
- Adam Harvey关于人脸检测的一个有趣的采访
- OpenCV人脸检测: 亚当-哈维在Vimeo上的可视化演示