opencv 中将级联分类器数据存储为xml文件,读取时非常复杂,为了降低复杂度
我将haar分类器数据按最简单的格式存储,只包含纯的数据,不含任何其它冗余信息
存储的顺序就是按cascade结构体中个成员的定义顺序来存储的。
具体的存储代码: int SaveCascade(CvHaarClassifierCascade *cascade)函数
// testtest.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <stdio.h> #include <iostream> #include <vector> #include "hongdingyi.h" #include <algorithm> #include <opencv2/opencv.hpp> using namespace cv; using namespace std; #pragma comment(lib,"opencv_objdetect249d.lib") int SaveCascade(CvHaarClassifierCascade *cascade); int main( int argc,char** argv ) { CvHaarClassifierCascade* cascade = 0; cvReleaseHaarClassifierCascade(&cascade);//这句一定要加,否则会报错,const char *cascade_name="D:\\连通域、填冲、细化、删除、边缘检测\\testtest\\testtest\\haarcascade_frontalface_alt.xml"; cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name ); SaveCascade(cascade); return 0; } int SaveCascade(CvHaarClassifierCascade *cascade) { FILE *haar; int i,j,k,m,tempi; double tempd; float tempf; if ((haar = fopen("haar_feature.txt","wb"))==NULL) { return -1; } tempi=cascade->flags; fwrite(&tempi,sizeof(int),1,haar); tempi =cascade->count; fwrite(&tempi,haar); tempi =cascade->orig_window_size.width; fwrite(&tempi,haar); tempi =cascade->orig_window_size.height; fwrite(&tempi,haar); tempi =cascade->real_window_size.width; fwrite(&tempi,haar); tempi =cascade->real_window_size.height; fwrite(&tempi,haar); tempd =cascade->scale; fwrite(&tempd,sizeof(double),haar); for (int i=0;i<cascade->count;i++) { tempi = cascade->stage_classifier[i].count; fwrite(&tempi,haar); tempf = cascade->stage_classifier[i].threshold; fwrite(&tempf,sizeof(float),haar); for (int j=0;j<cascade->stage_classifier[i].count;j++) { tempi =cascade->stage_classifier[i].classifier[j].count; fwrite(&tempi,haar); printf("cascade->stage_classifier[%d].classifier[%d].cout=%d\n",i,cascade->stage_classifier[i].classifier[j].count); for (int k=0;k<cascade->stage_classifier[i].classifier[j].count;k++) { tempi =cascade->stage_classifier[i].classifier[j].haar_feature[k].tilted; fwrite(&tempi,haar); for (int m=0;m<3;m++) { tempi =cascade->stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.x; fwrite(&tempi,haar); tempi =cascade->stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.y; fwrite(&tempi,haar); tempi =cascade->stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.width; fwrite(&tempi,haar); tempi =cascade->stage_classifier[i].classifier[j].haar_feature[k].rect[m].r.height; fwrite(&tempi,haar); tempf =cascade->stage_classifier[i].classifier[j].haar_feature[k].rect[m].weight; fwrite(&tempf,haar); } } tempf =*(cascade->stage_classifier[i].classifier[j].threshold); fwrite(&tempf,haar); tempf =*(cascade->stage_classifier[i].classifier[j].threshold+1); fwrite(&tempf,haar); tempi =*(cascade->stage_classifier[i].classifier[j].left); fwrite(&tempi,haar); tempi =*(cascade->stage_classifier[i].classifier[j].left+1); fwrite(&tempi,haar); tempi =*(cascade->stage_classifier[i].classifier[j].right); fwrite(&tempi,haar); tempi =*(cascade->stage_classifier[i].classifier[j].right+1); fwrite(&tempi,haar); tempf =*(cascade->stage_classifier[i].classifier[j].alpha); fwrite(&tempf,haar); tempf =*(cascade->stage_classifier[i].classifier[j].alpha+1); fwrite(&tempf,haar); } tempi =cascade->stage_classifier[i].next; fwrite(&tempi,haar); tempi =cascade->stage_classifier[i].child; fwrite(&tempi,haar); tempi =cascade->stage_classifier[i].parent; fwrite(&tempi,haar); } fclose(haar); return 0; }
注意: cvReleaseHaarClassifierCascade(&cascade);//这句一定要加,否则会报错,如下图:
对OpenCV中Haar特征CvHaarClassifierCascade等结构理解
在调用OpenCV中的级联分类器对目标进行分类时,都会将一个训练好的分类器(一个训练好的.xml文件)读入到一个CvHaarClassifierCascade结构中,如下:
CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( "haarcascade_frontalface_alt.xml",0,128)">0 );
那么这个CvHaarClassifierCascade结构体里面的内容都有哪些呢?
typedef struct CvHaarClassifierCascade { int flags; /* 标志位 */ int count; /* 分级分类器中强分类器的数量 */ CvSize orig_window_size; /* 训练中原始目标的大小 */ /* these two parameters are set by cvSetImagesForHaarClassifierCascade */ CvSize real_window_size; /* 待检测物体的大小 */ double scale; /* Haar块缩放的尺寸 */ CvHaarStageClassifier* stage_classifier; /* 定义强分类器数组 */ CvHidHaarClassifierCascade* hid_cascade; }CvHaarClassifierCascade;
第一个flags,还不是很清楚,在debug模式下,flags=1112539136(好吧,这个值很诡异),我也不是很清楚
第二个count,表示整个分级分类器中强分类器的数量,即最后参与级联的强分类器的个数
第三个orig_window_size,表示的是在训练时用的正样本的尺寸,OpenCV中的尺寸是20x20
第四个和第五个,注释中说了,这两个参数需要自己设置,具体每个参数看注释
第六个stage_classifier,是强分类器指针,指向一个强分类器数组,之前的count是多少,那么此处的强分类器就有多少
最后一个hid_cascade,还不是很清楚
下面来看上面第六个参数的强分类器结构体
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;
第一个count,表示该强分类器中,弱分类器的数量,即该强分类器由多少个弱分类器组成
第二个threshold,叠加分类器的阈值(好吧。。这个我也不知道)
第三个classifier,是一个指针,指向的是一个弱分类器数组,之前的count是多少,此处的弱分类器就有多少
后面3个都不清楚。。。(望知道的网友给予帮助)
下面是弱分类器的结构
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). 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. */ CvHaarFeature* haar_feature; float* threshold; int* left; int* right; float* alpha; }CvHaarClassifier;
第一个count,在opencv里,发现始终都是1,自己想了想,因为这个结构体记录的是一个弱分类器,自然弱分类器的个数就是1了。
第二个haar_feature,也是一个指针,指向一个(因为count是1)特征结构体CvHaarFeature,这个结构体中记录的内容是弱分类器的类型(包括该haar-like特征的位置,大小,以及种类,这个结构体会在下面给出,再细说)
第三个threshold,就是那个判别函数:h(x,f,p,theta) = (p*f(x) < p*theta ? 1 : 0),中的阈值theta
第四个left,第五个right不是很清楚(求知道的同学详解啊~~~~)
第六个alpha,就是这个弱分类器的权重(每一个强分类器都是由多个弱分类器按照各自的权重进行表决,而得到的)
特征的结构体如下
#define CV_HAAR_FEATURE_MAX 3 // 一个Haar特征由2~3个具有相应权重的矩形组成 typedef struct CvHaarFeature { int tilted; // 0 means up-right feature,1 means 45-rotated feature struct { CvRect r; float weight; } 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. }CvHaarFeature;
第一个参数titled,0表示该特征是标准的haar-like特征,1表示旋转45°后的特征
标准的haar-like特征如下:
而旋转45°后的特征如下:
第二个参数是个结构体数组,每个结构体中包括一个矩形和一个权重,这个数组的大小是CV_HAAR_FEATURE_MAX(3)(注释中说:此处可能有2~3个矩形,这里的矩形等看了下面的解释就知道了),这个矩形和权重有什么用呢?
在debug模式下,查看第一个弱分类器数组内的元素
第一个元素:
1 rect[0].r.x = 3 2 rect[0].r.y = 7 3 rect[0].r.width = 14 4 rect[0].r.height = 4 5 rect[0].weight = -1
第二个元素:
1].r.x = 1].r.y = 9 1].r.width = 1].r.height = 2 1].weight = 2
第三个元素则全都是0
这么看这些坐标,并不是很清楚,我们可以画个图:
由图可见,第一个矩形表示的是A+B区域,第二个矩形表示的是B区域。
此时再看一看每个元素的权重weight,结合积分图的概念,发现第一个矩形的积分图乘以其权重加上第二个积分图乘以其权重,恰好得到下述结果:
(A+B)*(-1)+B*2=B-A
看到这个公式,大家都不会陌生,这正式VJ论文中给出的众多haar-like模板中的其中一个模板的计算方法(此处不知如何表达,大家将就,看懂就行)
我们继续考察第二个弱分类器的特征部分,其特征参数如下:
rect[0].r.x = 1,rect[0].r.y = 2 rect[0].r.width = 18,rect[0].r.height = 4 rect[0].weight = -1 rect[1].r.x = 7,rect[1].r.y = 2 rect[1].r.width = 6,rect[1].r.height = 4 rect[1].weight = 3
数组的第三个元素依然都是是0,对其绘图:
第一块矩形区域是A+B+C,第二块矩形区域是B,积分图乘以权重,再相加,可得:
(A+B+C)*(-1)+B*3 = 2*B-A-C
也是haar-like特征模板之一(此处不知如何表达,大家将就,看懂就行)
刚刚找了好久,找到一个第三个元素权重不为0的,该数组三个元素如下:
rect[0].r.x = 0,rect[0].r.y = 2 rect[0].r.width = 20,rect[0].r.height = 6; rect[0].weight = -1 rect[1].r.x = 10,rect[1].r.y = 2 rect[1].r.width = 10,rect[1].r.height = 3; rect[1].weight = 2 rect[2].r.x = 0,rect[2].r.y = 5 rect[2].r.width = 10,rect[2].r.height = 3; rect[2].weight = 2
绘图可得:
将每个矩形乘以相应的权重,相加可得:
(A+B+C+D)*(-1)+2*B + 2*C = B+C-(A+D)