车牌识别--提取HOG特征

最近在研究车牌识别,利用到Opencv的ml.hpp,里面实现了各种机器学习算法,包括harrcascade、traincascade、支持向量机SVM提取HOG梯度向量特征,人工神经网络等等,而车牌识别又分为检测车牌位置和车牌号码识别。
车牌识别的几个步骤:
一、准备样本集(车牌、非车牌)
二、训练样本数据(SVM 支持向量机 提取HOG特征)
三、利用训练得到的分类器去分类车牌(Classify 分类器)
四、准备样本集(车牌号码)
五、训练样本数据(神经网络)
六、利用训练得到的分类器去识别车牌


一、首先要准备样本集

已经上传到CSDN,各位客官可以自行下载。(CSDN版本更新,上传的资源最低设置2分,不能设置为0分的,如果客官没有积分,把邮箱留下,我发给你)
正负样本:http://download.csdn.net/my/uploads
正样本:车牌
车牌识别--提取HOG特征_第1张图片

负样本:非车牌、干扰因素
车牌识别--提取HOG特征_第2张图片

样本数量:正负:1401 : 2175
样本大小:136*36 (宽高不固定,但要切合实际比例)

二、训练样本数据(SVM 支持向量机)

注意:样本数据路径不能为中文
这里提供一个SVM在线学习网,是一位台湾的大神写的,不用,里面介绍了SVM训练的各种参数含义
http://www.csie.ntu.edu.tw/~cjlin/libsvm/

1、环境搭建:
Opencv3.3.0
VS 2017 可以看我前面的博客:VS2017 配置 Opencv3.3.0环境
opencv3.3.0只支持X64位

2、读取样本图片:
用Linux 系统下的dirent.h文件,挺简单的,提供一下学习的链接C++查找文件
如果本地没有这个文件的话,需要去github上找到这个文件,下载clone到项目路径里就可以了。
提供的链接:dirent.h

//获取样本数据路径
vector<string> getFiles(const string folder) {
    vector<string> files;
    list<string> subfolders;
    subfolders.push_back(folder);
#ifdef WIN32
    while (!subfolders.empty()) {
        std::string current_folder(subfolders.back());
        if (*(current_folder.end() - 1) != '/') {
            current_folder.append("/*");
        }
        else {
            current_folder.append("*");
        }
        subfolders.pop_back();
        struct _finddata_t file_info;
        auto file_handler = _findfirst(current_folder.c_str(), &file_info);
        while (file_handler != -1) {
            //在Linux系统上文件目录第一、第二个文件都是'.' 和 '..' 省略掉。
            if ((!strcmp(file_info.name, ".") || !strcmp(file_info.name, ".."))) {
                if (_findnext(file_handler, &file_info) != 0) break;
                continue;
            }
            //判断是否是子目录
            if (file_info.attrib & _A_SUBDIR) {
                std::string folder(current_folder);
                folder.pop_back();
                folder.append(file_info.name);
                subfolders.push_back(folder.c_str());
            }
            else {
                std::string file_path;
                file_path.assign(current_folder.c_str()).pop_back();
                file_path.append(file_info.name);
                files.push_back(file_path);
            }
            if (_findnext(file_handler, &file_info) != 0) break;
        }
        _findclose(file_handler);
    }
#else
    while (!subfolders.empty()) {
        string current_folder(subfolders.back());
        if (*(current_folder.end() - 1) != '/') {
            current_folder.push_back('/');
        }
        DIR* pdir = opendir(current_folder.c_str());
        subfolders.pop_back();
        if (!pdir) {
            continue;
        }
        dirent* dir = NULL;
        while ((dir = readdir(pdir)) != NULL) {
            struct stat st;
            //在Linux系统上文件目录第一、第二个文件都是'.' 和 '..' 省略掉。
            if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) {
                continue;
            }
            if (!strcmp(dir->d_name, ".DS_Store")) {
                continue;
            }
            std::string file_path;
            file_path.append(current_folder.c_str());
            file_path.append(dir->d_name);

            if (lstat(file_path.c_str(), &st) < 0) {
                continue;
            }
            if (S_ISDIR(st.st_mode)) {
                std::string subfolder(current_folder);
                subfolder.append(dir->d_name);
                subfolders.push_back(subfolder.c_str());
            }
            else {
                files.push_back(file_path);
            }
        }
        closedir(pdir);
    }
