基于SVM和神经网络的车牌识别

基于SVM和神经网络的车牌识别

本文将介绍创建自动车牌识别(Automatic Number Plate Recognition, ANPR)所需的步骤。对于不同的情形,实现自动车牌识别会用不同的方法和技术,例如,IR摄像机、固定汽车位置、光照条件等。本文着手构造一个用来检测汽车车牌ANPR的应用,该应用处理的图像使从汽车2-3米处拍摄的,拍摄环境的光线昏暗模糊,并且与地面不平行、车牌在图像中有轻微的扭曲。

本文的主要目标是介绍图像分割、特征提取、模式识别基础以及两个重要的模式识别算法:支持向量机(Suport Vector Machine, SVM)和人工神经网络(Artificial Neural Network, ANN)。

本文主要内容:

1)       ANPR

2)       车牌检测

3)       车牌识别

 

一、    ANPR简介

自动车牌识别也称为自动车牌照识别(Automatic Vehicle Identification, AVI)、洗车车牌识别(Car Plate Recognition, CPR),它是一种使用光学字符识别(Optical Character Recognition, OCR)和其他方法(如,用图像分割与检测)来获取车辆牌照的监控方法。

对于一个ANPR系统,其最好结果可用一个红外(IR)摄像机来获取数据,因为在分割这一步中,对检测和OCR分割很简单、干净。并且误差最小。这是由光学的一些基本原理决定的,例如入射角等于反射角,当人看到光滑表面(如平面镜)时就会有这样的反映。粗糙表面(如纸)的反射会导致漫射或散射。多数车牌有一个称为回射的特性,车牌表面覆盖着一种材料,它由许多微小半球颗粒构成,会导致光线沿路反射回去。

如果使用结合了结构性红外光学投影器的摄像机,就可只获取红外光,这样就能得到很高品质的图像,对这种图像进行分割,然后检测和识别车牌。这种情况下的车牌独立于任意光照环境。

二、    ANPR算法

在解析ANPR算法代码之前,需要明白注意步骤和使用ANPR算法的任务。ANPR有两个主要步骤:车牌检测和车牌识别。车牌检测的目的是在整个视频帧中检测到车牌位置。当在图像中检测到车牌时,分割的车牌被传到第二个步骤,即车牌识别,它用OCR算法来识别车牌上的字母和数字。

下面将定义模式识别算法常用的三个步骤:

1)分割:这一步会检测并裁剪图像中每个感兴趣的块或区域;

2)特征提取:这一步对字符图像集的每个部分进行提取;

3)分类:这一步会从车牌识别那一步的结果中得到每个字符,或从车牌检测(plate detection)那一步中将夺得图像块分为“是车牌”或“不是车牌”;

除了这个主要的应用以外,模式识别算法的主要目的是检测和识别汽车车牌,下面简单介绍一下两个任务,这两个任务通常都不会解释。

第一是:如何训练模式识别系统;

第二是:如何评估模式识别系统。

 

三、    车牌检测

这一步要检测当前帧中所有的车牌。为了实现此功能,该步骤又分为两个主要步骤:图像分割和对分割的图像进行分类。这一步的功能不会解释因为将图像块作为一个向量特征。

在第一步(图像分割)中,将使用各种滤波器、形态学算子,以及轮廓算法来验证所获取图像中所有车牌的部分。

在第二步(分类)中,对每个图像块(即特征)将采用支持向量机(Support Vector Machine, SVM)作为分类器进行分类。在创建主要的应用之前,需要训练两个不同的类:车牌和非车牌号。这步所使用的图像使在汽车前面2-4米拍摄平行的正面视角彩色图像,这些图像有800像素宽。这些要求对确保正确的图像分割很重要。可创建一个多尺度图像算法来进行检测。

下面包括了车牌检测的所有过程:

1)       Sobel滤波器;

2)       阀值算子;

3)       闭形态学算子;

