使用OpenCV人脸识别程序建立人脸目标识别分类器xml的说明 2024-5-11 ccc

使用OpenCV人脸识别程序建立人脸目标识别分类器xml的说明

2024-5-11 ccc

首先准备正采样

使用opencv提供的采样文件:

trainingfaces_24-24.vec

其中包含16520个人脸对象来源为1652个人脸目标,每个目标生成10个不同的视角变形。

         其次准备负采样,负采样可以是任何不包含人脸目标的图片,满足如下要求:

  1. 可以是任意大小的图片,最好统一尺寸。
  2. 图片中不应包含任何人脸图像
  3. 尽量包含各类背景场景,风景、蓝天白云、城市高楼,远山森林等。

形成info格式的背景文件。

         注意,目标图片和背景图片作为训练分类器的样本来源称之为样本空间,指明训练过程中的采样是来自于指定的样本空间,而在训练过程中指定的采样数,npos和nneg则是自样本空间中选择出合适的样本个数(称之为采样数),程序使用指定个数的采样对分类器进行训练,每训练一个stage都使用样本空间中一个采样集

         级联分类器的stage数(级联数)确定了分类器的分类误差,样本空间确定分类目标种类和背景类型,npos和nneg确定stage使用的采样集大小。

         在opencv中有一个限制,npos + nneg不能大于32767,因为在opencv中定义CV_16SC1为16位整数类型,而在idxcache中数据类型定义为CV_IDX_MAT_TYPE = CV_16SC1如果npos+nneg大于32767,则在读采样的过程中开辟的索引缓冲区溢出,因此需要对缓冲区数据类型进行修改,是之为CV_IDX_MAT_TYPE = CV_32SC1

将在_cvhaartraining.h中定义的CV_IDX_MAT_TYPE修改为:

#define CV_IDX_MAT_TYPE CV_32SC1

使采样总计数可以超过32767,达到32位整数。

         一般情况下,npos和nneg仅仅是一个stage计算时所需要的采样数,越大计算的stage越慢,并且需要的内存也越大,因此没必要选择过大的npos和nneg。

         准备好采样图片后运行opencv的建立xml程序,其中stage数应该为适当值,这要根据样本空间的情况和flasealarmrate值做出选择,比如,默认的stagefalsealarmrate=0.5,则如果选择stages = 7,分类器cascade的falsealarmrate = exp(0.5, 7),即cascade的falsealarmrate是stage的falsealarmrate的7次方。默认的stages = 14,脱标报警(falsealarmrate)的误差应该为0.5的14次方(0.0000610)。选择适当的stages参数可以使分类器获得更高的精确度,识别率更高,但是训练过程也就更加漫长。

         在开始训练一个stage之前,首先需要从样本空间中读取指定个数npos的正采样和nneg个负采样。正采样按照vec文件指定的顺序读取,经过已存在的(已装入的)cascade验证为可以正确识别的采样图片,负采样则根据info文件提供的负样本空间图片顺序装入经过变换后的图片。图片变换的原则是移动抠图窗口,逐步读取图片,缩放源图后再移动窗口抠图,变换原图后再移动窗口抠图。所有装入的负采样都必须经过已存在的cascade检测,只有那些被检测为人脸的负采样才能作为训练stage的负采样(注意,此时的负采样是错误识别的负采样)。负采样读取程序如下(lbp和harr):

bool CvCascadeImageReader::NegReader::nextImg()

{

    Point _offset = Point(0,0);//设置偏移初值为(0,0)

    size_t count = imgFilenames.size();

    for( size_t i = 0; i < count; i++ ){

        src = imread( imgFilenames[last++], 0 );//last为读文件的序号

        if( src.empty() )

            continue;

        round += last / count;//累加一个小于1的数,

        round = round % (winSize.width * winSize.height);//窗口面积大小的余数

        last %= count;//当last = count 时,返回last = 0

        _offset.x = std::min( (int)round % winSize.width, src.cols - winSize.width );

        _offset.y = std::min( (int)round / winSize.width, src.rows - winSize.height );

        if( !src.empty() && src.type() == CV_8UC1 && _offset.x >= 0 && _offset.y >= 0 )

            break;

    }//计算当前last值下的抠图偏移值,注意round是一个累加值,last归0时round不一定归0,

//因此读入的同一个last序号图片,抠图位置也不一定相同,因此负采样的获得也不相同。

    if( src.empty() )

        return false; // no appropriate image

    point = offset = _offset;

    scale = max( ((float)winSize.width + point.x) / ((float)src.cols),

                 ((float)winSize.height + point.y) / ((float)src.rows) );

    //计算源图到被抠图像的缩放尺度

    Size sz( (int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F) );

    resize( src, img, sz );//缩放图像

    return true;

}