#endif
    return files;
}

3、创建SVM支持向量机,训练样本数据:
关于什么是HOG,什么又是梯度向量特征提取可以看我博客
深入浅出理解HOG特征—梯度方向直方图

/**--------------SVM(支持向量机)分类器训练与测试--------------------
*  SVM:通常用来进行模式识别、分类以及回归分析
*  使用SVM对于查找到的车牌号区域进行判断,进一步确定定位车牌号区域正确
*
*  流程:
*   1、加载全部样本(训练数据) 训练数据有正确的,有错误的。也就是分为车牌区域与非车牌区域
*   2、创建样本数据标签,标记对应样本分类(正确/错误)
*   3、训练、保存
*/
//正样本路径
char * SVM_POS;
//负样本路径
char * SVM_NEG;
//训练得到HOG特征数据保存路径
char * SVM_XML;
typedef struct TrainStruct {
    string file;  //正负样本路径
    int label;  //正负样本标识
};
/*
    提取HOG特征(像素梯度变化率)

*/
void getSvmHOGFeatures(Mat src, Mat & feature) {

    // 参数含义:窗口大小 Size(128, 64) ;  Size(16, 16):块大小,目前只支持Size(16, 16) ;
    //Size(8, 8):块的滑动步长,大小只支持是单元格cell_size大小的倍数
    //cell大小 Size(8, 8)目前只支持Size(8, 8);
    //nbins,方向分箱的个数 n表示在一个胞元(cell)中统计梯度的方向数目,例如箱子=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为360/9=40度
    HOGDescriptor hog(Size(128, 64), Size(16, 16), Size(8, 8), Size(8, 8), 9);
    std::vector<float> descriptor;

    //    Size dsize = Size(128c,64);
    Mat trainImg = Mat(hog.winSize, CV_32S);
    resize(src, trainImg, hog.winSize);

    //计算读入的图片的Hog特征
    hog.compute(trainImg, descriptor, Size(8, 8));
    Mat mat_featrue(descriptor);
    mat_featrue.copyTo(feature);
}
void tranSVMDATA() {
    printf("prepare tain  SVM car classify model...\n");
    //创建SVM
    Ptr  classifier = SVM::create();
    //设置SVM种类
    classifier->setType(SVM::C_SVC);//惩罚因子C,默认为0
                                    //设置采用的核函数
                                    //SVM::POLY默认值,当数据量比较大的时候 VM::POLY 是个不错的选择
    classifier->setGamma(SVM::SIGMOID);
    //设置 C-SVC, epsilon-SVR, and nu-SVR的惩罚因子数(优化),默认值是0
    classifier->setC(100);
    /*
    终止条件类型
    TermCriteria::Type 有三个:COUNT, EPS or COUNT + EPS
    TermCriteria::Type::COUNT  小于最大迭代次数或者元素数
    TermCriteria::Type::MAX_ITER 同上
    TermCriteria::Type::EPS 当达到所要求的精度或者参数变化是即停止迭代
    //设置终止条件    ,最大迭代10W次
    //所要求精度:是0.0001 就是 0.01%
    //classifier->setTermCriteria(TermCriteria(TermCriteria::Type::COUNT,10*10000,0.0001));

    cvTermCriteria同样设置终止条件
    Type::有三个
    CV_TERMCRIT_ITER    小于最大迭代次数或者元素数
    CV_TERMCRIT_NUMBER  同上
    CV_TERMCRIT_EPS     当达到所要求的精度或者参数变化是即停止迭代
    */
    // 训练的终止条件,迭代20000次,
    //0.00001: 迭代过程中,允许输入数据进行一定的变异,这个变异的比率最高要求的精度是0.0001 ,0.01%
    classifier->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001));
    //计算训练所用时长
    auto time_start = cv::getTickCount();
    //存储样本数据的路径和标志(正样本为1,负样本为-1)
    vector svm_data;
    Mat src_mat;
    //收集正样本
    vector<string> pos_files = getFiles(SVM_POS);
    for (auto file : pos_files) {
        svm_data.push_back({ file,1 });
    }
    //收集负样本
    vector<string> neg_files = getFiles(SVM_NEG);
    for (auto file : neg_files) {
        svm_data.push_back({ file,-1 });
    }
    Mat samples;
    vector<int> responses;
    //读取数据
    for (auto data : svm_data) {
        //灰度图方式读取  0灰度  1彩色
        Mat img = imread(data.file, 0);
        //剔除无用的样本
        if (!img.data) {
            printf("load imge failed  image: %s.\n", data.file.c_str());
            continue;
        }
        //二值化
        threshold(img, img, 0, 255, THRESH_BINARY + THRESH_OTSU);
        Mat feature;
        //获取HOG 特征,
        getSvmHOGFeatures(img,feature);
        // 修改图片的通道和行列数,其实是将一个图片的矩阵变成一个行向量
        //[[1, 2, 3],
        // [4, 5, 6],
        // [7, 8, 9]]
        //转变成[1,2,3,4,5,6,7,8,9]
        feature = feature.reshape(1, 1);
        // 将一维向量叠加,叠加成一个二维的向量
        //param m Added line(Mat).
        samples.push_back(feature);
        //相应把正负样本标签贴上
        responses.push_back(data.label);
    }
