这篇文章献给所有第一次听说车牌识别ANPR但需要短时间实现的苦逼同学们。

最近的小学期实训做的是一个车牌识别系统,说实话真不知道学校怎么想的,虽然说图像处理也算的上是数字媒体很重要的一块分支了,但咱这几年学的全是图形渲染啊。图形与图像虽然只差了一个字,但内容真是差了十万八千里了(当然这话是夸张了,事实上在使用shader进行特效渲染的最后一步往往都是在做图像处理,如Bloom, Outline, Field Depth等,但这些也只是用到了图像处理中很基础的一部分)。

小学期不到10天的时间要从零搞出个车牌识别系统,更别说我们所有组员全都有实习,老师大撒把,发了需求直接不管。当时本来还觉得这种应用广泛的东西网上肯定有现成的例子,随便改改就好,结果搜了半天,最令我崩溃的一句话就是:“这东西你要真做出来了就卖钱去吧”。擦,算了,求人不如求自己,最终在OpenCV的帮助下我看了两天资料,写了两天程序,居然就实现了,看到从原始图片中抠出车牌,再从车牌中抠出数字,再匹配出结果(这步是我同学做的),我自己都觉得神奇啊!虽然肯定是买不了钱,但还是很激动啊~~

废话不多说了,以后和我一样做这个实训项目的同学们可有福了,接下来就详细讲讲如何简单实现ANPR(Automatic Number Plate Recognition)吧。

配置环境

我使用的是OpenCV 2.3.1和VS2010,下载与配置方法在opencv的中国官网www.opencv.org.cn上都有详细介绍。像这种开源库最麻烦的就是环境配置了,什么makefile这种东西我看着就头大,当然对于我这种菜鸟人家提供了CMake来帮你进行傻瓜式的一键配置,不过这对于我来说还是麻烦,毕竟还要再装个程序。幸运的是我下载的是SuperPack版本,也就是说在opencv/build目录下已经有人家编译好的全部语言、开发平台的lib, dll以及头文件。虽然很大,但是下下来就能用^_^,咱的追求就是简单,更简单!

建立工程后要做的就是在工程属性的C++目录中将相应的include文件夹,lib文件夹配置进去,另外还要链接上你需要使用的库。

看网上一片教程说在属性——链接器——输入中配置附加依赖项:opencv_calib3d231d.lib; opencv_contrib231d.lib; opencv_core231d.lib;opencv_features2d231d.lib; opencv_flann231d.lib; opencv_gpu231d.lib;opencv_highgui231d.lib; opencv_imgproc231d.lib; opencv_legacy231d.lib;opencv_ml231d.lib; opencv_objdetect231d.lib; opencv_ts231d.lib;opencv_video231d.lib。

NND,这么多哪儿记得住啊,下次新建一个工程还得网上找这篇文章拷贝粘贴么?当然不用,只少在车牌识别系统中,我们所需要的只有三个库,而且在VS中我们可以使用预编译指令连接这些库,这样在你将工程拷贝给同学的时候就不用再担心环境配置的问题了。

#pragma comment(lib, “opencv_core231d.lib”)

#pragma comment(lib, “opencv_imgproc231d.lib”)

#pragma comment(lib, “opencv_highgui231d.lib”)

core是opencv的核心库,一些主要的数据结构都在这里定义,imgproc顾名思义包含了主要的图像处理函数,highgui是一个简单的显示框架,帮助快速创建窗口显示图像等,就好像opengl中的glut。如果使用MFC框架进行显示的话需要额外添加一个类CvvImage,具体情况网上随便一搜就有,不废话了。

基本算法

OpenCV只是一个提供基本图像处理方法的工具库,具体应用到车牌识别系统中,需要综合实用图像处理方法,可不能指望OpenCV中直接有个函数帮你把大部分事儿都做了(我一开始就这么期望的…)。

大致的算法网上可以找到很多论文,其中我主要参考了以下两篇:

http://blog.csdn.net/heihei723/article/details/728046

http://www.doc88.com/p-677404951164.html