4)       一个填充区域掩码;

5)       用红色标记(特征图像中)可能检测到的车牌;

6)       在执行SVM分类器后检测车牌。

 

四、    图像分割

图像分割是将图像分成多个区域的过程。该过程是为了分析而简化图像,同时也使特征提取更容易。

车牌分割有一个重要特征:假定从汽车前面拍摄图像,会在车牌上有大量竖直边(vertical edge),并且车牌不会被旋转,也没有透视扭曲(perspectivedistortion)。这一性质在分割图像时可采用来删除没有任何竖直边的那些区域。

在找到竖直边之前,需要将彩色图像转换为灰度图像(因为彩色对本任务没有任何用),删除可能由摄像机产生的噪声或其他环节噪声。利用5x5的高斯模糊来去噪。如果不用去噪方法,可能得到很多竖直边,从而造成检测失败。

Matimage = imread("car1.jpg");

Matimg_gray;

cvtColor(image,img_gray,CV_BGR2GRAY);//Áa¡¥a¨°¨¨ª?

blur(img_gray,img_gray,Size(5,5));//5x5?1¡êy¨£¤?

为了找到竖直边,将采用sobel滤波器来找到第一个水平导数(horizontal derivative)。导数是数学函数,它可用来在图像中查找竖直边。根据情况,使用x方向一阶导数,y方向0阶。

Matimg_sobel;

//??x¤?¨°°?¡ÁÌ?ºyê?¨¦¨°º¨²¡ÀÀ?

Sobel(img_gray,img_sobel,CV_8U,1,0,3,1,0);

在执行完sobel滤波器之后,将采用阀值滤波器来得到二值图像,所采用的阀值由otsu算法得到。Otsu算法的输入是一个8位图像,它将自动得到优化的阀值:

Mat img_threshold;

//¤¡ì¦Ì?¡§ê?Ì?Ì?t¦Ìª??

threshold(img_sobel,img_threshold,0,255,CV_THRESH_OTSU+CV_THRESH_BINARY);

通过采用一个闭形态学算子,可删除在每个竖直边缘线之间的空白区域,并连接有大量边的所有区域的。在这一部中,有可能包含车牌区域。

首先,需要定义在闭形态学算子中所使用的结构元素。可使用getStructuringElement函数来定义一个结构元素,它的维度大小为17x3,这可能与其他图像尺寸有所不同:

//¨¢1?¨®a?

Matelement=getStructuringElement(MORPH_RECT,Size(17,3));

在闭形态学算子中使用morphologyEx函数就会得到结构元素:

//À??¬?¡ì?Á¨®

morphologyEx(img_threshold,img_threshold,CV_MOP_CLOSE,element);

在使用这些函数后,就会得到包含车牌的区域,但多数区域都不包含车牌号。这些区域可用连通分量分析(connected-component analysis)或用findContours函数将其分开。

//̨¹ä¨²¡Âê??¤a¨¬a

         vector< vector < Point > >contours;

         findContours(img_threshold,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);

         //使º1®?¨°¢?̨¹ä¨²¡Â¤¡äÌ?Ì?À?yÁaÌ???

         vector<vector<Point>>::iterator  itc=contours.begin();

         vector<RotatedRect> rects;

         while(itc!=contours.end())

         {

                   RotatedRectmr=minAreaRect(Mat(*itc));

                   if(!verifySizes(mr))

                   {

                            itc=contours.erase(itc);

                   }

                   else

                   {

                            ++itc;

                            rects.push_back(mr);

                   }

         }

 

五、    分类

在预处理和分割完图像的所有部分后,需要决定每部分是否为车牌号。可使用支持向量机(Support Vector Machine, SVM)算法来完成该功能。

支持向量机是一种模式识别算法,它源于二分类的监督学习(supervised-learning)算法。监督学习是通过标签数据进行学习的机器学习算法。用户需要用一些标签数据来训练算法,标签数据是指每个样本都应该属于某个具体的类。

