Haar特征
Haar特征原理综述
Haar特征是一种反映图像的灰度变化的,像素分模块求差值的一种特征。它分为三类:边缘特征、线性特征、中心特征和对角线特征。用黑白两种矩形框组合成特征模板,在特征模板内用 黑色矩形像素和 减去 白色矩形像素和来表示这个模版的特征值。例如:脸部的一些特征能由矩形模块差值特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述在特定方向(水平、垂直、对角)上有明显像素模块梯度变化的图像结构。
如上图A、B、D模块的图像Haar特征为:v=Sum白-Sum黑
C 模块的图像Haar特征为:v=Sum白(左)+Sum白(右)-2*Sum黑
这里要保证白色矩形模块中的像素与黑色矩形的模块的像素数相同,所以乘2
对于一幅图像来说,可以通过通过改变特征模板的大小和位置,可穷举出大量的特征来表示一幅图像。上图的特征模板称为“特征原型”;特征原型在图像子窗口中扩展(平移伸缩)得到的特征称为“矩形特征”;矩形特征的值称为“特征值”。例如在24*24大小的图像中可以以坐标(0,0)开始宽为20高为20矩形模版计算上图A特征,也可以以坐标(0,2)开始宽为20高为20矩形模版计算上图A特征,也可以以坐标(0,0)开始宽为22高为22矩形模版计算上图A特征,这样矩形特征值随着类别、大小和位置的变化,使得很小的一幅很小的图像含有非常多的矩形特征。矩形特征值是矩形模版类别、矩形位置和矩形大小这三个因素的函数。
Haar特征计算方法
首先介绍下积分图:如下述代码所示,是opencv中存储图像积分图的结构体,可以看到有两种积分图sum积分图,titled sum积分图。sum积分图用来计算一般的垂直或水平的矩形特征,如下面的(a)、(b)、(c)、(d)。。。,而titled sum积分图用来计算倾斜45度的积分图,如下面的(1c)、(1d)、(2c)。。。
/* Prepared for training samples */typedef struct CvHaarTrainingData { CvSize winsize; /* training image size */ int maxnum; /* maximum number of samples */ CvMat sum; /* sum images (each row represents image) */ CvMat tilted; /* tilted sum images (each row represents image) */ CvMat normfactor; /* normalization factor */ CvMat cls; /* classes. 1.0 - object, 0.0 - background */ CvMat weights; /* weights */ CvMat* valcache; /* precalculated feature values (CV_32FC1) */ CvMat* idxcache; /* presorted indices (CV_IDX_MAT_TYPE) */} CvHaarTrainigData;
sum积分图主要的思想是将图像从起点开始到各个点所形成的矩形区域像素之和作为一个数组的元素保存在内存中,当要计算某个区域的像素和时可以直接索引数组的元素,不用重新计算这个区域的像素和。例如:对一个X*Y图像它的积分图也是一个X*Y的元素为int型的积分图,对应积分图中(x,y)位置值表示就是原图像矩形框[(0,0), (x,y)]内所有像素的和。如下图所示:
用公式表示:
其中p(i,j)表示原图在(i,j)位置像素值,f(x,y)表示积分图在(x,y)位置值
积分图能够在多种尺度下,使用相同的时间(常数时间)来计算不同的特征,因此大大提高了检测速度。如下图所示图片不位置不同大小矩形框的两个A特征,他们的计算方式分别为:
[f(x,y)+f(i,j)-f(s,t)-f(l,m)]-[f(s,t)+f(h,k)-f(i,j)-f(n,o)]
[f(X,Y])+f(I,J)-f(S,T)-f(L,M)]-[f(S,T)+f(H,K)-f(I,J)-f(N,O)]
可见两个不同大小的特征计算量却一样!!!
titled sum类积分图计算的是45°旋角的矩形特征。为了便于计算45°旋角的矩形特征,我们定义titled sum类积分图中(x,y)点值为RSAT(x,y),它表示原始图像中(x,y)左上角45°区域和左下角45°区域的像素和。
用公式表示如下:
对于每一点的积分图计算方法有如下递推公式 ,来节约性能,减少生重复计算:
而计算矩阵特征的特征值,是位于十字行矩形RSAT(x,y)之差。如下图的矩形特征为:
[RSAT(m,n)-RSAT(i,j)-RSAT(k,l)+RSAT(s,t)]-[RSAT(o,p)-RSAT(m,n)-RSAT(x,y)+RSAT(k,l)]
同理,该积分图也能够在多种尺度下,使用相同的时间(常数时间)来计算不同的45度矩形特征。
Haar特征对应的opencv源码
Viola提出的haar特征:
Lienhart等牛们提出的Haar-like特征:
opencv源码中负责创建几种haar特征的是函数icvCreateIntHaarFeatures, 在进入分类器训练函数后执行该函数,它根据大小为winsize的图,计算所有HaarFeature的rect的顶点坐标来确定特征并将它存入CvIntHaarFeatures结构体数组中。后续针对不同的图像只需用这些矩形框的顶点来获得积分图在该点的值就可以计算也图像该特征的具体值了。symmetric为0时表示创建所有特征,为1时表示目标图形是垂直对称的所以只需创建所有中心在左半边部位的特征。mode==0表示Viola提出的原始矩形特征,mode==1表示所有垂直的haar特征,mode==2表示所有特征
具体每种特征的名称如下图::
下面在源码中通过注释具体分析下代码的逻辑::::
/* * icvCreateIntHaarFeatures * * Create internal representation of haar features * * mode: * 0 - BASIC = Viola * 1 - CORE = All upright * 2 - ALL = All features */static CvIntHaarFeatures* icvCreateIntHaarFeatures( CvSize winsize, int mode, int symmetric ) { CvIntHaarFeatures* features = NULL; CvTHaarFeature haarFeature; /*内存存储器是一个可用来存储诸如序列,轮廓,图形,子划分等动态增长数据 结构的底层结构。 它是由一系列以同等大小的内存块构成,呈列表型*/ CvMemStorage* storage = NULL; CvSeq* seq = NULL; CvSeqWriter writer; int s0 = 36; /* minimum total area size of basic haar feature */ int s1 = 12; /* minimum total area size of tilted haar features 2 */ int s2 = 18; /* minimum total area size of tilted haar features 3 */ int s3 = 24; /* minimum total area size of tilted haar features 4 */ int x = 0; int y = 0; int dx = 0; int dy = 0; float factor = 1.0F; factor = ((float) winsize.width) * winsize.height / (24 * 24);#if 0 s0 = (int) (s0 * factor); s1 = (int) (s1 * factor); s2 = (int) (s2 * factor); s3 = (int) (s3 * factor);#else s0 = 1; s1 = 1; s2 = 1; s3 = 1;#endif /* CV_VECTOR_CREATE( vec, CvIntHaarFeature, size, maxsize ) */ storage = cvCreateMemStorage();/*创建一个内存存储器*/ cvStartWriteSeq( 0, sizeof( CvSeq ), sizeof( haarFeature ), storage, &writer ); /*定义了writer工具每次写入数据的大小,以及写入到哪个内存存储器。 在之后调用 CV_WRITE_SEQ_ELEM( haarFeature, writer )时就可以自动将一个haarFeature类型的数据写入内存存储器中*/ for( x = 0; x < winsize.width; x++ ) { for( y = 0; y < winsize.height; y++ )/*x,y确定了特征矩形的左上角坐标,所以这是遍历整个窗口*/ { for( dx = 1; dx <= winsize.width; dx++ ) {/*dx,dy确定了特征矩形的大小,所以这是遍历不矩形大小的特征*/ for( dy = 1; dy <= winsize.height; dy++ ) {/*接下来是针对不同的模版分别计算特征*/ // haar_x2 if ( (x+dx*2 <= winsize.width) && (y+dy <= winsize.height) ) { if (dx*2*dy < s0) continue; if (!symmetric || (x+x+dx*2 <=winsize.width)) {/*haar_x2::对应上述haar_x2图特征Sum黑-Sum白*/ haarFeature = cvHaarFeature( "haar_x2", x, y, dx*2, dy, -1, x+dx, y, dx , dy, +2 ); /* CV_VECTOR_PUSH( vec, CvIntHaarFeature, haarFeature, size, maxsize, step ) */ CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // haar_y2 if ( (x+dx <= winsize.width) && (y+dy*2 <= winsize.height) ) { if (dx*2*dy < s0) continue; if (!symmetric || (x+x+dx <= winsize.width)) {/*haar_y2::对应上图图haar_y2特征Sum黑-Sum白*/ haarFeature = cvHaarFeature( "haar_y2", x, y, dx, dy*2, -1, x, y+dy, dx, dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // haar_x3 if ( (x+dx*3 <= winsize.width) && (y+dy <= winsize.height) ) { if (dx*3*dy < s0) continue; if (!symmetric || (x+x+dx*3 <=winsize.width)) {/*haar_x3::对应上图图haar_x3特征,Sum黑*2-Sum白左-Sum白右*/ haarFeature = cvHaarFeature( "haar_x3", x, y, dx*3, dy, -1, x+dx, y, dx, dy, +3 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // haar_y3 if ( (x+dx <= winsize.width) && (y+dy*3 <= winsize.height) ) { if (dx*3*dy < s0) continue; if (!symmetric || (x+x+dx <= winsize.width)) {/*haar_y3::对应上图图haar_y3特征,Sum黑*2-Sum白上-Sum白下*/ haarFeature = cvHaarFeature( "haar_y3", x, y, dx, dy*3, -1, x, y+dy, dx, dy, +3 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } if( mode != 0 /*BASIC*/ ) { // haar_x4 if ( (x+dx*4 <= winsize.width) && (y+dy <= winsize.height) ) { if (dx*4*dy < s0) continue; if (!symmetric || (x+x+dx*4 <=winsize.width)) {/*haar_x4::对应上图图haar_x4特征,Sum黑-Sum白左-Sum白右*/ haarFeature = cvHaarFeature( "haar_x4", x, y, dx*4, dy, -1, x+dx, y, dx*2, dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // haar_y4 if ( (x+dx <= winsize.width ) && (y+dy*4 <= winsize.height) ) { if (dx*4*dy < s0) continue; if (!symmetric || (x+x+dx <=winsize.width)) {/*haar_y4::对应上图图haar_y4特征,Sum黑-Sum白上-Sum白下*/ haarFeature = cvHaarFeature( "haar_y4", x, y, dx, dy*4, -1, x, y+dy, dx, dy*2, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } } // x2_y2 if ( (x+dx*2 <= winsize.width) && (y+dy*2 <= winsize.height) ) { if (dx*4*dy < s0) continue; if (!symmetric || (x+x+dx*2 <=winsize.width)) {/*haar_x2_y2::对应上图图haar_x2_y2特征,Sum黑左上-Sum白上右-Sum白下左+Sum黑右下*/ haarFeature = cvHaarFeature( "haar_x2_y2", x , y, dx*2, dy*2, -1, x , y , dx , dy, +2, x+dx, y+dy, dx , dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } if (mode != 0 /*BASIC*/) { // point if ( (x+dx*3 <= winsize.width) && (y+dy*3 <= winsize.height) ) { if (dx*9*dy < s0) continue; if (!symmetric || (x+x+dx*3 <=winsize.width)) {/*point::对应上图图point特征,Sum黑中*8-Sum白*/ haarFeature = cvHaarFeature( "haar_point", x , y, dx*3, dy*3, -1, x+dx, y+dy, dx , dy , +9); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } } if (mode == 2 /*ALL*/) { // tilted haar_x2 (x, y, w, h, b, weight) if ( (x+2*dx <= winsize.width) && (y+2*dx+dy <= winsize.height) && (x-dy>= 0) ) { if (dx*2*dy < s1) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*tilted haar_x2::对应上图图tilted haar_x2特征,在tilted积分图中求得*/ haarFeature = cvHaarFeature( "tilted_haar_x2", x, y, dx*2, dy, -1, x, y, dx , dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // tilted haar_y2 (x, y, w, h, b, weight) if ( (x+dx <= winsize.width) && (y+dx+2*dy <= winsize.height) && (x-2*dy>= 0) ) { if (dx*2*dy < s1) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*tilted haar_y2::对应上图图tilted haar_y2特征,在tilted积分图中求得*/ haarFeature = cvHaarFeature( "tilted_haar_y2", x, y, dx, 2*dy, -1, x, y, dx, dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // tilted haar_x3 (x, y, w, h, b, weight) if ( (x+3*dx <= winsize.width) && (y+3*dx+dy <= winsize.height) && (x-dy>= 0) ) { if (dx*3*dy < s2) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*tilted haar_x3::对应上图图tilted haar_x3特征,在tilted积分图中求得*/ haarFeature = cvHaarFeature( "tilted_haar_x3", x, y, dx*3, dy, -1, x+dx, y+dx, dx , dy, +3 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // tilted haar_y3 (x, y, w, h, b, weight) if ( (x+dx <= winsize.width) && (y+dx+3*dy <= winsize.height) && (x-3*dy>= 0) ) { if (dx*3*dy < s2) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*tilted haar_y3::对应上图图tilted haar_y3特征,在tilted积分图中求得*/ haarFeature = cvHaarFeature( "tilted_haar_y3", x, y, dx, 3*dy, -1, x-dy, y+dy, dx, dy, +3 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // tilted haar_x4 (x, y, w, h, b, weight) if ( (x+4*dx <= winsize.width) && (y+4*dx+dy <= winsize.height) && (x-dy>= 0) ) { if (dx*4*dy < s3) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*同上*/ haarFeature = cvHaarFeature( "tilted_haar_x4", x, y, dx*4, dy, -1, x+dx, y+dx, dx*2, dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } // tilted haar_y4 (x, y, w, h, b, weight) if ( (x+dx <= winsize.width) && (y+dx+4*dy <= winsize.height) && (x-4*dy>= 0) ) { if (dx*4*dy < s3) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*同上*/ haarFeature = cvHaarFeature( "tilted_haar_y4", x, y, dx, 4*dy, -1, x-dy, y+dy, dx, 2*dy, +2 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } /* // tilted point if ( (x+dx*3 <= winsize.width - 1) && (y+dy*3 <= winsize.height - 1) && (x-3*dy>= 0)) { if (dx*9*dy < 36) continue; if (!symmetric || (x <= (winsize.width / 2) )) {/*tilted point::对应上图图tilted point特征,在tilted积分图中求得*/ haarFeature = cvHaarFeature( "tilted_haar_point", x, y, dx*3, dy*3, -1, x, y+dy, dx , dy, +9 ); CV_WRITE_SEQ_ELEM( haarFeature, writer ); } } */ } } } } } seq = cvEndWriteSeq( &writer ); features = (CvIntHaarFeatures*) cvAlloc( sizeof( CvIntHaarFeatures ) + ( sizeof( CvTHaarFeature ) + sizeof( CvFastHaarFeature ) ) * seq->total ); features->feature = (CvTHaarFeature*) (features + 1); features->fastfeature = (CvFastHaarFeature*) ( features->feature + seq->total ); features->count = seq->total; features->winsize = winsize; cvCvtSeqToArray( seq, (CvArr*) features->feature ); cvReleaseMemStorage( &storage ); /*特征的rect由坐标表示转换为由像素索引表示*/ icvConvertToFastHaarFeature( features->feature, features->fastfeature, features->count, (winsize.width + 1) ); return features; }