//抠图过程

bool CvCascadeImageReader::NegReader::get( Mat& _img )

{

    CV_Assert( !_img.empty() );

    CV_Assert( _img.type() == CV_8UC1 );

    CV_Assert( _img.cols == winSize.width );

    CV_Assert( _img.rows == winSize.height );

    if( img.empty() )

        if ( !nextImg() )

            return false;

    //指定抠图区域在point(x,y)位置和窗口winSize范围

    Mat mat( winSize.height, winSize.width, CV_8UC1,

        (void*)(img.data + point.y * img.step + point.x * img.elemSize()), img.step );

    mat.copyTo(_img);//将指定范围图像拷贝到_img。

    //准备下一次读取负采样的参数

    if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols )

        point.x += (int)(stepFactor * winSize.width);//改变point.x位置

    else{

        point.x = offset.x;//point.x位置返回到抠图初始位置

        if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows )

            point.y += (int)(stepFactor * winSize.height);//改变point.y位置

        else{

            point.y = offset.y;//初始化抠图point.y位置

            scale *= scaleFactor;//缩放scale,其中scaleFactor = 1.4145常量(根号2)

            if( scale <= 1.0F )

                resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ) );

            else{

                if ( !nextImg() )

                    return false;

            }

        }

    }

    return true;

}

nextImg()函数读入指定last序号的采样图片到src,并根据当前的last计算round,然后获得offset和scale,并根据scale把scr缩放到img。注意,在nextImg()中,每一个读文件,都计算一个scale,是根据src的大小进行计算的,在get()中,则是根据scaleFactor进行进一步缩放,直到不能再进一步缩放为止,才读入新图片。而offset的偏移过程则是根据stepFactor = 0.5进行point(x,y)移动抠图,直到不能进一步移动了,才进行下一步的缩放(一般为缩小)。因此在准备背景图片时,只需要选择不同场景的图片即可,不需要对图片进行剪裁和缩放处理操作,因为读背景图片的操作可以自己处理剪裁和缩放。

    注意,这里给出的仅仅是opencv背景图片采集的一种算法,你也可以自己建立一套背景图片采集算法,因为这套算法会产生大量的冗余图片,因此也增加了大量的冗余计算,如果使用采集算法排除掉冗余性,则可以极大地提高训练效率。

注意,无论给出的负样本多少,在最后,程序都将花费大量的时间进行负样本的装入,因为在给出的负样本图片中能够满足要求的负采样越来越靠后了,因此需要大量的重复计算。

关于训练过程

         下面是使用相同样本空间和参数进行的两种特征类型(lbp/harr)的boost级联GAB类型的分类器的训练过程,设置的样本参数为:

         正样本数:2000

         负样本数:1000

         正样本空间:16132x24x24

         负样本空间:8446x36x36

训练过程中,程序每次从正样本空间中读出满足要求的2000个正样本,从负样本空间中读出满足要求的1000个负样本,组成训练采样集,训练一个stage分类器。

         分类器的识别效率如下:

对于stages=5

                                                                          Harr                                                                                     lbp

                   对于stages=7

      

                                        Harr                                                                           lbp

         对于stage=9        

                                                                               Harr                                                                           lbp

         对于stage=11

                                                                                Harr                                                                           lbp

         对于stage=15

                  

                                                                                    Harr                                                                           lbp

         对于stage=18

                                                                                 Harr                                                                           lbp

对于stage=19               

                                               Harr                                                                           lbp

从上面图片的识别过程来看,harr特征明显低于lbp特征,但是需要注意,在训练过程中harr的每一个stagen分类器都是在stage数达到之后终止的训练过程,也就是说stage数达到后,训练误差(特别是falsealarmrate误差)并没有达到要求的值。而lbp则是在stage数达到后训练误差(特别是falsealarmrate误差)也达到了要求的值。

对于harr的第stage=18和stage=19这两个分类器,几乎没有分别,这说明给定的采样空间中(特别是负采样空间)已经耗尽了所有的可用场景属性,再也不能提供更精确的识别特征了,如果不增加负样本空间,继续的训练没有意义。