这俩篇都通俗易懂,算法比较简单实用,适合初学者上手。从整体看,车牌号识别主要分三步走:1.提取车牌 2.提取字符 3.字符匹配识别。下面就一一来介绍一下具体步骤,及其使用到的opencv函数,函数的具体使用方法同学们就自己查查吧,懒得赘述了。

车牌提取

灰度化:灰度化的概念就是将一张三通道RGB颜色的图像变成单通道灰度图,为接下来的图像处理做准备。

CvCvtColor。cvCvtColor(image, grayScale, CV_BGR2GRAY);

竖向边缘检测:首先车牌上的数字都有很锐利的边缘,另外这些边缘主要都是纵向的,因此通过这一步可以去除图像上的大量无用信息。

    sobel = cvCreateImage(cvGetSize(grayScale), IPL_DEPTH_16S,1);

cvSobel(grayScale, sobel, 2, 0, 7);

IplImage* temp = cvCreateImage(cvGetSize(sobel), IPL_DEPTH_8U,1);

cvConvertScale(sobel, temp, 0.00390625, 0);

第一步首先创建一张深度为16位有符号(-65536~65535)的的图像区域保持处理结果。

第二步进行x方向的sobel检测,算子的大小(最后一个参数)我选择了7*7,完全时瞎选的,可以结合实际效果进行调整。

最后将图像格式转换回8位深度已进行下一步处理

自适应二值化处理:二值化的处理强化了锐利的边缘,进一步去除图像中无用的信息,使用过程中主要注意阀值的选取,我为了省事儿使用了opencv自带的自适应的的二值化处理,缺点是无用信息有点多,但车牌数字信息也会更为凸显。

cvThreshold(sobel, threshold, 0, 255, CV_THRESH_BINARY| CV_THRESH_OTSU);

最后的参数CV_THRESH_OTSU就是使用自适应算法,千万不要看学习OpenCV那本书上介绍的cvAdaptiveThreshold方法,那完全时披着二值化皮的边缘检测函数,坑死人!

形态学(膨胀腐蚀)处理:膨胀与腐蚀的处理效果就如其名字一样,我们通过膨胀连接相近的图像区域,通过腐蚀去除孤立细小的色块。通过这一步,我们希望将所有的车牌号字符连通起来,这样为我们接下来通过轮廓识别来选取车牌区域做准备。由于字符都是横向排列的,因此要连通这些字符我们只需进行横向的膨胀即可。

//自定义1*3的核进行X方向的膨胀腐蚀

IplConvKernel* kernal = cvCreateStructuringElementEx(3,1, 1, 0, CV_SHAPE_RECT);

cvDilate(threshold, erode_dilate, kernal, 2);//X方向膨胀连通数字

vErode(erode_dilate, erode_dilate, kernal, 4);//X方向腐蚀去除碎片

cvDilate(erode_dilate, erode_dilate, kernal, 2);//X方向膨胀回复形态

//自定义3*1的核进行Y方向的膨胀腐蚀

kernal = cvCreateStructuringElementEx(1, 3, 0, 1, CV_SHAPE_RECT);

cvErode(erode_dilate, erode_dilate, kernal, 1);// Y方向腐蚀去除碎片

cvDilate(erode_dilate, erode_dilate, kernal, 2);//回复形态

进行膨胀腐蚀操作需要注意的是要一次到位,如果一次膨胀没有连通到位,那么再次腐蚀将会将图像回复原装,因此我首先做了2次迭代的膨胀,保证数字区域能连通起来,再进行4次迭代腐蚀,尽可能多的去除小块碎片,随后2次迭代膨胀,保证膨胀次数与腐蚀次数相同,以回复连通区域形态大小。

矩形轮廓查找与筛选:经过上一步操作,理论上来说车牌上的字符连通成一个矩形区域,通过轮廓查找我们可以定位该区域。当然,更为准确的说,经过上面的操作,我们将原始图片中在X方向排列紧密的纵向边缘区域连通成了一个矩形区域,出了车牌符合这个特点外,其他一些部分如路间栏杆,车头的纹理等同样符合。因此我们会找到很多这样的区域,这就需要我们进一步根据一些关于车牌特点的先验知识对这些矩形进行进一步筛选。最终,定位车牌所在的矩形区。