// 训练数据的格式,OpenCV规定 samples 中的数据都是需要32位浮点型
// 因为TrainData::create 第一个参数是规定死的要cv_32F
    samples.convertTo(samples, CV_32FC1);
    // samples 将图片和样本标签合并成为一个训练集数据
    // 第二个参数的原因是,我们的samples 中的每个图片数据的排列都是一行
    auto train_data = TrainData::create(samples, SampleTypes::ROW_SAMPLE, responses);
    auto time_end = cv::getTickCount();
    auto time_per_frame = (time_end - time_start) / cv::getTickFrequency();
    printf("prepare train data  time : %3.5f\n", time_per_frame);
    time_start = cv::getTickCount();

    //线性内核,训练
    //参数除了最后一个true,其他均为默认值
    //最后一个为true 的原因:则会创建更平衡的验证子集 也就是如果是2类分类的话能得到更准确的结果
    classifier->trainAuto(train_data, 10, SVM::getDefaultGrid(SVM::C),
        SVM::getDefaultGrid(SVM::GAMMA), SVM::getDefaultGrid(SVM::P),
        SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF),
        SVM::getDefaultGrid(SVM::DEGREE), true);
    //    classifier->train(trainning_mat,ROW_SAMPLE,label); //SVM训练
    classifier->save(SVM_XML);
    time_end = cv::getTickCount();
    time_per_frame = (time_end - time_start) / cv::getTickFrequency();
    printf("train finished  time: %3.5f ,model save : %s  \n", time_per_frame, SVM_XML);
}

三、利用训练得到的分类器去分类车牌(XML) 花了将近一个多小时训练 o(╥﹏╥)o

车牌识别--提取HOG特征_第3张图片

车牌识别--提取HOG特征_第4张图片

至此,SVM HOG特征训练完成。先码到这里,剩下的几个步骤看后面再找个时间补上。
四、准备样本集(车牌号码)
五、训练样本数据(神经网络)
六、利用训练得到的分类器去识别车牌

同时将我的资源提供给大家
HOG_SVM_DATA.XML文件

在这段时间的学习里,看到几篇深入学习HOG特征和梯度向量的外国文档,感觉写的不错,分享给各位客官。

https://hal.inria.fr/inria-00548512/document/
https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients
https://software.intel.com/en-us/ipp-dev-reference-histogram-of-oriented-gradients-hog-descriptor
http://www.learnopencv.com/histogram-of-oriented-gradients
http://www.learnopencv.com/handwritten-digits-classification-an-opencv-c-python-tutorial

你可能感兴趣的:(c++,opencv,计算机视觉)