关于训练参数的意义

         上述训练是在样本空间中选定2000个正样本(顺序读取)和1000个负样本(顺序读取并缩放、位移)进行stage训练,得出的各个不同stage级联分类器的过程。其中使用相同的增强类型GAB和误差设定。如果所有误差设定不变,而改变选定样本参数,会怎样呢,下面是扩大选定样本的数量(不改变样本空间),然后看一下结果:

         正采样数:4000

         负采样数:2000

         正样本空间:16132x24x24

         负样本空间:8446x36x36

                            Harr4000_5                                                             harr2000_5

                            Lbp4000_5                                                               lbp2000_5

等比例改变采样数(4000/2000、2000/1000),在相同的样本空间下,效果不大。没有明显的识别率提升。

增加负采样的数量,如下参数:

         正采样数:2000

         负采样数:4000

         正样本空间:16132x24x24

         负样本空间:8446x36x36

同样是stages=5,结果如下:

                   harr2000_4000_5                                                              harr4000_2000_5

增加负采样,有助于排除背景干扰。

                        lbp_2000_4000_5                                                              lbp_4000_2000_5

增加负采样,有助于排除背景干扰,但是效果不很明显。

        

我们注意到在训练xml的过程中,样本空间中被使用的采样是顺序装入的,对于正采样,排除当前cascade不能正确识别的样本,顺序装入样本空间中后续采样,对于负采样则排除正确识别的采样,从样本空间中继续装入那些不能正确识别的负采样(注意,负采样的正确识别是说,非目标被识别成非目标,非目标被识别成目标则说明这个负采样是不能被正确识别的)。随着stages的训练深入,负采样的正确识别率升高,因此,在负样本空间中装入的负采样绝大部分都能够被正确识别,得到指定的训练用负采样的几率会随着stages的深入而降低,程序需要花费大量的时间在样本空间中检测负采样,以获得参数指定的负采样个数。

         是不是在开始训练的时就增加负采样的数量,就可能在较少的stage下就能够获得比较满意的xml。下面继续增加负采样参数,如下参数:

         正采样数:2000

         负采样数:8000

         正样本空间:16132x24x24

         负样本空间:8446x36x36

                      harr_2000_8000_5                                                         harr_2000_4000_5

                            lbp_2000_8000_5                                                     lbp_2000_4000_5

无论是harr特征还是lbp特征,在相同的负样本空间中增加负采样数都不能够显著地排除背景干扰。

         要想排除背景干扰,必需增加stages数,因为stages的深入,能够显著第排除负采样中重复的采样,使得样本空间中大量的重复采样被排除在训练过程之外,这还可以增加训练速度。

         正样本是表示检测目标类型的样本,负样本是表示目标所处环境的样本。在训练过程中初始设置为空cascade分类器,此时,读入的正采样默认为是完全可正确识别的采样,而读入的负采样则是完全不可识别的采样(错误识别)。从而建立一个在这些样本上的一个stage(由多个cart组成),然后以该stage级联成一个cascade(只有一个stage),重新对样本空间进行读取并检测,再获得一组参数指定的正负采样,其中正采样是通过所获得的cascade进行正确识别的采样,负采样则是通过cascade错误识别的采样。由于cascade的过滤,之前的一些采样被过滤掉(消耗掉),样本空间中的后续样本被读出,然后对新的采样集进行stage计算。以上叙述说明了样本空间、采样集和stage数的关系,使用一定的样本空间(vec和info)并指定采样集参数(正采样数、负采样数),和适当的stages数,能够快速训练出理想的分类器cascade。

         注意,负采样是通过cascade过滤形成的,较大的负采样可能会增加采样集的空间占有率,加大计算量,较多的stages数可能会耗尽采样空间,在读入负采样时增加读入时间。因此,选择适当的训练参数进行分类器训练时一个关键技术。

         一般而言,正采样的重复数比较少,因此正采样数的选择比较容易,比如选择正样本空间的70%—80%。负样本空间的重复较多,因此选择适当的负采样数能减少空间占用和加快训练过程,并增加排除样本的重复率。下面是按此原则进行的harr和lbp两种特征的训练过程。

         正采样数:12000

         负采样数:4000

         正样本空间:16132x24x24

         负样本空间:8446x36x36

         级联数:Stages = 10

                   harr_12000_4000_10                                                        lbp_12000_4000_10

减少采样数,增加stages数,结果如下:

         正采样数:1000

         负采样数:500

         正样本空间:16132x24x24

         负样本空间:8446x36x36

         级联数:Stages = 18

                        harr_1000_500_18                                                      lbp_1000_500_18

                         harr_1000_500_18_1                                                           lbp_1000_500_18_1

                         h_1000_500_18_peolpe                                                      l_1000_500_18_people

