赞
踩
深入理解OpenCV:实用计算机视觉项目解析
本文用来学习的项目来自书籍《实用计算机视觉项目解析》第5章Number Plate Recognition 提供的源代码
书名:《Mastering OpenCV with Practical Computer Vision Projects》
链接:http://pan.baidu.com/s/1qWwjIzy 密码:zkmp
http://blog.csdn.net/gdut2015go/article/details/46770997
将介绍创建自动车牌识别(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;
}
一、ANPR简介:
Automatic Number Plate Recognition (ANPR),,是一种使用Optical Character Recognition (OCR)和其他分割、检测方法来读取汽车注册牌照的算法。最好的ANPR算法结果是由红外线照相机拍摄图片得到的。因为车牌的特殊材质,夜间会有逆反射效果,看不清车牌。但是现在我们不使用IR图片,我们使用常规图片,这样就增加了我们检测错误和识别错误的等级,以显示我们的算法有多牛逼【老外的意思,有逆反射的图片我没试过】。下面给出,反射、散射、逆反射的示意图:
每个国家的车牌规格都不一样,这里使用西班牙的车牌,左边4个为数字,右边2个为字母,车牌以白色为背景。具体字符间隔如下图所示:
ANPR算法大体分为两个步骤:
1.车牌检测:检测车牌在图像中的位置
2.车牌识别:使用OCR算法检测车牌上的字母数字字符
这篇博文今天只讲车牌检测【提取车牌、SVM如何训练】,车牌识别为下一篇博文,搬到Android系统为下下篇博文
二、车牌检测
大体也分为两个步骤:
1.图像分割:采用一系列不同的滤波器、形态学操作、轮廓算法和验证算法,提取图像中可能包含车牌的区域。
2.图像分类:对每个图像块使用支持向量机SVM分类,并由代码自动创建正负样本【正:有车牌,负:无车牌】(车牌规格统一:800像素宽,拍摄位置大概离车2-4米远)
整个车牌检测部分,会涉及以下内容:
Sobel filter
Threshold operation
Close morphologic operation
Mask of one filled area
Possible detected plates marked in red (features images)
Detected plates after the SVM classifier
假设车牌图片没有旋转和变形,则车牌分割的一个重要特征是车牌中有大量的垂直边缘。这个特征可以通过在第一阶段剔除没有任何垂直边缘的区域来提取。车牌原图:
具体算法步骤如下:
1.将彩色图像转化为灰度图,并采用5*5模版对图像进行高斯模糊来退出由照相机或其他环境噪声(如果不这么做,我们会得到很多垂直边缘,导致错误检测。)
2.使用Sobel滤波器求一阶水平方向导数,以此寻找垂直边缘
3.使用Otsu自适应阈值算法获得图像二值化的阈值,并由此得到一副二值画图片
4.采用闭操作,去除每个垂直边缘线之间的空白空格,并连接所有包含 大量边缘的区域(这步过后,我们将有许多包含车牌的候选区域)
5.由于大多数区域并不包含车牌,我们使用轮廓外接矩形的纵横比和区域面积,对这些区域进行区分。
a.首先使用findContours找到外部轮廓
b.使用minAreaRect获得这些轮廓的最小外接矩形,存储在vector向量中
c.使用面积和长宽比,作基本的验证【阈值:长宽比为4.727272,允许误差范围正负40%,面积范围15*15至125*125】
经过判断后的轮廓图:
6.由于每个车牌都包含白色背景属性。我们为了更精确的裁剪图像,可以使用floodfill算法【用指定颜色填充某一密闭区域,相当于油漆桶的功能】来提取那些旋转的矩形。
不会翻译,不怎么明白,各位这步直接看代码吧
第一步的原文:get several seeds near the last rotated rectangle center. Then get the minimum size of plate between the width and height, and use it to generate random seeds near the patch center.】总之,得到每个矩形的中心,然后求每个矩形各自长宽的较小值,再用随机数和这个较小值得到中心附近的种子点
第二步的原文:for each seed, we use a floodFill function to draw a new mask image to store the new closest cropping region:
第三部的翻译:对这些裁剪区域,再次用纵横比和区域面积进行验证,再去除图像的旋转,并裁剪图像到统一尺寸,均衡化图像的灰度
下面,分别给出这三步的结果图:
第一步的图像,绿色为矩形中心,黄色为种子点,不知道大家是否能看清楚:
第二步的图片,上图有5处种子区域,故有5个模版mask图像【代表最近邻接区域】:
第三步的结果图,注意:这里的结果就是训练SVM的正负样本,只要人工挑选一下:
下面给出以上部分的完整代码【我讨厌一段段的写:)】
以上部分,就是自动生成正负样本的代码。比人工去QQ截图好多了:)
在介绍SVM车牌分类之前,我介绍怎么训练SVM【注意:SVM的实现是个庞大的工程,我一直没有自己弄过,这里使用的还是opencv封装的SVM】
如何训练:
正样本75张包含车牌的图像和35张不包含车牌的144*33图像。【还有其他更好的特征来训练SVM,PCA,傅立叶变换,纹理分析等等】。
如何获取样本及存放训练数据。
通过上述图像分割步骤,我们可以得到车牌及非车牌图像,我们把二者都执行reshaple(1,1),再存放到trainImage的矩阵中,并修改对应trainLables矩阵的0-1值,然后把trainData改为32为浮点数系,再把trainData和trainLabel直接写进xml文件【也就是说xml中包含了样本图像的像素值和样本分类标记】
具体代码:
最后,给出使用Opencv提供的SVM分类器,对图像进行分了的完整代码【对一副图像判断其中是否含有西班牙车牌】:
劳什子外国人搞了车牌类,好吧,我挑和本文有关的都贴出来吧
这里关于OPENCV的各种函数配置,我一点没提,因为如果不懂原理,就不要用人家成熟的东西,否则永远被动,被opencv牵着走。
继上一篇文章后,现在要做的就是从车牌图像上使用optical character recognition算法将字符提取出来。对于每一块被检测的车牌,使用带监督的神经网络机器学习算法来识别字符。
本文内容:
1.字符分割
2.神经网络训练方法
3.使用神经网络预测字符
一、字符分割【OCR Segment】
在使用神经网络对每个字符进行预测之前,我们必须从车牌图像中扣取改字符图片,因此有如下步骤:
本文的输入图像为上一篇文章的车牌:
a.二值化车牌
b.求轮廓
c.求最小外接矩形
d.用纵横比及面积,筛选外接矩形
e.调整统一矩形大小并保存每个字符的图片【注意:分割得到顺序和车牌字符顺序无关,可能不同】
代码:
二、神经网络训练
1.多层感知机简介:
多层感知机结构:【隐层数量为1层或多层,实际上自从引入了深度学习后,才有多层】
其中,每个神经元结构如下:
每个神经元都是相似的且每个神经元都有自己的判定边界,有多个输入和多个输出。不同权重的输入结合激励函数得到不同的输出。常见的激励函数有S型、高斯型、上图的hadrlim型。单层的单个神经元可以将输入向量分为两类,而一个有S个神经元的感知机,可以将输入向量分为2^S类
2.获取训练数据
和上一篇训练SVM所使用的特征不同,现在使用每个字符的累计直方图和低分辨率采样图像构成的高维向量作为训练神经网络的特征。训练的样本矩阵P为N*M,其中N(行)代表各个样本图片的融合特征,M(列)为类别。从书中给的已经训练好的orc.xml看,N有675行,M有30列,30列代表西班牙车牌有30种字符0-9和20个英文字母组成,675是这么来的,比如字符0有35张图片样本,对应产生35行高维向量,字符1有40张样本图片,对应产生40行高维向量,然后按照不同分辨率5*5、10*10、15*15、20*20采样【书中ocr.xml只有675,只采用5*5分辨率】。矩阵P实际上是对每一种高维向量的类别标注:
在Opencv中使用多层感知机需要配置training data矩阵、classes矩阵、隐层神经元数量。其中,训练数据矩阵和列别标识矩阵均从ocr.xml文件获取【下文会介绍】,这里只采用单隐层,包含10个神经元,输入层为675行,输出层为30行。
计算ocr.xml文件具体步骤:
a.将上一步分割得到的每个字符进行人工分类【可放在不同目录下】,比如最终字符0有35张图片,字符a有30张图片并定义数组【这些数字之和为675】:
具体训练代码为:
具体代码:
又用到了车牌类,这里面有车牌字符相对位置调整的函数,都给出来吧:
Plate.h:
这边运行时间略长,大概10s以下吧。这就是Android不能做太多图像处理的原因,运行速度不给力啊。
上上后面做的评估是对隐层神经元数量和不同分辨率的一种统计,没多大花头,以后要用再看吧。而且车牌识别已经做烂了,没什么动力了~~
好吧,下一篇尝试将车牌检测与识别都移植到android上试试。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。