OpenCV原则解读HAAR+Adaboost

因为人脸检测项目。用途OpenCV在旧分类中的训练效果。因此该检测方法中所使用的分类归纳。加上自己的一些理解。重印一些好文章记录。



文章http://www.61ic.com/Article/DaVinci/TMS320DM646x/201310/50733.html攻克了下面函数移植到DSP上的一些问题

以下为HAAR特征检測的详细流程:http://blog.csdn.net/nongfu_spring/article/details/38977555


一.在计算每一个窗体的haar值时。使用CvHidHaarClassifierCascade结构的casecade,因此须要下面步骤。

1. 创建CvHidHaarClassifierCascade结构相应的casecade

即申请内存,并填写casecade中相关的头信息。如有多少个stage, 每一个stage下有多少个tree。每一个tree下有多少个node,以及相关的阈值等信息。

该操作调用的函数是icvCreateHidHaarClassifierCascade

2. 填写每一个haar feature相应的积分图中矩形框的指针。

包含casecade指向的积分图的指针sum,很多其它的是相应haar特征相应的矩形框指针以及权重。

每一个haar特征相应着2个或者3个带权重的矩形框。分别用p0,p1,p2,p3指向每一个矩形框的四个顶点在积分图中的相应位置。

另外,这里haar特征相应的矩形框是依据窗体大小变化的。

如样本是20*20,某个haar特征相应的某一个矩形框是4*4,当scanWindow的窗体放大为40*40时,该矩形框也扩大为8*8

全部的矩形框顶点的指针都是基于原图的积分图的。当窗体缩放时,其haar特征相应的矩形框的顶点位置也会发生相应的缩放。

该操作调用的函数是cvSetImageForHaarClassifierCascade


二.有了CvHidHaarClassifierCascade结构的casecade,就能够计算每一个window相应的stage值了。实际上。在每一个window尺寸上创建好casecade后,就会计算该window大小下全部窗体的stage值,保存满足条件的那些窗体。然后再创建下一个缩放window尺寸上的casecade,并替换掉上一个尺寸的casecade,再计算新window大小下全部窗体的stage值。继续保存满足条件的那些窗体。如此循环,直至检測窗体小于样本尺寸,或者检測窗体大于原图尺寸。当中计算每一个固定尺寸窗体的stage值的过程见三中详述。


三.计算每个window尺寸上全部窗体的stage值。将满足条件的窗体保存下来。这个过程用cvRunHaarClassifierCascadeSum函数推断,当cvRunHaarClassifierCascadeSum返回值大于0。才会保存此时检測的窗体位置,作为备选,參与后面的聚类过程。

cvRunHaarClassifierCascadeSum函数调用icvEvalHidHaarClassifier来计算出每一个树相应节点的haar特征值,然后再和该节点的阈值比較,假设小于阈值选择左边分支作为当前node的结果。否则选择右值作为当前node的结果;直至分支的索引小于等于0。此时得到的alpha为该树的计算结果。

当用icvEvalHidHaarClassifier计算全部树的节点后。再推断全部树的累积和(全部树的alpha之和)是否大于stage阈值,假设大于阈值则返回1,否则返回负值。返回1,则再进行下一个stage计算。直至全部的stage计算完成。而且每一个累积和都大于每一个stage对应的阈值。则cvRunHaarClassifierCascadeSum返回1

从上面能够看出,须要比較两次一个是node thresh的比較,一个是stage thresh的比較,比較node thresh时选择left_val或者right_val作为比較的结果。比較stage thresh时将全部的node结果累加起来,若累加结果大于stage thresh则算作通过比較。标记当前窗体是有效窗体。

这时一个窗体计算完成,保存此时检測的窗体位置,作为备选。參与后面的聚类过程。

然后平移窗体,反复上述步骤,直至窗体移动到图像的右下边界。


四.当全部满足尺寸要求的窗体遍历完成,并将满足条件的窗体保存完成后。再对保存的窗体进行聚类,和最小邻域过滤。

cvLoadHaarClassifierCascade

从文件里装载训练好的级联分类器或者从OpenCV中嵌入的分类器数据库中导入

CvHaarClassifierCascade* cvLoadHaarClassifierCascade(
                         const char* directory,
                         CvSize orig_window_size );