SVM会创建一个或多个超平面,这些超平面可用来判断数据属于哪个类。一个经典的SVM实例是,对一个只有两个类的二维平面的点集合,SVM搜索的最优直线以将不同类的点分开。

在开始分类之前,需要训练分类器,该工作主要在应用开始之前完成,这称为离线训练。离线训练并不是一件容易的事,因为它需要充足的数据来训练系统,但不是数据集越大就能得到最好的结果。本项目并没有充足的数据,因为并没有公开的车牌数据库。因此,需要拍摄数百张汽车照片,然后预处理并分割它们。

为了简单理解机器学习是如何工作的,可对分类器算法使用图像像素特征(注意:有更好的方法和特征用于训练svm, 比如,主成分分析(PrincipalComponents Analysis, PCV)、傅里叶变换、纹理分析等)。

 

六、    OCR分割

首先,对获取的车牌图像用直方图均衡进行处理,将其作为OCR函数的输入,然后采用阀值滤波器对图像进行处理,并将处理后的图像作为查找轮廓(find contour)算法的输入。

这个分割的过程的代码如下:

Mat img_threshold;

threshold(image,img_threshold,60,255,CV_THRESH_BINARY_INV);

if(DEBUG)

{

       imshow(“Threshold plate”,img_threshold);

}

Mat img_contours;

img_threshold.copyTo(img_contours);

vector<vector<Point>> contours;

findContours(img_contours,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);

使用CV_THRESH_BINARY_INV可将白色输入值变为黑色,将黑色输入值变为白色,从而反转阀值化的输出结果。为了得到每个字符的轮廓,这是有必要的,因为轮廓算法会查找白色像素。

对每个检测到的轮廓,需要验证其大小并删除所有尺寸较小或宽高比不正确的区域。在本项目中,正确的车牌字符的宽高比约为45/77,但由于字符会有旋转或扭曲,允许车牌字符的宽高比有35%的误差。如果一块区域的这个比率超过标准比率的80%,可认为这个区域为黑色块,而不是一个字符。可用countNonZero函数来计算像素值大于0的像素个数:

Bool OCR::verifySizes(Mat r)

{Float aspect=45.0f/77.0f;

Float charAspect=(float)r.cols/(float)r.rows;

Float error=0.35;

Float minHeight=15;

Float maxHeight=28;

Float minAspect=0.2;

Float maxAspect=aspect+aspect+aspect*error;

Float area=countNonZero(r);

Float bbArea=r.cols*r.rows;

Float percPixels=area/bbArea;

If(percPixels<0.8 && charAspect>minAspect &&charAspect<maxAspect&&r.rows>=minHeight&&r.rows<maxHeight)

Return true;

Else

Return false;}

如果一个分割的区域是字符,则必须要对其预处理,使它所有字符有一样的大小和位置,然后用辅助类charsegment将其保存到一个向量中。该类保存分割后的字符图像和用于调整字符所需的位置,因为查找轮廓算法不会按所需顺序返回轮廓。

 

七、    特征提取

为了用人工神经网络进行训练和分类,下面将对每个分割出来的字符进行特征提取。

与用SVM进行车牌检测时的特征提取不同,这里不会使用所有图像像素作为特征,而是采用光学字符识别中更常用的特征,这些特征包含了水平和竖直累积直方图,以及低分辨的图像样本。

对每个字符,通过使用countNonZero函数来按列或按行统计非零像素个数,并将其保存到新的数据矩阵mhist中。对mhist进行归一化处理,其过程为:通过minMaxLoc函数找到该矩阵的最大值,将它的每个元素都除以这个最大值,并通过convertTo函数来最终实现。创建名为ProjectedHistogram的函数,用它来实现累积直方图,这个函数将二值图像和直方图类型(水平或竖直)作为输入:

