//使用样例,其中_cascade为CvHaarClassifierCascade结构,
//cascade为CvHidHaarClassifierCascade结构
for(i = 0; i < _cascade->count; i++ )
{
int j, k, l;
for(j = 0; j < cascade->stage_classifier[i].count;j++ )
{
for(l = 0; l < cascade->stage_classifier[i].classifier[j].count;l++ )
{
CvHaarFeature* feature = &_cascade->stage_classifier[i].classifier[j].haar_feature[l];
CvHidHaarFeature* hidfeature = &cascade->stage_classifier[i].classifier[j].node[l].feature;
}
}
}
*******************************************************************************
typedef struct CvHaarClassifierCascade
{
int flags;
int count; //表示有多少个stage,即多少个stage_classifier
CvSize orig_window_size;
CvSize real_window_size;
double scale;
CvHaarStageClassifier* stage_classifier;
CvHidHaarClassifierCascade* hid_cascade;
} CvHaarClassifierCascade;
在检测时,用的是hid_cascade;下面介绍CvHidHaarClassifierCascade结构。
CvHidHaarClassifierCascade中的stage_classifier指向了stage的首地址,保存着一系列连续的stage。每个stage中包含了一系列的树,每棵树都有一系列的节点。
typedef struct CvHidHaarClassifierCascade
{
int count; //表示有多少个stage,即多少个stage_classifier
int isStumpBased;
int has_tilted_features;
int is_tree;
double inv_window_area;
CvMat sum, sqsum, tilted;
CvHidHaarStageClassifier* stage_classifier;
sqsumtype *pq0, *pq1, *pq2, *pq3;
sumtype *p0, *p1, *p2, *p3;
void** ipp_stages;
} CvHidHaarClassifierCascade;
typedef struct CvHidHaarStageClassifier
{
int count; //表示有多少个树,即多少个classifier
float threshold;
CvHidHaarClassifier* classifier;
Int two_rects;
struct CvHidHaarStageClassifier* next;
struct CvHidHaarStageClassifier* child;
struct CvHidHaarStageClassifier* parent;
} CvHidHaarStageClassifier;
typedef struct CvHidHaarClassifier
{
int count; //表示有多少个树节点,即多少个node
//CvHaarFeature* orig_feature;
CvHidHaarTreeNode* node;
float* alpha;
} CvHidHaarClassifier;
typedef struct CvHidHaarTreeNode
{
CvHidHaarFeature feature;
float threshold; //判断往左还是往右的阈值,大于阈值往左,小于阈值往右
int left;
int right;
} CvHidHaarTreeNode;
typedef struct CvHidHaarFeature
{
struct
{
sumtype *p0, *p1, *p2, *p3;
float weight;
} rect[CV_HAAR_FEATURE_MAX];
} CvHidHaarFeature;
其中CV_HAAR_FEATURE_MAX为3,一个CvHidHaarFeature表示一个Haar特征,即3个或者2个rect,以及rect相应的权重。
下面为HAAR特征检测的具体流程:
一.在计算每个窗口的haar值时,使用CvHidHaarClassifierCascade结构的casecade,因此需要以下步骤。
1. 创建CvHidHaarClassifierCascade结构对应的casecade。
即申请内存,并填写casecade中相关的头信息,如有多少个stage,每个stage下有多少个tree,每个tree下有多少个node,以及相关的阈值等信息。
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特征对应的矩形框的顶点位置也会发生相应的缩放。
二.有了CvHidHaarClassifierCascade结构的casecade,就可以计算每个window对应的stage值了。实际上,在每一个window尺寸上创建好casecade后,就会计算该window大小下所有窗口的stage值,保存满足条件的那些窗口。然后再创建下一个缩放window尺寸上的casecade,并替换掉上一个尺寸的casecade,再计算新window大小下所有窗口的stage值,继续保存满足条件的那些窗口。如此循环,直至缩放的窗口小于样本的尺寸,或者缩放的窗口大于原图的尺寸。其中计算每个固定尺寸窗口的stage值的过程见三中详述。
三.计算每一个window尺寸上所有窗口的stage值。将满足条件的窗口保存下来。这个过程用cvRunHaarClassifierCascadeSum函数判断,当cvRunHaarClassifierCascadeSum返回值大于0,才会保存此时检测的窗口位置,作为备选,参与后面的聚类过程。
1. static int cvRunHaarClassifierCascadeSum ( const CvHaarClassifierCascade* _cascade,CvPoint pt, int start_stage )
其中入参cascade为分类器,pt为检测窗口的左上角的坐标。其中窗口大小已经在外层保存了。start_stage为计算串口haar值的起始stage,若为0,则所有的stage都参与计算。
该函数计算指定窗口中分类器对应的haar值,并根据分类器的结果判定是否满足要求。满足返回1,不满足返回0。
2. cvRunHaarClassifierCascadeSum函数调用icvEvalHidHaarClassifier来计算出每个树对应节点的haar特征值,然后再和该节点的阈值比较,如果小于阈值,就进去左边分支;如果大于阈值,就进入右边分支;直至分支的索引小于等于0,此时得到的alpha为该树的计算结果。
icvEvalHidHaarClassifier函数在计算指定窗口中分类器对应的haar值,使用了积分图。即使用该windown尺寸下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 );
3. 当用icvEvalHidHaarClassifier计算所有树的节点后,再判断所有树的累积和(所有树的alpha之和)是否大于stage阈值,如果大于阈值则返回1,否则返回负值。返回1,则再进行下一个stage计算,直至所有的stage计算完毕,并且每个累积和都大于每个stage相应的阈值,则cvRunHaarClassifierCascadeSum返回1。
这时一个窗口计算完毕,保存此时检测的窗口位置,作为备选,参与后面的聚类过程。
然后平移窗口,重复上述,2,3的步骤,直至窗口移动到图像的右下边界。
四.当所有满足尺寸要求的窗口遍历完毕,并将满足条件的窗口保存完毕后,再对保存的窗口进行聚类,和最小邻域过滤。