directory:训练好的级联分类器的路径

orig_window_size:级联分类器训练中採用的检測目标的尺寸。由于这个信息没有在级联分类器中存储。所以要单独指出,可是在haarcascade_frontalface_alt.xml中有指出size的大小。

函数 cvLoadHaarClassifierCascade 用于从文件里装载训练好的利用哈尔特征的级联分类器,或者从OpenCV中嵌入的分类器数据库中导入。分类器的训练能够应用函数haartraining(具体察看opencv/apps/haartraining),orig_window_size是在训练分类器时就确定好的,改动它并不能改变检測的范围或精度。

须要注意的是,这个函数已经过时了。

如今的目标检測分类器通常存储在 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边缘检測)、CV_HAAR_SCALE_IMAGE(缩放图像检測)、CV_HAAR_FIND_BIGGEST_OBJECT(寻找最大的目标)、CV_HAAR_DO_ROUGH_SEARCH(做粗略搜索)

假设CV_HAAR_DO_CANNY_PRUNING被设定,函数利用Canny边缘检測器来排除一些边缘非常少或者非常多的图像区域。由于这种区域一般不含被检目标。人脸检測中通过设定阈值使用了这个方案,并因此提高了检測速度。当然该标记是在未定义CV_HAAR_SCALE_IMAGE下使用的,也就是说使用缩放检測窗体的形式定义的

假设CV_HAAR_SCALE_IMAGE被设定则在每个scale上缩放图像检測。假设未定义则缩放检測窗体进行检測。当缩放检測窗体检測的时候是不能返回rejectLevels和levelWeights的。

假设CV_HAAR_FIND_BIGGEST_OBJECT被设定。假设没有设定CV_HAAR_SCALE_IMAGE,保存当前检測窗体中面积最大的矩形,无论设定没有设定CV_HAAR_SCALE_IMAGE,最后都输出一个面积最大的矩形(假设检測结果不为空的话),具体分析能够參考cvHaarDetectObjectsForROC

假设CV_HAAR_DO_ROUGH_SEARCH设定了,则最小的缩放比例为0.6。否则为0.4。仅在缩放检測窗体中有效

if( findBiggestObject )

   flags &= ~(CV_HAAR_SCALE_IMAGE|CV_HAAR_DO_CANNY_PRUNING);

从以上代码能够看出,寻找最大目标优先于缩放图像和边缘检測,也就是说假设同一时候定义了以上三项,则以寻找最大目标为准

其次从代码结构上缩放图像在前缩放检測窗体在后。因此假设定义了缩放图像,则不进行边缘检測


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> (比如, 对于视频会议的图像区域).

程序内部调用cvHaarDetectObjectsForROC实现目标检測,返回结果存储在返回值CvSeq中。使用CvAvgComp结构体能够实现CvSeq到Rect的转换

vector<CvAvgComp> vecAvgComp;

Seq<CvAvgComp>(_objects).copyTo(vecAvgComp);

objects.resize(vecAvgComp.size());

std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect());

也能够使用cvGetSeqElem实现数据的读取

CvRect face_rect = *(CvRect*)cvGetSeqElem( _objects, i, 0 );

cvHaarDetectObjectsForROC
运行目标检測。能够返回rejectLevels和levelWeights
CvSeq* cvHaarDetectObjectsForROC( const CvArr* _img,
                     CvHaarClassifierCascade* cascade, CvMemStorage* storage,
                     std::vector<int>& rejectLevels, std::vector<double>& levelWeights,
                     double scaleFactor, int minNeighbors, int flags,
                     CvSize minSize, CvSize maxSize, bool outputRejectLevels );

在该函数cvHaarDetectObjectsForROC的内部调用了下面函数进行目标的检測,须要注意的是假设定义了多尺度检測也就是參数flags中设置了CV_HAAR_SCALE_IMAGE。则在兴许检測中在每个scale上使用cvSetImagesForHaarClassifierCascade设置图像(scale=1.0),使用HaarDetectObjects_ScaleImage_Invoker进行并行运算(能够返回rejectLevels和levelWeights)。而假设没有设置多尺度检測则仅仅在当前尺度上使用cvSetImagesForHaarClassifierCascade设置图像(scale*=scaleFactor,须要注意的是当设置了CV_HAAR_FIND_BIGGEST_OBJECT则scaleFactor=1/scaleFactor。而且scale从最大開始不断减小到1。否则scale从1開始不断增大到最大),使用HaarDetectObjects_ScaleCascade_Invoker进行并行运算(不能够返回rejectLevels和levelWeights)


