CvHaarFeature
- #define CV_HAAR_FEATURE_MAX 3
- typedef struct CvHaarFeature
- {
- int tilted;
- struct
- {
- CvRect r;
- float weight;
- } rect[CV_HAAR_FEATURE_MAX];
- }
- CvHaarFeature;
一个 harr 特征由 2-3 个具有相应权重的矩形组成
titled : /* 0 means up-right feature, 1 means 45--rotated feature */
rect[CV_HAAR_FEATURE_MAX]; /* 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 */
CvHaarClassifier
- typedef struct CvHaarClassifier
- {
- int count;
- CvHaarFeature* haar_feature;
- float* threshold;
- int* left;
- int* right;
- float* alpha;
- }
- 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).
left[i] - index of the left child (or negated index if the left child is a leaf)
right[i] - index of the right child (or negated index if the right child is a leaf)
threshold[i] - branch threshold. if feature responce is <= threshold, left branch is chosen, otherwise right branch is chosed.
alpha[i] - output value correponding to the leaf. */
CvHaarStageClassifier
- 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;
/* 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 */
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 */
- CvHaarClassifierCascade
- typedef struct CvHidHaarClassifierCascade CvHidHaarClassifierCascade;
- typedef struct CvHaarClassifierCascade
- {
- int flags;
- int count;
- CvSize orig_window_size;
- CvSize real_window_size;
- double scale;
- CvHaarStageClassifier* stage_classifier;
- CvHidHaarClassifierCascade* hid_cascade;
- }
- CvHaarClassifierCascade;
/* cascade or tree of stage classifiers */
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 */
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 */
所有的结构都代表一个级联boosted Haar分类器。级联有下面的等级结构:
Cascade:
Stage1:
Classifier11:
Feature11
Classifier12:
Feature12
...
Stage2:
Classifier21:
Feature21
...
...
整个等级可以手工构建,也可以利用函数cvLoadHaarClassifierCascade从已有的磁盘文件或嵌入式基中导入。
特征检测用到的函数:
cvLoadHaarClassifierCascade
从文件中装载训练好的级联分类器或者从OpenCV中嵌入的分类器数据库中导入
CvHaarClassifierCascade* cvLoadHaarClassifierCascade(
const char* directory,
CvSize orig_window_size );
directory :训练好的级联分类器的路径
orig_window_size: 级联分类器训练中采用的检测目标的尺寸 。因为这个信息没有在级联分类器中存储,所有要单独指出。
函数 cvLoadHaarClassifierCascade 用于从文件中装载训练好的利用海尔特征的级联分类器,或者从OpenCV中嵌入的分类器数据库中导入。分类器的训练可以应用函数 haartraining (详细察看opencv/apps/haartraining)
函数 已经过时了。现在的目标检测分类器通常存储在 XML 或 YAML 文件中,而不是通过路径导入。从文件中导入分类器,可以使用函数 cvLoad 。
cvReleaseHaarClassifierCascade
释放haar classifier cascade。
void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade );
cascade :双指针类型指针指向要释放的cascade. 指针由函数声明。
函数 cvReleaseHaarClassifierCascade 释放cascade的动态内存,其中cascade的动态内存或者是手工创建,或者通过函数 cvLoadHaarClassifierCascade 或 cvLoad分配。
cvHaarDetectObjects
检测图像中的目标
- 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) );
image 被检图像
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> (例如, 对于视频会议的图像区域).
cvSetImagesForHaarClassifierCascade
为隐藏的cascade(hidden cascade)指定图像
void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade,
const CvArr* sum, const CvArr* sqsum,
const CvArr* tilted_sum, double scale );
cascade 隐藏 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
int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade,
CvPoint pt, int start_stage=0 );
cascade Haar 级联分类器
pt 待检测区域的左上角坐标。待检测区域大小为原始窗口尺寸乘以当前设定的比例系数。当前窗口尺寸可以通过cvGetHaarClassifierCascadeWindowSize重新得到。
start_stage 级联层的初始下标值(从0开始计数)。函数假定前面所有每层的分类器都已通过。这个特征通过函数cvHaarDetectObjects内部调用,用于更好的处理器高速缓冲存储器。
函数 cvRunHaarHaarClassifierCascade 用于对单幅图片的检测。在函数调用前首先利用 cvSetImagesForHaarClassifierCascade设定积分图和合适的比例系数 (=> 窗口尺寸)。当分析的矩形框全部通过级联分类器每一层的时返回正值(这是一个候选目标),否则返回0或负值。
使用针对某目标物体训练的级联分类器在图像中找到包含目标物体的矩形区域,并且将这些区域作为一序列的矩形框返。
code:
- #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 );//函数 cvPyrDown 使用 Gaussian 金字塔分解对输入图像向下采样。首先它对输入图像用指定滤波器进行卷积,然后通过拒绝偶数的行与列来下采样图像。
- 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;
- }
========================================================================================================
以下来自: http://blog.sina.com.cn/s/blog_790bb7190100qm66.html
OpenCV的人脸检测主要是调用训练好的cascade(Haar分类器)来进行模式匹配。
cvHaarDetectObjects,先将图像灰度化,根据传入参数判断是否进行canny边缘处理(默认不使用),再进行匹配。匹配后收集找出的匹配块,过滤噪声,计算相邻个数如果超过了规定值(传入的min_neighbors)就当成输出结果,否则删去。
匹配循环:将匹配分类器放大scale(传入值)倍,同时原图缩小scale倍,进行匹配,直到匹配分类器的大小大于原图,则返回匹配结果。匹配的时候调用cvRunHaarClassifierCasca
cvRunHaarClassifierCasca
函数 cvRunHaarClassifierCasca
为了了解OpenCV人脸检测中寻找匹配图像的详细过程,就把cvHaarDetectObjects和cvRunHaarClassifierCasca
附cvHaarDetectObjects代码:
- CV_IMPL CvSeq*
- cvHaarDetectObjects( const CvArr* _img,
- CvHaarClassifierCascade* cascade,
- CvMemStorage* storage, double scale_factor,
- int min_neighbors, int flags, CvSize min_size )
- {
- int split_stage = 2;
-
- CvMat stub, *img = (CvMat*)_img; //CvMat多通道矩阵 *img=_img指针代换传入图
- CvMat *temp = 0, *sum = 0, *tilted = 0, *sqsum = 0, *norm_img = 0, *sumcanny = 0, *img_small = 0;
- CvSeq* seq = 0;
- CvSeq* seq2 = 0; //CvSeq可动态增长元素序列
- CvSeq* idx_seq = 0;
- CvSeq* result_seq = 0;
- CvMemStorage* temp_storage = 0;
- CvAvgComp* comps = 0;
- int i;
-
- #ifdef _OPENMP
- CvSeq* seq_thread[CV_MAX_THREADS] = {0};
- int max_threads = 0;
- #endif
-
- CV_FUNCNAME( “cvHaarDetectObjects” );
-
- __BEGIN__;
-
- double factor;
- int npass = 2, coi; //npass=2
- int do_canny_pruning = flags & CV_HAAR_DO_CANNY_PRUNING; //true做canny边缘处理
-
- if( !CV_IS_HAAR_CLASSIFIER(cascade) )
- CV_ERROR( !cascade ? CV_StsNullPtr : CV_StsBadArg, “Invalid classifier cascade” );
-
- if( !storage )
- CV_ERROR( CV_StsNullPtr, “Null storage pointer” );
-
- CV_CALL( img = cvGetMat( img, &stub, &coi ));
- if( coi )
- CV_ERROR( CV_BadCOI, “COI is not supported” ); //一些出错代码
-
- if( CV_MAT_DEPTH(img->type) != CV_8U )
- CV_ERROR( CV_StsUnsupportedFormat, “Only 8-bit images are supported” );
-
- CV_CALL( temp = cvCreateMat( img->rows, img->cols, CV_8UC1 ));
- CV_CALL( sum = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ));
- CV_CALL( sqsum = cvCreateMat( img->rows + 1, img->cols + 1, CV_64FC1 ));
- CV_CALL( temp_storage = cvCreateChildMemStorage( storage ));
-
- #ifdef _OPENMP
- max_threads = cvGetNumThreads();
- for( i = 0; i < max_threads; i++ )
- {
- CvMemStorage* temp_storage_thread;
- CV_CALL( temp_storage_thread = cvCreateMemStorage(0)); //CV_CALL就是运行,假如出错就报错。
- CV_CALL( seq_thread[i] = cvCreateSeq( 0, sizeof(CvSeq), //CvSeq可动态增长元素序列
-
- sizeof(CvRect), temp_storage_thread ));
- }
- #endif
-
- if( !cascade->hid_cascade )
- CV_CALL( icvCreateHidHaarClassifierCascade(cascade) );
-
- if( cascade->hid_cascade->has_tilted_features )
- tilted = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //多通道矩阵 图像长宽+1 4通道
-
- seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvRect), temp_storage ); //创建序列seq 矩形
- seq2 = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), temp_storage ); //创建序列seq2 矩形和邻近
- result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), storage ); //创建序列result_seq 矩形和邻近
-
- if( min_neighbors == 0 )
- seq = result_seq;
-
- if( CV_MAT_CN(img->type) > 1 )
- {
- cvCvtColor( img, temp, CV_BGR2GRAY ); //img转为灰度
- img = temp;
- }
-
- if( flags & CV_HAAR_SCALE_IMAGE ) //flag && 匹配图
- {
- CvSize win_size0 = cascade->orig_window_size; //CvSize win_size0为分类器的原始大小
- int use_ipp = cascade->hid_cascade->ipp_stages != 0 &&
- icvApplyHaarClassifier_32s32f_C1R_p != 0; //IPP相关函数
-
- if( use_ipp )
- CV_CALL( norm_img = cvCreateMat( img->rows, img->cols, CV_32FC1 )); //图像的矩阵化 4通道.
- CV_CALL( img_small = cvCreateMat( img->rows + 1, img->cols + 1, CV_8UC1 )); //小图矩阵化 单通道 长宽+1
-
- for( factor = 1; ; factor *= scale_factor ) //成scale_factor倍数匹配
- {
- int positive = 0;
- int x, y;
- CvSize win_size = { cvRound(win_size0.width*factor),
- cvRound(win_size0.height*factor) }; //winsize 分类器行列(扩大factor倍)
- CvSize sz = { cvRound( img->cols/factor ), cvRound( img->rows/factor ) }; //sz 图像行列(缩小factor倍) 三个Cvsize
- CvSize sz1 = { sz.width – win_size0.width, sz.height – win_size0.height }; //sz1 图像 减分类器行列
- CvRect rect1 = { icv_object_win_border, icv_object_win_border,
- win_size0.width – icv_object_win_border*2, //icv_object_win_border (int) 初始值=1
- win_size0.height – icv_object_win_border*2 }; //矩形框rect1
- CvMat img1, sum1, sqsum1, norm1, tilted1, mask1; //多通道矩阵
- CvMat* _tilted = 0;
-
- if( sz1.width <= 0 || sz1.height <= 0 ) //图片宽或高小于分类器–>跳出
- break;
- if( win_size.width < min_size.width || win_size.height < min_size.height ) //分类器高或宽小于给定的mini_size的高或宽–>继续
- continue;
- //CV_8UC1见定义.
- //#define CV_MAKETYPE(depth,cn) ((depth) + (((cn)-1) << CV_CN_SHIFT))
- //深度+(cn-1)左移3位 depth,depth+8,depth+16,depth+24.
- img1 = cvMat( sz.height, sz.width, CV_8UC1, img_small->data.ptr ); //小图的矩阵化 img1 单通道
- sum1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, sum->data.ptr ); //长宽+1 4通道8位 多通道矩阵
- sqsum1 = cvMat( sz.height+1, sz.width+1, CV_64FC1, sqsum->data.ptr ); //长宽+1 4通道16位
- if( tilted )
- {
- tilted1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, tilted->data.ptr ); //长宽+1 4通道8位
- _tilted = &tilted1; //长宽+1 4通道8位
- }
- norm1 = cvMat( sz1.height, sz1.width, CV_32FC1, norm_img ? norm_img->data.ptr : 0 ); //norm1 图像 减 分类器行列 4通道
- mask1 = cvMat( sz1.height, sz1.width, CV_8UC1, temp->data.ptr ); //mask1 灰度图
-
- cvResize( img, &img1, CV_INTER_LINEAR ); //img双线性插值 输出到img1
- cvIntegral( &img1, &sum1, &sqsum1, _tilted ); //计算积分图像
-
- if( use_ipp && icvRectStdDev_32s32f_C1R_p( sum1.data.i, sum1.step,
- sqsum1.data.db, sqsum1.step, norm1.data.fl, norm1.step, sz1, rect1 ) < 0 )
- use_ipp = 0;
-
- if( use_ipp ) //如果ipp=true (intel视频处理加速等的函数库)
- {
- positive = mask1.cols*mask1.rows; //mask1长乘宽–>positive
- cvSet( &mask1, cvScalarAll(255) ); //mask1赋值为255
- for( i = 0; i < cascade->count; i++ )
- {
- if( icvApplyHaarClassifier_32s32f_C1R_p(sum1.data.i, sum1.step,
- norm1.data.fl, norm1.step, mask1.data.ptr, mask1.step,
- sz1, &positive, cascade->hid_cascade->stage_classifier[i].threshold,
- cascade->hid_cascade->ipp_stages[i]) < 0 )
- {
- use_ipp = 0; //ipp=false;
- break;
- }
- if( positive <= 0 )
- break;
- }
- }
-
- if( !use_ipp ) //如果ipp=false
- {
- cvSetImagesForHaarClassifierCascade( cascade, &sum1, &sqsum1, 0, 1. );
- for( y = 0, positive = 0; y < sz1.height; y++ )
- for( x = 0; x < sz1.width; x++ )
- {
- mask1.data.ptr[mask1.step*y + x] =
- cvRunHaarClassifierCascade( cascade, cvPoint(x,y), 0 ) > 0; //匹配图像.
- positive += mask1.data.ptr[mask1.step*y + x];
- }
- }
-
- if( positive > 0 )
- {
- for( y = 0; y < sz1.height; y++ )
- for( x = 0; x < sz1.width; x++ )
- if( mask1.data.ptr[mask1.step*y + x] != 0 )
- {
- CvRect obj_rect = { cvRound(y*factor), cvRound(x*factor),
- win_size.width, win_size.height };
- cvSeqPush( seq, &obj_rect ); //将匹配块放到seq中
- }
- }
- }
- }
- else //!(flag && 匹配图)
- {
- cvIntegral( img, sum, sqsum, tilted );
-
- if( do_canny_pruning )
- {
- sumcanny = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //如果 做canny边缘检测
- cvCanny( img, temp, 0, 50, 3 );
- cvIntegral( temp, sumcanny );
- }
-
- if( (unsigned)split_stage >= (unsigned)cascade->count ||
- cascade->hid_cascade->is_tree )
- {
- split_stage = cascade->count;
- npass = 1;
- }
-
- for( factor = 1; factor*cascade->orig_window_size.width < img->cols – 10 && //匹配
- factor*cascade->orig_window_size.height < img->rows – 10;
- factor *= scale_factor )
- {
- const double ystep = MAX( 2, factor );
- CvSize win_size = { cvRound( cascade->orig_window_size.width * factor ),
- cvRound( cascade->orig_window_size.height * factor )};
- CvRect equ_rect = { 0, 0, 0, 0 };
- int *p0 = 0, *p1 = 0, *p2 = 0, *p3 = 0;
- int *pq0 = 0, *pq1 = 0, *pq2 = 0, *pq3 = 0;
- int pass, stage_offset = 0;
- int stop_height = cvRound((img->rows – win_size.height) / ystep);
-
- if( win_size.width < min_size.width || win_size.height < min_size.height ) //超边跳出
- continue;
-
- cvSetImagesForHaarClassifierCascade( cascade, sum, sqsum, tilted, factor ); //匹配
- cvZero( temp ); //清空temp数组
-
- if( do_canny_pruning ) //canny边缘检测
- {
- equ_rect.x = cvRound(win_size.width*0.15);
- equ_rect.y = cvRound(win_size.height*0.15);
- equ_rect.width = cvRound(win_size.width*0.7);
- equ_rect.height = cvRound(win_size.height*0.7);
-
- p0 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step) + equ_rect.x;
- p1 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step)
- + equ_rect.x + equ_rect.width;
- p2 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step) + equ_rect.x;
- p3 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step)
- + equ_rect.x + equ_rect.width;
-
- pq0 = (int*)(sum->data.ptr + equ_rect.y*sum->step) + equ_rect.x;
- pq1 = (int*)(sum->data.ptr + equ_rect.y*sum->step)
- + equ_rect.x + equ_rect.width;
- pq2 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step) + equ_rect.x;
- pq3 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step)
- + equ_rect.x + equ_rect.width;
- }
-
- cascade->hid_cascade->count = split_stage; //分裂级
-
- for( pass = 0; pass < npass; pass++ )
- {
- #ifdef _OPENMP
- #pragma omp parallel for num_threads(max_threads), schedule(dynamic)
- #endif
- for( int _iy = 0; _iy < stop_height; _iy++ )
- {
- int iy = cvRound(_iy*ystep);
- int _ix, _xstep = 1;
- int stop_width = cvRound((img->cols – win_size.width) / ystep);
- uchar* mask_row = temp->data.ptr + temp->step * iy;
-
- for( _ix = 0; _ix < stop_width; _ix += _xstep )
- {
- int ix = cvRound(_ix*ystep); // it really should be ystep
-
- if( pass == 0 ) //第一次循环 做
- {
- int result;
- _xstep = 2;
-
- if( do_canny_pruning ) //canny边缘检测
- {
- int offset;
- int s, sq;
-
- offset = iy*(sum->step/sizeof(p0[0])) + ix;
- s = p0[offset] – p1[offset] – p2[offset] + p3[offset];
- sq = pq0[offset] – pq1[offset] – pq2[offset] + pq3[offset];
- if( s < 100 || sq < 20 )
- continue;
- }
-
- result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy), 0 ); //匹配结果存到result里
- if( result > 0 )
- {
- if( pass < npass – 1 )
- mask_row[ix] = 1;
- else
- {
- CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
- #ifndef _OPENMP //如果用OpenMP
- cvSeqPush( seq, &rect ); //result 放到seq中
- #else //如果不用OpenMP
- cvSeqPush( seq_thread[omp_get_thread_num()], &rect ); //result放到seq_thread里
- #endif
- }
- }
- if( result < 0 )
- _xstep = 1;
- }
- else if( mask_row[ix] ) //不是第一次
- {
- int result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy),
- stage_offset );
- if( result > 0 )
- {
- if( pass == npass – 1 ) //如果是最后一次
- {
- CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
- #ifndef _OPENMP
- cvSeqPush( seq, &rect );
- #else
- cvSeqPush( seq_thread[omp_get_thread_num()], &rect );
- #endif
- }
- }
- else
- mask_row[ix] = 0;
- }
- }
- }
- stage_offset = cascade->hid_cascade->count;
- cascade->hid_cascade->count = cascade->count;
- }
- }
- }
-
- #ifdef _OPENMP
- // gather the results //收集结果
- for( i = 0; i < max_threads; i++ )
- {
- CvSeq* s = seq_thread[i];
- int j, total = s->total;
- CvSeqBlock* b = s->first;
- for( j = 0; j < total; j += b->count, b = b->next )
- cvSeqPushMulti( seq, b->data, b->count ); //结果输出到seq
- }
- #endif
-
- if( min_neighbors != 0 )
- {
- // group retrieved rectangles in order to filter out noise 收集找出的匹配块,过滤噪声
- int ncomp = cvSeqPartition( seq, 0, &idx_seq, is_equal, 0 );
- CV_CALL( comps = (CvAvgComp*)cvAlloc( (ncomp+1)*sizeof(comps[0])));
- memset( comps, 0, (ncomp+1)*sizeof(comps[0]));
-
- // count number of neighbors 计算相邻个数
- for( i = 0; i < seq->total; i++ )
- {
- CvRect r1 = *(CvRect*)cvGetSeqElem( seq, i );
- int idx = *(int*)cvGetSeqElem( idx_seq, i );
- assert( (unsigned)idx < (unsigned)ncomp );
-
- comps[idx].neighbors++;
-
- comps[idx].rect.x += r1.x;
- comps[idx].rect.y += r1.y;
- comps[idx].rect.width += r1.width;
- comps[idx].rect.height += r1.height;
- }
-
- // calculate average bounding box 计算重心
- for( i = 0; i < ncomp; i++ )
- {
- int n = comps[i].neighbors;
- if( n >= min_neighbors )
- {
- CvAvgComp comp;
- comp.rect.x = (comps[i].rect.x*2 + n)/(2*n);
- comp.rect.y = (comps[i].rect.y*2 + n)/(2*n);
- comp.rect.width = (comps[i].rect.width*2 + n)/(2*n);
- comp.rect.height = (comps[i].rect.height*2 + n)/(2*n);
- comp.neighbors = comps[i].neighbors;
-
- cvSeqPush( seq2, &comp ); //结果输入到seq2
- }
- }
-
- // filter out small face rectangles inside large face rectangles 在大的面块中找出小的面块
- for( i = 0; i < seq2->total; i++ ) //在seq2中寻找
- {
- CvAvgComp r1 = *(CvAvgComp*)cvGetSeqElem( seq2, i ); //r1指向结果
- int j, flag = 1;
-
- for( j = 0; j < seq2->total; j++ )
- {
- CvAvgComp r2 = *(CvAvgComp*)cvGetSeqElem( seq2, j );
- int distance = cvRound( r2.rect.width * 0.2 );
-
- if( i != j &&
- r1.rect.x >= r2.rect.x – distance &&
- r1.rect.y >= r2.rect.y – distance &&
- r1.rect.x + r1.rect.width <= r2.rect.x + r2.rect.width + distance &&
- r1.rect.y + r1.rect.height <= r2.rect.y + r2.rect.height + distance &&
- (r2.neighbors > MAX( 3, r1.neighbors ) || r1.neighbors < 3) )
- {
- flag = 0;
- break;
- }
- }
-
- if( flag )
- {
- cvSeqPush( result_seq, &r1 ); //添加r1到返回结果.
-
- }
- }
- }
-
- __END__;
- CV_IMPL CvSeq*
- cvHaarDetectObjects( const CvArr* _img,
- CvHaarClassifierCascade* cascade,
- CvMemStorage* storage, double scale_factor,
- int min_neighbors, int flags, CvSize min_size )
- {
- int split_stage = 2;
- CvMat stub, *img = (CvMat*)_img; //CvMat多通道矩阵 *img=_img指针代换传入图
- CvMat *temp = 0, *sum = 0, *tilted = 0, *sqsum = 0, *norm_img = 0, *sumcanny = 0, *img_small = 0;
- CvSeq* seq = 0;
- CvSeq* seq2 = 0; //CvSeq可动态增长元素序列
- CvSeq* idx_seq = 0;
- CvSeq* result_seq = 0;
- CvMemStorage* temp_storage = 0;
- CvAvgComp* comps = 0;
- int i;
- #ifdef _OPENMP
- CvSeq* seq_thread[CV_MAX_THREADS] = {0};
- int max_threads = 0;
- #endif
- CV_FUNCNAME( “cvHaarDetectObjects” );
- __BEGIN__;
- double factor;
- int npass = 2, coi; //npass=2
- int do_canny_pruning = flags & CV_HAAR_DO_CANNY_PRUNING; //true做canny边缘处理
- if( !CV_IS_HAAR_CLASSIFIER(cascade) )
- CV_ERROR( !cascade ? CV_StsNullPtr : CV_StsBadArg, “Invalid classifier cascade” );
- if( !storage )
- CV_ERROR( CV_StsNullPtr, “Null storage pointer” );
- CV_CALL( img = cvGetMat( img, &stub, &coi ));
- if( coi )
- CV_ERROR( CV_BadCOI, “COI is not supported” ); //一些出错代码
- if( CV_MAT_DEPTH(img->type) != CV_8U )
- CV_ERROR( CV_StsUnsupportedFormat, “Only 8-bit images are supported” );
- CV_CALL( temp = cvCreateMat( img->rows, img->cols, CV_8UC1 ));
- CV_CALL( sum = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ));
- CV_CALL( sqsum = cvCreateMat( img->rows + 1, img->cols + 1, CV_64FC1 ));
- CV_CALL( temp_storage = cvCreateChildMemStorage( storage ));
- #ifdef _OPENMP
- max_threads = cvGetNumThreads();
- for( i = 0; i < max_threads; i++ )
- {
- CvMemStorage* temp_storage_thread;
- CV_CALL( temp_storage_thread = cvCreateMemStorage(0)); //CV_CALL就是运行,假如出错就报错。
- CV_CALL( seq_thread[i] = cvCreateSeq( 0, sizeof(CvSeq), //CvSeq可动态增长元素序列
- sizeof(CvRect), temp_storage_thread ));
- }
- #endif
- if( !cascade->hid_cascade )
- CV_CALL( icvCreateHidHaarClassifierCascade(cascade) );
- if( cascade->hid_cascade->has_tilted_features )
- tilted = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //多通道矩阵 图像长宽+1 4通道
- seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvRect), temp_storage ); //创建序列seq 矩形
- seq2 = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), temp_storage ); //创建序列seq2 矩形和邻近
- result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), storage ); //创建序列result_seq 矩形和邻近
- if( min_neighbors == 0 )
- seq = result_seq;
- if( CV_MAT_CN(img->type) > 1 )
- {
- cvCvtColor( img, temp, CV_BGR2GRAY ); //img转为灰度
- img = temp;
- }
- if( flags & CV_HAAR_SCALE_IMAGE ) //flag && 匹配图
- {
- CvSize win_size0 = cascade->orig_window_size; //CvSize win_size0为分类器的原始大小
- int use_ipp = cascade->hid_cascade->ipp_stages != 0 &&
- icvApplyHaarClassifier_32s32f_C1R_p != 0; //IPP相关函数
- if( use_ipp )
- CV_CALL( norm_img = cvCreateMat( img->rows, img->cols, CV_32FC1 )); //图像的矩阵化 4通道.
- CV_CALL( img_small = cvCreateMat( img->rows + 1, img->cols + 1, CV_8UC1 )); //小图矩阵化 单通道 长宽+1
- for( factor = 1; ; factor *= scale_factor ) //成scale_factor倍数匹配
- {
- int positive = 0;
- int x, y;
- CvSize win_size = { cvRound(win_size0.width*factor),
- cvRound(win_size0.height*factor) }; //winsize 分类器行列(扩大factor倍)
- CvSize sz = { cvRound( img->cols/factor ), cvRound( img->rows/factor ) }; //sz 图像行列(缩小factor倍) 三个Cvsize
- CvSize sz1 = { sz.width – win_size0.width, sz.height – win_size0.height }; //sz1 图像 减分类器行列
- CvRect rect1 = { icv_object_win_border, icv_object_win_border,
- win_size0.width – icv_object_win_border*2, //icv_object_win_border (int) 初始值=1
- win_size0.height – icv_object_win_border*2 }; //矩形框rect1
- CvMat img1, sum1, sqsum1, norm1, tilted1, mask1; //多通道矩阵
- CvMat* _tilted = 0;
- if( sz1.width <= 0 || sz1.height <= 0 ) //图片宽或高小于分类器–>跳出
- break;
- if( win_size.width < min_size.width || win_size.height < min_size.height ) //分类器高或宽小于给定的mini_size的高或宽–>继续
- continue;
- //CV_8UC1见定义.
- //#define CV_MAKETYPE(depth,cn) ((depth) + (((cn)-1) << CV_CN_SHIFT))
- //深度+(cn-1)左移3位 depth,depth+8,depth+16,depth+24.
- img1 = cvMat( sz.height, sz.width, CV_8UC1, img_small->data.ptr ); //小图的矩阵化 img1 单通道
- sum1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, sum->data.ptr ); //长宽+1 4通道8位 多通道矩阵
- sqsum1 = cvMat( sz.height+1, sz.width+1, CV_64FC1, sqsum->data.ptr ); //长宽+1 4通道16位
- if( tilted )
- {
- tilted1 = cvMat( sz.height+1, sz.width+1, CV_32SC1, tilted->data.ptr ); //长宽+1 4通道8位
- _tilted = &tilted1; //长宽+1 4通道8位
- }
- norm1 = cvMat( sz1.height, sz1.width, CV_32FC1, norm_img ? norm_img->data.ptr : 0 ); //norm1 图像 减 分类器行列 4通道
- mask1 = cvMat( sz1.height, sz1.width, CV_8UC1, temp->data.ptr ); //mask1 灰度图
- cvResize( img, &img1, CV_INTER_LINEAR ); //img双线性插值 输出到img1
- cvIntegral( &img1, &sum1, &sqsum1, _tilted ); //计算积分图像
- if( use_ipp && icvRectStdDev_32s32f_C1R_p( sum1.data.i, sum1.step,
- sqsum1.data.db, sqsum1.step, norm1.data.fl, norm1.step, sz1, rect1 ) < 0 )
- use_ipp = 0;
- if( use_ipp ) //如果ipp=true (intel视频处理加速等的函数库)
- {
- positive = mask1.cols*mask1.rows; //mask1长乘宽–>positive
- cvSet( &mask1, cvScalarAll(255) ); //mask1赋值为255
- for( i = 0; i < cascade->count; i++ )
- {
- if( icvApplyHaarClassifier_32s32f_C1R_p(sum1.data.i, sum1.step,
- norm1.data.fl, norm1.step, mask1.data.ptr, mask1.step,
- sz1, &positive, cascade->hid_cascade->stage_classifier[i].threshold,
- cascade->hid_cascade->ipp_stages[i]) < 0 )
- {
- use_ipp = 0; //ipp=false;
- break;
- }
- if( positive <= 0 )
- break;
- }
- }
- if( !use_ipp ) //如果ipp=false
- {
- cvSetImagesForHaarClassifierCascade( cascade, &sum1, &sqsum1, 0, 1. );
- for( y = 0, positive = 0; y < sz1.height; y++ )
- for( x = 0; x < sz1.width; x++ )
- {
- mask1.data.ptr[mask1.step*y + x] =
- cvRunHaarClassifierCascade( cascade, cvPoint(x,y), 0 ) > 0; //匹配图像.
- positive += mask1.data.ptr[mask1.step*y + x];
- }
- }
- if( positive > 0 )
- {
- for( y = 0; y < sz1.height; y++ )
- for( x = 0; x < sz1.width; x++ )
- if( mask1.data.ptr[mask1.step*y + x] != 0 )
- {
- CvRect obj_rect = { cvRound(y*factor), cvRound(x*factor),
- win_size.width, win_size.height };
- cvSeqPush( seq, &obj_rect ); //将匹配块放到seq中
- }
- }
- }
- }
- else //!(flag && 匹配图)
- {
- cvIntegral( img, sum, sqsum, tilted );
- if( do_canny_pruning )
- {
- sumcanny = cvCreateMat( img->rows + 1, img->cols + 1, CV_32SC1 ); //如果 做canny边缘检测
- cvCanny( img, temp, 0, 50, 3 );
- cvIntegral( temp, sumcanny );
- }
- if( (unsigned)split_stage >= (unsigned)cascade->count ||
- cascade->hid_cascade->is_tree )
- {
- split_stage = cascade->count;
- npass = 1;
- }
- for( factor = 1; factor*cascade->orig_window_size.width < img->cols – 10 && //匹配
- factor*cascade->orig_window_size.height < img->rows – 10;
- factor *= scale_factor )
- {
- const double ystep = MAX( 2, factor );
- CvSize win_size = { cvRound( cascade->orig_window_size.width * factor ),
- cvRound( cascade->orig_window_size.height * factor )};
- CvRect equ_rect = { 0, 0, 0, 0 };
- int *p0 = 0, *p1 = 0, *p2 = 0, *p3 = 0;
- int *pq0 = 0, *pq1 = 0, *pq2 = 0, *pq3 = 0;
- int pass, stage_offset = 0;
- int stop_height = cvRound((img->rows – win_size.height) / ystep);
- if( win_size.width < min_size.width || win_size.height < min_size.height ) //超边跳出
- continue;
- cvSetImagesForHaarClassifierCascade( cascade, sum, sqsum, tilted, factor ); //匹配
- cvZero( temp ); //清空temp数组
- if( do_canny_pruning ) //canny边缘检测
- {
- equ_rect.x = cvRound(win_size.width*0.15);
- equ_rect.y = cvRound(win_size.height*0.15);
- equ_rect.width = cvRound(win_size.width*0.7);
- equ_rect.height = cvRound(win_size.height*0.7);
- p0 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step) + equ_rect.x;
- p1 = (int*)(sumcanny->data.ptr + equ_rect.y*sumcanny->step)
- + equ_rect.x + equ_rect.width;
- p2 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step) + equ_rect.x;
- p3 = (int*)(sumcanny->data.ptr + (equ_rect.y + equ_rect.height)*sumcanny->step)
- + equ_rect.x + equ_rect.width;
- pq0 = (int*)(sum->data.ptr + equ_rect.y*sum->step) + equ_rect.x;
- pq1 = (int*)(sum->data.ptr + equ_rect.y*sum->step)
- + equ_rect.x + equ_rect.width;
- pq2 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step) + equ_rect.x;
- pq3 = (int*)(sum->data.ptr + (equ_rect.y + equ_rect.height)*sum->step)
- + equ_rect.x + equ_rect.width;
- }
- cascade->hid_cascade->count = split_stage; //分裂级
- for( pass = 0; pass < npass; pass++ )
- {
- #ifdef _OPENMP
- #pragma omp parallel for num_threads(max_threads), schedule(dynamic)
- #endif
- for( int _iy = 0; _iy < stop_height; _iy++ )
- {
- int iy = cvRound(_iy*ystep);
- int _ix, _xstep = 1;
- int stop_width = cvRound((img->cols – win_size.width) / ystep);
- uchar* mask_row = temp->data.ptr + temp->step * iy;
- for( _ix = 0; _ix < stop_width; _ix += _xstep )
- {
- int ix = cvRound(_ix*ystep); // it really should be ystep
- if( pass == 0 ) //第一次循环 做
- {
- int result;
- _xstep = 2;
- if( do_canny_pruning ) //canny边缘检测
- {
- int offset;
- int s, sq;
- offset = iy*(sum->step/sizeof(p0[0])) + ix;
- s = p0[offset] – p1[offset] – p2[offset] + p3[offset];
- sq = pq0[offset] – pq1[offset] – pq2[offset] + pq3[offset];
- if( s < 100 || sq < 20 )
- continue;
- }
- result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy), 0 ); //匹配结果存到result里
- if( result > 0 )
- {
- if( pass < npass – 1 )
- mask_row[ix] = 1;
- else
- {
- CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
- #ifndef _OPENMP //如果用OpenMP
- cvSeqPush( seq, &rect ); //result 放到seq中
- #else //如果不用OpenMP
- cvSeqPush( seq_thread[omp_get_thread_num()], &rect ); //result放到seq_thread里
- #endif
- }
- }
- if( result < 0 )
- _xstep = 1;
- }
- else if( mask_row[ix] ) //不是第一次
- {
- int result = cvRunHaarClassifierCascade( cascade, cvPoint(ix,iy),
- stage_offset );
- if( result > 0 )
- {
- if( pass == npass – 1 ) //如果是最后一次
- {
- CvRect rect = cvRect(ix,iy,win_size.width,win_size.height);
- #ifndef _OPENMP
- cvSeqPush( seq, &rect );
- #else
- cvSeqPush( seq_thread[omp_get_thread_num()], &rect );
- #endif
- }
- }
- else
- mask_row[ix] = 0;
- }
- }
- }
- stage_offset = cascade->hid_cascade->count;
- cascade->hid_cascade->count = cascade->count;
- }
- }
- }
- #ifdef _OPENMP
- // gather the results //收集结果
- for( i = 0; i < max_threads; i++ )
- {
- CvSeq* s = seq_thread[i];
- int j, total = s->total;
- CvSeqBlock* b = s->first;
- for( j = 0; j < total; j += b->count, b = b->next )
- cvSeqPushMulti( seq, b->data, b->count ); //结果输出到seq
- }
- #endif
- if( min_neighbors != 0 )
- {
- // group retrieved rectangles in order to filter out noise 收集找出的匹配块,过滤噪声
- int ncomp = cvSeqPartition( seq, 0, &idx_seq, is_equal, 0 );
- CV_CALL( comps = (CvAvgComp*)cvAlloc( (ncomp+1)*sizeof(comps[0])));
- memset( comps, 0, (ncomp+1)*sizeof(comps[0]));
- // count number of neighbors 计算相邻个数
- for( i = 0; i < seq->total; i++ )
- {
- CvRect r1 = *(CvRect*)cvGetSeqElem( seq, i );
- int idx = *(int*)cvGetSeqElem( idx_seq, i );
- assert( (unsigned)idx < (unsigned)ncomp );
- comps[idx].neighbors++;
- comps[idx].rect.x += r1.x;
- comps[idx].rect.y += r1.y;
- comps[idx].rect.width += r1.width;
- comps[idx].rect.height += r1.height;
- }
- // calculate average bounding box 计算重心
- for( i = 0; i < ncomp; i++ )
- {
- int n = comps[i].neighbors;
- if( n >= min_neighbors )
- {
- CvAvgComp comp;
- comp.rect.x = (comps[i].rect.x*2 + n)/(2*n);
- comp.rect.y = (comps[i].rect.y*2 + n)/(2*n);
- comp.rect.width = (comps[i].rect.width*2 + n)/(2*n);
- comp.rect.height = (comps[i].rect.height*2 + n)/(2*n);
- comp.neighbors = comps[i].neighbors;
- cvSeqPush( seq2, &comp ); //结果输入到seq2
- }
- }
- // filter out small face rectangles inside large face rectangles 在大的面块中找出小的面块
- for( i = 0; i < seq2->total; i++ ) //在seq2中寻找
- {
- CvAvgComp r1 = *(CvAvgComp*)cvGetSeqElem( seq2, i ); //r1指向结果
- int j, flag = 1;
- for( j = 0; j < seq2->total; j++ )
- {
- CvAvgComp r2 = *(CvAvgComp*)cvGetSeqElem( seq2, j );
- int distance = cvRound( r2.rect.width * 0.2 );
- if( i != j &&
- r1.rect.x >= r2.rect.x – distance &&
- r1.rect.y >= r2.rect.y – distance &&
- r1.rect.x + r1.rect.width <= r2.rect.x + r2.rect.width + distance &&
- r1.rect.y + r1.rect.height <= r2.rect.y + r2.rect.height + distance &&
- (r2.neighbors > MAX( 3, r1.neighbors ) || r1.neighbors < 3) )
- {
- flag = 0;
- break;
- }
- }
- if( flag )
- {
- cvSeqPush( result_seq, &r1 ); //添加r1到返回结果.
- }
- }
- }
- __END__;