OpenCV 之 HaarTraining 算法剖析

来源:互联网 发布:python 10分钟 编辑:程序博客网 时间:2024/06/11 17:50

1.总体框架

要训练一个Haar分类器,总体上包括3步:1)准备正负样本;2)用CreateSamples程序建正样本集;3)用HaarTraining程序训练,得到最终的分类器模型(xml文件)。

2.样本准备

HaarTraining需要使用正样本和负样本进行训练。下面分别进行描述。

2.1 正样本

对于正样本,通常的做法是先把所有正样本裁切好,并对尺寸做规整(即缩放至指定大小)。

由于HaarTraining训练时输入的正样本是vec文件,所以需要使用OpenCV自带的CreateSamples程序将准备好的正样本转换为vec文件。转换的步骤如下:

1) 制作一个正样本描述文件,用于描述正样本文件名(包括绝对路径或相对路径),正样本数目以及各正样本在图片中的位置和大小。典型的正样本描述文件如下:
face_100/face00001.bmp 1 0 0 20 20
face_100/face00002.bmp 1 0 0 20 20
face_100/face00003.bmp 1 0 0 20 20

2) 运行CreateSamples程序。如果直接在VC环境下运行,可以在Project\Settings\Debug属性页的Program arguments栏设置运行参数。下面是一个运行参数示例:
-info F:\FaceDetect\samples.dat -vec F:\FaceDetect\samples.vec -num 200 -w 20 -h 20
表示有200个样本,样本宽20,高20,正样本描述文件为samples.dat,结果输出到samples.vec。

3) 运行完了会生成一个*.vec的文件。该文件包含正样本数目,宽高以及所有样本图像数据。

2.2 负样本

负样本图像可以是不含有正样本模式的任何图像,比如一些风景照等。训练时,OpenCV需要一个负样本描述文件,该文件只需包含所有负样本的文件名及绝对(或相对)路径名。以下是一个负样本描述文件内容示例:
nonface_200/00001.bmp
nonface_200/00002.bmp
nonface_200/00003.bmp


负样本描述文件的生成方法可参照正样本描述文件生成方法。


负样本图像的大小只要不小于正样本就可以,在使用负样本时,OpenCV自动从负样本图像中抠出一块和正样本同样大小的区域作为负样本,具体可查看函数icvGetNextFromBackgroundData()。具体抠图过程为:
1) 确定抠图区域的左上角坐标(Point.x, Point.y)
2) 确定一个最小缩放比例,使得原负样本图像缩放后恰好包含选中负样本区域
3) 对原负样本图象按计算好的缩放比例进行缩放


3.训练

准备好正样本集(即samples.vec文件),负样本集及其描述文件后,就可以开始训练了。

在VC中进行调试,我在调试时设置的参数如下:

-data F:\FaceDetect\trainout -vec F:\FaceDetect\samples.vec -bg F:\FaceDetect\negatives.dat -nstages 3 -nsplits 2 -minhitrate 0.999 -maxfalsealarm 0.5 -npos 100 -nneg 200 -w 20 -h 20 -mem 512 -eqw 1 -mode ALL -bt GAB -minpos 50

为调试方便,正负样本数目用的非常少,正样本100个,负样本200个。


3.1 创建Haar特征

函数icvCreateIntHaarFeatures( winsize, mode, symmetric )负责创建所有可能的Haar特征。Mode决定使用基本的5种特征还是所有upright特征抑或所有特征。Symmetric为1时表示只创建Haar特征的中心在左半部分的所有特征,为0时创建所有特征。当训练人脸图像时,由于人脸的左右对称性可以设置Symmetric为1,以加速训练。

3.2 载入正样本

int icvGetHaarTrainingDataFromVec( CvHaarTrainingData* data, int first, int count,
CvIntHaarClassifier* cascade,
const char* filename,
int* consumed )

函数icvGetHaarTrainingDataFromVec()负责从正样本集*.vec文件中载入count个正样本。在程序第一次运行到此(即训练第一个分类器之前)时,只要正样本集中有count个样本,就一定能取出count个正样本。在以后运行到此时,有可能取不到count个样本,因为必须是用前面的级联强分类器分类为正样本(即分类正确的样本)的样本才会被取出作为下一个强分类器训练样本,具体可参考icvGetHaarTrainingData和icvEvalTreeCascadeClassifierFilter函数。
传递返回值的Consumed参数表示为取count个正样本,查询过的正样本总数。
此外,函数内还通过调用icvGetAuxImages计算积分图像。


3.3 载入负样本

int icvGetHaarTrainingDataFromBG( CvHaarTrainingData* data, int first, int count,
CvIntHaarClassifier* cascade, double* acceptance_ratio )

函数icvGetHaarTrainingDataFromBG ()负责从负样本集中载入count个负样本。在程序第一次运行到此(即训练第一个分类器之前)时,只要负样本集中有count个样本,就一定能取出count个负样本。在以后运行到此时,有可能取不到count个样本,因为必须是用前面的级联强分类器分类为正样本的样本(即分类错误的样本)才会被取出作为下一个强分类器训练样本,具体可参考icvGetHaarTrainingDataFromBG和icvEvalTreeCascadeClassifierFilter函数。
传递返回值的acceptance_ratio参数记录的是实际取出的负样本数与查询过的负样本数之比(acceptance_ratio = ((double) count) / consumed_count),也就是虚警率,用于判断已训练的级联分类器是否达到指标,若达到指标,则停止训练过程。
此外,函数内还通过调用icvGetAuxImages计算积分图像。

