车辆检测(视觉分类器训练)

    xiaoxiao2024-12-18  57

    车辆检测(视觉分类器训练) ------------------原创作品

    使用同样的方法,你可以训练自己的识别物体。

    一、准备训练数据

    训练需要一定数量样本。样本分两类:负样本和正样本。负样本是指不包含物体的图像。正样本是需要检测的物体的图像(最好不要包含其他图像,避免正样本干扰,提高训练正确 性),负样本需要手工准备,大小没有要求,只要不要包含需要检测的物体就行。

    正样本可以只要几张,其余的可通过 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 ); }

    原创作品,转载请注明出处。。。。

    最新回复(0)