下面我们直接从一个人脸检测的demo程序来建立感性认识。
// FaceD.cpp : ////////////////////////////////////////////////////// // 人脸检测C++版本 // Editor:LinJM ////////////////////////////////////////////////////// #include "stdafx.h" #include <cstdio> #include <cstdlib> #include <Windows.h> #include <vector> #include <iostream> #include <string> #include <opencv2\opencv.hpp> using namespace std; using namespace cv; int _tmain(int argc, _TCHAR* argv[]) { //加载Haar特征检测分类器 const string CascadeFileName = "D:\\OpenCV2.4.6\\opencv\\data\\haarcascades\\haarcascade_frontalface_default.xml"; CascadeClassifier cascade= CascadeClassifier::CascadeClassifier(CascadeFileName); // 载入图像 const string pstrImageName = "C:\\Image\\AsiaGroup.jpg"; Mat SrcImage = imread(pstrImageName,CV_LOAD_IMAGE_COLOR); Mat GrayImage; cvtColor(SrcImage,GrayImage,CV_BGR2GRAY); // 人脸识别与标记 if (!cascade.empty()) { CvScalar FaceCirclecolors[] = { {{0, 0, 255}}, {{0, 128, 255}}, {{0, 255, 255}}, {{0, 255, 0}}, {{255, 128, 0}}, {{255, 255, 0}}, {{255, 0, 0}}, {{255, 0, 255}} }; vector<cv::Rect> faces; DWORD dwTimeBegin, dwTimeEnd; dwTimeBegin = GetTickCount(); // 识别 cascade.detectMultiScale(GrayImage, faces); dwTimeEnd = GetTickCount(); cout<<"人脸个数:"<<faces.end()-faces.begin() <<"识别用时:"<<dwTimeEnd - dwTimeBegin<<"ms\n"; // 标记 int n = 0; for( vector<cv::Rect>::const_iterator i = faces.begin(); i <faces.end(); i++,n++) { Point center; int radius; center.x = cvRound((i->x + i->width * 0.5)); center.y = cvRound((i->y + i->height * 0.5)); radius = cvRound((i->width + i->height) * 0.25); circle(SrcImage, center, radius, FaceCirclecolors[n % 8],2); } } namedWindow("人脸识别"); imshow("人脸识别",SrcImage); cvWaitKey(0); return 0; }
接下来,我们从理论上来加深对人脸检测的认识:
目标检测方法最初由Paul Viola提出,并由Rainer Lienhart 对这一方法进行了改善。
- 首先,利用样本(大约几百幅样本图片)的harr特征进行分类器训练,得到一个级联的boosted分类器。训练样本分为正例样本和反例样本,其中正例样本是指待检目标样本(例如人脸或汽车等),反例样本指其它任意图片,所有的样本图片都被归一化为同样的尺寸大小(例如,20x20)。
- 分类器训练完以后,就可以应用于输入图像中的感兴趣区域(与训练样本相同的尺寸)的检测。检测到目标区域(汽车或人脸)分类器输出为1,否则输出为0。为了检测整副图像,可以在图像中移动搜索窗口,检测每一个位置来确定可能的目标。 为了搜索不同大小的目标物体,分类器被设计为可以进行尺寸改变,这样比改变待检图像的尺寸大小更为有效。所以,为了在图像中检测未知大小的目标物体,扫描程序通常需要用不同比例大小的搜索窗口对图片进行几次扫描。
分类器中的“级联”是指最终的分类器是由几个简单分类器级联组成。在图像检测中,被检窗口依次通过每一级分类器,这样在前面几层的检测中大部分的候选区域就被排除了,全部通过每一级分类器检测的区域即为目标区域。目前支持这种分类器的boosting技术有四种:
- Discrete Adaboost,
- Real Adaboost
- Gentle Adaboost
- Logitboost
"boosted"即指级联分类器的每一层都可以从中选取一个boosting算法(权重投票),并利用基础分类器的自我训练得到。基础分类器是至少有两个叶结点的决策树分类器。Haar特征是基础分类器的输入,主要描述如下。目前的算法主要利用下面的Harr特征。
每个特定分类器所使用的特征用形状、感兴趣区域中的位置以及比例系数(这里的比例系数跟检测时候采用的比例系数是不一样的,尽管最后会取两个系数的乘积值)来定义。例如在第二行特征(2c)的情况下,响应计算为覆盖全部特征整个矩形框(包括两个白色矩形框和一个黑色矩形框)象素的和减去黑色矩形框内象素和的三倍 。每个矩形框内的象素和都可以通过积分图象很快的计算出来。
CvHaarFeature, CvHaarClassifier, CvHaarStageClassifier, CvHaarClassifierCascade
Boosted Haar 分类器结构
#define CV_HAAR_FEATURE_MAX 3 /*''一个 harr 特征由2-3个具有相应权重的矩形组成''*/ typedef struct CvHaarFeature { int tilted; /* 0 means up-right feature, 1 means 45--rotated feature */ /* 2-3 rectangles with weights of opposite signs and with absolute(绝对的) values inversely proportional to the areas of the rectangles. if rect[2].weight !=0, then the feature consists of 3 rectangles, otherwise it consists of 2 */ struct { CvRect r; float weight; } rect[CV_HAAR_FEATURE_MAX]; } CvHaarFeature;
/* a single tree classifier (stump in the simplest case) that returns the response for the feature 一个单独树分类器(根部是最简单的情形)返回相应的图像位置(i.e.求窗口子矩形的像素和) at the image location (i.e. pixel sum over subrectangles of the window) and gives out 并且根据返回给出值 a value depending on the responce */ typedef struct CvHaarClassifier { int count; /* number of nodes in the decision tree 决策树中间的节点数目*/ /* these are "parallel" arrays. Every index i corresponds to a node of the decision tree (root has 0-th index). 有并行数组。每一个索引i对应和决策树的一个节点(根部有0-th索引)。 left[i] - index of the left child (or negated index if the left child is a leaf) left[i]-左边部分的索引(或者左边部分是叶子的时候否定索引) right[i] - index of the right child (or negated index if the right child is a leaf) right[i]-右边部分的索引(或者右边部分是叶子的时候否定索引) threshold[i] - branch threshold. if feature responce is <= threshold, left branch is chosen, otherwise right branch is chosed. threshold[i]-树枝阈值。如果特征相应<=阈值,左边分支被选择,否则,右边分支被选择。 alpha[i] - output value correponding to the leaf. alpha[i]-相应的叶子输出值*/ CvHaarFeature* haar_feature; float* threshold; int* left; int* right; float* alpha; } CvHaarClassifier;
/* a boosted battery of classifiers(=stage classifier): the stage classifier returns 1 if the sum of the classifiers' responces is greater than threshold and 0 otherwise */ typedef struct CvHaarStageClassifier { int count; /* number of classifiers in the battery */ float threshold; /* threshold for the boosted classifier */ CvHaarClassifier* classifier; /* array of classifiers */ /* these fields are used for organizing trees of stage classifiers,rather than just stright cascades */ int next; int child; int parent; } CvHaarStageClassifier;
typedef struct CvHidHaarClassifierCascade CvHidHaarClassifierCascade; /* cascade or tree of stage classifiers 级联或者阶段分类器树*/ typedef struct CvHaarClassifierCascade { int flags; /* signature签名 */ int count; /* number of stages阶段数目 */ CvSize orig_window_size; /* original object size (the cascade is trained for) 原始对象尺寸(级联训练的对象)*/ /* these two parameters are set by cvSetImagesForHaarClassifierCascade这里的两个参数被cvSetImagesForHaarClassifierCascade设置*/ CvSize real_window_size; /* current object size当前对象大小 */ double scale; /* current scale 当前的比例*/ CvHaarStageClassifier* stage_classifier; /* array of stage classifiers 阶段分类器数组*/ CvHidHaarClassifierCascade* hid_cascade; /* hidden optimized representation of the cascade,created by 级联的隐藏最优表达,由 cvSetImagesForHaarClassifierCascade创建 cvSetImagesForHaarClassifierCascade */ } CvHaarClassifierCascade;
所有的结构都代表一个级联boosted Haar分类器。级联有下面的等级结构:
Cascade:
Stage1:
Classifier11:
Feature11
Classifier12:
Feature12
...
Stage2:
Classifier21:
Feature21
...
...
整个等级可以手工构建,也可以利用函数cvLoadHaarClassifierCascade从已有的磁盘文件或嵌入式基中导入。
cvLoadHaarClassifierCascade
从文件中装载训练好的级联分类器或者从OpenCV中嵌入的分类器数据库中导入
<span style="font-size:18px">CvHaarClassifierCascade* cvLoadHaarClassifierCascade( const char* directory, CvSize orig_window_size );</span>
directory
训练好的级联分类器的路径
orig_window_size
级联分类器训练中采用的检测目标的尺寸。因为这个信息没有在级联分类器中存储,所以要单独指出。
函数 cvLoadHaarClassifierCascade 用于从文件中装载训练好的利用Harr特征的级联分类器,或者从OpenCV中嵌入的分类器数据库中导入。分类器的训练可以应用函数haartraining(详细察看opencv/apps/haartraining) 这个数值是在训练分类器时就确定好的,修改它并不能改变检测的范围或精度。
需要注意的是,这个函数已经过时了。现在的目标检测分类器通常存储在 XML 或 YAML 文件中,而不是通过路径导入。从文件中导入分类器,可以使用函数 cvLoad 。
cvReleaseHaarClassifierCascade
释放haar classifier cascade。
void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade );
cascade
双指针类型指针指向要释放的cascade. 指针由函数声明。
函数 cvReleaseHaarClassifierCascade 释放cascade的动态内存,其中cascade的动态内存或者是手工创建,或者通过函数 cvLoadHaarClassifierCascade 或 cvLoad分配。
cvHaarDetectObjects
检测图像中的目标
<span style="font-size:18px">typedef struct CvAvgComp { CvRect rect; /* bounding rectangle for the object (average rectangle of a group)对目标标记矩形边界(一系列矩形的平均) */ int neighbors; /* number of neighbor rectangles in the group一些列矩形中领域矩形的数量 */ } CvAvgComp; CvSeq* cvHaarDetectObjects( const CvArr* image, CvHaarClassifierCascade* cascade, CvMemStorage* storage, double scale_factor=1.1, int min_neighbors=3, int flags=0, CvSize min_size=cvSize(0,0) );</span>
被检图像
cascade
harr 分类器级联的内部标识形式
storage
用来存储检测到的一序列候选目标矩形框的内存区域。
scale_factor
在前后两次相继的扫描中,搜索窗口的比例系数。例如1.1指将搜索窗口依次扩大10%。
min_neighbors
构成检测目标的相邻矩形的最小个数(缺省-1)。如果组成检测目标的小矩形的个数和小于min_neighbors-1 都会被排除。如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上。
flags
操作方式。当前唯一可以定义的操作方式是 CV_HAAR_DO_CANNY_PRUNING。如果被设定,函数利用Canny边缘检测器来排除一些边缘很少或者很多的图像区域,因为这样的区域一般不含被检目标。人脸检测中通过设定阈值使用了这种方法,并因此提高了检测速度。
min_size
检测窗口的最小尺寸。缺省的情况下被设为分类器训练时采用的样本尺寸(人脸检测中缺省大小是~20×20)。
函数 cvHaarDetectObjects 使用针对某目标物体训练的级联分类器在图像中找到包含目标物体的矩形区域,并且将这些区域作为一序列的矩形框返回。函数以不同比例大小的扫描窗口对图像进行几次搜索(察看cvSetImagesForHaarClassifierCascade)。 每次都要对图像中的这些重叠区域利用cvRunHaarClassifierCascade进行检测。 有时候也会利用某些继承(heuristics)技术以减少分析的候选区域,例如利用 Canny 裁减 (prunning)方法。 函数在处理和收集到候选的方框(全部通过级联分类器各层的区域)之后,接着对这些区域进行组合并且返回一系列各个足够大的组合中的平均矩形。调节程序中的缺省参数(scale_factor=1.1, min_neighbors=3, flags=0)用于对目标进行更精确同时也是耗时较长的进一步检测。为了能对视频图像进行更快的实时检测,参数设置通常是:scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING, min_size=<minimum possible face size> (例如, 对于视频会议的图像区域).
例子:利用级联的Haar classifiers寻找检测目标(e.g. faces).
#include "cv.h" #include "highgui.h" CvHaarClassifierCascade* load_object_detector( const char* cascade_path ) { return (CvHaarClassifierCascade*)cvLoad( cascade_path ); } void detect_and_draw_objects( IplImage* image, CvHaarClassifierCascade* cascade, int do_pyramids ) { IplImage* small_image = image; CvMemStorage* storage = cvCreateMemStorage(0); CvSeq* faces; int i, scale = 1; /* if the flag is specified, down-scale the 输入图像 to get a performance boost w/o loosing quality (perhaps) */ if( do_pyramids ) { small_image = cvCreateImage( cvSize(image->width/2,image->height/2), IPL_DEPTH_8U, 3 ); cvPyrDown( image, small_image, CV_GAUSSIAN_5x5 ); scale = 2; } /* use the fastest variant */ faces = cvHaarDetectObjects( small_image, cascade, storage, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING ); /* draw all the rectangles */ for( i = 0; i < faces->total; i++ ) { /* extract the rectanlges only */ CvRect face_rect = *(CvRect*)cvGetSeqElem( faces, i, 0 ); cvRectangle( image, cvPoint(face_rect.x*scale,face_rect.y*scale), cvPoint((face_rect.x+face_rect.width)*scale, (face_rect.y+face_rect.height)*scale), CV_RGB(255,0,0), 3 ); } if( small_image != image ) cvReleaseImage( &small_image ); cvReleaseMemStorage( &storage ); } /* takes image filename and cascade path from the command line */ int main( int argc, char** argv ) { IplImage* image; if( argc==3 && (image = cvLoadImage( argv[1], 1 )) != 0 ) { CvHaarClassifierCascade* cascade = load_object_detector(argv[2]); detect_and_draw_objects( image, cascade, 1 ); cvNamedWindow( "test", 0 ); cvShowImage( "test", image ); cvWaitKey(0); cvReleaseHaarClassifierCascade( &cascade ); cvReleaseImage( &image ); } return 0; }
cvSetImagesForHaarClassifierCascade
为隐藏的cascade(hidden cascade)指定图像
<span style="font-size:18px">void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade, const CvArr* sum, const CvArr* sqsum, const CvArr* tilted_sum, double scale );</span>
隐藏 Harr 分类器级联(Hidden Haar classifier cascade), 由函数 cvCreateHidHaarClassifierCascade生成
sum
32-比特,单通道图像的积分图像(Integral (sum) 单通道 image of 32-比特 integer format). 这幅图像以及随后的两幅用于对快速特征的评价和亮度/对比度的归一化。 它们都可以利用函数 cvIntegral从8-比特或浮点数 单通道的输入图像中得到。
sqsum
单通道64比特图像的平方和图像
tilted_sum
单通道32比特整数格式的图像的倾斜和(Tilted sum)
scale
cascade的窗口比例. 如果 scale=1, 就只用原始窗口尺寸检测 (只检测同样尺寸大小的目标物体) - 原始窗口尺寸在函数cvLoadHaarClassifierCascade中定义 (在 "<default_face_cascade>"中缺省为24x24), 如果scale=2, 使用的窗口是上面的两倍 (在face cascade中缺省值是48x48 )。 这样尽管可以将检测速度提高四倍,但同时尺寸小于48x48的人脸将不能被检测到。
函数 cvSetImagesForHaarClassifierCascade 为hidden classifier cascade 指定图像 and/or 窗口比例系数。 如果图像指针为空,会继续使用原来的图像(i.e. NULLs 意味这"不改变图像")。比例系数没有 "protection" 值,但是原来的值可以通过函数 cvGetHaarClassifierCascadeScale 重新得到并使用。这个函数用于对特定图像中检测特定目标尺寸的cascade分类器的设定。函数通过cvHaarDetectObjects进行内部调用,但当需要在更低一层的函数cvRunHaarClassifierCascade中使用的时候,用户也可以自行调用。
cvRunHaarClassifierCascade
在给定位置的图像中运行 cascade of boosted classifier
<span style="font-size:18px">int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade, CvPoint pt, int start_stage=0 );</span>
cascade
Haar 级联分类器
pt
待检测区域的左上角坐标。待检测区域大小为原始窗口尺寸乘以当前设定的比例系数。当前窗口尺寸可以通过cvGetHaarClassifierCascadeWindowSize重新得到。
start_stage
级联层的初始下标值(从0开始计数)。函数假定前面所有每层的分类器都已通过。这个特征通过函数cvHaarDetectObjects内部调用,用于更好的处理器高速缓冲存储器。
函数 cvRunHaarClassifierCascade 用于对单幅图片的检测。在函数调用前首先利用 cvSetImagesForHaarClassifierCascade设定积分图和合适的比例系数 (=> 窗口尺寸)。当分析的矩形框全部通过级联分类器每一层的时返回正值(这是一个候选目标),否则返回0或负值。