目标检测分为3个阶段
1. 样本创建
2. 训练分类器
3. 使用训练好的分类器进行目标检测
级联分类器
源地址http://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/objdetect/doc/cascade_classification.html
基于Haar特征的用于目标检测的级联分类器
下面描述的目标检测器最初由PaulViola提出,由RainerLienhart改进,
论文分别是Paul Viola and Michael J.Jones. Rapid Object Detection using a Boosted Cascade of Simple Features. IEEECVPR, 2001. The paper is available online athttp://www.ai.mit.edu/people/viola/
RainerLienhart and Jochen Maydt. An Extended Set of Haar-like Features for RapidObject Detection. IEEE ICIP 2002, Vol. 1, pp. 900-903, Sep. 2002. This paper,as well as the extended technical report, can be retrieved athttp://www.lienhart.de/Publications/publications
首先,一个分类器(这里指的是使用类Haar特征的提升的分类器的级联)由几百个特定的目标(一张脸或一辆汽车)样本训练,这些目标称为正样本,缩放到了相同的大小(比如20x20),训练还需要负样本,相同大小的任意图片
一个分类器训练之后,它可以应用到输入图像的感兴趣的区域(和训练时使用的大小相同)。如果这个区域很可能显示了目标(脸或汽车),分类器输出1,否则输出0.为了在整个图像中搜索目标,可以在整个图片中移动搜索窗口并使用分类器检查每个窗口。考虑到我们感兴趣的目标可能大小不同,分类器被设计为可以很容易的调整大小,这比调整图片的大小要更高效。所以寻找一个未知大小的目标,扫描程序可能需要按照不同大小扫描多次。
分类器名字中的”级联“Cascade指的是最后的分类器包含几个简单的分类器(stage),这些stage依次应用到一个感兴趣的区域,直到某个stage下拒绝这个区域(stage检测为不是目标)或者全部的stage都通过了这个区域,就是说全都检测为目标。”提升“boosted的意思是级联分类器的每一个stage自身是复杂的,是使用某一种提升技巧组合基本的分类器构建而成,当前支持DiscreteAdaboost, Real Adaboost, Gentle Adaboost and
Logitboost 4种。基本分类器是至少有两个叶子的决策树。类Haar特征是基本分类器的输入。当前算法使用下列类Haar特征
一个特定的分类器使用的特征由它检测区域的形状,位置以及大小指定,由浅色区域累加值减去深色区域累加值计算,矩形区域的像素值之和可以用积分图快速计算。
新的C++接口也支持LBP特征。
样本创建和分类器训练
我刚刚发现级联分类器的训练有中文版的官方文档,,,
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/user_guide/ug_traincascade.html
里面对于样本创建和级联分类器的训练描述很具体,里面的辅助工具在配置环境变量的时候已经用过一个,用来验证环境变量配置是否成功,使用的时候直接在命令行里使用即可。
下面是我的操作的记录
我检测的目标是戴眼镜的头部
我的目录结构如图所示,neg文件夹里存放负样本,pos文件夹里存放正样本,正样本是我手动从图片里截出来的,只包含正样本,但是大小不同,所以需要后面使用工具创建样本
①生成bg.txt文件:在0723目录下【新建】cmd.txt文件,写进cmd.exe,然后保存,把后缀名改为bat,双击运行cmd.bat,输入 dir /bneg > bg.txt,得到bg.txt文件
然后使用记事本的【替换】功能,把My全部替换为neg/My,将文件的每一行改为neg/MyPhoto2015_0721_190001.jpg的形式
②生成info.dat文件:根据官网文档,结合我的目录结构,info.dat的每一行应该是
pos/MyPhoto2015_0721_193020.jpg 1 0 0 167 251的形式,我的正样本已经手动裁剪过了,并且每个样本只含有一个数目,所以后面跟着 1 位置是 0 0,但是我的样本大小是不一样的,我的正样本大概有五百个,一个个手动添加太麻烦,我写了几行java程序GetInfo.java(附在本文最后),读取图片的宽高像素,直接写到info.dat文件,里面硬编码了路径,只有一个命令行参数,区分当前是哪个目录,区分是training下的0722目录还是0723目录,使用如下:
新建一个getinfo.bat文件,内容如下:0723就是指定目录
javac GetInfo.java
java GetInfo 0723
pause
双击运行后结果如下:
已经符合要求
③下一步就是创建样本
新建一个create_samples.bat文件,内容如下:
del p.vec
pause
opencv_createsamples -info info.dat -vec p.vec -w 50 -h 50 -show TRUE -num 580
pause
各个参数就是根据官方文档给出的含义设定的
双击运行后如下,可以看到已经生成的p.vec文件
④开始训练,还是创建一个start_traing.bat文件,用批处理文件的好处不用每次训练的时候都要手动敲一长串命令,可以简单修改后重复使用。内容如下:
opencv_traincascade -data ./RESULT -vec p.vec -bg bg.txt -numPos 500 -numNeg 300 -numStages 5 ^
-stageType BOOST -featureType HAAR -w50 -h 50 ^
-bt GAB -minHitRate 0.995 -maxFalseAlarmRate 0.5 -weightTrimRate 0.95 -maxDepth 1 -maxWeakCount 1 ^
-mode ALL > train.log
我把它的输出重定向到了train.log 文件里面
注意^后面不能有多余空格
最好手动创建RESULT文件夹,否则好像会出错,还有训练的参数很多,要理解每个参数的意义,填写正确,比如我当时急于开始训练,没有仔细阅读官方的文档,参数的设置在网上搜了一个就直接套用,程序抛出了一个异常,提示No sufficient samples,就是我的-numPos参数写的是500,当时我的正样本只有300多个,然后就又加了200个样本,然后发现其实是参数设置的问题,,,
我的笔记本内存只有4G,,,,还经常出现内存分配不了的异常,,,,
设置完毕后就可以开始训练了,双击start_traing.bat开始训练!
训练时间一般得几个小时吧,可以看看train.log看一看训练进度,,,
GetInfo.java代码
import java.awt.image.BufferedImage; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import javax.imageio.ImageIO; public class GetInfo { public static void main(String[] args) { if(args.length<1){ System.out.println("需要一个参数"); System.out.println("比如 0722"); System.out.println("生成到 E:\\training\\0722\\info.dat"); return; } File info = new File("E:\\training\\"+args[0]+"\\info.dat"); info.delete(); if(!info.exists()){ try { info.createNewFile(); } catch (IOException e2) { e2.printStackTrace(); } } FileWriter fileWritter=null; try { fileWritter = new FileWriter(info.getAbsolutePath(),true); } catch (IOException e1) { e1.printStackTrace(); } BufferedWriter infoWritter = new BufferedWriter(fileWritter); String path = "E:\\training\\"+args[0]+"\\pos"; File file = new File(path); File[] fileList = file.listFiles(); BufferedImage bi = null; for(int i=0;i<fileList.length;i++){ if(fileList[i].isFile()){ try { bi = ImageIO.read(fileList[i]); } catch (IOException e) { e.printStackTrace(); } int width = bi.getWidth(); int height = bi.getHeight(); try { infoWritter.write("pos/"+fileList[i].getName()+" 1 0 0 "+width+" "+height); infoWritter.newLine(); } catch (IOException e) { e.printStackTrace(); } } } try { infoWritter.flush(); infoWritter.close(); } catch (IOException e) { e.printStackTrace(); } } }
版权声明:本文为博主原创文章,未经博主允许不得转载。