开发配置
OpenCV的例程中已经带有了人脸检测的例程,位置在:OpenCV\samples\facedetect.cpp文件,OpenCV的安装与这个例子的测试可以参考我之前的博文Linux 下编译安装OpenCV。
网上能够找到关于OpenCV人脸检测的例子也比较多,大多也都是基于这个例程来更改,只是多数使用的是OpenCV 1.0的版本,而OpenCV2.0以后由于模块结构的更改,很多人并没有将例程运行起来。如果是新版的OpenCV跑旧的例程,编译运行出错的话,需要确保:
- #include "opencv2/objdetect/objdetect.hpp" 头文件被引用,老的头文件包含可能会提示找不到定义
- libopencv_objdetect243.dll.a 库需要加入链接
之前找了几个例程,不尽如人意,于是决定还是改自带的例程更靠谱,更多的信息,已经在程序中添加注释,参见程序吧。
pro文件的工程配置,具体路径按照安装路径更改,Linux下也一样。
人脸检测基础知识整理
下面整理下人脸检测的相关知识。
人脸检测从整体来看分为四个部分:
1、Face detection 人脸识别,即识别出这是人的脸,而不管他是谁的。
2、Face preprocessing 面部预处理,即提取出脸部图像。
3、Collect and learn faces 脸部的特征采集和学习
4、Face recognition 脸部识别,找出最相近的相近脸部图像。
“基于知识的方法主要利用先验知识将人脸看作器官特征的组合,根据眼睛、眉毛、嘴巴、鼻子等器官的特征以及相互之间的几何位置关系来检测人脸。基于统计的方法则将人脸看作一个整体的模式——二维像素矩阵,从统计的观点通过大量人脸图像样本构造人脸模式空间,根据相似度量来判断人脸是否存在。在这两种框架之下,发展了许多方法。目前随着各种方法的不断提出和应用条件的变化,将知识模型与统计模型相结合的综合系统将成为未来的研究趋势。”(来自论文《基于Adaboost的人脸检测方法及眼睛定位算法研究》)
人脸检测算法的可靠性很大程度上依赖于分类器的设计,在2001年,Viola和Jones两位大牛发表了经典的《Rapid Object Detection using a Boosted Cascade of Simple Features》【1】和《Robust Real-Time Face Detection》【2】,在AdaBoost算法的基础上,使用Haar-like小波特征和积分图方法进行人脸检测,他俩不是最早使用提出小波特征的,但是他们设计了针对人脸检测更有效的特征,并对AdaBoost训练出的强分类器进行级联。这可以说是人脸检测史上里程碑式的一笔了,也因此当时提出的这个算法被称为Viola-Jones检测器。又过了一段时间,Rainer Lienhart和Jochen Maydt两位大牛将这个检测器进行了扩展【3】,最终形成了OpenCV现在的Haar分类器。在OpenCV2.0中又扩充了基于LBP特征的人脸检测器,某些情况下LBP特征比Haar来的更为快速。
在进行识别时首先通过大量的具有比较明显的haar特征(矩形)的物体图像用模式识别的方法训练出分类器,分类器是个级联的,每级都以大概相同的识别率保留进入下一级的具有物体特征的候选物体,而每一级的子分类器则由许多haar特征构成(由积分图像计算得到,并保存下位置),有水平的、竖直的、倾斜的,并且每个特征带一个阈值和两个分支值,每级子分类器带一个总的阈值。识别物体的时候,同样计算积分图像为后面计算haar特征做准备,然后采用与训练的时候有物体的窗口同样大小的窗口遍历整幅图像,以后逐渐放大窗口,同样做遍历搜索物体;每当窗口移动到一个位置,即计算该窗口内的haar特征,加权后与分类器中haar特征的阈值比较从而选择左或者右分支值,累加一个级的分支值与相应级的阈值比较,大于该阈值才可以通过进入下一轮筛选。当通过分类器所有级的时候说明这个物体以大概率被识别。
程序设计
如果单纯是对功能进行实现,有了官方自带的例程做参考,移植实现并不是很难,几乎不用费太大的功夫,自带例程对照着OpenCV参考手册还是比较好理解,这部分例程已经成功在Linux(Ubuntu和嵌入式Linux)以及Windows下实现,后面实验室基于Qt设计的实验软件,也整合了进去。
程序参考本文后面给出的参考程序,当然最权威的还是软件自带例程,实现人脸检测的另外一个关键就是训练文件,基于Haar和LBP特征的人脸检测可以自动的对大量数据图片进行训练,训练结果存储在XML文件中以供使用,这些级联分类器一般需要训练上千幅人脸图片和上万幅非人脸图片,这些训练过程往往需要很长的时间(LBP特征需要几个小时,Harr特征可能甚至需要一个星期)不过OpenCV已经提供了不同种类的训练好的文件,因此我们可以方便的通过载入这些训练好的级联分类器XML文件来实现人脸、眼睛、鼻子等检测。
OpenCV的训练文件在源码目录的data文件夹下,里面包含haarcascades、hogcascades、lbpcascades,在haarcascades文件下包含大量的针对不同目标的训练文件,如下图所示:
文件名已经体现了文件的功能,因此只需要载入对应的文件即可。
到这里还只是进行了一个非常初步的研究,下一步的学习和识别还需要多多积累。
实验
根据自带例程,将人脸检测算法加入我所做的实验软件中,分别选择不同的分类器进行实验,下图为实验结果。
人脸检测实验
实现特定的器官检测右眼、鼻子等检测
测试程序
QT += core QT -= gui TARGET = cvcap CONFIG += console CONFIG -= app_bundle TEMPLATE = app SOURCES += main.cpp INCLUDEPATH+=D:\OpenCV\build\include INCLUDEPATH+=D:\OpenCV\build\include\opencv LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_core243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_highgui243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_imgproc243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_video243.dll.a LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_objdetect243.dll.a
主程序,具体地方都已经注释。这里是打开摄像头读取数据,同样可以自己打开图片。
#include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/objdetect/objdetect.hpp> #include <QDebug> using namespace cv; void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip ); int main() { VideoCapture cap(0); //打开默认摄像头 if(!cap.isOpened()) { return -1; } Mat frame; Mat edges; CascadeClassifier cascade, nestedCascade; bool stop = false; //训练好的文件名称,放置在可执行文件同目录下 cascade.load("haarcascade_frontalface_alt.xml"); nestedCascade.load("haarcascade_eye_tree_eyeglasses.xml"); while(!stop) { cap>>frame; detectAndDraw( frame, cascade, nestedCascade,2,0 ); if(waitKey(30) >=0) stop = true; } return 0; } void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip ) { int i = 0; double t = 0; //建立用于存放人脸的向量容器 vector<Rect> faces, faces2; //定义一些颜色,用来标示不同的人脸 const static Scalar colors[] = { CV_RGB(0,0,255), CV_RGB(0,128,255), CV_RGB(0,255,255), CV_RGB(0,255,0), CV_RGB(255,128,0), CV_RGB(255,255,0), CV_RGB(255,0,0), CV_RGB(255,0,255)} ; //建立缩小的图片,加快检测速度 //nt cvRound (double value) 对一个double型的数进行四舍五入,并返回一个整型数! Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); //转成灰度图像,Harr特征基于灰度图 cvtColor( img, gray, CV_BGR2GRAY ); //改变图像大小,使用双线性差值 resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); //变换后的图像进行直方图均值化处理 equalizeHist( smallImg, smallImg ); //程序开始和结束插入此函数获取时间,经过计算求得算法执行时间 t = (double)cvGetTickCount(); //检测人脸 //detectMultiScale函数中smallImg表示的是要检测的输入图像为smallImg,faces表示检测到的人脸目标序列,1.1表示 //每次图像尺寸减小的比例为1.1,2表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大 //小都可以检测到人脸),CV_HAAR_SCALE_IMAGE表示不是缩放分类器来检测,而是缩放图像,Size(30, 30)为目标的 //最小最大尺寸 cascade.detectMultiScale( smallImg, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30)); //如果使能,翻转图像继续检测 if( tryflip ) { flip(smallImg, smallImg, 1); cascade.detectMultiScale( smallImg, faces2, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); r++ ) { faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height)); } } t = (double)cvGetTickCount() - t; // qDebug( "detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.) ); for( vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++ ) { Mat smallImgROI; vector<Rect> nestedObjects; Point center; Scalar color = colors[i%8]; int radius; double aspect_ratio = (double)r->width/r->height; if( 0.75 < aspect_ratio && aspect_ratio < 1.3 ) { //标示人脸时在缩小之前的图像上标示,所以这里根据缩放比例换算回去 center.x = cvRound((r->x + r->width*0.5)*scale); center.y = cvRound((r->y + r->height*0.5)*scale); radius = cvRound((r->width + r->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } else rectangle( img, cvPoint(cvRound(r->x*scale), cvRound(r->y*scale)), cvPoint(cvRound((r->x + r->width-1)*scale), cvRound((r->y + r->height-1)*scale)), color, 3, 8, 0); if( nestedCascade.empty() ) continue; smallImgROI = smallImg(*r); //同样方法检测人眼 nestedCascade.detectMultiScale( smallImgROI, nestedObjects, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH //|CV_HAAR_DO_CANNY_PRUNING |CV_HAAR_SCALE_IMAGE , Size(30, 30) ); for( vector<Rect>::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++ ) { center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale); center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale); radius = cvRound((nr->width + nr->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } } cv::imshow( "result", img ); }