车牌识别EasyPR(3)——SVM模型判断车牌

13 篇文章 19 订阅
订阅专栏

     本文开始分析车牌定位模块后续步骤的车牌判断模块。车牌判断模块是EasyPR中的基于机器学习模型的一个模块,这个模型就是SVM(支持向量机)。
  我们已经知道,车牌定位模块的输出是一些候选车牌的图片。但如何从这些候选车牌图片中甄选出真正的车牌,就是通过SVM模型判断/预测得到的。

输入:       

 

输出:          

      简单来说,EasyPR的车牌判断模块就是将候选车牌的图片一张张地输入到SVM模型中,然后问它,这是车牌么?如果SVM模型回答不是,那么就继续下一张,如果是,则把图片放到一个输出列表里。最后把列表输入到下一步处理。由于EasyPR使用的是列表作为输出,因此它可以输出一副图片中所有的车牌,不像一些车牌识别程序,只能输出一个车牌结果。

           

 

                                                                      图1 EasyPR输出多个车牌

  现在,让我们一步步地,进入这个SVM模型的核心看看,它是如何做到判断一副图片是车牌还是不是车牌的?本文主要分为三个大的部分:

  1. SVM应用:描述如何利用SVM模型进行车牌图片的判断。
  2. SVM训练:说明如何通过一系列步骤得到SVM模型。
  3. SVM调优:讨论如何对SVM模型进行优化,使其效果更加好。

一、SVM应用

  人类是如何判断一个张图片所表达的信息呢?简单来说,人类在成长过程中,大脑记忆了无数的图像,并且依次给这些图像打上了标签,例如太阳,天空,房子,车子等等。你们还记得当年上幼儿园时的那些教科书么,上面一个太阳,下面是文字。图像的组成事实上就是许多个像素,由像素组成的这些信息被输入大脑中,然后得出这个是什么东西的回答。

        我们在SVM模型中一开始输入的原始信息也是图像的所有像素,然后SVM模型通过对这些像素进行分析,输出这个图片是否是车牌的结论。SVM模型处理的是最简单的情况,它只要回答是或者不是这个“二值”问题,比从许多类中检索要简单很多。

  我们可以看一下SVM进行判断的代码:

int CPlateJudge::plateJudge(const vector<Mat>& inVec,
                                  vector<Mat>& resultVec)
{
    int num = inVec.size();
    for (int j = 0; j < num; j++)
    {
        Mat inMat = inVec[j];
        Mat p = histeq(inMat).reshape(1, 1);//做直方图均衡,把图像矩阵变成1行
        p.convertTo(p, CV_32FC1);
        int response = (int)svm.predict(p);
        if (response == 1)
        {
            resultVec.push_back(inMat);
        }
    }
    return 0;
}

  reshape函数比较有意思,它既可以改变矩阵的通道数,又可以对矩阵元素进行序列化,非常有用的一个函数。

Mat Mat::reshape(int cn, int rows=0) const

参数比较少,但设置的时候却要千万小心。

  1. cn: 表示通道数(channels), 如果设为0,则表示保持通道数不变,否则则变为设置的通道数。
  2. rows: 表示矩阵行数。 如果设为0,则表示保持原有的行数不变,否则则变为设置的行数。

SVM判断过程 :  

        首先我们读取这幅图片,然后把这幅图片转为1行的行的行向量,接着调用svm的方法predict;perdict方法返回的值是1的话,就代表是车牌,否则就不是。

      svm的perdict方法的输入是待预测数据的特征,也称之为features。在这里,我们输入的特征是图像全部的像素。由于svm要求输入的特征应该是一个向量,而Mat是与图像宽高对应的矩阵,因此在输入前我们需要使用reshape(1,1)方法把矩阵拉伸成向量。除了全部像素以外,也可以有其他的特征,具体看第三部分“SVM调优”。

        predict方法的输出是float型的值,我们需要把它转变为int型后再进行判断。如果是1代表就是车牌,否则不是。这个"1"的取值是由你在训练时输入的标签决定的。标签,又称之为label,代表某个数据的分类。
  以上就是svm模型判断的全过程。事实上,在你使用EasyPR的过程中,这些全部都是透明的。你不需要转变图片格式,也不需要调用svm模型preditct方法,这些全部由EasyPR在内部调用。

我们应该做什么?

      这里的关键在于CvSVM这个类。我在前面的机器学习论文中介绍过,机器学习过程的步骤就是首先你搜集大量的数据,然后把这些数据输入模型中训练,最后再把生成的模型拿出来使用。
  训练和预测两个过程是分开的。也就是说你们在使用EasyPR时用到的CvSVM类是我在先前就训练好的。我是如何把我训练好的模型交给各位使用的呢?CvSVM类有个方法,把训练好的结果以xml文件的形式存储,我就是把这个xml文件随EasyPR发布,并让程序在执行前先加载好这个xml。这个xml的位置就是在文件夹Model下面--svm.xml文件。

                                                   

  如果看CPlateJudge的代码,在构造函数中调用了LoadModel()这个方法。

CPlateJudge::CPlateJudge()
{
    //cout << "CPlateJudge" << endl;
    m_path = "model/svm.xml";
    LoadModel();
}

LoadModel()方法的主要任务就是装载model文件夹下svm.xml这个模型。

void CPlateJudge::LoadModel()
{
    svm.clear();
    svm.load(m_path.c_str(), "svm");
}

         如果你把这个xml文件换成其他的,那么你就可以改变EasyPR车牌判断的内核,从而实现你自己的车牌判断模块。
  后面的部分全部是告诉你如何有效地实现一个自己的模型(也就是svm.xml文件)。如果你对EasyPR的需求仅仅在应用层面,那么到目前的了解就足够了。如果你希望能够改善EasyPR的效果,定制一个自己的车牌判断模块,那么请继续往下看。

