车辆检测(视觉分类器训练) ------------------原创作品
使用同样的方法,你可以训练自己的识别物体。
一、准备训练数据
训练需要一定数量样本。样本分两类:负样本和正样本。负样本是指不包含物体的图像。正样本是需要检测的物体的图像(最好不要包含其他图像,避免正样本干扰,提高训练正确 性),负样本需要手工准备,大小没有要求,只要不要包含需要检测的物体就行。
正样本可以只要几张,其余的可通过 opencv_createsamples 创建,这个函数是怎么创建其他正样本的呢?通过将现有的图像进行X,Y,Z方向的旋转和背景颜色一系列随机变化产生。
开始正题,首先准备训练数据,创建一个cartraining文件夹,所有的训练工作在里面进行。
1、 负样本准备
负样本照片都放在cartraining/negative_gray目录,负样本最好都是用灰度图像,可提高样本训练速度。若你的图像不是灰度图像,请是用我的图像处理程序,原始图片放在negative文件夹中,处理后的图片会在negative_gray文件夹中生成。
// An highlighted block
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <stdio.h>
using namespace cv;
using namespace std;
int main(int argc, char* argv[])
{
Mat src_img,gray_image,src_result;
int i,j=0;
char src_img_name[40]={0},dst_img_name[40]={0};
//负样本图像处理,
INT PICNUM=100; //需修改下面的图片数量
for(i=1;i<=PICNUM;i++)
{
src_img_name[40]={0},dst_img_name[40]={0};
sprintf(src_img_name,"cartraining/negative/%d.jpg",i);
cout<<src_img_name<<endl;
src_img=imread(src_img_name,1);
if(src_img.empty()) continue;
cvtColor( src_img, gray_image, CV_BGR2GRAY );
j++;
sprintf(dst_img_name,"cartraining/negative_gray/neg_%d.jpg",j);
cout<<"write:"<<dst_img_name<<endl;
imwrite(dst_img_name,gray_image);
}
}
图片:
在与negative_gray同一级目录创建neg.txt文件,内容包含里面的每张图片;
创建方法可在windows中使用CMD进入dos,在图像目录下 执行dir /b >neg.txt生成neg.txt文件,neg.txt文件在cartraining目录中。然后再替换成如下形式(图像顺序不管):(特别注意 windows下生成的路径与Linux中斜杠方向是相反的)
2、 正样本准备
下面准备正样本图片,正样本图片必须满足图片尺寸大小相同,一般为30X30大小,且同样为灰度图片,图片中尽量避免其他物体出现。我的正样本图像存放在cartraining/positive_small_size目录。原始准备的彩色图片在cartraining/positive目录,需要使用我的图片处理程序获得正样本图像:
同样方法准备正样本,在windows中使用CMD进入dos,图像目录下 执行dir /b >pos.txt生成pos.txt文件, pos.txt文件在cartraining目录中。与neg.txt不同的是,后面需要跟一些数据参数,数据参数同样使用替换的方式追加。
下面说下参数意义:1 表示该图片只有一个检测图像,0 0 为检测图像起点 30 30 为检测图像长度和宽度。
3、 获得正样本描述文件
使用opencv_createsamples程序获得正样本描述符。opencv_createsamples 函数的参数如下:
• -vec <vec_file_name>
输出文件,也叫正样本描述文件,后面训练需要。内含用于训练的正样本。
• -img <image_file_name>
输入图像文件名(例如一个公司的标志)。
• -bg <background_file_name>
背景图像的描述文件,文件中包含一系列的图像文件名,这些图像将被随机选作物体的背景。
• -num <number_of_samples>
生成的正样本的数目。
• -bgcolor <background_color>
背景颜色(目前为灰度图);背景颜色表示透明颜色。因为图像压缩可造成颜色偏差,颜色的容差可以由 -bgthresh 指定。所有处于 bgcolor-bgthresh 和 bgcolor+bgthresh 之间的像素都被设置为透明像素。
• -bgthresh <background_color_threshold>
• -inv
如果指定该标志,前景图像的颜色将翻转。
• -randinv
如果指定该标志,颜色将随机地翻转。
• -maxidev <max_intensity_deviation>
前景样本里像素的亮度梯度的最大值。
• -maxxangle <max_x_rotation_angle>
X轴最大旋转角度,必须以弧度为单位。
• -maxyangle <max_y_rotation_angle>
Y轴最大旋转角度,必须以弧度为单位。
• -maxzangle <max_z_rotation_angle>
Z轴最大旋转角度,必须以弧度为单位。
• -show
很有用的调试选项。如果指定该选项,每个样本都将被显示。如果按下 Esc 键,程序将继续创建样本但不再显示。
• -w <sample_width>
输出样本的宽度(以像素为单位)。
• -h <sample_height>
输出样本的高度(以像素为单位)。
使用opencv_createsamples命令获取正样本的样本描述符用于训练,命令如下:
./opencv_createsamples –vec pos.vec –info pos.txt –num 507 –w 30 –h 30
参数说明:–vec pos.vec 用于指定样本描述文件的输出名称
–info pos.txt 正样本信息文件
–num 507 正样本数目为507张
–w 30 –h 30 样本宽度和高度都为30
命令结束后,会生产pos.vec文件,下面将用该文件进行训练。
上述命令是在LINUX中的使用,在windows中方法类似。
进行车辆识别所需要用到的资源如下:
'工具组链接:'
https://download.csdn.net/download/xfjy2010/11205487
链接是Linux的训练工具组,包含正样本处理程序(正样本处理程序需要make 命令自己编译)、样本创建程序opencv_createsamples和训练程序opencv_traincascade :
'样本原始资源获取:'
https://download.csdn.net/download/xfjy2010/11205471
如果觉得处理样本图片麻烦,当然也有处理好后的样本图片,可以直接用于训练
处理后的可直接用于训练的样本:'
https://download.csdn.net/download/xfjy2010/11205479
二、样本训练
训练使用opencv_traincascade 命令,opencv_traincascade 的命令行参数,以用途分组介绍:
- 通用参数:
o -data <cascade_dir_name>
目录名,如不存在训练程序会创建它,用于存放训练好的分类器。
o -vec <vec_file_name>
包含正样本的vec文件名(由 opencv_createsamples 程序生成)。
o -bg <background_file_name>
背景描述文件,也就是包含负样本文件名的那个描述文件。
o -numPos <number_of_positive_samples>
每级分类器训练时所用的正样本数目。
o -numNeg <number_of_negative_samples>
每级分类器训练时所用的负样本数目,可以大于 -bg 指定的图片数目。
o -numStages <number_of_stages>
训练的分类器的级数。
o -precalcValBufSize <precalculated_vals_buffer_size_in_Mb>
缓存大小,用于存储预先计算的特征值(feature values),单位为MB。
o -precalcIdxBufSize <precalculated_idxs_buffer_size_in_Mb>
缓存大小,用于存储预先计算的特征索引(feature indices),单位为MB。内存越大,训练时间越短。
o -baseFormatSave
这个参数仅在使用Haar特征时有效。如果指定这个参数,那么级联分类器将以老的格式存储。 - 级联参数:
o -stageType <BOOST(default)>
级别(stage)参数。目前只支持将BOOST分类器作为级别的类型。
o -featureType<{HAAR(default), LBP}>
特征的类型: HAAR - 类Haar特征; LBP - 局部纹理模式特征。
o -w
o -h
训练样本的尺寸(单位为像素)。必须跟训练样本创建(使用 opencv_createsamples 程序创建)时的尺寸保持一致。
该命令参数很多,用到的就那么几个。
在cartraining目录下执行:
./opencv_traincascade –data xml –vec pos.vec –bg neg.txt –numPos 400 –numNeg 1044 –w 30 –h 30
参数说明:
–data xml 训练产生的数据文件到xml文件夹下,在该文件夹下将产生所需的分类器文件,
–vec pos.vec 正样本描述文件
–bg neg.txt 负样本的信息文件
–numPos 400 正样本数目 (注意:数目最好是你所准备的正样本数目的80%左右,原因在于每高一层训练,算法会增加正样本数目,不然后面训练的层级高了以后,就会报错)
–numNeg 1044 负样本数目
–w 30 –h 30 训练样本长宽,必须和创建的正样本一致。
执行训练命令,训练时间为大约为6个小时的样子,训练层级为19层,在xml文件夹下将产生分类器,cascade.xml为我们最终需要的分类器,其他的是中间层级产生的分类器文件。若训练中途断掉,可以使用它们接着训练。
训练好的XML分类器下载:'
https://download.csdn.net/download/xfjy2010/11205491
三、效果检验:
正样本数量507张,负样本1044张,得到的分类器效果还是不错,若要达到更好的效果,可以提高样本数量,训练层级也进行提高。
测试代码如下:
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
static void help()
{
cout << "\nThis program demonstrates the use of cv::CascadeClassifier class to detect objects (Face + eyes). You can use Haar or LBP features.\n"
"This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.\n"
"It's most known use is for faces.\n"
"Usage:\n"
"./facedetect [--cascade=<cascade_path> this is the primary trained classifier such as frontal face]\n"
" [--nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]\n"
" [--scale=<image scale greater or equal to 1, try 1.3 for example>]\n"
" [--try-flip]\n"
" [filename|camera_index]\n\n"
"see facedetect.cmd for one call:\n"
"./facedetect --cascade=\"../../data/haarcascades/haarcascade_frontalface_alt.xml\" --nested-cascade=\"../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml\" --scale=1.3\n\n"
"During execution:\n\tHit any key to quit.\n"
"\tUsing OpenCV version " << CV_VERSION << "\n" << endl;
}
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip );
string cascadeName;
string nestedCascadeName;
int main( int argc, const char** argv )
{
VideoCapture capture;
Mat frame, image;
string inputName;
bool tryflip;
CascadeClassifier cascade, nestedCascade;
double scale;
cv::CommandLineParser parser(argc, argv,
"{help h||}"
"{cascade|../../../data/haarcascades/haarcascade_frontalface_alt.xml|}"
"{nested-cascade|../../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|}"
//hogcascade_pedestrians
"{scale|1|}{try-flip||}{@filename||}"
);
if (parser.has("help"))
{
help();
return 0;
}
cascadeName = parser.get<string>("cascade");
nestedCascadeName = parser.get<string>("nested-cascade");
scale = parser.get<double>("scale");
if (scale < 1)
scale = 1;
tryflip = parser.has("try-flip");
if(tryflip){
cout<<"try_flip"<<endl;
}
inputName = parser.get<string>("@filename");
if (!parser.check())
{
parser.printErrors();
return 0;
}
if ( !nestedCascade.load( nestedCascadeName ) )
cerr << "WARNING: Could not load classifier cascade for nested objects" << endl;
cascadeName="cartraining/xml/cascade.xml";
if( !cascade.load( cascadeName ) )
{
cerr << "ERROR: Could not load classifier cascade" << endl;
help();
return -1;
}
//image = imread( "lena.jpg", 1 );
image = imread( "cartraining/test/timg2.jpg", 1 );
if(image.empty()) cout << "Couldn't read ../data/lena.jpg" << endl;
if( capture.isOpened() )
{
cout << "Video capturing has been started ..." << endl;
for(;;)
{
capture >> frame;
if( frame.empty() )
break;
Mat frame1 = frame.clone();
detectAndDraw( frame1, cascade, nestedCascade, scale, tryflip );
char c = (char)waitKey(10);
if( c == 27 || c == 'q' || c == 'Q' )
break;
}
}
else
{
cout << "Detecting face(s) in " << inputName << endl;
if( !image.empty() )
{
detectAndDraw( image, cascade, nestedCascade, scale, tryflip );
waitKey(0);
}
else if( !inputName.empty() )
{
/* assume it is a text file containing the
list of the image filenames to be processed - one per line */
FILE* f = fopen( inputName.c_str(), "rt" );
if( f )
{
char buf[1000+1];
while( fgets( buf, 1000, f ) )
{
int len = (int)strlen(buf);
while( len > 0 && isspace(buf[len-1]) )
len--;
buf[len] = '\0';
cout << "file " << buf << endl;
image = imread( buf, 1 );
if( !image.empty() )
{
detectAndDraw( image, cascade, nestedCascade, scale, tryflip );
char c = (char)waitKey(0);
if( c == 27 || c == 'q' || c == 'Q' )
break;
}
else
{
cerr << "Aw snap, couldn't read image " << buf << endl;
}
}
fclose(f);
}
}
}
return 0;
}
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip )
{
double t = 0;
vector<Rect> faces, faces2;
const static Scalar colors[] =
{
Scalar(255,0,0),
Scalar(255,128,0),
Scalar(255,255,0),
Scalar(0,255,0),
Scalar(0,128,255),
Scalar(0,255,255),
Scalar(0,0,255),
Scalar(255,0,255)
};
Mat gray, smallImg;
cvtColor( img, gray, COLOR_BGR2GRAY );
double fx = 1 / scale;
resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT );
equalizeHist( smallImg, smallImg );
t = (double)getTickCount();
cascade.detectMultiScale( smallImg, faces,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
|CASCADE_SCALE_IMAGE,
Size(30, 30) );
if( tryflip )
{
flip(smallImg, smallImg, 1);
cascade.detectMultiScale( smallImg, faces2,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
|CASCADE_SCALE_IMAGE,
Size(30, 30) );
for( vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end(); ++r )
{
faces.push_back(Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
}
}
t = (double)getTickCount() - t;
printf( "detection time = %g ms\n", t*1000/getTickFrequency());
for ( size_t i = 0; i < faces.size(); i++ )
{
Rect r = faces[i];
Mat smallImgROI;
vector<Rect> nestedObjects;
Point center;
Scalar color = colors[i%8];
int radius;
double aspect_ratio = (double)r.width/r.height;
if( 0.75 < aspect_ratio && aspect_ratio < 1.3 )
{
center.x = cvRound((r.x + r.width*0.5)*scale);
center.y = cvRound((r.y + r.height*0.5)*scale);
radius = cvRound((r.width + r.height)*0.25*scale);
circle( img, center, radius, color, 3, 8, 0 );
}
else
rectangle( img, cvPoint(cvRound(r.x*scale), cvRound(r.y*scale)),
cvPoint(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)),
color, 3, 8, 0);
if( nestedCascade.empty() )
continue;
smallImgROI = smallImg( r );
nestedCascade.detectMultiScale( smallImgROI, nestedObjects,
1.1, 2, 0
//|CASCADE_FIND_BIGGEST_OBJECT
//|CASCADE_DO_ROUGH_SEARCH
//|CASCADE_DO_CANNY_PRUNING
|CASCADE_SCALE_IMAGE,
Size(30, 30) );
for ( size_t j = 0; j < nestedObjects.size(); j++ )
{
Rect nr = nestedObjects[j];
center.x = cvRound((r.x + nr.x + nr.width*0.5)*scale);
center.y = cvRound((r.y + nr.y + nr.height*0.5)*scale);
radius = cvRound((nr.width + nr.height)*0.25*scale);
circle( img, center, radius, color, 3, 8, 0 );
}
}
imshow( "result", img );
}
原创作品,转载请注明出处。。。。