注意函数icvGetHaarTrainingDataFromBG中一个主要的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亿。

当正负样本顺利载入,屏幕上会出现类似下面的输出界面:


含义:
POS(正样本): 取出的正样本数目查询过的正样本数目 两者之比
NEG(负样本): 取出的负样本数目查询过的负样本数目 两者之比


上面的输出由下面两行代码得到:
{
printf( "POS: %d %d %f\n", poscount, consumed, ((double) poscount)/consumed );
printf( "NEG: %d %g\n", negcount, false_alarm );
// false_alarm = ((double) negcount) / consumed_negcount;
}


3.4 计算Haar特征值

void icvPrecalculate( CvHaarTrainingData* data, CvIntHaarFeatures* haarFeatures,
int numprecalculated )

函数icvPrecalculate ()负责计算所有取出的正负样本的前numprecalculated个Haar特征值(由icvGetTrainingDataCallback实现),并且对每种特征,将所有样本标号按其特征值升序排序(由cvGetSortedIndices实现,每种特征分别排序)。

Numprecalculated的计算公式如下:
numprecalculated = (int) ( ((size_t) mem) * ((size_t) 1048576) /
( ((size_t) (npos + nneg)) * (sizeof( float ) + sizeof( short )) ) );
其中mem 是内存大小,以M为单位,1048576=1024*1024,表示1M字节。sizeof( float )为保存一个特征值需占用的字节数,sizeof( short )表示对特征值排序后保存一个排序序号需占用的字节数。


3.5 训练一个强分类器

CvIntHaarClassifier* icvCreateCARTStageClassifier( CvHaarTrainingData* data,
CvMat* sampleIdx,
CvIntHaarFeatures* haarFeatures,
float minhitrate,
float maxfalsealarm,
int symmetric,
float weightfraction,
int numsplits,
CvBoostType boosttype,
CvStumpError stumperror,
int maxsplits )

函数icvCreateCARTStageClassifier负责训练一个强分类器。


3.6 保存强分类器信息到临时文件中

函数icvSaveStageHaarClassifier负责将新训练得到的强分类器信息保存到临时文件AdaBoostCARTHaarClassifier.txt中。icvSaveStageHaarClassifier首先根据当前强分类器在级联强分类器中序号(cur_node->idx)在dirname目录下创建一个文件夹,然后在该文件夹下创建AdaBoostCARTHaarClassifier.txt文件,并将强分类器信息写到这个文件中。


3.7 将级联强分类器信息写到一个XML文件中

首先是从先前保存的临时文件中读取级联强分类器信息(cascade = cvLoadHaarClassifierCascade( dirname, cvSize(winwidth,winheight) ) )然后用cvSave( xml_path, cascade )将级联强分类器信息保存到xml文件中。至此,整个训练部分完毕。

3.8 测试最终分类器性能

这部分工作通过简单地调用两个函数实现。调用icvGetHaarTrainingDataFromVec测试检出率;通过调用icvGetHaarTrainingDataFromBG测试虚警率。

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 烦自己的孩子想弄死他怎么办 楼道经常有小孩在门口恶作剧怎么办 2岁宝宝咳嗽咳吐了怎么办 不小心把鱼刺吃下去该怎么办 儿子在学校被老师甩耳光我该怎么办 家长诬陷老师打她家孩子耳光怎么办 一岁宝宝总觉得有痰怎么办 一岁宝宝感冒咳嗽有痰怎么办 哭的时候踹不过气来应该怎么办 孩子在幼儿园被老师罚站怎么办 孩子屁股打肿了又红又紫怎么办 生完孩子两个月一直浑身疼怎么办 宝宝被蚊子叮咬后红肿硬怎么办 小孩屁股青一块紫一块打的怎么办 小孩的手被鞭子抽红了怎么办 孩子每次写作业都要挨打挨骂怎么办 儿子四岁脾气特别大怎么办呢 月子里屁股被开水烫了怎么办 学生打闹家长只找老师责任怎么办 两个学生打闹受伤的孩子家长怎么办 小孩学习不好做家长的该怎么办 对学习不入门的小孩家长该怎么办 孩子老做作业发神上课不专心怎么办 儿子成绩考得差不专心未来怎么办 五年级学生写字慢又丑怎么办 宝宝上课坐不住不听老师话怎么办 三岁宝宝特调皮打他还还手怎么办 怀孕40天不知道喝酒了怎么办 怀孕四十天的时候喝酒抽烟了怎么办 宝宝怀孕三十天左右喝酒了怎么办 两个人都喝酒了意外怀孕怎么办 不知道自己怀孕了喝了很多酒怎么办 不知道自己怀孕了喝了一次酒怎么办 我儿子11岁了有多动症怎么办 面对老师的冷暴力家长该怎么办? 面对无德的老师家长该怎么办 如果你家长屏蔽老师老师该怎么办 小孩出完水痘后身上出现疱疹怎么办 脑子里兴奋的头疼怎么办 吃什么药 一个月宝宝异常兴奋不睡觉怎么办 四个月宝宝晚上兴奋不睡觉怎么办