二、SVM训练

      简而言之,SVM训练部分的目标就是通过一批数据,然后生成一个代表我们模型的xml文件。EasyPR中所有关于训练的方法都可以在svm_train.cpp中找到(1.0版位于train/code文件夹下,1.1版位于src/train文件夹下)。

  一个训练过程包含5个步骤,见下图: 
                                

                                                                           图2 一个完整的SVM训练流程

  下面具体讲解一下这5个步骤,步骤后面的括号里代表的是这个步骤主要的输入与输出。

1、preprocss(原始数据->学习数据(未标签))

  预处理步骤主要处理的是原始数据到学习数据的转换过程。原始数据(raw data),表示你一开始拿到的数据。这些数据的情况是取决你具体的环境的,可能有各种问题。学习数据(learn data),是可以被输入到模型的数据。

  为了能够进入模型训练,必须将原始数据处理为学习数据,同时也可能进行了数据的筛选。比方说你有10000张原始图片,出于性能考虑,你只想用 1000张图片训练,那么你的预处理过程就是将这10000张处理为符合训练要求的1000张。你生成的1000张图片中应该包含两类数据:真正的车牌图片和不是车牌的图片。如果你想让你的模型能够区分这两种类型。你就必须给它输入这两类的数据。

  通过EasyPR的车牌定位模块PlateLocate可以生成大量的候选车牌图片,里面包括模型需要的车牌和非车牌图片。但这些候选车牌是没有经过分类的,也就是说没有标签。下步工作就是给这些数据贴上标签。

2、label (学习数据(未标签)->学习数据)

  训练过程的第二步就是将未贴标签的数据转化为贴过标签的学习数据。我们所要做的工作只是将车牌图片放到一个文件夹里,非车牌图片放到另一个文件夹里。在EasyPR里,这两个文件夹分别叫做HasPlate和NoPlate。如果你打开train/data/plate_detect_svm 后,你就会看到这两个压缩包,解压后就是打好标签的数据(1.1版本在同层learn data文件夹下面)。

        贴标签后的车牌数据如下图:

贴标签后的非车牌数据下图:

拥有了贴好标签的数据以后,下面的步骤是分组,也称之为divide过程。

3、divide (学习数据->分组数据)

  分组这个过程是EasyPR1.1版新引入的方法。

  在贴完标签以后,我拥有了车牌图片和非车牌图片共几千张。在我直接训练前,不急。先拿出30%的数据作为测试集,只用剩下的70%数据进行SVM模型的训练,训练好的模型再用这30%数据进行一个效果测试。

                              

       在EasyPR1.0版是没有test data概念的,所有数据都输入训练,然后直接在原始的数据上进行测试。
  在divide的过程中,注意无论在train data和test data中都要保持数据的标签,也就是说车牌数据仍然归到HasPlate文件夹,非车牌数据归到NoPlate文件夹。于是,车牌图片30%归到 test data下面的hasplate文件夹,70%归到train data下面的hasplate文件夹,非车牌图片30%归到test data下面的noplate文件夹,70%归到train data下面的noplate文件夹。于是在文件夹train 和 test下面又有两个子文件夹,他们的结构树就是下图:

                                                              

divide数据结束以后,我们就可以进入真正的机器学习过程。也就是对数据的训练过程。

4、train (训练数据->模型)

  模型在代码里的代表就是CvSVM类。在这一步中所要做的就是加载train data,然后用CvSVM类的train方法进行训练。这个步骤只针对的是上步中生成的总数据70%的训练数据。

  具体来说,分为以下几个子步骤:
  1) 加载待训练的车牌数据。见下面这段代码。

void getPlate(Mat& trainingImages, vector<int>& trainingLabels)
{

    char * filePath = "train/data/plate_detect_svm/HasPlate/HasPlate";
    vector<string> files;

    getFiles(filePath, files );

    int size = files.size();
    if (0 == size)
        cout << "No File Found in train HasPlate!" << endl;

    for (int i = 0;i < size;i++)
    {
        cout << files[i].c_str() << endl;
        Mat img = imread(files[i].c_str());

        img= img.reshape(1, 1);
                trainingImages.push_back(img);
                trainingLabels.push_back(1);
    }
}

     车牌图像存储在的是一个vector<Mat>中,而标签数据我存储在的是一个vector<int>中。我将train/HasPlate中的图像依次取出来,存入vector<Mat>。每存入一个图像,同时也往vector<int>中存入一个int值1,也就是说图像和标签分别存在不同的vector对象里,但是保持一一对应的关系。

      2) 加载待训练的非车牌数据,见下面这段代码中的函数。基本内容与加载车牌数据类似,不同之处在于文件夹是train/NoPlate,并且我往vector<int>中存入的是int值0,代表无车牌。

void getNoPlate(Mat& trainingImages, vector<int>& trainingLabels)
{

    char * filePath = "train/data/plate_detect_svm/NoPlate/NoPlate";
    vector<string> files;

    getFiles(filePath, files );
    int size = files.size();
    if (0 == size)
        cout << "No File Found in train NoPlate!" << endl;

    for (int i = 0;i < size;i++)
    {
        cout << files[i].c_str() << endl;
        Mat img = imread(files[i].c_str());
        
        img= img.reshape(1, 1);
                trainingImages.push_back(img);
                trainingLabels.push_back(0);
    }
}

   3) 将两者合并。目前拥有了两个vector<Mat>和两个vector<int>。将代表车牌图片和非车牌图片数据的两个vector<Mat>组成一个新的Mat--trainingData,而代表车牌图片与非车牌图片标签的两个vector<int>组成另一个Mat--classes。接着做一些数据类型的调整,以让其符合svm训练函数train的要求。这些做完后,数据的准备工作基本结束,下面就是参数配置的工作。