首先来看轮廓检测:

IplImage* copy = cvCloneImage(img);

CvMemStorage* storage = cvCreateMemStorage();

CvSeq* contours;

cvFindContours(copy, storage, &contours);

        

while(contours != nullptr)

{

         rects.push_back(cvBoundingRect(contours));//list<CvRect> rects

         contours= contours->h_next;

}

使用list存储全部查找到的CvRect,因为接下来我们要频繁的对容器中的元素进行插入删除操作。

矩形的筛选算法完全就要自己写啦~这一步筛选效果的好坏直接决定了整个一套识别算法是否能得到一个好的结果。在之后对于算法的调整也主要是集中于这一部分,调整一些先验知识的参数。我设计的筛选算法主要涉及这几个部分:1.大小(图片大小的5%以下) 2.位置(图片高度的40%~90%之间) 3.X方向合并(有时车牌会处理成两个或多个相邻的矩形区,需要进行合并) 4.大小形状(宽高比)

最后,找到筛选之后最大的那个矩形就是了,注意下图的红色线框!

 

字符提取

字符提取的步骤与车牌提取的思想大致相同,但无需再做形态学处理。仍然是灰度化->二值化->轮廓检测->自定义筛选->定位。(另外提一句,二值化之后可能还要做一下反色处理,因为有黄底/白底 - 黑字这种车牌,不过因为给我们的样本中没有这类车牌,因此偷个懒就不做了^^)

相应的步骤所使用的OpenCV函数与之前别无二致,因此不做赘述,这里主要讲一下自定义的筛选和去除边框铆钉这一核心步骤。

首先,最容易导致字符提取失败的情况是亮色的边框与铆钉,他们会与车牌字母连通在一起,导致轮廓检测失败,如下图几种情况:

3与H和铆钉连接,在轮廓检测是无法提取

全部字符的底部都连住了车牌底框,轮廓检测将彻底失败

因此在进行第一次初步的轮廓检测之后,还需要进行除边框与铆钉的处理,之后再进行轮廓检测,再经过筛选,基本上妥妥的能分离出每个字符了。

去除边框:首先对图像进行Y方向的逐步裁剪,直至轮廓检测可以检测到超过5个轮廓。这一步保证不会与铆钉连接的中间几个字符可以通过轮廓检测识别出来。

去除铆钉:根据识别出来的几个字符矩形轮廓确定车牌照所有字符的上下界,根据其切割图像。(前提是认为所有字符是水平排列的,因此当车牌倾斜时需要用到更为复杂的算法)

下图显示了这一过程:

矩形筛选:同学们肯定也注意到了,上图中左右两个边框在作轮廓拾取时也可以获取到,而且很容易与数字1混淆,另外想D, 0, P, R, 8, 9, 6这种数字也会把内部的封闭区域检测出轮廓,因此同车牌提取一样,对检测出的矩形轮廓还要进一步筛选,筛选主要包括排序;高度、宽窄、面积(去除左右边框、圆点、8,p等小的内轮廓);包含检测剔除(去除0、D、非单连通的汉字这种大的内轮廓)。

中文字符定位:中文字符由于有些是非单连通的,因此在进行筛选时可能会被筛掉,即使没有被筛掉,所得的矩形也很可能并非包含了全部字体,如京。因此在字符提取的最后一步我们需要重定位中文字符的矩形截取框。算法很简单,以第二个字符的矩形轮廓为基准,大小不变,y坐标不变,根据字符间平均间距(不包括2-3字符的间距)确定x坐标即可。

double d = lastChar.x + 0.5 * lastChar.width - llChar.x- llChar.width * 0.5 - avgWidth;

注意在求字符间平均间距的时候不要简单的用a.next.x– a.x这种方法,一旦那个字符是数组1结果会错的很难看。要用字符中点间距减去字符正常宽带这种方式。获取正常宽度的算法也很简单,根据先验知识I的矩形包围框宽度一般小于25像素(我提取的车牌大小统一为400 * 100),因此只要找到一个宽度大于25的矩形框,把它的宽度即可作为平均宽度。