对于使用一定特征而言,采样数和stage的的关系可以从falsealarmrate的给定值计算出来。比如stage=10,falsealarmrate=0.5,则无论指定什么样的采样数,对于特定样本空间其最终的分类器误差必定是小于Exp(falsealarmrate,10),即为falsealarmrate的10次方。而正采样数则表明分类器可检测的目标范围(比如,采样空间有16000个样本,分类器仅适用头2000个就完成了训练,则分类器对于样本空间中2000样本之后的样本种类就可能会识别出错),负采样也是如此。训练过程消耗掉的样本是分类器可以正确检测的样本。

         使用正确的采样数和stage级联数,在训练中消耗掉样本空间中的所有采样,是训练参数的最佳选择。

         如果样本空间中的所有样本都已经消耗掉,也就是在最后一个stage级联的计算中采样读入的是样本空间最后的样本(每一个stage级联,逐渐增加正采样数和逐渐减少负采样数),可以加快训练过程并不损失分类器检测精度和检测种类范围。

                   Harr_2000_1000_18                                                        harr_1000_500_18

左侧分类器识别显然没有右侧的好,它们都使用了stage=18参数,只是负采样数不一样,一个是2000/1000,一个是1000/500。

                        Harr_2000_1000_18_1                                                          harr_1000_500_18_1

                          Harr_2000_1000_18_2                                                                 harr_1000_500_18_2

以上harr特征的训练中可以看出采样数的参数的作用,训练一个stage时,可以根据当前使用的样本空间指定不同的采样数,使得读入采样的耗散数达到最佳(用脱标报警的stage次方检验),逐级改变参数,一级一级训练(逐级增加增采样,减少负采样),如下:

         1、5000/5000/3(harr)

2、6000/4000/5(harr)

3、3000/2000/10(harr)

4、2000/1000/15(harr)

4、1000/500/18(harr)

从上述识别效果可以看出,对于stages小于15的cascade分类器,npos和nneg的数量变化不明显,对于stages大于15的cascade分类器,减少npos和nneg会减少采样读入时间,减少stage计算量,识别效果也比较明显增加。

         因为选择的背景样本空间有限,所以最终的cascade效果不是很好,如果增加背景info的图片数和种类,可能效果会比较满意。

增加背景info的样本空间

         所有上面的info样本空间都是基于300张图片生成的36x36的8000多张背景情况下的训练过程。下面改变背景info样本空间如下:

  • 直接使用300张源图
  1. 训练参数:harr1000/100/10

POS: 1000 1037 0.964320

NEG: 100 0.00484355//负采样读入样本数 = 20646

在训练的样本空间中读入了1037个正样本,获得1000个可用的正采样。在背景样本空间中读入了20646个负样本,获得100个可用的负采样。

        2.训练参数:harr1000/100/15

POS: 1000 1063 0.940734

NEG: 100 0.000780969//负采样读入样本数 = 128046

        3.训练参数:harr1000/100/18

POS: 1000 1086 0.920810

NEG: 100 0.000707439//负采样读入样本数 = 141354

         4.训练参数:harr1000/100/20

POS: 1000 1096 0.912409

NEG: 100 0.000489222//负采样读入样本数 = 204406

        5.训练参数:harr1000/100/30

POS: 1000 1143 0.874891

NEG: 100 5.17787e-005 = 0.0000517787//负采样读入样本数 = 1931296

        6.训练参数:harr1000/100/40

POS: 1000 1196 0.836120

NEG: 100 1.20734e-006 = 0.00000120734//负采样读入样本数 = 82826709

从上面的识别过程来看,当stages大于20后,效果就已经很明显了。当背景的消耗达到了千万级别,stages = 35 则完全达到了识别要求。

         因为背景图片样本只给出了一般形式的图片,并没有考虑全面的场景需求,可能在一些复杂的背景环境下仍有错误地背景出现,这就需要添加新背景重新训练。

         以上训练过程也说明,样本空间给定后,采样参数npos和nneg影响训练计算时间,而stages数则影响负样本的过滤时间,当stages大于15后,如果nneg过大,则负样本的过滤过程将会消绝大部分训练的计算时间。

  • 增加源图到600张

在将负样本空间增加到600张图片后,由于采样参数仍然是100,因此stage训练过程中对负样本的消耗没有太大的改变,也就是说负样本的排除率反而下降了,因此识别的效率也下降了。

  1. 训练参数:harr1000/100/10