Mat classes;//(numPlates+numNoPlates, 1, CV_32FC1);
    Mat trainingData;//(numPlates+numNoPlates, imageWidth*imageHeight, CV_32FC1 );

    Mat trainingImages;
    vector<int> trainingLabels;

    getPlate(trainingImages, trainingLabels);
    getNoPlate(trainingImages, trainingLabels);

    Mat(trainingImages).copyTo(trainingData);
    trainingData.convertTo(trainingData, CV_32FC1);
    Mat(trainingLabels).copyTo(classes);

       4) 配置SVM模型的训练参数。SVM模型的训练需要一个CvSVMParams的对象,这个类是SVM模型中训练对象的参数的组合,如何给这里的参数赋值,是很有讲究的一个工作。注意,这里是SVM训练的核心内容,也是最能体现一个机器学习专家和新手区别的地方。机器学习最后模型的效果差异有很大因素取决与模型训练时的参数,尤其是SVM,有非常多的参数供你配置(见下面的代码)。参数众多是一个问题,更为显著的是,机器学习模型中参数的一点微调都可能带来最终结果的巨大差异。

CvSVMParams SVM_params;
    SVM_params.svm_type = CvSVM::C_SVC;
    SVM_params.kernel_type = CvSVM::LINEAR; //CvSVM::LINEAR;
    SVM_params.degree = 0;
    SVM_params.gamma = 1;
    SVM_params.coef0 = 0;
    SVM_params.C = 1;
    SVM_params.nu = 0;
    SVM_params.p = 0;
    SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);

 opencv官网文档对CvSVMParams类的各个参数有一个详细的解释。如果你上过SVM课程的理论部分,你可能对这些参数的意思能搞的明白。但在这里,我们可以不去管参数的含义,因为我们有更好的方法去解决这个问题。

                

                                                                  图3 SVM各参数的作用

        原因在于:EasyPR1.0使用的是liner核,也称之为线型核,因此degree和gamma还有coef0三个参数没有作用。同时,在这里SVM模型用作的问题是分类问题,那么nu和p两个参数也没有影响。最后唯一能影响的参数只有C。到了EasyPR1.1版本以后,默认使用的是RBF核,因此需要调整的参数多了一个gamma。

  以上参数的选择都可以用自动训练(train_auto)的方法去解决,在下面的SVM调优部分会具体介绍train_auto。

      5) 开始训练。OK!数据载入完毕,参数配置结束,一切准备就绪,下面就是交给opencv的时间。我们只要将前面的 trainingData,classes,以及CvSVMParams的对象SVM_params交给CvSVM类的train函数就可以。另外,直接使用CvSVM的构造函数,也可以完成训练过程。例如下面这行代码:

CvSVM svm(trainingData, classes, Mat(), Mat(), SVM_params);

训练开始后,慢慢等一会。机器学习中数据训练的计算量往往是非常大的,即便现代计算机也要运行很长时间。具体的时间取决于你训练的数据量的大小以及模型的复杂度。在我的2.0GHz的机器上,训练1000条数据的SVM模型的时间大约在1分钟左右。

  训练完成以后,我们就可以用CvSVM类的对象svm去进行预测了。如果我们仅仅需要这个模型,现在可以把它存到xml文件里,留待下次使用:

    FileStorage fsTo("train/svm.xml", cv::FileStorage::WRITE);
    svm.write(*fsTo, "svm");

5、test (测试数据->评判指标)

   将30%的测试数据以及它们的标签加载入内存,这个过程与加载训练数据的过程是一样的。接着使用我们训练好的SVM模型去判断这些图片。对我们的模型做指标评判的过程。

         首先,测试数据是有标签的数据,这意味着我们知道每张图片是车牌还是不是车牌。另外,用新生成的svm模型对数据进行判断,也会生成一个标签,叫做“预测标签”。“预测标签”与“标签”一般是存在误差的,这也就是模型的误差。这种误差有两种情况:

                      1.这副图片是真的车牌,但是svm模型判断它是“非车牌”;

                     2.这幅图片不是车牌,但svm模型判断它是“车牌”。

         无疑,这两种情况都属于svm模型判断失误的情况。我们需要设计出来两个指标,来分别评测这两种失误情况发生的概率。这两个指标就是下面要说的“准确率”(precision)和“召回率”(recall)。

  准确率是统计在我已经预测为车牌的图片中,真正车牌数据所占的比例。假设我们用ptrue_rtrue表示预测(p)为车牌并且实际(r)为车牌的数量,而用ptrue_rfalse表示实际不为车牌的数量。准确率的计算公式是:

                                                

        召回率是统计真正的车牌图片中,我预测为车牌的图片所占的比例。同上,我们用ptrue_rtrue表示预测与实际都为车牌的数量。用pfalse_rtrue表示实际为车牌,但我预测为非车牌的数量。召回率的计算公式是:

                                              

      recall的公式与precision公式唯一的区别在于右下角。precision是ptrue_rfalse,代表预测为车牌但实际不是的数量;而recall是pfalse_rtrue,代表预测是非车牌但其实是车牌的数量。

  简单来说,precision指标的期望含义就是要“查的准”,recall的期望含义就是“不要漏”。

  值得说明的是,precise和recall这两个值自然是越高越好。但是如果一个高,一个低的话效果会如何,如何跟两个都中等的情况进行比较?机器学习界又引入了FScore这个数值。当precise和recall两者中任一者较高,而另一者较低是,FScore都会较低。两者中等的情况下Fscore表现比一高一低要好。当两者都很高时,FScore会很高。FScore的计算公式如下图:

                                                         

