python做神经网络识别车牌_OpenCV进阶之路:神经网络识别车牌字符

python做神经⽹络识别车牌_OpenCV进阶之路:神经⽹络识
别车牌字符
1. 关于OpenCV进阶之路
前段时间写过⼀些关于OpenCV基础知识⽅⾯的系列⽂章,主要内容是⾯向OpenCV初学者,介绍OpenCV中⼀些常⽤的函数的接⼝和调⽤⽅法,相关的内容在OpenCV的⼿册⾥都有更详细的解释,当时⾃⼰也是边学边写,权当为⼀种笔记的形式,所以难免有浅尝辄⽌的感觉,现在回头看来,很多地⽅描述上都存在不⾜,以后有时间,我会重新考虑每⼀篇⽂章,让成长系列对基础操作的介绍更加详细⼀些。
OpenCV进阶之路相⽐于成长系列,不会有太多的基础函数的介绍,相对来说会更偏向于⼯程实践,通过解决实际问题来说明某些较⾼级函数的⽤法和注意事项,主要内容会集中在特征提取、机器学习和⽬标跟踪⼏个⽅向。所以这个系列⽂章知识点没有先后顺序之分,根据个⼈平时⼯作学习中遇到的问题⽽定。
这篇⽂章主要介绍OpenCV中神经⽹络的⽤法,并通过车牌字符的识别来说明⼀些参数设置,函数调⽤顺序等,⽽关于神经⽹络的原理在博客机器学习分类⾥已经详细的讲解与实现了,所以本⽂中就不多加说明。
2. 车牌字符识别
车牌识别是计算机视觉在实际⼯程中⼀个⾮常成功的应⽤,虽然现在技术相对来说已经成熟,但是围绕着车牌定位、车牌⼆值化、车牌字符识别等⽅向,还是不时的有新的算法出现。通过学习车牌识别来提升⾃⼰在图像识别⽅⾯的⼯程经验是⾮常好的,因为它⾮常好的说明了计算机视觉的⼀般过程:
图像$\to$预处理$\to$图像分析$\to$⽬标提取$\to$⽬标识别
⽽整个车牌识别过程实际上相当于包含了两个上述过程:1,是车牌的识别;2,车牌字符的识别。
这篇⽂章其实主要是想介绍OpenCV中神经⽹络的⽤法,⽽不是介绍车牌识别技术。所以我们主要讨论的内容集中在车牌字符的识别上,关于定位、分割等不多加叙述叙述。
3. 字符特征提取
在深度学习(将特征提取作为训练的⼀部分)这个概念引⼊之前,⼀般在准备分类器进⾏识别之前都需要进⾏特征提取。因为⼀幅图像包含的内容太多,有些信息能区分差异性,⽽有些信息却代表了共性。所以我们要进⾏适当的特征提取把它们之间的差异性特征提取出来。
这⾥⾯我们计算⼆种简单的字符特征:梯度分布特征、灰度统计特征。这两个特征只是配合本篇⽂章
来说明神经⽹络的普遍⽤法,实际中进⾏字符识别需要考虑的字符特征远远要⽐这复杂,还包括相似字特征的选取等,也由于⼯作上的原因,这⼀部分并不深⼊的介绍。
1,⾸先是梯度分布特征,该特征计算图像⽔平⽅向和竖直⽅向的梯度图像,然后通过给梯度图像分划不同的区域,进⾏梯度图像每个区域亮度值的统计,以下是算法步骤:
<1>将字符由RGB转化为灰度,然后将图像归⼀化到16*8。
遥控飞机制作<2>定义soble⽔平检测算⼦:$x\_mask = [-1,0,1;-2,0,2; –1,0,1]$和竖直⽅向梯度检测算⼦$y\_mask=x\_mask^T$。
<3>对图像分别⽤$mask\_x$和$mask\_y$进⾏图像滤波得到$SobelX$和$SobelY$,下图分别代表原图像、$SobelX$和$SobelY$。
<4>对滤波后的图像,计算图像总的像素和,然后划分4*2的⽹络,计算每个⽹格内的像素值的总和。
<5>将每个⽹络内总灰度值占整个图像的百分⽐统计在⼀起写⼊⼀个向量,将两个⽅向各⾃得到的向量并在⼀起,组成特征向量。
1 void calcGradientFeat(const Mat& imgSrc, vector&feat)
csilv2 {
3 float sumMatValue(const Mat& image); //计算图像中像素灰度值总和
4
5 Mat image;
6 cvtColor(imgSrc,image,CV_BGR2GRAY);
7 resize(image,image,Size(8,16));8
9 //计算x⽅向和y⽅向上的滤波
10 float mask[3][3] = { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1} };11
12 Mat y_mask = Mat(3, 3, CV_32F, mask) / 8;13 Mat x_mask = y_mask.t(); //转置
盾构机过站14 Mat sobelX, sobelY;15
16 filter2D(image, sobelX, CV_32F, x_mask);17 filter2D(image, sobelY, CV_32F, y_mask);18
19 sobelX =abs(sobelX);20 sobelY =abs(sobelY);21
22 float totleValueX =sumMatValue(sobelX);23 float totleValueY =sumMatValue(sobelY);24
25 //将图像划分为4*2共8个格⼦,计算每个格⼦⾥灰度值总和的百分⽐
26 for (int i = 0; i < ws; i = i + 4)27 {28 for (int j = 0; j < ls; j = j + 4)29 {30 Mat subImageX = sobelX(Rect(j, i, 4, 4));31 feat.push_back(sumMatValue(subImageX) /totleValueX);32 Mat subImageY= sobelY(Rect(j, i, 4, 4));33
feat.push_back(sumMatValue(subImageY) /totleValueY);34 }35 }36 }37 float sumMatValue(const Mat&image)38 {39 float sumValue = 0;40 int r =ws;41 int c =ls;42 if(image.isContinuous())43 {44 c = r*c;45 r = 1;46 }47 for (int i = 0; i < r; i++)48 {49 const uchar* linePtr = image.ptr(i);50 for (int j = 0; j < c; j++)51 {52 sumValue +=linePtr[j];53 }54 }55 returnsumValue;56 }
calcGradientFeat
2,第⼆个特征⾮常简单,只需要将图像归⼀化到特定的⼤⼩,然后将图像每个点的灰度值作为特征即可。
<1>将图像由RGB图像转换为灰度图像;
<2>将图像归⼀化⼤⼩为$8×4$,并将图像展开为⼀⾏,组成特征向量。
4. OpenCV中的神经⽹络
关于神经⽹络的原理我的博客⾥已经写了两篇⽂章,并且给出了C++的实现,所以这⾥我就不提了,下⾯主要说明在OpenCV中怎么使⽤它提供的库函数。
CvANN_MLP是OpenCV中提供的⼀个神经⽹络的类,正如它的名字⼀样(multi-layer perceptrons),它是⼀个多层感知⽹络,它有⼀个输⼊层,⼀个输出层以及1或多个隐藏层。
灌肠袋4.1. ⾸先我们来创建⼀个⽹络,我们可以利⽤CvANN_MLP的构造函数或者create函数。
1 CvANN_MLP::CvANN_MLP(const Mat& layerSizes, int activateFunc=CvANN_MLP::SIGMOID_SYM, double fparam1=0, double fparam2=0);
2 void CvANN_MLP::create(const Mat& layerSizes, int activateFunc=CvANN_MLP::SIGMOID_SYM, double fparam1=0,
double fparam2=0 );
上⾯是分别是构造函数和cteate成员函数的接⼝,我们来分析各个形参的意思。
layerSizes:⼀个整型的数组,这⾥⾯⽤Mat存储。它是⼀个1*N的Mat,N代表神经⽹络的层数,第$i$列的值表⽰第$i$层的结点数。这⾥需要注意的是,在创建这个Mat时,⼀定要是整型的,uchar和float型都会报错。
⽐如我们要创建⼀个3层的神经⽹络,其中第⼀层结点数为$x_1$,第⼆层结点数为$x_2$,第三层结点数为$x_3$,则layerSizes可以采⽤如下定义:
1 Mat layerSizes=(Mat_(1,3)<
或者⽤⼀个数组来初始化:
1 int ar[]={x1,x2,x3};
2 Mat layerSizes(1,3,CV_32S,ar);
activateFunc:这个参数⽤于指定激活函数,不熟悉的可以去看我博客⾥的这篇⽂章《神经⽹络:感知器与梯度下降》,⼀般情况下我们⽤SIGMOID函数就可以了,当然你也可以选择正切函数或⾼斯函数作为激活函数。OpenCV⾥提供了三种激活函数,线性函数
(CvANN_MLP::IDENTITY)、sigmoid函数(CvANN_MLP::SIGMOID_SYM)和⾼斯激活函数(CvANN_MLP::GAUSSIAN)。
后⾯两个参数则是SIGMOID激活函数中的两个参数$\alpha$和$\beta$,默认情况下会都被设置为1。
$$f(x)=\beta \frac{1-e^{-\alpha x}}{1+e^{-\alpha x}}$$
4.2. 设置神经⽹络训练参数
神经⽹络训练参数的类型存放在CvANN_MLP_TrainParams这个类⾥,它提供了⼀个默认的构造函数,我们可以直接调⽤,也可以⼀项⼀项去设。
1 CvANN_MLP_TrainParams::CvANN_MLP_TrainParams()
2 {低频声波吹灰器
3 term_crit = cvTermCriteria( CV_TERMCRIT_ITER +
CV_TERMCRIT_EPS, 1000, 0.01);4 train_method =RPROP;5 bp_dw_scale = bp_moment_scale = 0.1;6 rp_dw0 = 0.1;
rp_dw_plus = 1.2; rp_dw_minus = 0.5;7 rp_dw_min = FLT_EPSILON; rp_dw_max = 50.;8 }
它的参数⼤概包括以下⼏项。
term_crit:终⽌条件,它包括了两项,迭代次数(CV_TERMCRIT_ITER)和误差最⼩值(CV_TERMCRIT_EPS),⼀旦有⼀个达到条件就终⽌训练。
train_method:训练⽅法,OpenCV⾥提供了两个⽅法⼀个是很经典的反向传播算法BACKPROP,另⼀个是弹性反馈算法RPROP,对第⼆种训练⽅法,没有仔细去研究过,这⾥我们运⽤第⼀种⽅法。
光化学衍生器剩下就是关于每种训练⽅法的相关参数,针对于反向传播法,主要是两个参数,⼀个是权值更新率bp_dw_scale和权值更新冲量
bp_moment_scale。这两个量⼀般情况设置为0.1就⾏了;太⼩了⽹络收敛速度会很慢,太⼤了可能会让⽹络越过最⼩值点。
我们⼀般先运⽤它的默认构造函数,然后根据需要再修改相应的参数就可以了。如下⾯代码所⽰,我们将迭代次数改为了5000次。
1 CvANN_MLP_TRainParams param;2
<_crit=cvTermCriteria(CV_TerMCrIT_ITER+CV_TERMCRIT_EPS,5000,0.01);
4.3. 神经⽹络的训练
我们先看训练函数的接⼝,然后按接⼝去准备数据。
1 int CvANN_MLP::train(const Mat& inputs, const Mat& outputs, const Mat& sampleWeights, const Mat& sampleIdx=Mat(), CvANN_MLP_TrainParams params=CvANN_MLP_TrainParams(), int flags=0 );
inputs:输⼊矩阵。它存储了所有训练样本的特征。假设所有样本总数为nSamples,⽽我们提取的特征维数为ndims,则inputs是⼀个$nSamples*ndims$的矩阵,我们可以这样创建它。
1 Mat inputs(nSamples,ndims,CV_32FC1); //CV_32FC1说明它储存的数据是float型的。
我们需要将我们的训练集,经过特征提取把得到的特征向量存储在inputs中,每个样本的特征占⼀⾏。
outputs:输出矩阵。我们实际在训练中,我们知道每个样本所属的种类,假设⼀共有nClass类。那么我们将outputs设置为⼀个nSample ⾏nClass列的矩阵,每⼀⾏表⽰⼀个样本的预期输出结果,该样
本所属的那类对应的列设置为1,其他都为0。⽐如我们需要识别0-9这10个数字,则总的类数为10类,那么样本数字“3”的预期输出为[0,0,1,0,0,0,0,0,0,0];
sampleWeights:⼀个在使⽤RPROP⽅法训练时才需要的数据,所以这⾥我们不设置,直接设置为Mat()即可。
sampleIdx:相当于⼀个遮罩,它指定哪些⾏的数据参与训练。如果设置为Mat(),则所有⾏都参与。
params:这个在刚才已经说过了,是训练相关的参数。
flag:它提供了3个可选项参数,⽤来指定数据处理的⽅式,我们可以⽤逻辑符号去组合它们。UPDATE_WEIGHTS指定⽤⼀定的算法去初始化权值矩阵⽽不是⽤随机的⽅法。NO_INPUT_SCALE和NO_OUTPUT_SCALE分别⽤于禁⽌输⼊与输出矩阵的归⼀化。
⼀切都准备好后,直接开始训练吧!
4.4. 识别
识别是通过Cv_ANN_MLP类提供的predict来实现的,知道原理的会明⽩,它实际上就是做了⼀次向前传播。
1 float CvANN_MLP::predict(const Mat& inputs, Mat& outputs) const
在进⾏识别的时候,我们对图像进⾏特征提取,把它保存在inputs⾥,通过调⽤predict函数,我们得到⼀个输出向量,它是⼀个1*nClass 的⾏向量,其中每⼀列说明它与该类的相似程度(0-1之间),也可以说是置信度。我们只⽤对output求⼀个最⼤值,就可得到结果。这个函数的返回值是⼀个⽆⽤的float值,可以忽略。
5. 车牌字符识别测试
1,我们需要读取所有的训练样本,将它们的路径在保存在vector中。
这⾥⾯我的车牌字符,因为1和I、0和O是⼀样的,所以数字加字母⼀共34类,其中每类有200个样本图像,共34*200个训练样本。
2,计算特征。我们按顺序读⼊图像,调⽤特征计算函数,把得到的结合保存在input对应的⾏中,同时把图像对应的预期输出保存在output中。
3,创建神经⽹络,这⾥我们计算得到的特征维数为48维,所以我们简单的设计⼀个3层的神经⽹络,输⼊层有48个结点,隐藏层也为48个结点,输出层为34个结点。然后神经⽹络的训练⽅法选⽤BACKPROP,迭代次数设置为5000次。
4,调⽤训练函数进⾏训练,并保存训练得到的权值矩阵,直接调⽤save成员函数即可。
nnetwork.save(“l”);
5,识别测试,我们可以⽤单张图像进⾏测试,也可以选定⼀个测试集去进⾏测试,⽐如可以⽤⼀半的图像作为训练集,⼀半的图像作为测试集。这⾥我们可以加载已经训练好的权值矩阵,⽽不⽤重新训练,只要开始有保存了xml⽂件。但是记得你还是要创建⼀个⽹络后,才能加载进来。
1 int NNClassifier::classifier(const Mat&image)
2 {
3 Mat nearest(1, nclass, CV_32FC1, Scalar(0));
4 Mat charFeature;
5 calcFeature(image, charFeature);6
7 neuralNetwork.predict(charFeature, nearest);8 Point maxLoc;9 minMaxLoc(nearest, NULL, NULL, NULL, &maxLoc);10 int result =maxLoc.x;11 returnresult;12 }
这⾥我简单的做了⼀下测试,在这两个特征下,⽹络设置为3层[48,48,34],⼀半图像为测试集,得到的识别率为98%,我相信通过尝试调整⽹络的层数以及选⽤更好的特征,⼀定会得到更满意的识别率。PS(⼯作中⽤的是SVM识别器,正常采集到的车牌,字符识别率在99.8%以上)。但是神经⽹络识别器有个很⼤的优点就是,⼀旦⽹络训练好,识别需要的数据⽂件⾮常⼩,⽽且速度很快。
6. 字符样本的下载
看到⽂章下的评论多是需求字符样本的,希望拿到字符样本的同学不要将其⽤于商业⽤途或者创建分享下载的链接。博⽂⾥⽤的样本是每类200张图像的测试样本,下⾯给出⼀份每类50个图像的样本⼦集,⽤来做测试已经够了。

本文发布于:2024-09-23 07:27:44,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/255235.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:图像   训练   特征   车牌
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议