OpenCV中有两个程序可以训练级联分类器: opencv_haartraining 和opencv_traincascade。opencv_traincascade是一个新程序,使用OpenCV 2.x API 以C++ 编写。这二者主要的区别是opencv_traincascade支持 Haar、Hog和 LBP(Local Binary Patterns) 三种特征,并易于增加其他的特征。与Haar特征相比,LBP特征是整数特征,因此训练和检测过程都会比Haar特征快几倍。LBP和Haar特征用于检测的准确率,是依赖训练过程中的训练数据的质量和训练参数。训练一个与基于Haar特征同样准确度的LBP的分类器是可能的。
opencv_traincascadeandopencv_haartraining所输出的分类器文件格式并不相同。注意,新的级联检测接口(参考objdetect模块中的CascadeClassifier类)支持这两种格式。opencv_traincascade可以旧格式导出训练好的级联分类器。但是在训练过程被中断后再重启训练过程,opencv_traincascade andopencv_haartraining不能装载与中断前不同的文件格式。
opencv_traincascade程序使用TBB来处理多线程。如果希望使用多核并行运算加速,请使用TBB来编译OpenCV。还有一些与训练相关的辅助程序。
opencv_createsamples用来准备训练用的正样本数据和测试数据。opencv_createsamples能够生成能被opencv_haartraining和opencv_traincascade程序支持的正样本数据。它的输出为以 *.vec 为扩展名的文件,该文件以二进制方式存储图像。
opencv_performance可以用来评估分类器的质量。它读入一组标注好的图像,运行分类器并报告性能,如检测到物体的数目,漏检的数目,误检的数目,以及其他信息。
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/user_guide/ug_traincascade.html
http://www.jb51.cc/article/p-ybepdqqb-bdz.html
1 建立样本集pos和neg
pos中存放正样本的图像,可以是一张图像或多张图像。neg中存放包含负样本的图像,可以是任意图像,但是这些图像中不能包含待检测的物体。
注意:
一般需要一个很大的负样本库送给训练程序进行训练。如果是绝对刚性的物体,如OpenCV的标志,只有一张正样本图像,那么可以通过对物体图像的随机旋转、改变标志亮度以及将标志放在任意的背景上而获得大量的正样本;如果是人脸,需要几百甚至几千个正样本。在待检测物体是人脸的情况下,需要考虑所有的人种、年龄、表情甚至胡子的样式。
2 生成正\负样本描述文件negdata.txt
(1)负样本描述文件negdata.txt
在命令行窗口输入“cd d:\%....%\pos”路径切换到neg文件夹下,输入“dir/b>negdata.txt”就会在neg文件夹下生成描述文件negdata.txt,存放neg中所有图片的文件名,注意要去掉最后的negdata.txt。
注意:
如果把negdata.txt保存在neg文件夹外面,需要在每一行外面添加相对路径或绝对路径来指出样本的所在位置。一种方法是将negdata.txt文件中的内容复制到word中,使用替换功能实现快速修改。neg替换为neg\neg
(2)正样本描述文件posdata.txt
与建立negdata.txt一样建立posdata.txt,只不过在正样本描述文件中需要指出目标在每个样本中的数量和位置如pos\1.bmp 1 x1 y1 x2 y2,其中(x1,y1,x2,y2)为目标所在的矩形框,又如pos\1.bmp2 x1 y1 x2 y2 x1’ y1’ x2’ y2’
因为我们准备的正样本基本都是目标,因此只需在文件名后增加1 0 0 width height即可。
注意:
1若正样本图像是不同尺寸的,一方面可以使用ImageResize或matlab将图像统一成同一尺寸在生成posdata.txt,或程序ImageToTxt直接生成具有不同尺寸图像的正样本描述文件。在此阶段不归一化的话,后续生成.vec文件的时候在程序中自动归一化。
2样本描述文件与图像要一致,可以存在图像但没有写在描述文件中,即有多余的图像,但千万不要在描述文件中写不存在的图像。
问题:
归一化尺寸的方法是否对训练结果有影响?opencv_createsamples采用何种方法进行归一化?
sample = cvCreateImage( cvSize(winwidth,winheight ),IPL_DEPTH_8U,1 );
fscanf( info,"%d %d %d%d",&x,&y,&width,&height )
cvSetImageROI( src,cvRect( x,y,width,height ) );
cvResize( src,sample,
width >=sample->width && height >= sample->height ? CV_INTER_AREA :CV_INTER_LINEAR );
CV_INTER_NN 最近邻插值
CV_INTER_LINER 双线性插值,缺省情况
CV_INTER_AREA 使用像素关系重采样,当图像缩小时候可以避免波纹出现,当图像方法时类似CV_INTER_NN
CV_INTER_CUBIC 立方插值
3 生成.vec文件
生成的正样本数目以及随机的程度都可以通过opencv_createsamples的命令行参数控制。
使用create.dat调用%Opencv%\vs2008\bin\Release\ opencv_createsamples.exe
在createsamples.cpp中查阅参数设置
-info 输入正样本描述文件,默认NULL
-img 输入图像文件名,默认NULL
-bg 负样本描述文件,文件中包含一系列的被随机选作物体背景的图像文件名,默认NULL
-num 生成正样本的数目,默认1000
-bgcolor 背景颜色,表示透明颜色,默认0
-bgthresh 颜色容差,所有处于bgcolor-bgthresh和bgcolor+bgthresh之间的像素被置为透明像素,也就是将白噪声加到前景图像上,默认80
-inv 前景图像颜色翻转标志,如果指定颜色翻转,默认0(不翻转)
-randinv 如果指定颜色将随机翻转,默认0
-maxidev 前景图像中像素的亮度梯度最大值,默认40
-maxxangle X轴最大旋转角度,以弧度为单位,默认1.1
-maxyangle Y轴最大旋转角度,以弧度为单位,默认1.1
-maxzangle Z轴最大旋转角度,以弧度为单位,默认0.5
输入图像沿着三个轴进行旋转,旋转角度由上述3个值限定。
-show 如果指定,每个样本都将被显示,按下Esc键,程序将继续创建样本而不在显示,默认为0(不显示)
-scale 显示图像的缩放比例,默认4.0
-w 输出样本宽度,默认24
-h 输出样本高度,默认24
将正样本描述文件中的正样本转换为灰度图像并缩放到-w-h尺寸存入vec文件中。
(1)如果设置-img和-vec
调用cvCreateTrainingSamples,采用一张图像创建训练样本
(2)如果设置-img、-bg和-info
调用cvCreateTestSamples,采用一张图像创建测试样本。-bg在这里又有什么作用?目的是作为背景创建测试图像。
(3)如果设置-info和-vec(采用正样本描述文件中的图像创建训练样本)
调用cvCreateTrainingSamplesFromInfo,在cvCreateTrainingSamplesFromInfo中将读取样本,并resize后调用icvWriteVecHeader和icvWriteVecSample创建vec文件。
调用cvShowVecSamples查看和检查保存在vec文件中正样本
上述参数在create.dat中设置好,最后有一个pause,等待显示结果:Done.Created num samples
4 训练过程
使用train.dat调用%Opencv%\vs2008\bin\Release\ opencv_traincascade.exe
在traincascade.cpp中查阅参数设置
1基本参数
-data 目录名,存放训练好的分类器,如果不存在训练程序自行创建
-vec 正样本.vec文件,由opencv_createsamples生成
-bg 负样本描述文件
-numPos 每级分类器训练时所用到的正样本数目
-numNeg 每级分类器训练时所用到的负样本数目,可以大于-bg指定的图片数目
-numStages 训练分类器的级数
-precalcValBufSize 缓存大小,用于存储预先计算的特征值,单位MB
-precalcIdxBufSize 缓存大小,用于存储预先计算的特征索引,单位M币
-baseFormatSave 仅在使用Haar特征时有效,如果指定,级联分类器将以老格式存储
2级联参数cascadeParams
-stageType 级联类型,staticconst char* stageTypes[] = { CC_BOOST };
-featureType 特征类型,staticconst char* featureTypes[] = { CC_HAAR,CC_LBP,CC_HOG };
-w
-h 训练样本的尺寸,必须跟使用opencv_createsamples创建的训练样本尺寸保持一致
3Boosted分类器参数stageParams
-bt Boosted分类器类型
DAB-discrete Adaboost,RAB-RealAdaboost,LB-LogiBoost,GAB-Gentle Adaboost
-minHitRate 分类器的每一级希望得到的最小检测率,总的最大检测率大约为
min_hit_rate^number_of_stages
-maxFalseAlarmRate 分类器的每一级希望得到的最大误检率,总的误检率大约为
max_false_rate^number_of_stages
-weightTrimRate Specifies whether trimming should beused and its weight. 一个还不错的数值是0.95
-maxDepth 弱分类器的最大深度,一个不错数值是1,二叉树
-maxWeightCount 每一级中弱分类器的最大数目
4Haar特征参数featureParams
-mode 训练过程使用的Haar特征类型,CORE-Allupright ALL-All Features BASIC-Viola
上述参数设置好后调用CvCascadeClassifier::train进行训练
将上述内容在train.dat中编辑好,运行即可。训练最终生成一个-data指定级联分类器的文件夹和一个cascade.xml文件,其余文件都是中间结果,当训练程序被中断之后,再重新运行训练程序将读入之前的训练结果,无需从头重新训练,训练结束后可以删除这些文件。
在cascade.xml文件中主要有stageType,featureType,width,height,stageParams,featureParams,stageNum,stages和features节点。
stages中的stage数目是自己设定的,每个stage又包含多个weakClassifiers,每个weakClassifier又包含一个internalNodes和一个leafValues。internalNodes中四个变量代表一个node,分别为node中的left/right标记,特征池中的ID和threshold。leafValues中两个变量代表一个node,分别为leftleaf和right leaf值。
features是分类器的特征池,每个Haar特征包含一个矩形rect和要提取的特征序号,每个Hog特征/LBP特征包含一个矩形。
注意:
1 包含负样本的图像一定不小于在create中设置的尺寸
负样本图像可以是不同的尺寸,但是图像尺寸应该比训练窗口的尺寸大,在使用负样本图像时,OpenCV自动从负样本图像中抠出一块和正样本同样大小的区域作为负样本。具体可查阅icvGetNextFromBackgroundData,具体抠图过程为:
1)确定抠图区域的左上角坐标(Point.x,Point.y)
2)确定一个最小缩放比例,使得原负样本图像缩放后恰好包含选中负样本区域
3)对原负样本图象按计算好的缩放比例进行缩放
4)在缩放后的图像上抠出负样本。
2 –numPos一般比实际正样本数量少200-300,-numNeg是否存在同样的情况?正负样本选择规则?
如果出现:训练停留在一个分类器长达几小时没有相应,问题出现在取负样本的那个函数icvGetHaarTrainingDataFromBG中;只有当之前的强分类器对负样本集内的样本全部分类正确时才会出现死循环,因为只要有一个样本会被错分为正样本,那么通过count次扫描整个负样本集就能得到count个负样本,当然这count个负样本实际上就是一个负样本的count个拷贝。为避免这种情况,负样本集中的样本数需要足够多 。
不过此时的分类器已经完全额、可以使用,因为它的误检率已经很低,从实用性上时没有任何问题的。所以我们可以通过设置-nstages 这个参数来限制分类器级数,适当时候停止并生成xml文件。
从CvCascadeBoost::train中去查阅
函数poscount=icvGetHaarTrainingDataFromVec( training_data,npos,
(CvIntHaarClassifier*)tcc,vecfilename,&consumed )负责从正样本集*.vec 文件中载入 count(npos)个正样本。在程序第一次运行到此(即训练第一个分类器之前)时,只要正样本集中有 count 个样本,就一定能取出 count 个正样本。在以后运行到此时,有可能取不到 count 个样本,因为
必须是用前面的级联强分类器((CvIntHaarClassifier*) tcc)分类为正样本(即分类正确的样本)的样本才会被取出作为下一个强分类器训练样本,具体可参考 icvGetHaarTrainingData和icvEvalTreeCascadeClassifierFilter函数。
训练负样本,具体可参考icvGetHaarTrainingDataFromBG和icvEvalTreeCascadeClassifierFilter函数。
inticvGetHaarTrainingDataFromBG(CvHaarTrainingData* data,int first,int count,
CvIntHaarClassifier*cascade,double* acceptance_ratio,const char * filename = NULL )
传递返回值的 acceptance_ratio 参数记录的是实际取出的负样本数与查询过的负样本数(如果通过前面级联stage强分类器的负样本数很少时,那么程序会循环重复读取负样本,并用thread_consumed_count计数)之比(acceptance_ratio = ((double) count) / consumed_count),也就是虚警率,用于判断已训练的级联分类器是否达到指标,若达到指标,则停止训练过程。
注意函数icvGetHaarTrainingData中一个主要的 For 循环:
for( i = first; i < first +count; i++ ) //共读取 count 个负样本,当读取不到
{//这么多负样本时将出现死循环!
对上面代码中的注释有必要进一步说明一下:只有当之前的强分类器对负样本集内的样本全部分类正确时才会出现死循环。因为只要有一个样本会被错分为正样本,那么通过 count次扫描整个负样本集就能得到 count 个负样本,当然这 count 个负样本实际上就是一个负样本的 count 个拷贝。为避免这些情况的发生,负样本集中的样本数需要足够多。
在负样本图像大小与正样本大小完全一致时,假设最终的分类器虚警率要求是falsealarm,参加训练的负样本要求是 count 个,则需要的负样本总数可计算如下:TotalCount = count / falsealarm
以 Rainer Lienhart 的文章中的一些参数为例,falsealarm=0.5^20=9.6e-07,count=3000,
则 TotalCount=3000/(0.5^20)= 3,145,728,000=31 亿。
函数 icvGetHaarTrainingDataFromBG ()负责从负样本集中载入 count 个负样本。在程序第一次运行到此(即训练第一个分类器之前)时,只要负样本集中有 count 个样本,就一定能取出 count 个负样本。在以后运行到此时,有可能取不到 count 个样本,因为必须是用前面的级联强分类器分类为正样本的样本(即分类错误的样本)才会被取出作为下一个强分类器的负样本输入。
对于int icvGetHaarTrainingData( CvHaarTrainingData* data,
CvGetHaarTrainingDataCallbackcallback,void* userdata,
int*consumed,double* acceptance_ratio )
这个函数的解释:
这是个对于读取正负样本通用的函数,区别在于callback的调用。在这个函数中有个变量thread_getcount,表示将样本分为正样本的数目(不论这个样本是负样本还是正样本)。
传递返回值的 Consumed 参数表示为取 count 个正样本,查询过的正样本总数。对于负样本为空(null),没有返回值。
3 之前遇到过10*20的不能训练Hog特征的分类器?Hog特征是否存在尺寸限制?
查阅Hog特征的计算方法,Opencv中HogDescriptor