模型测试以及评价指标是EasyPR1.1中新增的功能。在svm_train.cpp的最下面可以看到这三个指标的计算过程。

三、训练心得

  通过以上5个步骤,我们就完成了模型的准备,训练,测试的全部过程。下面,说一说过程中的几点心得。

  1. 完善EasyPR的plateLocate功能

  在1.1版本中的EasyPR的车牌定位模块仍然不够完善。如果你的所有的图片符合某种通用的模式,参照前面的车牌定位的 几篇教程,以及使用EasyPR新增的Debug模式,你可以将EasyPR的plateLocate模块改造为适合你的情况。于是,你就可以利用EasyPR为你制造大量的学习数据。通过原始数据的输入,然后通过plateLocate进行定位,再使用EasyPR已有的车牌判断模块进行图片的分类,于是你就可以得到一个基本分好类的学习数据。下面所需要做的就是人工核对,确认一下,保证每张图片的标签是正确的,然后再输入模型进行训练。

      2. 使用“逐次迭代自动标签法”。

  上面讨论的贴标签方法是在EasyPR已经提供了一个训练好的模型的情况下。如果一开始手上任何模型都没有,该怎么办?假设目前手里有成千上万个通过定位出来的各种候选车牌,手工一个个贴标签的话,岂不会让人累吐血?在前文中说过,我在一开始贴标签过程中碰到了这个问题,在不断被折磨与痛苦中,我发现了一个好方法,大幅度减轻了这整个工作的痛苦性。

  这个方法被我称为“逐次迭代自动标签法”。

  方法核心很简单。就是假设你有3000张未分类的图片。你从中选出1%,也就是30张出来,手工给它们每个图片进行分类工作。好的,如今你有了30张贴好标签的数据了,下步你把它直接输入到SVM模型中训练,获得了一个简单粗旷的模型。之后,你从图片集中再取出3%的图片,也就是90张,然后用刚训练好的模型对这些图片进行预测,根据预测结果将它们自动分到hasplate和noplate文件夹下面。分完以后,你到这两个文件夹下面,看看哪些是预测错的,把hasplate里预测错的移动到noplate里,反之,把noplate里预测错的移动到hasplate里。

  接着,你把一开始手工分类好的那30张图片,结合调整分类的90张图片,总共120张图片再输入svm模型中进行训练。于是你获得一个比最开始粗旷模型更精准点的模型。然后,你从3000张图片中再取出6%的图片来,用这个模型再对它们进行预测,分类....

  以上反复。你每训练出一个新模型,用它来预测后面更多的数据,然后自动分类。这样做最大的好处就是你只需要移动那些被分类错误的图片。其他的图片已经被正 确的归类了。注意,在整个过程中,你每次只需要对新拿出的数据进行人工确认,因为前面的数据已经分好类了。因此,你最好使用两个文件夹,一个是已经分好类 的数据,另一个是自动分类数据,需要手工确认的。这样两者不容易乱。每次从未标签的原始数据库中取出的数据不要多,最好不要超过上次数据的两倍。这样可以保证你的模型的准确率稳步上升。

        整个方法的原理很简单,就是不断迭代循环细化的思想。跟软件工程中迭代开发过程有异曲同工之妙。你只要理解了其原理,很容易就可以复用在任何其他机器学习模型的训练中,从而大幅度(或者部分)减轻机器学习过程中贴标签的巨大负担。


  回到一个核心问题,对于开发者而言,什么样的方法才是自己实现一个svm.xml的最好方法。有以下几种选择。

  1.你使用EasyPR提供的svm.xml,这个方式等同于你没有训练,那么EasyPR识别的效率取决于你的环境与EasyPR的匹配度。运气好的话,这个效果也会不错。但如果你的环境下的车牌跟EasyPR默认的不一样。那么可能就会有点问题。

  2.使用EasyPR提供的训练数据,例如train/data文件下的数据,这样生成的效果等同于第一步的,不过你可以调整参数,试试看模型的表现会不会更好一点。

  3.使用自己的数据进行训练。这个方法的适应性最好。首先你得准备你原始的数据,并且写一个处理方法,能够将原始数据转化为学习数据。下面你调用EasyPR的PlateLocate方法进行处理,将候选车牌图片从原图片截取出来。你可以使用EasyPR已有的svm模型对这些候选图片进行预标签。然后再进行肉眼确认和手工调整,以生成标准的贴好标签的数据。后面的步骤就可以按照分组,训练,测试等过程顺次走下去。如果你使用了EasyPR1.1版本,后面的这几个过程已经帮你实现好代码了,你甚至可以直接在命令行选择操作。

四、SVM调优

       SVM调优部分,是通过对SVM的原理进行了解,并运用机器学习的一些调优策略进行优化的步骤。

  在这个部分里,最好要懂一点机器学习的知识。同时,本部分也会讲的尽量通俗易懂,让人不会有理解上的负担。在EasyPR1.0版本中,SVM模型的代码完全参考了mastering opencv书里的实现思路。从1.1版本开始,EasyPR对车牌判断模块进行了优化,使得模型最后的效果有了较大的改善。

  具体说来,本部分主要包括如下几个子部分:1.RBF核;2.参数调优;3.特征提取;4.接口函数;5.自动化。

