traincascade中不同特征的生成
traincascade和haartrainning的主要区别:
1. haartrainning只能使用haar特征,而traincascade可以使用haar,lbp,hog特征,并且还容易扩展其他的特征。
2. traincascade的组织结构相对清晰,即先根据指定的特征,生成相应的特征,然后使用boost的方法依次训练处每个stage的树。
3. traincascade中使用CvFeatureEvaluator::init函数完成了各种特征的初始化。该函数调用generateFeatures()从而生成各个最初的特征。当我们使用不同的特征时,会重载generateFeatures()函数,从而生成相应的特征。
(1)haar特征,CvHaarEvaluator::generateFeatures()
void CvHaarEvaluator::generateFeatures()
{
int mode = ((const CvHaarFeatureParams*)
((CvFeatureParams*)featureParams))->mode;
int offset = winSize.width +1;
for( int x = 0; x < winSize.width; x++ ) {
for( int y = 0; y < winSize.height; y++ ) {
for( int dx = 1; dx <= winSize.width; dx++ ) {
for( int dy = 1; dy <=winSize.height; dy++ ) {
// haar_x2
if ((x+dx*2 <=winSize.width)&&(y+dy <= winSize.height)) {
features.push_back(Feature( offset, false,
x, y, dx*2, dy, -1,
x+dx, y, dx , dy, +2 ) );
}
// haar_y2
if ((x+dx <=winSize.width)&&(y+dy*2 <= winSize.height)) {
features.push_back(Feature( offset, false,
x, y, dx, dy*2, -1,
x, y+dy, dx, dy, +2 ) );
}
// haar_x3
if ((x+dx*3 <=winSize.width)&&(y+dy <= winSize.height)) {
features.push_back(Feature( offset, false,
x, y, dx*3, dy, -1,
x+dx,y, dx , dy, +3 ) );
}
// haar_y3
if ((x+dx <=winSize.width)&&(y+dy*3 <= winSize.height)){
features.push_back(Feature( offset, false,
x,y, dx, dy*3, -1,
x, y+dy, dx, dy, +3 ) );
}
if( mode !=CvHaarFeatureParams::BASIC ) {
// haar_x4
if ((x+dx*4<=winSize.width)&&(y+dy<= winSize.height)) {
features.push_back(Feature( offset, false,
x, y, dx*4, dy, -1,
x+dx, y, dx*2, dy, +2 ) );
}
// haar_y4
if ((x+dx<=winSize.width)&&(y+dy*4<=winSize.height)) {
features.push_back(Feature( offset, false,
x, y, dx,dy*4, -1,
x, y+dy, dx, dy*2, +2 ) );
}
}
// x2_y2
if((x+dx*2<=winSize.width)&&(y+dy*2<=winSize.height)) {
features.push_back( Feature( offset, false,
x, y, dx*2, dy*2, -1,
x, y, dx, dy, +2,
x+dx, y+dy, dx, dy, +2 ) );
}
if (mode !=CvHaarFeatureParams::BASIC) {
if((x+dx*3<=winSize.width)&&(y+dy*3<=winSize.height)){
features.push_back(Feature( offset, false,
x , y , dx*3, dy*3, -1,
x+dx, y+dy, dx , dy ,+9) );
}
}
if (mode ==CvHaarFeatureParams::ALL) {
// tilted haar_x2
if( (x+2*dx <= winSize.width) && (y+2*dx+dy <= winSize.height)&& (x-dy>= 0) ) {
features.push_back(Feature( offset, true,
x, y, dx*2, dy, -1,
x, y, dx, dy, +2 ) );
}
// tilted haar_y2
if( (x+dx <= winSize.width) && (y+dx+2*dy <= winSize.height)&& (x-2*dy>= 0) ) {
features.push_back( Feature( offset,true,
x, y, dx, 2*dy, -1,
x, y, dx, dy, +2 ) );
}
// tilted haar_x3
if ( (x+3*dx <= winSize.width)&& (y+3*dx+dy <= winSize.height) && (x-dy>= 0) )
{
features.push_back(Feature( offset, true,
x, y, dx*3, dy, -1,
x+dx, y+dx, dx, dy, +3 ) );
}
// tilted haar_y3
if( (x+dx <= winSize.width) && (y+dx+3*dy <= winSize.height)&& (x-3*dy>= 0) )
{
features.push_back(Feature( offset, true,
x, y, dx, 3*dy, -1,
x-dy, y+dy, dx, dy, +3 ) );
}
// tilted haar_x4
if( (x+4*dx <= winSize.width) && (y+4*dx+dy <= winSize.height)&& (x-dy>= 0) )
{
features.push_back(Feature( offset, true,
x, y, dx*4, dy, -1,
x+dx, y+dx, dx*2, dy, +2 ) );
}
// tilted haar_y4
if( (x+dx <= winSize.width) && (y+dx+4*dy <= winSize.height)&& (x-4*dy>= 0) )
{
features.push_back(Feature( offset, true,
x, y, dx, 4*dy, -1,
x-dy, y+dy, dx, 2*dy, +2 ) );
}
}
}
}
}
}
numFeatures = (int)features.size();
}
(2)lbp特征,void CvLBPEvaluator::generateFeatures()
CvLBPEvaluator类的定义如下:
class CvLBPEvaluator : public CvFeatureEvaluator
{
public:
virtual~CvLBPEvaluator() {}
virtual voidinit(const CvFeatureParams *_featureParams,
int_maxSampleCount, Size _winSize );
virtual voidsetImage(const Mat& img, uchar clsLabel, int idx);
virtual floatoperator()(int featureIdx, int sampleIdx) const
{ return(float)features[featureIdx].calc( sum, sampleIdx); }
virtual voidwriteFeatures(FileStorage &fs,const Mat& featureMap);
protected:
virtual voidgenerateFeatures();
class Feature
{
public:
Feature();
Feature( intoffset, int x, int y, int _block_w, int _block_h );
uchar calc( constMat& _sum, size_t y ) const;
void write(FileStorage &fs ) const;
Rect rect;
int p[16];
};
vector<Feature>features;
Mat sum;
};
void CvLBPEvaluator::generateFeatures()
{
int offset =winSize.width + 1;
for( int x = 0; x <winSize.width; x++ )
for( int y = 0; y< winSize.height; y++ )
for( int w = 1; w <= winSize.width / 3;w++ )
for( int h= 1; h <= winSize.height / 3; h++ )
if((x+3*w<=winSize.width)&&(y+3*h<= winSize.height))
features.push_back(Feature(offset,x,y,w,h));
numFeatures =(int)features.size();
}
由于CvLBPEvaluator类继承了Feature类,而Feature类中的int p[16]用来保存计算lbp特征的九宫格中16个顶点的位置偏移。这16个具体位置如下:
其中不同的lbp特征是通过调整上面九宫格的左上角位置和每个小格的长宽来实现的。在generateFeatures()中的四个for中,前两个for循环用来调整九宫格左上角的偏移位置,后面两个for循环用来调整九宫格中每个小格的长和宽。然后计算每一个LBP九宫格下的p[16]。p[16]的计算如下:
CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int_blockWidth, int _blockHeight )
{
Rect tr = rect =cvRect(x, y, _blockWidth, _blockHeight);
CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr,offset )
tr.x += 2*rect.width;
CV_SUM_OFFSETS( p[2],p[3], p[6], p[7], tr, offset )
tr.y +=2*rect.height;
CV_SUM_OFFSETS( p[10],p[11], p[14], p[15], tr, offset )
tr.x -= 2*rect.width;
CV_SUM_OFFSETS( p[8],p[9], p[12], p[13], tr, offset )
}
#define CV_SUM_OFFSETS( p0, p1, p2, p3, rect, step ) \
/* (x, y) */ \
(p0) = (rect).x + (step) * (rect).y; \
/* (x + w, y) */ \
(p1) = (rect).x +(rect).width + (step) * (rect).y; \
/* (x + w, y) */ \
(p2) = (rect).x +(step) * ((rect).y + (rect).height); \
/* (x + w, y + h)*/ \
(p3) = (rect).x + (rect).width + (step) *((rect).y + (rect).height);
lbp特征最终是通过一个二进制数表示的。以下面的九宫格为例说明。
将九宫格中0~8的小格分别与4小格的灰度比较,如果大于4的灰度,则对应位置的值为1; 如果小于4的灰度,则对应位置的值为0。然后按照顺时针或者逆时针的顺序,将其值排列起来。
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
假设下图是0~8的小格分别与4小格的灰度的比较结果,则对应的lbp值为:
01000111(按照0->1->2->5->8->7->6->3的顺时针顺序)
0 |
1 |
0 |
1 |
4 |
0 |
1 |
1 |
0 |
当然,顺序不同,lbp值也不同,但是只要所有的lbp计算时都使用同样的顺序就ok。
Opencv中使用的就是上面的顺序,即如下图的箭头所示:
0号块是8位数值中的最高位,即为128或者0;
1号块是8位数值中的次高位,即为64或者0;
2号块是8位数值中的第5位,即为32或者0;
5号块是8位数值中的第4位,即为16或者0;
8号块是8位数值中的第3位,即为8或者0;
7号块是8位数值中的第2位,即为4或者0;
6号块是8位数值中的第1位,即为2或者0;
3号块是8位数值中的第0位,即为1或者0;
下面是opencv中的lbp特征值的具体计算,即通过p[16]的偏移,计算出每个小块的灰度值。下面的_sum对应着图像的积分图,所以通过四个顶点的计算可以求出对应块的值。
inline uchar CvLBPEvaluator::Feature::calc(const Mat &_sum,size_t y) const
{
const int* sum =_sum.ptr<int>((int)y);
int cval = sum[p[5]] -sum[p[6]] - sum[p[9]] + sum[p[10]];
return (uchar)(
(sum[p[0]]-sum[p[1]]-sum[p[4]]+sum[p[5]] >= cval?128:0)| //0
(sum[p[1]]-sum[p[2]]-sum[p[5]]+sum[p[6]] >= cval? 64:0)| //1
(sum[p[2]]-sum[p[3]]-sum[p[6]]+sum[p[7]] >= cval? 32:0)| //2
(sum[p[6]]-sum[p[7]]-sum[p[10]]+sum[p[11]]>=cval? 16:0)| //5
(sum[p[10]]-sum[p[11]]-sum[p[14]]+sum[p[15]]>=cval?8:0)| //8
(sum[p[9]]-sum[p[10]]-sum[p[13]]+sum[p[14]]>=cval? 4:0)| //7
(sum[p[8]]-sum[p[9]]-sum[p[12]]+sum[p[13]] >=cval? 2:0)| //6
(sum[p[4]]-sum[p[5]]-sum[p[8]]+sum[p[9]]>= cval ? 1:0)); //3
}