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

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

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

一、准备训练数据

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

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

图片: Alt

在与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 的命令行参数,以用途分组介绍:

  1. 通用参数:
    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特征时有效。如果指定这个参数,那么级联分类器将以老的格式存储。
  2. 级联参数:
    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 );
}


在这里插入图片描述

在这里插入图片描述
原创作品,转载请注明出处。。。。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值