4.1、RBF核

       SVM中最关键的技巧是核技巧。“核”其实是一个函数,通过一些转换规则把低维的数据映射为高维的数据。在机器学习里,数据跟向量是等同的意思。例如,一个 [174, 72]表示人的身高与体重的数据就是一个两维的向量。在这里,维度代表的是向量的长度。(务必要区分“维度”这个词在不同语境下的含义,有的时候我们会说向量是一维的,矩阵是二维的,这种说法针对的是数据展开的层次。机器学习里讲的维度代表的是向量的长度,与前者不同)

  简单来说,低维空间到高维空间映射带来的好处就是可以利用高维空间的线型切割模拟低维空间的非线性分类效果。也就是说,SVM模型其实只能做线型分类,但是在线型分类前,它可以通过核技巧把数据映射到高维,然后在高维空间进行线型切割。高维空间的线型切割完后在低维空间中最后看到的效果就是划出了一条复杂的分线型分类界限从这点来看,SVM并没有完成真正的非线性分类,而是通过其它方式达到了类似目的,可谓“曲径通幽”。

  SVM模型总共可以支持多少种核呢。根据官方文档,支持的核类型有以下几种:

  1. liner核,也就是无核。
  2. rbf核,使用的是高斯函数作为核函数。
  3. poly核,使用多项式函数作为核函数。
  4. sigmoid核,使用sigmoid函数作为核函数。

  liner核和rbf核是所有核中应用最广泛的。

  liner核,虽然名称带核,但它其实是无核模型,也就是没有使用核函数对数据进行转换。因此,它的分类效果仅仅比逻辑回归好一点。在EasyPR1.0版中,我们的SVM模型应用的是liner核。我们用的是图像的全部像素作为特征。

  RBF核,将输入数据的特征维数进行一个维度转换,具体会转换为多少维?这个等于你输入的训练量。假设你有500张图片,rbf核会把每张图片的数据转 换为500维的。如果你有1000张图片,rbf核会把每幅图片的特征转到1000维。这么说来,随着你输入训练数据量的增长,数据的维数越多。更方便在高维空间下的分类效果,因此最后模型效果表现较好。

       但是,rbf核的使用是需要条件的。

  当你的数据量很大,但是每个数据量的维度不大时,才适合用rbf核。相反,当你的数据量不多,但是每个数据量的维数都很大时,适合用线型核。

  在EasyPR1.0版中,我们用的是图像的全部像素作为特征,那么根据车牌图像的136×36的大小来看的话,就是4896维的数据,再加上我们输入的是彩色图像,也就是说有R,G,B三个通道,那么数量还要乘以3,也就是14688个维度。这是一个非常庞大的数据量,你可以把每幅图片的数据理解为长度为14688的向量。这个时候,数据维度很大,而数据总数很少,如果用rbf核的效果反而不如无核。

  在EasyPR1.1版本时,输入训练的数据有3000张图片,每个数据的特征改用直方统计,共有172个维度。这个场景下,如果用rbf核的话,就会将每个数据的维度转化为与数据总数一样的数量,也就是3000的维度,可以充分利用数据高维化后的好处。

        通过使用rbf核来训练,充分发挥了非线性模型分类的优势,因此带来了较好的分类效果。但是,使用rbf核也有一个问题,那就是参数设置的问题。在rbf训练的过程中,参数的选择会显著的影响最后rbf核训练出模型的效果。因此必须对参数进行最优选择。

4.2、参数调优

  传统的参数调优方法是人手完成的。机器学习工程师观察训练出的模型与参数的对应关系,不断调整,寻找最优的参数。由于机器学习工程师大部分时间在调整模型的参数,也有了“机器学习就是调参”这个说法。

  幸好,opencv的svm方法中提供了一个自动训练的方法。也就是由opencv帮你,不断改变参数,训练模型,测试模型,最后选择模型效果最好的那些参数。整个过程是全自动的,完全不需要你参与,你只需要输入你需要调整参数的参数类型,以及每次参数调整的步长即可。

  现在有个问题,如何验证svm参数的效果?机器学习模型中专门有一个数据集,是用来验证参数效果的。也就是交叉验证集(cross validation set,简称validate data) 这个概念。

  validate data就是专门从train data中取出一部分数据,用这部分数据来验证参数调整的效果。比方说现在有70%的训练数据,从中取出20%的数据,剩下50%数据用来训练,再用训练出来的模型在20%数据上进行验证。这20%的数据就叫做validate data。真正拿来训练的数据仅仅只是50%的数据。

        在train data上训练,然后在validate data上验证参数的效果。所以说,在一个更一般的机器学习场景中,机器学习工程师会把数据分为train data,validate data,以及test data。在train data上训练模型,用validate data测试参数,最后用test data测试模型和参数的整体表现。

        opencv的train_auto函数,设置划分多少个子分组,以及validate data所占的比例。然后train_auto函数会自动帮你从你输入的train data中划分出一部分的validate data,然后自动测试,选择表现效果最好的参数。

train_auto函数!既帮我们划分了参数验证的数据集,还帮我们一步步调整参数,最后选择效果最好的那个参数。

  train_auto函数的调用代码如下:

    svm.train_auto(trainingData, classes, Mat(), Mat(), SVM_params, 10, 
                CvSVM::get_default_grid(CvSVM::C),
                CvSVM::get_default_grid(CvSVM::GAMMA), 
                CvSVM::get_default_grid(CvSVM::P), 
                CvSVM::get_default_grid(CvSVM::NU), 
                CvSVM::get_default_grid(CvSVM::COEF),
                CvSVM::get_default_grid(CvSVM::DEGREE),
                true);

  训练时间较长,因为每次调整参数又得重新训练一次。作者最近的一次训练的耗时为1个半小时)。训练完毕后,看看模型和参数在test data上的表现把。99%的precise和98%的recall。非常棒,比任何一次手工配的效果都好。