无论是使用缩放检測图像,还是使用缩放检測窗体,检測完成后对窗体进行合并
    if( minNeighbors != 0 || findBiggestObject )
    {
        if( outputRejectLevels )
            groupRectangles(rectList, rejectLevels, levelWeights, minNeighbors, GROUP_EPS ); // 关于group的方法參考《OpenCV函数解读之groupRectangles
        else
            groupRectangles(rectList, rweights, std::max(minNeighbors, 1), GROUP_EPS);
    }
    else
        rweights.resize(rectList.size(),0);

从上面的代码能够看出假设定义了CV_HAAR_FIND_BIGGEST_OBJECT,而且输出outputRejectLevels为true的话是肯定没有结果的,由于假设定义了CV_HAAR_FIND_BIGGEST_OBJECT。是不能进行缩放图像检測的(CV_HAAR_SCALE_IMAGE被置为0)。不进行缩放图像检測也就不能返回rejectLevels和levelWeights,因此假设想返回rejectLevels和levelWeights。则必须不能定义CV_HAAR_FIND_BIGGEST_OBJECT而且必须定义CV_HAAR_SCALE_IMAGE。同一时候outputRejectLevels必须为true。当然假设定义不恰当非常可能返回值中存在object可是不存在rejectLevels和levelWeights。


icvCreateHidHaarClassifierCascade
创建hid haar级联分类器
/* create more efficient internal representation of haar classifier cascade */
static CvHidHaarClassifierCascade* icvCreateHidHaarClassifierCascade(CvHaarClassifierCascade* cascade);

创建隐形HAAR级联分类器的原因是提高HAAR级联分类器内部处理的效率,CvHidHaarClassifierCascadeCvHaarClassifierCascade结构是类似的,仅仅只是使用指针将stage联系起来,參考《OpenCV结构解读之CvHaarClassifierCascade等
icvCreateHidHaarClassifierCascade函数中,分为下面几步将cascade中的stage_classifier与hid_cascade联系起来:
1) 遍历stage_classifier。统计stage、tree以及node总数,查看是否存在倾斜特征,检查每一个rect是否超出检測窗体界限
2) 通过上述得到的数据初始化CvHidHaarClassifierCascade 指针对象,其数据结构能够參考《OpenCV结构解读之CvHaarClassifierCascade等

疑问:为什么拷贝了全部的联系,可是没有拷贝rect呢?
原因:针对不同的检測窗体如24*24,48*48,检測窗体的尺寸可能是不同的,所以rect的缩放比例也是不同的。所以每次运算的时候须要依据当前检測窗体大小来寻找不同的的rect的位置    
            for( l = 0; l < node_count; l++ )
            {
                CvHidHaarTreeNode* node = hid_classifier->node + l;
                CvHaarFeature* feature = classifier->haar_feature + l;
                memset( node, -1, sizeof(*node) );
                node->threshold = classifier->threshold[l];
                node->left = classifier->left[l];
                node->right = classifier->right[l];
                // 假设第2个rect不存在的话则将隐形的feature置0。否则将变量tree_rects置为0
                if( fabs(feature->rect[2].weight) < DBL_EPSILON ||
                    feature->rect[2].r.width == 0 ||
                    feature->rect[2].r.height == 0 )
                    memset( &(node->feature.rect[2]), 0, sizeof(node->feature.rect[2]) );
                else
                    hid_stage_classifier->two_rects = 0;
            }
从上面的代码能够看出。将node中的全部数据赋值为-1,当仅仅存在两个rect的时候,将node的第三个rect赋值为0,


cvSetImagesForHaarClassifierCascade

通过缩放比例为隐藏的cascade(hidden cascade)指定积分图像、平方和图像与倾斜和图像、特征矩形

void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                          const CvArr* sum, const CvArr* sqsum,
                                          const CvArr* tilted_sum, double scale );