最后总结下来,提取字符的流程是这样的 灰度化 -> 二值化 -> 轮廓检测 –> 去除边框 ->轮廓检测 -> 大小筛选 -> 去除铆钉 –> 轮廓检测 -> 筛选 -> 重定位中文字符

字符匹配

进行匹配之前你首先需要有一套标准字符模板作为参考,然后将提取出来的字符图片与每个模板进行某种算法的匹配,求得一个匹配值,最终将最佳匹配结果作为该字符图片所代表的字符。这种方法比较笨,也耗时,但是简单,好实现,如果你对这方面想有更深入研究可以在网上搜索一下OCR(Optical Character Recognition)。

那么匹配算法是什么样的呢?OpenCV提供了许多匹配函数,如直方图匹配,Hu矩匹配,轮廓匹配。如果你上网上搜字符匹配,你可能最多看到的就是模板匹配,还有什么字符匹配最适合用模板匹配之类的话。我可以在这里负责任的告诉你,以上方法统统都不行!!

至少在我的算法中切出来的字符图片用不了,尤其是神马模板匹配,可能是我的理解不对,模板检测是用来在一张大图中检测出一部分形状的,和我想要的根本就是风马牛不相及嘛;还有那个轮廓检测,听名字感觉很靠谱啊~但实际是如果你得到的字符不是标准到跟模板一模一样(在这个项目中主要是线条的粗细),效果差极了。

怎么办?显然这时我已经绕进去了,不过好在我同学还保持清醒,最终的解决办法简单的无法想象。。。。逐像素点匹配!神马?这从如此高深的算法跳到如此简单的方式我一时不能接受,但是我负责任的告诉你,如果你不考虑速度、实时性,也不觉得使用这么简单的方法掉价的话,这个方法绝对靠谱!!!(注意这个项目可没打算卖钱也没打算申请啥技术专利啊^_^;)

 

逐点匹配:很简单,经过处理的带匹配图片与模板图片此时大小相同,且都为2值图(非0即255)遍历全部像素点,记录两张图中值不同的像素个数,除以全部像素数量即为匹配率。显然越接近0越匹配。

近似区分:上一步的匹配结果已经非常好了,但对于一些容易混淆的字符还需要进一步区分如0和U, B和8和9等等。对于这些字符,我们在通过逐点匹配后不能马上认定,而需要进行特征检测。最简单的特征如连通区域个数(区分0 – U, 8–9),直线检测(区分B和8)等等。

改进

加入图像旋转变化,这点对于识别倾斜车牌及其上的数字很重要,可用的方法再这篇文章中http://www.doc88.com/p-677404951164.html有提到,有时间可以试试效果。

二值化前加入锐化处理,采用OpenCV的自适应阀值函数会提取出大量模糊的边缘及碎片,直接影响是导致字符变粗,易于车牌边框连接,影响匹配效果。

中文字符的识别,目前纯靠人品。。。。