4.3、特征提取

         在rbf核介绍时提到过,输入数据的特征的维度现在是172,那么这个数字是如何计算出来的?现在的特征用的是直方统计函数,也就是先把图像二值化,然后统计图像中一行元素中1的数目,由于输入图像有36行,因此有36个值,再统计图像中每一列中1的数目,图像有136列,因此有136个值,两者相加正好等于172。新的输入数据的特征提取函数就是下面的代码:

// ! EasyPR的getFeatures回调函数
// !本函数是获取垂直和水平的直方图图值
void getHistogramFeatures(const Mat& image, Mat& features)
{
    Mat grayImage;
    cvtColor(image, grayImage, CV_RGB2GRAY);
    Mat img_threshold;
    threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
    features = getTheFeatures(img_threshold);
}

       我们输入数据的特征不再是全部的三原色的像素值了,而是抽取过的一些特征。从原始的图像到抽取后的特征的过程就被称为特征提取的过程。在1.0版中没有特征提取的概念,是直接把图像中全部像素作为特征的。全部像素的输入是最低级的做法,当时的准确率有92%,随后结合了rbf核与新特征训练的模型达到的precise在99%左右,而且recall也有98%,这真是个令人咋舌并且非常惊喜的成绩。

  此外会可以使用SFIT特征提取或者HOG特征提取,由于时间原因,这两者我没有实现,但是把函数留在了那里。留待以后有时间完成。

4.4、接口函数

  由于有SIFT以及HOG等特征没有实现,而且未来有可能会有更多有效的特征函数出现。因此我把特征函数抽象为接口。使用回调函数的思路实现。所有回调函数的代码都在feature.cpp中,开发者可以实现自己的回调函数,并把它赋值给EasyPR中的某个函数指针。

  关于特征其实有更多的思考,原始的SVM模型的输入是图像的全部像素,正如人类在小时候通过图像识别各种事物的过程。后来SVM模型的输入是经过抽取的特 征。正如随着人类接触的事物越来越多,会发现单凭图像越来越难区分一些非常相似的东西,于是学会了总结特征。例如太阳就是圆的,黄色,在天空等,可以凭借 这些特征就进行区分和判断。

  从本质上说,特征是区分事物的关键特性。这些特性,一定是从某些维度去看待的。例如,苹果和梨子,一个是绿色,一个是黄色,这就是颜色的维度;鱼和鸟,一个在水里,一个在空中,这是位置的区分,也就是空间的维度。特征,是许多维度中最有区分意义的维度。传统数据仓库中的OLAP,也称为多维分析,提供了人类从多个维度观察,比较的能力。通过人类的观察比较,从多个维度中挑选出来的维度,就是要分析目标的特征。从这点来看,机器学习与多维分析有了关联。多维分析提供了选择特征的能力。而机器学习可以根据这些特征进行建模。

   机器学习界也有很多算法,专门是用来从数据中抽取特征信息的。例如传统的PCA(主成分分析)算法以及最近流行的深度学习中的 AutoEncoder(自动编码机)技术。这些算法的主要功能就是在数据中学习出最能够明显区分数据的特征,从而提升后续的机器学习分类算法的效果。

  说一个特征学习的案例。大众的两款车--迈腾与帕萨特实在太像了。后来我通过仔细观察,终于发现了一个明显区分两辆车的特征,后来我再也没有认错过。这个特征就是:迈腾的前脸有四条银杠,而帕萨特只有三条,迈腾比帕萨特多一条银杠。可以这么说,就是这么一条银杠,分割了北和南两个地方生产的汽车。

                            
      在这里区分的过程,就是通过不断学习与研究才发现了这些区分的特征,这充分说明了事物的特征也是可以被学习的。如果让机器学习中的特征选择方法PCA和AutoEncoder来分析的话,按理来说它们也应该找出这条银杠,否则它们就无法做到对这两类最有效的分类与判断。如果没有找到的话,证明我们目前的特征选择算法还有很多的改进空间(与这个案例类似的还有大众的另两款车,高尔夫和Polo。它们两的区分也是用同样的道理。相比迈腾和帕萨特,高尔夫和Polo价格差别的更大,所以区分的特征也更有价值)。

4.5、自动化

  最后我想简单谈一下EasyPR1.1新增的自动化训练功能与命令行。大家可能看到第二部分介绍SVM训练时我将过程分成了5个步骤。事实上,这些步骤中的很多过程是可以模块化的。一开始的时候我写一些不相关的代码函数,帮我处理各种需要解决的问题,例如数据的分组,打标签等等。但后来,我把思路理清后,我觉得这几个步骤中很多的代码都可以通用。于是我把一些步骤模块化出来,形成通用的函数,并写了一个命令行界面去调用它们。在你运行EasyPR1.1版后,在你看到的第一个命令行界面选择“3.SVM训练过程”,你就可以看到这些全部的命令。

                                                  

                                                              图4 svm训练命令行

       这里的命令主要有6个部分。第一个部分是最可能需要修改代码的地方,因为每个人的原始数据(raw data)都是不一样的,因此你需要在data_prepare.cpp中找到这个函数,改写成适应你格式的代码。接下来的第二个部分以后的功能基本都可以复用。例如自动贴标签(注意贴完以后要人工核对一下)。第三个到第六部分功能类似。如果你的数据还没分组,那么你执行3以后,系统自动帮你分组,然后训练,再测试验证。第四个命令行省略了分组过程。第五个命令行部分省略训练过程。第六个命令行省略了前面所有过程,只做最后模型的测试部分。

  回顾一下SVM调优的五个思路。第一部分是rbf核,也就是模型选择层次,根据你的实际环境选择最合适的模型。第二部分是参数调优,也就是参数优化层次,这部分的参数最好通过一个验证集来确认,也可以使用opencv自带的train_auto函数。第三部分是特征抽取部分,也就是特征甄选,要能选择出最能反映数据本质区别的特征来。在这方面,pca以及深度学习技术中的autoencoder可能都会有所帮助。第四部分是通用接口部分,为了给优化留下空间,需要抽象出接口,方便后续的改进与对比。第五部分是自动化部分,为了节省时间,将大量可以自动化处理的功能模块化出来,然后提供一些方便的操作界面。前三部分是从机器学习的效果来提高,后两部分是从软件工程的层面去优化。

  总结起来,就是模型,参数,特征,接口,模块五个层面。通过这五个层面,可以有效的提高机器学习模型训练的效果与速度,从而降低机器学习工程实施的难度与提升相关的效率。当需要对机器学习模型进行调优的时候,我们可以从这五个层面去考虑。