POS: 1000 1037 0.964320

NEG: 100 0.00484355//负采样读入样本数 = 20646

        2.训练参数:harr1000/100/15

POS: 1000 1066 0.938086

NEG: 100 0.00100953//负采样读入样本数 = 99055

        3.训练参数:harr1000/100/18

POS: 1000 1090 0.917431

NEG: 100 0.000841446//负采样读入样本数 = 118843

        4.训练参数:harr1000/100/20

POS: 1000 1099 0.909918

NEG: 100 0.000404029//负采样读入样本数 = 247512

        5.训练参数:harr1000/100/30

POS: 1000 1145 0.873362

NEG: 100 5.40441e-005 = 0.0000540441//负采样读入样本数 = 1850481

        6.训练参数:harr1000/100/40

POS: 1000 1206 0.829187

NEG: 100 6.78488e-006 = 0.00000678488负采样读入样本数 = 14749262

         说明,以上训练过程,只是将背景样本空间(负样本空间)进行扩展,增加了300张背景图片,在相同的stages、npos、nneg参数的情况下,训练出来的cascade分类器效果就有了一定的差异,也就是在训练过程中,负样本空间对负采样的排除过程有一定的影响。在一定的样本空间下,npos和nneg的选择与stages一定要有一个匹配,这样才能充分利用当前样本空间提高cascade的识别精度。

         一般的harr特征下stage应该在15左右,此时的npos和nneg应根据机器的容量进行选择,特别是nneg的选择,应该估算:

         当stages = 15时,cascade的识别误差为exp(0.5,15) = 0.0000305176。此时如果设nneg为1000,则要达到这个误差精度,需要读入负样本的次数应该为32767976。此时提供的样本空间通过上一级stages系列组成的cascade过滤对负样本空间的耗散与提供的样本空间容量相关。样本空间中采样图片的读取由harr框架程序确定,不同的算法有不同的效果。

         在当前的例子中如果想要在600个背景图片提供的负样本空间中达到满意的效果,一是可以提高nneg的数量,或者进一步提高stages的数值,比如npos = 1000,nneg=1000,stages=40。或则npos=1000,nneg=100,stages=50,都能提高cascade的识别精度。

         最终结论,对于一定的样本空间当指定的样本耗散达到空间的70%后,样本空间所提供的识别目标种类和背景类型能够正常识别。负样本空间的排除过程与负样本读入算法(抠取算法)有关,不同的算法有快速排除背景的能力,本例中使用的是opencv中提供的算法,稍有改动(将随机过程改为顺序过程),因此背景图片的排列顺序可能会影响到负采样的过滤过程。样本空间的耗散过程与npos/nneg/stages相关,同时也与样本空间的大小相关,特别是负样本空间,要在一定的stages下达到想要的耗散比率,需要适当调节nneg。可以使用一个函数测算nneg和npos,使之在不同的stage下读入不同的npos和nneg,以获得相同的耗散率。

         cascade分类器的训练过程实际上是一个负样本的排除过程。根据贝叶斯的条件概率原理,多个stages的级联分类,将负样本逐级过滤,最终得出分类结果。这个结果是概率意义上的,不可能达到100%,但是在一般识别情况下,使用一定的参数训练出来的cascade分类器能够满足目标检测的初步需求,即“目标肯定包含在检测结果当中”。然后可以利用其它环境条件将非目标结果进一步排除,减少复杂运算过程。这种分类器可用于流水线上的产品定位等特殊目标、痕迹检测。

         使用opencv的目标检测及分类器cascade框架,可以训练出识别范围特殊的分类器,对特定目标进行视频动态检测,定位,和监控,比如车牌定位。

   下面是OpenCV的xml文件haarcascade_frontalface_alt.xml的检测结果与cascade_src_l1000_100_30.xml和_decinfoimgs12_src_h1000_100_30.xml检测结果的比较。

                   Opencv_0                                                lbp_0                                                   harr_0

                         Opencv_1                                                  lbp_1                                                     harr_1

                                                                                       Opencv_2

                                                                                      Lbp_2

                                                                                           Harr_2

                                                                                       Opencv_3

                                                                                            Lbp_3

                                                                                           Harr_3

                                                                                           Opencv_4

                                                                                                Lbp_4

                                                                                              Harr_4

注意,其中的opencv分类器时20x20的,而lbp和harr都是24x24的。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值