从零使用OpenCV快速实现简单车牌识别系统相关推荐

  1. 【OpenCV+Qt】使用车牌识别系统EasyPR识别车牌号

    EasyPR是一个中文的开源车牌识别系统,其车牌识别划分为了两个过程:即车牌检测(Plate Detection)和字符识别(Chars Recognition)两个过程: 车牌检测(Plate De ...

  2. opencv学习—简单车牌识别操作(python)

    opencv学习-简单车牌识别操作(python) 目录 opencv学习-简单车牌识别操作(python) 利用opencv进行车牌识别的详细流程如下: 1.车牌检测 2.分割车牌号并进行识别 3. ...

  3. OpenCV(项目)车牌识别1 -- 车牌提取(形态学)

    目录 一.形态学车牌提取(简单:单情景) 1.读取图片,转灰度图 2.提取轮廓(Sobel算子提取y方向边缘) 3.自适应二值化 4.闭运算处理,把图像闭合.揉团,使图像区域化 5.腐蚀/膨胀去噪得到 ...

  4. OpenCV学习案例之车牌识别EasyPR

    OpenCV学习案例之车牌识别easyPR 起始 github上开源中文车牌识别库比较少: HyperLPR,基于深度学习高性能中文车牌识别库,支持python.c++, 可以在Android,Lin ...

  5. Python 基于 opencv 的车牌识别系统, 可以准确识别车牌号

    大家好,我是程序员徐师兄,6 年大厂程序员经验,点击关注我 简介 毕业设计基于Opencv的车牌识别系统 车牌搜索识别找出某个车牌号 对比识别车牌系统 车牌数据库认证系统 车牌图文搜索系统 车牌数据库 ...

  6. 毕业设计 : 车牌识别系统实现【全网最详细】 - opencv 卷积神经网络 机器学习 深度学习

    文章目录 0 简介 1 车牌识别原理和流程 1.1 车牌定位 1.2 基于图形图像学的定位方法. 1.3 基于机器学习的定位方法. 1.4 字符分割 1.5 字符识别 2 基于机器学习的车牌识别 2. ...

  7. python的opencv 车牌识别 开源_毕节进出口车牌识别系统怎么样

    毕节进出口车牌识别系统怎么样 gzheu8il 毕节进出口车牌识别系统怎么样 系统拓扑图如下:该系统以社区中心机房为枢纽,有机的将智慧家居住户.社区数字化服务.物业数字化管理.社区智能化管理结合起来, ...

  8. 基于Opencv的开源的中文车牌识别系统

    真正的大师,永远都怀着一颗学徒的心! 一.项目简介 基于Opencv的开源的中文车牌识别系统. 二.实现功能 车牌定位 车牌判断 车牌监测 字符分割 字符鉴别 字符识别 车牌识别 车牌抽象 训练车牌识 ...

  9. c# opencv车牌识别_毕设有着落了!一套开源的,基于SpringBoot的车牌识别系统

    阅读本文大概需要 4 分钟. 前言 这个项目是良月柒在逛社区时发现的,刚看到它,思绪直接被拉回了几年前,当初有同学的毕设就是停车场管理系统,关键的功能--车牌识别,连硬件都整上了,一整套流程跑下来,p ...

最新文章

  1. fusioncompute中cpu可以设置的qos参数有哪些?_kubernetes 中 Qos 的设计与实现
  2. 【转】python编码大坑详解2
  3. voc生成xml 代码
  4. 【知识图谱】知识融合
  5. 简述微型计算机的工作原理,高教自学考试微机原理及应用模拟试题
  6. 在一个html中使用另一个html数据,如何为某些HTML标签存储任意数据
  7. Android各个版本API的区别
  8. 终于,J 神还是加入了 Google!
  9. Exchange 2007中批量修改用户邮箱配额
  10. POJ2653 Pick-up sticks
  11. oracle期末试题及答案,oracle期末考试试题及答案
  12. bmp 和JPG有什么区别
  13. 根据名称获取对应的拼音码首字母大写
  14. 怎样把m4a转换mp3格式?
  15. matlab 锁相环仿真,MATLAB锁相环仿真程序求解
  16. Sam Altman 山姆奥特曼:关于生产率(工作效率)
  17. 批量关闭公众号推送_微信发大招,长期不读的公众号可“批量关闭”!
  18. [XUPT_ACM]寒假第二次比赛题解
  19. 索尼美能达50微-版本区别及实拍测评(sony/minolta)50 f2.8 macro
  20. CAP原理以及选取的场景

热门文章

  1. 求100的阶乘有多少个约数?
  2. error LNK2019: 无法解析的外部符号
  3. 大数据发力 LTE端到端KPI指标可精细优化
  4. 关于MBSE设计推进
  5. 转移熵matlab,转移熵
  6. MongoDB 启动失败 ERROR: child process failed, exited with 1
  7. 什么是计算机网络 它有哪些主要功能,什么是计算机网络?计算机网络的功能和特点有哪些...
  8. spring boot测试方法报Failed to determine a suitable driver class的解决办法
  9. 浅谈数据开发神器——数栈离线开发平台(BatchWorks)
  10. 长沙理工大学计算机网络试题,长沙理工大学考试试卷(计算机网络)祥解.doc