Mat OCR::ProgetedHistogram(Mat img,int t)

{

      Int sz=(t)?img.rows:img.cols;

      Matmhist=Mat::zeros(1,sz,CV_32F);

      For(int j=0;j<sz;j++)

{

  Matdata=(t)?img.row(j):img.col(j);

  Mhist.at<float>(j)=countNonZero(data);

}

Double min,max;

minMaxLoc(mhist,&min,&max);

if(max>0)

{

Mhist.covertTo(mhist,-1,1.0f/max,0);

}

Return mhist;

}

 

八、    OCR分类

在分类这一步,将使用机器学习算法中的人工神经网络。更具体一点,使用多层感知器(Multi-Layer Perception,MLP),它是常见的ANN算法。

MLP由包含一个输入层、包含一个输出层和一个或多个隐藏层的神经网络组成。每一层由一个或多个神经元同前一层和后一层相连。

在MLP中的所有神经元都差不多,每个神经元都有几个输入(连接前一层)神经元和输出(连接后一层)神经元,该神经元会将相同值传递给与之相连的多个输出神经元。每个神经元通过输入权重加上一个偏移项来计算输出值,并由所选择的激励函数(activation function)来进行转换。

有三种广泛使用的激励函数:恒等函数、Sigmoid函数和高斯函数,最常用的默认激励函数为Sigmoid函数。

一个ANN训练网络将一个特征向量作为输入,将该向量传递到隐藏层,然后通过权重和激励函数来计算结果,并将结果传递到下一层,直到最后传递给输出层才结束,输出层是指其神经元类别编号为神经网络的层数。

Opencv为ANN定义了一个CvANN_MLP类。通过create函数来初始化该类,初始化时需要指定以下参数的值:神经网络的层数、神经元数、激励数、alpha和beta。

Void OCR::train(Mat TrainData,Mat classes,int nlayers)

{

     MatlayerSizes(1,3,CV_32SC1);

     layerSizes.at<int>(0)=TrainData.cols;

layerSizes.at<int>(1)=nlayers;

layerSizes.at<int>(2)=numCharaters;

ann.create(layerSizes,CvANN_MLP::SIGMOID_SYM,1,1);

Mat trainClasses;

trainClasses.create(TrainData.rows,numCharacters,CV_32FC1);

for(int i=0;i<trainClasses.rows;k++)

{

     For(intk=0;k<trainClasses.cols;k++)

{

     If(k==classes.at<int>(i))

trainClasses.at<float>(I,k)=1;

else

trainClasses.at<float>(I,k)=0;

}

}

Mat weights(1,TrainData.rows,CV_32FC1,Scalar::all(1));

Ann.train(TrainData,trainClasses,weights);

Trained=true;

}

 

九、    评价

本项目到此已经完成,但当训练像OCR这样的机器学习算法时,需要知道所使用的最佳特征和参数,以及如何修正项目中出现的分类、识别和检测错误。

需要在不同情形和参数下评价当前开发的这个系统,评价错误的产生,获取让错误最小的参数。

本文用下面这些变量来评价这个OCR应用:低分辨率图像特征的大小和隐藏层的隐藏神经元数。

评价程序会获取每个下采样特征矩阵,然后取100行用作训练,而其他行用作测试ANN算法,然后给出其误差。

在训练前,要测试每个随机样本并检测其输出是否正确。如果输出不正确,将增加错误计算变量的值,然后通过将错误计算的值除以样本数来进行评价。这意味着用随机数据训练,其错误率会在0和1之间。

Float test(Mat samples, Mat classes)

{

Float errors=0;

For(int i=0;i<samples.rows;i++)

{

  Intresult=ocr.classify(samples.row(i));

  If(result!=classes.at<int>(i))

  Errors++;

}

Return errors/samples.rows;

}


你可能感兴趣的:(神经网络,机器视觉,图像处理,模式识别,车牌识别)