OpenCV中级联分类器Cascade训练方法,实现目标检测
1.OpenCV中级联分类器简介
- OpenCV中可供训练的分类器有两个:
- opencv_haartraining
在其官方文档中提到opencv_haartraining已经是一个过时的应用 - opencv_traincascade
- 该分类器是2.x版本中基于C++写的新版本的分类器。opencv_traincascade支持Haar和LBP。与Haar特征相比,LBP特征是整数特征,因此训练和检测过程都会比Haar特征快几倍。
- LBP和Haar特征用于检测的准确率,是依赖训练过程中的训练数据的质量和训练参数。
- opencv_haartraining
2.准备分类器训练所需的官方工具
-
1)opencv_createsamples
- 用于准备训练数据的正样本和测试样本。
- opencv_createsamples可以生成支持opencv_haartraining和opencv_traincascade分类器的正样本数据。
- 输出文件是以.vec为后缀的包含图像信息的二进制数据类型。
-
2)opencv_traincascade
- 用于训练cascade分类器,注意stage。
-
3)opencv_performance
- 可以用来评估分类器的质量。
- 但仅对opencv_haartraining生成的分类器有效。
注意:所用文件位置:
- 在已配好OpenCV环境的状态下(一般在编译目录下):
- Linux:/usr/local/bin/ 或者 …/opencv-3.4.1/build/bin
- Windows:…\opencv\build\x64\vc12\bin
3.准备训练数据(正样本、负样本)
classifier: 用于存放训练好的.xml文件。需要手动建立,否则最后stage保存会出现问题。
pos: 用于存放正样本图像
neg: 用于存放负样本图像
opencv_creatsamples
opencv_traincascade
pos.txt、neg.txt: 用于存放正负样本图像路径(可不必手动创建,之后自动生成)
-
2)正负样本
-
建议正样本和负样本的数量比例最好在1:2和1:3之间(为了尽可能地保证训练效果,当然不在这个比例之间也可训练),负样本图像中不要包含检测的目标并且要尽可能的多样化
-
将正样本规格化为同一尺寸,并转灰度(最好是8的倍数,我这里用的是32×32,也可以56×56等等,但是不推荐正样本尺寸太大)
-
负样本不需要规格化,但是要保证负样本的尺寸普遍大于正样本的尺寸,实在不放心可以把负样本给规格化了保证都大于正样本的尺寸
-
归一化后正负样本示例
附1:归一化文件名指令:
i=1; for x in *; do mv $x $i.jpg; let i=i+1; done
附2:归一化图像尺寸、灰度cv代码:
#include<iostream> #include<string> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { int imageNum = 29; string Path = "../../pos"; Mat image_src, image_gray; for(int i = 1;i<=imageNum;i++){ string imagePath = cv::format("%s/%d.jpg",Path.c_str(),i); cout<<imagePath<<endl; image_src = cv::imread(imagePath); cv::resize(image_src,image_src,cv::Size(32,32)); cvtColor(image_src, image_gray, CV_BGR2GRAY); imshow("gray",image_gray); imwrite(imagePath,image_gray); cv::waitKey(30); } cout<<"Finish!"<<endl; return 0; }
-
4.训练
-
1)生成正样本描述文件
- 生成正样本路径:
ls pos/*.* > pos.txt
- 替换描述信息:
在pos.txt文本中ctrl+H将所有的jpg替换成jpg 1 0 0 32 32。 jpg后面的五个数字信息分别表示样本中目标个数、目标在图像中的起始位置x、y、样本的尺寸大小width、height。因为我的正样本只包含目标,所以样本的起始位置为0 0。
- 生成正样本路径:
-
利用opencv_creatsamples生成描述文件.vec:
./opencv_createsamples -vec pos.vec -info pos.txt -num 29 -w 32 -h 32
其中-info表示指定的描述文件,-vec指定输出的vec文件名,-w-h表示规定的样本尺寸,-num表示创建的样本数目。
生成了pos.vec即说明成功:
-
2)生成负样本相对路径
注意:与正样本不同,负样本需要的为相对路径,但不需额外信息
ls ./neg/*.* > neg.txt
-
3)利用opencv_traincascade训练生成.xml
sudo ./opencv_traincascade -data classifier -vec pos.vec -bg neg.txt -numStages 20 -minHitRate 0.999 -maxFalseAlarmRate 0.5 -numPos 29 -numNeg 30 -w 92 -h 112 -mode ALL -precalcValBufSize 1024 -precalcIdxBufSize 1024 -featureType LBP
-
data <cascade_dir_name>目录名,如不存在训练程序会创建它,用于存放训练好的分类器。
-
vec <vec_file_name>包含正样本的vec文件名(由opencv_createsamples程序生成)。
-
bg <background_file_name>背景描述文件,也就是包含负样本文件名的那个描述文件。
-
numPos <number_of_positive_samples>每级分类器训练时所用的正样本数目。
-
numNeg <number_of_negative_samples>每级分类器训练时所用的负样本数目,可以大于 -bg 指定的图片数目。
-
numStages <number_of_stages>训练的分类器的级数。
-
precalcValBufSize<precalculated_vals_buffer_size_in_Mb>缓存大小,用于存储预先计算的特征值(feature values),单位为MB。
-
precalcIdxBufSize<precalculated_idxs_buffer_size_in_Mb>缓存大小,用于存储预先计算的特征索引(feature indices),单位为MB。
-
baseFormatSave这个参数仅在使用Haar特征时有效。如果指定这个参数,那么级联分类器将以老的格式存储。
-
其他人经验:(precalcValBufSize和precalcIdxBufSize这两个参数不知道是给整个训练过程分配的内存大小还是每个stage分配的内存大小,还有一些说法说这个是给每个样本分配的内存。。。我尝试设置为1024的时候报错内存不足,后来改成了256就可以了,但是设置得太小会导致训练的时间过长,建议多尝试几次尽量把这两个参数设置得大一点)
训练完毕生成的cascade.xml:
-
5.检测代码(仅供参考)
#include<iostream>
#include<string>
#include<opencv2/opencv.hpp>
#include<opencv2/calib3d/calib3d.hpp>
#define IMAGE_NUM 136
using namespace std;
using namespace cv;
int main() {
Mat image_src, image_gray, image_sample;
static int sample_num = 1;
for(int i = 1;i<=IMAGE_NUM;i++){
string imagePath = cv::format("../src_data/%d.jpg",i);
image_src = imread(imagePath);
cout<<imagePath<<endl;
cvtColor(image_src, image_gray, CV_BGR2GRAY);
equalizeHist(image_gray, image_gray); //直方图均衡化,增加对比度
String face_cascade_name = "../cascade.xml";
CascadeClassifier face_cascade, eyes_cascade; //载入分类器
if (!face_cascade.load(face_cascade_name)) { //加载脸部分类器失败
cout << "Load haarcascade_face.xml failed!" << endl;
return 0;
}
vector<Rect> faceRect;
face_cascade.detectMultiScale(image_gray, faceRect, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30));//检测
for (size_t i = 0; i < faceRect.size(); i++){
rectangle(image_src, faceRect[i], Scalar(0, 0, 255)); //用矩形画出检测到的位置
image_sample = image_gray(faceRect[i]);
string samplePath = cv::format("../data/%d.jpg",sample_num++);
imwrite(samplePath,image_sample); //保存样本
}
cv::imshow("src", image_src); //显示当前帧
string imagePath_save = cv::format("../data/image/%d.jpg",i);
imwrite(imagePath_save,image_src);
cv::waitKey(50);
}
cv::waitKey(0);
return 0;
}