opencv中有两个函数可以训练分类器opencv_haartraining.exe和opencv_traincascade.exe,前者只能训练haar特征,后者可以用HAAR、LBP和HOG特征训练分类器。这两个函数都可以在opencv\build\x86\vc10\bin文件夹下找到。opencv_traincascade.exe训练的是adaboost的级联分类器,这里不讲adaboost的级联分类器的原理,具体讲解用opencv_traincascade.exe来训练一个分类器的步骤。
步骤:
一、获取样本
首先要采集正样本和负样本。以行人检测为例,正样本就是各种各样的行人,负样本就是非人照片。样本个数最好在上千个,个数太少训练出来的分类器不能准确的检测行人,网上对正负样本的个数比例不尽相同,有的说3:1有的说7:3,具体的还是要自己去实验,把正负样本分别放在不同的文件夹下,可以命名为pos、neg。正负样本的图片都需要建立描述文件,文件是txt文件,里面存放的是正负样本的相对路径,正样本除存放相对路径外,还需要标出目标所在图像中的位置,如下图所示。
下面代码是获取正负样本及txt描述文件的鼠标回调代码:
void on_mouse(int event, int x, int y, int flags, void *ustc)
{
static Point pre_pt(-1, -1);
static Point cur_pt(-1, -1);
if (event == CV_EVENT_LBUTTONDOWN)
{
gray.copyTo(img);
pre_pt = Point(x, y);
}
else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))//摁下左键,flags为1
{
img.copyTo(tmp);
cur_pt = Point(x, y);
rectangle(tmp, pre_pt, cur_pt, Scalar(0, 255, 0, 0), 2, 8, 0);
imshow("img", tmp);
}
else if (event == CV_EVENT_LBUTTONUP)
{
gray.copyTo(img);
img.copyTo(tmp);
cur_pt = Point(x, y);
rectangle(tmp, pre_pt, cur_pt, Scalar(0, 255, 0, 0), 2, 8, 0);
imshow("img", tmp);
int width = abs(pre_pt.x - cur_pt.x);
int height = abs(pre_pt.y - cur_pt.y);
int pix_x = min(cur_pt.x, pre_pt.x);
int pix_y = min(cur_pt.y, pre_pt.y);
if (width == 0 || height == 0)//负样本
{
cout<<"选择作为负样本"<
先在工程下创建pos和neg两个文件夹,分别用来存放正负样本,再在两个文件夹中分别创建pos.txt和neg.txt文件。左键点击一下选取为负样本,存在"…/train/neg/“目录下,同时记录在neg.txt文件中。摁住左键画矩形框选取正样本对象,后按y保存在”…/train/pos/"目录下,同时记录在pos.txt文件中。
ps:对正负样本的几点说明。。。
正负样本都要转化成灰度图,而且对于正样本用haar特征训练是规格化成20x20或其他大小,最好不要太大,过多的haar特征会影响分类器的训练时间;对于LBP特征正样本要规格化为24x24大小,而对于HOG要规格化成64*64. 负样本对尺寸没有统一要求,在训练对应的分类器时,选择的负样本尺寸一定要大于等于正样本规定的尺寸。
a,正样本就是人的图片就行了,尽量包含少的背景。
b,负样本有两点要求:一,不能包含正样本且尽可能多的提供场景的背景图;二,负样本尽可能的多,而且要多样化,和正样本有一定的差距但是差别也不要太大,否则容易在第一级就全部被分类器reject,训练时不能显示负样本的个数,从而导致卡死。
二、生成样本描述文件
将上一步采集好的正负样本文件夹pos、neg和opencv自带的训练函数opencv_createsamples.exe和opencv_traincascade.exe放在一个文件夹下如boost文件夹,另外建一个xml文件夹。如下图:
下面生成pos.vec文件:
打开cmd命令窗,进入到boost文件夹下,输入:opencv_createsamples.exe -info pos\pos.txt -vec pos.vec -bg neg\neg.txt -num 2000 -w 24 -h 24
回车之后文件夹下就会出现pos.vec文件。
以上参数的含义如下:
-vec
-img
-bg
-num
-bgcolor
-bgthresh
-inv:如果指定,颜色会反色
-randinv:如果指定,颜色会任意反色
-maxidev
-maxangel
-maxangle
-maxzangle
-show:如果指定,每个样本会被显示出来,按下"esc"会关闭这一开关,即不显示样本图片,而创建过程
继续。这是个有用的debug 选项。
-w
-h
只需要对正样本进行以上操作,负样本不需要生成vec文件。。。
三·、训练分类器
在cmd命令行下输入:opencv_traincascade.exe -data xml -vec pos.vec -bg neg\neg.txt -numPos 1800 -numNeg 1200 -numStages 20 -featureType LBP -w 24 -h 24
,各参数需要根据自己样本数大小等实际而定。
以上参数含义如下:
-data
目录名,如不存在训练程序会创建它,用于存放训练好的分类器。
-vec
包含正样本的vec文件名(由 opencv_createsamples 程序生成)。
-bg
背景描述文件,也就是包含负样本文件名的那个描述文件。
-numPos
每级分类器训练时所用的正样本数目。其指设置为正样本数量的85%(这是一个保守值)。具体的也要根据级联器的层数来决定的。因为每个stages都是会增加图片数量来进行分类。
-numNeg
每级分类器训练时所用的负样本数目,可以大于 -bg 指定的图片数目。
-numStages
训练的分类器的级数。
-precalcValBufSize
缓存大小,用于存储预先计算的特征值(feature values),单位为MB。
-precalcIdxBufSize
缓存大小,用于存储预先计算的特征索引(feature indices),单位为MB。内存越大,训练时间越短。
-baseFormatSave
这个参数仅在使用Haar特征时有效。如果指定这个参数,那么级联分类器将以老的格式存储。
级联参数:
-stageType
级别(stage)参数。目前只支持将BOOST分类器作为级别的类型。
-featureType<{HAAR(default), LBP}>
特征的类型: HAAR - 类Haar特征; LBP - 局部纹理模式特征。
-w
-h
训练样本的尺寸(单位为像素)。必须跟训练样本创建(使用 opencv_createsamples 程序创建)时的尺寸保持一致。
Boosted分类器参数:
-bt <{DAB, RAB, LB, GAB(default)}>
Boosted分类器的类型: DAB - Discrete AdaBoost, RAB - Real AdaBoost, LB - LogitBoost, GAB - Gentle AdaBoost。
-minHitRate
分类器的每一级希望得到的最小检测率。总的检测率大约为 min_hit_rate^number_of_stages。总检测率即为整个级联器的检测召回率,
-maxFalseAlarmRate
分类器的每一级希望得到的最大误检率。总的误检率大约为 max_false_alarm_rate^number_of_stages. 为整个级联器的误检率
-weightTrimRate
Specifies whether trimming should be used and its weight. 一个还不错的数值是0.95。
-maxDepth
弱分类器树最大的深度。一个还不错的数值是1,是二叉树(stumps)。
-maxWeakCount
每一级中的弱分类器的最大数目。The boosted classifier (stage) will have so many weak trees (<=maxWeakCount), as needed to achieve the given -maxFalseAlarmRate.
类Haar特征参数:
-mode
选择训练过程中使用的Haar特征的类型。 BASIC 只使用右上特征, ALL 使用所有右上特征和45度旋转特征。
注意:
acceptanceRatio的含义,实际取出的负样本数与查询过的负样本数之比
1、查询过的负样本是指,从原始负样本中通过滑动和scale获取的预处理负样本,实际取出的负样本为那些通过了前n-1级强分类器后仍被判为正样本的负样本。
2、实际取出的负样本数一般都会达到设置的-numNeg。
第n级时的acceptanceRatio = Stage0.FAStage1.FA … *Stagen.FA
3、当acceptanceRatio(n) 很低时,则表示经过n-1级强分类器仍能被错判为正样本的负样本的几率已经降到很小了,然而我们又必须要在每一级Stage处得到numNeg个负样本,这时就会不断地在原始图像上滑动和scale,直到攒够numNeg个才会开始第n级强分类器的训练。这也是为什么Stage的级数越高,NEG count的计数速度就越慢。
4、当某一Stage(n)的FA为0时,则acceptanceRatio(n+1)必为0,也就表示在第n+1级Stage再也取不到一个负样本,此时就会终止训练,并提示:Train dataset for temp stage can not be filled. Branch training terminated.
四、可能出现的问题
1、在用opencv_traincascade训练分类器的时候,遇到了报错如下:
Train dataset for temp stage can not be filled. Branch training terminated.
原因:1)负样本描述文件neg.txt不能带路径名,即 : -bg neg.txt 是合法的, -bg negdata/neg.txt是非法的。所以必须把neg.txt文件跟exe文件放在同一个目录下 ;
2)当切换了操作系统时,会因为txt文件的格式问题而导致了负样本读取失败。比如:在windows操作系统下生出了neg.txt,但是在ubuntu下进行训练,这样就会导致错误,这是因为windows下txt文件换行符’\r’在ubuntu下无法识别。
2、在用opencv_traincascade训练分类器的时候,遇到了报错如下:
“Cascade classifier can’t be trained. Check the used training parameters.”
正样本数量必须大于10,需要重新创建数据集并重新试验。
N 为训练层数
HR 击中率,
FA 虚警,只有当每一层训练的FA低于你的命令中声明的maxfalsealarm数值才会进入下一层训练。
3、如果出现 Parameters can not be written, because file xml/params.xml can not be opened 错误,则自己需要手动创建一个文件夹xml;
4、如果出现如下问题.
Traincascade Error:Bad argument(Can not get new positive sample.The most possible reason is insufficient count of samples in given vec-file.
记得一定要numPos小于vec_file文件里面的数 一般numPos为0.9num_in_vec或者为0.8num_in_vec
没有问题后,等待训练结束会产生一个cascade.xml文件,存在训练好的adaboost级联分类器的模型
5、问题:Required leaf false alarm rate achieved. Branch training terminated.
解析:虚警率已经达标 不再继续训练 ,这里不能说是一个错误,只能说制作出来的xml文件可能较差。
解决办法:先测试一下生成的cascade.xml,如果效果没有达到你的预期,有以下几个解决方案:
1:maxfalsealarm值应该设定到0.4 - 0.5之间
2:正负样本数太少,增大样本数
五、利用训练好的模型进行目标检测
将cascade.xml文件拷贝在工程下,然后加载部分代码如下:
string face_cascade_name = "../cascade.xml";
CascadeClassifier face_cascade;
...
...
void detectAndDisplay(Mat face)
{
std::vector faces;
Mat face_gray;
// cvtColor(face, face_gray, CV_BGR2GRAY); //rgb类型转换为灰度类型
equalizeHist(face, face_gray); //直方图均衡化
face_cascade.detectMultiScale(face_gray, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(1, 1));
for (int i = 0; i < faces.size(); i++){
Point center(faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5);
ellipse(face, center, Size(faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar(255, 0, 0), 2, 7, 0);
}
imshow("行人识别", face);
}
摄像头就可以进行实时检测,当有目标出现时,就会有椭圆框将其圈出。