cascade:Harr 分类器级联(Haar classifier cascade)

sum:32-比特,单通道图像的积分图像(Integral (sum) 单通道 image of 32-比特 integer format). 这幅图像以及随后的两幅用于对高速特征的评价和亮度/对照度的归一化。 它们都能够利用函数 cvIntegral从8-比特或浮点数 单通道的输入图像中得到。

sqsum:单通道64比特图像的平方和图像

tilted_sum:单通道32比特整数格式的图像的倾斜和图像(Tilted sum)。假设存在倾斜特征该參数有不为NULL

以上三个參数:积分图像,平方和图像以及倾斜和是通过cvIntergral函数计算得到的,每个缩放比例下计算一次。

scale:cascade的窗体比例. 假设 scale=1, 就仅仅用原始窗体尺寸检測 (仅仅检測相同尺寸大小的目标物体) - 原始窗体尺寸在函数cvLoadHaarClassifierCascade中定义 (在 "<default_face_cascade>"中缺省为24x24), 当对图像缩放时scale设置为1.0,当对检測窗体缩放时设置为scale*scaleFactor。



cvRunHaarClassifierCascade

在给定位置的图像中执行cascade of boosted classifier。在HaarDetectObjects_ScaleCascade_Invoker中调用,也就是在缩放检測窗体检測时调用该函数

该函数内部调用了函数cvRunHaarClassifierCascadeSum


int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                CvPoint pt, int start_stage=0 );


cvRunHaarClassifierCascadeSum

在给定位置的图像中执行cascade of boosted classifier,在HaarDetectObjects_ScaleImage_Invoker中调用。也就是在缩放检測图像检測时调用该函数

static int

cvRunHaarClassifierCascadeSum( const CvHaarClassifierCascade* _cascade,

                               CvPoint pt, double& stage_sum, int start_stage );

cascade:Haar 级联分类器

pt:待检測区域的左上角坐标。待检測区域大小为原始窗体尺寸乘以当前设定的比例系数。当前窗体尺寸能够通过cvGetHaarClassifierCascadeWindowSize又一次得到

stage_sum:权重之和

start_stage:级联层的初始下标值(从0開始计数)。函数假定前面全部每层的分类器都已通过。这个特征通过函数cvHaarDetectObjects内部调用,用于更好的处理器快速缓冲存储器。

函数 cvRunHaarClassifierCascade 用于对单幅图片的检測。在函数调用前首先利用 cvSetImagesForHaarClassifierCascade设定积分图和合适的比例系数 (=> 窗体尺寸)。当分析的矩形框所有通过级联分类器从start_stage開始的每一层时(当前stage的alpha总和大于当前stage的threshold),返回正值(1)(这是一个候选目标),否则返回0或负值。

函数中依据当前分类器是否为tree、是否仅仅有一个node来分为三种不同的计算方法。



icvEvalHidHaarClassifier

CV_INLINE

double icvEvalHidHaarClassifier( CvHidHaarClassifier* classifier,

                                 double variance_norm_factor,

                                 size_t p_offset );


icvEvalHidHaarClassifier函数在计算指定窗体中分类器对应的haar值。使用了积分图。

即使用该window尺寸下casecade中全部haar特征的p0,p1,p2,p3。每移动一次窗体。p0,p1,p2,p3指针移动对应的位置,再计算(*p0+*p3-*p1-*p2),其值为图像中矩形框位置的灰度和,将其乘以对应的权重,就可以得到haar特征值。

以下为对应的代码:

do

{

CvHidHaarTreeNode* node = classifier->node + idx;

double t = node->threshold * variance_norm_factor;

double sum = calc_sum(node->feature.rect[0],p_offset) * node->feature.rect[0].weight;

sum += calc_sum(node->feature.rect[1],p_offset) * node->feature.rect[1].weight;

if( node->feature.rect[2].p0 )

sum += calc_sum(node->feature.rect[2],p_offset) * node->feature.rect[2].weight;

idx = sum < t ?

node->left : node->right;

}while( idx > 0 );






版权声明:本文博主原创文章,博客,未经同意不得转载。

posted @ 2015-09-28 08:04  blfshiye  阅读(408)  评论(0编辑  收藏  举报