转载自: EasyPR--开发详解(6)SVM开发详解

基于SVM车牌判别(一)
weixin_43915511的博客
04-12 2799
基于SVM车牌判别(一)1 项目背景2 详细设计 1 项目背景 车牌识别技术是智慧城市建设的重要组成部分,常被用于道闸系统、交通监控系统、停车场系统等重要场合,对于保障行人生命安全、提高交通管理秩序具有重要意义。准确、鲁棒性强的中文车牌识别系统是在中国部署智慧城市及智能交通系统的关键。 现有的车牌识别技术主要分为车牌定位、字符分割和字符识别三个部分,车牌定位主要利用边缘检测和颜色检测来定位车牌区...
svm训练车牌,作为二分类判断是否为车牌
08-22
提取车牌样本的水平和垂直直方图特征,再加上其canny边缘特征作为svm的训练特征进行训练,得到的分类器作为二分类,判断是否为真车牌。作为一种定位车牌过滤的模块具有重要的作用。
基于Open CV 3 训练的车牌识别SVM模型
02-24
基于OpenCV3的SVM训练出来的车牌识别模型,能识别全国各地蓝底白字的车牌类型。
C++下EasyPR中文车牌识别
rdsvdfbfsdfg的博客
08-04 191
EasyPR简介EasyPR 的目标是成为一个简单、高效、准确的非限制场景 (unconstrained situation) 下的车牌识别库。它基于openCV这个开源库。这意味着你可以获取全部源代码,并且移植到opencv支持的所有平台。
OpenCV中的「SVM分类器」:基本原理、函数解析和示例代码_python opencv svm 图像分类
最新发布
2401_84139728的博客
05-11 992
不知道你们用的什么环境,我一般都是用的Python3.6环境和pycharm解释器,没有软件,或者没有资料,没人解答问题,都可以免费领取(包括今天的代码),过几天我还会做个视频教程出来,有需要也可以领取~给大家准备的学习资料包括但不限于:Python 环境、pycharm编辑器/永久激活/翻译插件python 零基础视频教程Python 界面开发实战教程Python 爬虫实战教程Python 数据分析实战教程python 游戏开发实战教程Python 电子书100本。
opencv 视觉项目学习笔记(二): 基于 svm 和 knn 车牌识别
为他种太阳的博客
09-06 1323
车牌识别的属于常见的 模式识别 ,其基本流程为下面三个步骤: (1)分割: 检测并检测图像中感兴趣区域; (2)特征提取: 对字符图像集中的每个部分进行提取; (3)分类: 判断图像快是不是车牌或者 每个车牌字符的分类。 车牌识别分为两个步骤, 车牌检测, 车牌识别, 都属于模式识别。
基于opencv的SVM车牌号码识别模型训练(C++)QT
qq_15985873的博客
05-25 4965
车牌号码识别的SVM模型训练,包括前期准备,HOG特性计算的具体步骤,以及该过程中需要注意的事项,总结SVM模型训练参数设置要求,包括数据集、数据标签、类型选择、核函数特征等。
基于OpenCV的交通标志识别(SVM+Hu不变矩, 部分测试源代码)
weixin_30352191的博客
03-13 1528
最近跟着老师做一个交通识别的项目, 总算明白了一个道理, 这水啊, 不去亲自蹚上一遭就不知道有多深, 更根本的原因当然还是自己学的不够扎实, 不够好. 经过了一个寒假的折磨,终于做出了一个原型来, 想到了自己当时被折磨的头疼的样子,想着将一部分源代码发上来, 希望可以帮助到别人. 呵呵,废话不多说了 这里我发的是一个手写字符识别的程序(这是在编写交通标志的过程中产生的,因为当时手头...
EasyPR(车牌识别系统)VS+Opencv环境配置的关键点
光年外的星空
03-18 1万+
EasyPR是一个开源的中文车牌识别系统,但是很多人刚开始开始学习EasyPR总是遇到环境配置的问题,经常没把环境配置好,导致运行出各种错误。本文介绍EasyPR在64位的电脑中环境配置的关键点。以及一种可以解决CLC.exe文件缺失问题的方法。
OpenCV自学笔记17. 基于SVM和神经网络的车牌识别(一)
热门推荐
两鬓已不能斑白的专栏
07-18 1万+
基于SVM和神经网络的车牌识别(一) 本系列文章参考自《深入理解OpenCV实用计算机视觉项目解析》仅作学习用途 图像预处理 本篇用到的测试图片为: Step1. 首先,读入并显示图像,代码如下: string in = "images/2715DTZ.jpg"; Mat image = imread(in, IMREAD_GRAYSCALE); // IMREAD_GRAYSCAL
基于SVM车牌识别算法
qq_40231851的博客
11-21 1953
使用Python实现基于SVM车牌识别算法,调用Opencv库实现
基于深度学习的车牌检测识别(Pytorch)(ResNet +Transformer)
datayx的文章
02-17 4691
向AI转型的程序员都关注了这个号????????????车牌识别概述基于深度学习的车牌识别,其中,车辆检测网络直接使用YOLO侦测。而后,才是使用网络侦测车牌与识别车牌号。车牌的侦测网络,采用的是resnet18,网络输出检测边框的仿射变换矩阵,可检测任意形状的四边形。车牌号序列模型,采用Resnet18+transformer模型,直接输出车牌号序列。数据集上,车牌检测使用CCPD 2019数据集,在训练检测模型...
车牌识别SVM,已经训练好的样本集,直接可用于车牌识别
07-02
车牌识别SVM,已经训练好的样本集,直接可用于车牌识别。包括两个文件,svm.dat和svmchinese.dat,其中前一个文件用于识别字母和数字;后一个文件用于识别汉字。
LBF/HOG特征SVM的train和trainAuto范例
11-29
在开源的车牌识别系统EasyPR中,用SVM(支持向量机)模型甄选出候选车牌中真正的车牌。目前EasyPR1.4的SVM模型输入的是LBP特征,本代码将EasyPRsvm_train.cpp独立出来,并添加了HOG特征用来作为SVM的输入。
基于 SVM 车牌识别.doc基于 SVM 车牌识别.doc
10-19
车牌识别中,SVM被用来训练模型以区分不同字符。 1.1 主要研究内容 车牌识别系统主要包含以下几个关键步骤: 1. **车牌图像采集**:利用摄像头捕捉车辆图像,并将其传输到计算机进行后续处理。 2. **预处理**...
Python opencv+svm训练 车牌识别系统
04-07
车牌识别
基于SVM与人工神经网络的车牌识别OpenCV&C++实现
04-28
基于SVM与人工神经网络的车牌识别算法,使用了OpenCV的图像处理函数,在VS2013 + OpenCV 2.4.9平台上实现
matlab程序SVM车牌识别
03-04
matlab程序SVM车牌识别
第五章 采用SVM和神经网络的车牌识别
zhazhiqiang2010的专栏
03-13 6970
【原文:http://blog.csdn.net/raby_gyl/article/details/11617875】 书名:《Mastering OpenCV with Practical Computer Vision Projects》 由于添加了一个*号,显示乱码,不晓得怎么回事,为了不耽误大家看可以下载word版本的翻译:http://download.csdn.
svm车牌识别模型工作原理
05-21
SVM(Support Vector Machine)是一种常用的机器学习算法,可用于图像识别任务,包括车牌识别。其工作原理如下: 1. 特征提取:首先需要对车牌图像进行特征提取,常用的特征包括颜色、形状和纹理等。 2. 数据准备:将提取的特征转换为数值型数据,组成训练集和测试集。 3. 模型训练:使用训练集数据训练SVM模型,即寻找一个最优的超平面来分离不同类别的数据点。 4. 模型测试:使用测试集数据测试模型的准确率和召回率,调整模型参数以提高识别率。 5. 车牌识别:对于新的车牌图像,提取特征后使用训练好的SVM模型进行分类,识别该车牌的字符和数字,最终输出识别结果。 需要注意的是,SVM模型对于特征的选择和参数的调整非常敏感,需要进行精细的特征工程和模型调参才能取得较好的识别效果。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • Python的from和import用法 168919
  • YOLO框架简述 141555
  • 常用的三种插值算法 108769
  • 图像处理-小波变换 102952
  • Ubuntu各个版本下载 102412

分类专栏

  • 视频剪辑 4篇
  • 机器学习 31篇
  • 车牌识别 13篇
  • 神经网络 30篇
  • 人脸识别 13篇
  • Python学习 28篇
  • C++学习 24篇
  • 点云处理 8篇
  • 图像特征 24篇
  • opencv 17篇
  • MTCNN 8篇

最新评论

  • FFT简介小结

    comeontaojun: 厉害,很清晰

  • PCL 学习(2)——基本数据类型与点云数据拼接

    shanhedian2013: concate的时候,按你的写法,要对指针解引用,就不报错了

  • MUSIC算法及MATLAB实现

    swimmingfish23: 您私信我

  • MUSIC算法及MATLAB实现

    swimmingfish23: 就是写成bpsk之类的调制形式

  • MUSIC算法及MATLAB实现

    xzf1130: 您好,您知道怎么处理实际信号吗?请赐教

大家在看

  • 学C语言好还是学C++好呢? 374
  • 3.Python语言结构——《跟老吕学Python》 206
  • Qt/QML学习-状态与过渡
  • Qt/QML学习-C++与QML混合编程

最新文章

  • 查看opencv的mat数据类型及相互转换
  • matlab读取txt数据文件
  • GPS采集设备命令
2022年3篇
2021年4篇
2020年23篇
2019年249篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值

深圳SEO优化公司惠州外贸网站制作布吉百度网站优化龙岗优化吉祥建网站南澳网站开发布吉百度网站优化坪地网站建设坪地百度标王布吉网站关键词优化西乡企业网站改版松岗SEO按天扣费盐田网页设计盐田阿里店铺托管龙岗百搜标王石岩网站关键词优化塘坑百搜词包福田设计公司网站吉祥网页设计坂田百度网站优化排名罗湖百度爱采购丹竹头网站优化按天计费东莞高端网站设计大芬企业网站建设南联百度竞价包年推广平湖关键词排名罗湖网站seo优化丹竹头网站开发坪山优化广州网站优化按天计费大浪企业网站改版歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化