车牌识别采购、研发纵览

车牌识别采购、研发纵览

车牌识别采购、研发纵览

《车牌识别采购、研发纵览》是目前最详细的关于车牌识别的资料。本书分两部分:

第一编,介绍车牌识别的技术知识,包括目前我国主要的车牌识别产品名单、车牌识别产品必备/附加的功能,以及车牌识别的应用范围;

第二编,介绍车牌识别的研发知识,其中包括,车牌识别程序的模块、车牌识别程序内核模块的各种类型以及各个子模块的详细算法。

第一编对于广大车牌识别采购人员十分重要;对于广大车牌识别开发人员,要在熟悉了解第一编的基础之上,研读第二编。

第1编 采购

这是《车牌识别采购、研发纵览》的前半部分,是广大车牌识别采购者的必读部分。对于车牌识别研发人员来说也是有很大的参考价值,而且对后面的开发有极大的帮助。

第1章 车牌识别供应商

通过本章,采购人员可以对我国的车牌识别技术水平有一个概括性的认识;研发人员也应该能够了解到这一技术目前的成熟程度。

第1节 车牌识别供应商列表

车牌识别并没有统一的国际标准。本章仅仅列举我国的车牌识别供应商。(因为车牌识别市场比较热门,下面的表格难免遗漏一些新的成员)

名称网址备注
绿睿(推荐)www.voicing.cn高识别率,速度快
SoftWellwww.szsoftwell.com较高识别率,速度慢
PlateDSPwww.platedsp.com识别率较高,速度快
CubicPlatewww.cubicimage.com普及较广,识别率,速度都是中游水平
SupPlatewww.dragonskytech.com速度都是中游水平
汉王www.hanwang.com.cn无法拿到试用软件
车牌识别供应商 列表
第2节 车牌识别供应商举例——绿睿科技

车牌识别属于知识密集型产业。研发车牌识别需要大量的高级软件人才。绿睿科技公司正是一家拥有大量软件人才的公司。她座落于我国的硅谷——北京中关村。“爱国,勤奋踏实,艰苦创业”是该公司的主题。绿睿公司希望能和合作伙伴一起,实现我国的产业升级和结构性调整,提升合作伙伴产品的附加值。让更多的国内公司积极参与世界经济产业链条中,上游的竞争。

绿睿科技以研发为主,而以市场推广为辅;绿睿集中精力提高产品质量,而将更多利润让给合作伙伴。

绿睿科技全体同仁艰苦创业,于2009年推出车牌识别这一智能交通领域产品。绿睿拥有该产品的核心技术和独立的知识产权,而且不断地对产品进行更新换代。

有关绿睿科技更详细的资料请登陆该公司官方网站:www.voicing.cn。以下是该公司的标示:

 

第2章 车牌识别的应用

车牌识别主要用于智能交通领域。随着计算机软硬件的不断升级,车牌识别所依赖的环境日臻完善,车牌识别的功能越来越丰富,应用场合也越来越多。

第1节 车牌识别应用列表
用途描述
停车场管理对持用相同的RFID的车辆,检查车牌是否相同。智能停车场对车牌识别的要求主要集中在准确度方面,对时间要求不是很苛刻。
车辆稽查 治安卡口在城市的交通要道,架设“电子眼”,及时获取“肇事逃逸”车辆地理信息。对车牌识别准确度和速度都有较高要求。另外整个系统应该有强大的数据支持,保存历史资料。
高速公路收费目前很多高速公路是“贷款筑路,收费还钱”。联网收费时,需要通过车牌识别系统计算汽车高速公路里程。
移动稽查官方为打击走私、偷税、违禁等的非法活动,对疑犯车辆进行跟踪。用车牌识别系统快速检查过往车辆。
地磅称重管理用于库存管理。
交通信息采集对卡口车辆进行统计,依靠车牌识别对过往车辆计数。获得车流密度随时间变化的关系。
路桥、隧道收费类似高速公路,实现过往车辆自动收费。同时记录车辆过往时的照片,方便收费系统核实。
重要机关单位汽车出入口管理重要机关单位(比如驻军部队)管理出入车辆。要求车牌识别系统能够识别军用车牌。
闯红灯记录车牌识别系统实时接收红灯信号,根据路口逻辑计算车道上是否允许通过车辆,对违规车辆进行记录,并自动提交罚单。
城市交通管理配合指纹识别,人脸识别对城市交通进行管理。比如限制超载,或者记录车内乘客过少(为了保护环境,轿车内最好有三个以上的人)情况。
车牌识别应用列表 列表
第2节 车牌识别应用举例——智能停车场

智能停车场是车牌识别的典型应用。利用智能停车场系统公司、企业、机关等对车辆可以进行方便的管理;智能停车场系统还有助于提高单位形象,加强内部管理。

智能停车场系统包括,RFID卡、读卡器、道闸、感应线圈、视频系统、MIS系统和车牌识别模块。其中视频系统包括摄像机,视频传输电缆,视频显示终端。停车场一般有多个出入口。所有出入口都配置有联网的计算机,组成MIS系统,负责对车辆数据的记录和处理。出入口同时都配置,车牌识别设备,对进出车辆信息进行采集工作。

当车辆进入停车场时,司机必须刷RFID卡。智能停车场系统根据RFID卡信息对车辆的合法性进行判断,并允许合法车辆通过道闸进入停车场。同时,智能停车场系统记录车辆进入时的视频信息。

当车辆离开停车场时,司机也必须刷RFID卡。根据RFID卡信息,系统找到该车进入停车场的时间点;并计算停车时间长度,提供收费依据。

为了防止不法分子的破坏,智能停车场系统要根据数据库信息,进行RFID卡信息和车牌信息的比对。一旦发现RFID卡信息和车牌信息不匹配,立即报警。所以智能停车场系统必须集成车牌识别模块。

 

第3章 配套产品

车牌识别作为一项核心技术必须结合其他技术才能被广泛应用。同时资优价廉的车牌识别软件必然会提高相关产品的销售量,其他产品因此实现高附加值。

第1节 照明光源

目前来说,照明光源普遍采用电光源(把电能转换成光能)。因为车牌识别系统本身也消耗电能,所以车牌识别的照明光源全部采用电光源。从发光原理上讲,点光源一般分为以下三大类。

热辐射光源 利用电流的热效应。让光源达到三千度以上的温度。光源便开始发出可见光。热辐射光源通常色温较低,比如我们平常见到黄色光源。性能优良的车牌识别系统不会受此影响。

 

气体放电发光 这类光源是利用气体在通过电流时发光的原理制成的。某些光源色彩丰富,但是并不适合车牌识别。荧光灯使用的是水银蒸气发光,虽然发出白光,但是光线分散,不容易投射到车牌上。也不是夜间车牌识别理想光源。

 

半导体光源 在电场作用下半导体p-n节发光,电能利用效率高,是近年来新开发的光源类型。可以采用高功率白色的半导体光源,同时也方便将光束投射到车牌上。是夜间车牌识别的理想光源。

 

第2节 信息系统

信息系统,全称为管理信息系统—Management Information System,亦即所谓的MIS系统。

MIS系统主要指惊醒日常事务操作的系统。这种系统对各种事件的属性进行记录,并且可以输出统计信息。

MIS系统的核心是CS(也就是所谓的客户端/服务器 client/server)结构,也有基于BS结构的MIS系统,但是CS的MIS系统足以满足车牌的需要,所以BS的车牌识别系统并不多见。下图为一电力系统的MIS系统:

 

需要指出的是,MIS系统和车牌识别同为软件。在整个车牌识别系统中互相配合工作。一般来说,MIS系统理论较为成熟,开发难度不大;而车牌系统理论并没有完全成熟,开发难度极大。但是MIS系统必须针对单个项目单独开发,不容易复制。这就大大提高了MIS系统本身的成本,通常情况下MIS系统要比车牌识别核心模块报价高。因为车牌识别软件较容易复制,从而分担成本。

第3节 相机

车牌识别只能用数码相机,不可以使用传统的模拟相机。数码相机利用感光电子元件把光学信号转换成数字图像信号。感光元件有两种CCD和CMOS。CCD相机质优价高,对车牌识别来说没有必要。如果是对于普通的车牌识别系统,CMOS相机足以满足要求。

相机种类很多,具体到车牌识别相机也有很多种。这里列举一下适合车牌识别使用的相机。

工业相机:性能较好,它速度快,清晰度高。有的借助图像采集卡可以得到更好的效果。适合条件比较恶劣的环境。

 

监视摄像头:这是目前普及最广的用于车牌识别的相机。产品技术都比较成熟。架设车牌识别系统时完全可以利用一有的监视系统。对原有系统进行升级。

 

QQ摄像头:根据摩尔定律,电子产品——QQ摄像头的性能不断提高,价格不断下降。目前的QQ摄像头性能已经达到或者超过普通监视摄像头的性能水平;而价格十分便宜。将来可能成为车牌识别相机的首选。

 

第4节 视频传输

视频传输设备主要分为模拟传输和数字传输两种形式。

虽然数字传输方式具有很多优点。但是模拟技术比较成熟,产品种类多,所以模拟传输也比较普及。模拟视频信号,到达计算机前转换成数字信号即可进行车牌识别。

 

数字信号普遍采用以太网传输,使用超五类线。其传输速率高大1000兆,传输距离远达100米完全可以满足车牌识别的需要。数字视频信号经过压缩后,可以节省带宽,不过这对相机有一定的要求。目前车牌识别系统中的视频信号很少压缩。

 

另外在特殊场合下,并不需要传输视频,而仅仅需要传输单帧图像即可。比如交通路口,闯红灯的汽车触发相机拍照,相机仅把这时抓拍的图片传至计算机即可,大大节省了带宽。同时这样方法也适合停车场。

第5节 RFID

RFID的英文全称是Radio Frequency Identification。中文名称:射频识别、射频标示,俗称电子标签。RFID最大的特点就是非接触式自动识别。这一过程是通过射频信号完成的。

当RFID卡进入读卡器识别范围时,读卡器会发出射频信号。这时RFID卡就会感应出电流,RFID芯片利用感应出的电流提供能量,也发出射频信号,这个信号加载有标示信息。读卡器捕获到RFID卡发出的射频信号,即可得到RFID卡的ID值。

RFID被广泛应用于停车场和高速路收费系统中。为了更加安全的使用RFID,经常需要把RFID卡的ID值和汽车车牌号绑定起来。这就需要整个系统中集成车牌识别模块。

 

第4章 软件接口

车牌识别做为一个软件模块,需要提供软件接口,才能够被整合到整个车牌识别系统中,正常工作。

车牌识别适合何种软件接口,下面将一一介绍:

第1节 DLL

DLL的全称是Dynamic Linkable Library,中文名称是动态链接库。他不能单独的运行,需要其它程序加载。DLL最大的优点是应用程序共享代码和其他只读资源,有效地节省系统开支。

更为重要的是DLL迎合了程序的模块思想。所谓的模块就是一个功能相对完整的软件“零件”。一台机器的零部件可能来自于不同的生产厂家;同样的道理,车牌识别应用系统中,车牌识别模块可以由其它厂家提供。

车牌识别系统中,相机驱动程序本质上也是一个动态连接库。

除了windows系统外,linux系统也有类似的思想,不过叫做elf。其中的字母l标示连接的意思。同样地,车牌识别的动态库也可以提供linux的版本。

第2节 ActiveX

ActiveX的本质也是上一节中提到的动态链接库。不过ActiveX的接口更为方便。AcitveX由开发人员事先打包,可以方便的嵌入包括浏览器的各种软件之中。很多语言包括Java都支持ActiveX控件。

ActiveX没有很好的跨平台性,目前仅仅能在windows上运行。虽然如此,但是考虑车牌系统目前也都是在windows系列操作系统上运行,所以车牌识别模块封装成ActiveX控件的优越性也不会打折扣。

另外网页上的ActiveX有一定的安全问题,但是这并不会影响到车牌识别系统的安全性。

第3节 COM

和ActiveX控件一样,COM的本质也是动态链接库,COM也是有一种共享代码的方法。有了COM,软件工程就可以像是搭积木一样进行,甚至普通人都可以编程序。

对COM的调用者来说,他们看到的每个COM是一个类,类有接口。类和接口都是用GUID标示的。这里的GUID本质上是一个全球唯一的数,任何编程语言都可以处理。

但是目前车牌识别模块,提供COM借口得并不是很多。原因在于COM技术已经过时,微软已经放弃了对COM的维护工作。COM也不如ActiveX控件来得方便。

COM同样也只能用到Windows系列操作系统上。

 

第5章 触发

车牌识别系统,最简单的流程是系统从相机取出图像,然后进行识别。如果相机视野内有车牌,那么系统记录当前的图像和车牌号。如果视野内没有车牌,则要丢弃当前的图像。

但是如果计算机对图像处理较慢,那么当计算机正处理当前图像的时候,下一幅图像已经准备好了。这样的话,计算机就无法响应用户的操作。用户就会感觉计算机较慢,或者死机。

解决的方法就是利用“触发技术”。

第1节 视频触发

本章一开始提到的车牌系统的最简单的流程,就是视频触发。根据视频信号,如果相机视野内有车牌,整个系统启动一个操作。如果相机视野内没有车牌,整个系统处于休眠状态。

如果车牌识别模块的运算速度较快,视频触发一般没有大的问题。但是较快的速度,势必影响识别率。低识别率是视频触发固有的缺点,虽然随着硬件速度的提高,这个缺点变得越来越小。

为了弥补视频触发的不足,人们引入了“线圈触发”的概念。

第2节 线圈触发

当有汽车功过道闸的时候,汽车会触发感应线圈。接下来,感应线圈触发相机拍照。然后图片传送到计算机,计算机对图像进行识别。最后系统记录识别信息和图像信息。在没有图像到来的时候,整个系统处于休眠状态。计算机也没有必要识别车牌,这个时候计算机的任务较少,可以很好的响应用户操作。

对车牌识别软件来说,识别时间限制被大大放宽。可以更为准确的定位识别车牌。所以一般线圈触发的系统,车牌识别率都很高。

线圈触发被广泛应用于停车场和交通路口环境中。

在施工过程中,线圈的铺设可能会有一定的额外工作量。相对于视频触发的系统,这一工作显然是多余的。这是线圈触发系统的一个缺点。

第3节 RFID触发

当汽车通过道闸的时候,用户需要主动刷RFID卡。刷卡机读取用户RFID卡信息后,触发相机拍照。接下来,照片被传送到计算机,进行车牌识别操作。这便是RFID触发的工作流程。

显然,这种触发方式拥有和线圈触发相同的识别率。但却无法应用于交通路口。RFID卡触发实际上受到RFID卡普及程度的限制。

也有上述多种触发技术结合的方案。比如视频触发和线圈触发相结合,这样既能满足系统在无法埋设感应线圈场合下的应用,又能在有感应线圈的环境中提高识别率。

第6章 车牌识别必备功能

第1节 基础功能
车牌定位

车牌定位是车牌识别的基础。也是车牌识别过程中最重要的部分。车牌定位的成功率直接决定车牌识别率。当光线环境比较好、分辨率较大的时候,车牌定位比较容易;但是如果光线环境不好,分辨率较低,车牌定位就比较困难。另外还有很多因素影响车牌定位的成功率,比如图像因观察角度造成的旋转、拉伸和斜切;再比如车牌遭到污染。上述情况都会影响车牌成功定位。

如果增加车牌定位的容忍值,虽然会提高车牌定位的成功率,但是会出现错误定位的情况:把背景图像的一部分,当作车牌。当图像内没有车牌时,质量不好的车牌识别软件比较容易出现这种错误。

字符识别

字符识别功能包括识别汉字、英文字母和阿拉伯数字。汉字的范围一般来说仅仅包括省、自治区和直辖市的简称。在所有的英文字母中,I和O,分别不容易和1和0区分。但是字母I和O可以做为发证机关代号,在车牌中出现,因为发证机关代号不可以取数字1和0。

0和D或者Q无法区分是字符识别经常出现的错误。8和B对于质量较差的车牌识别软件成功区分的概率也很小。

在同一地区,大部分车牌的省、自治区和直辖市的简称都是相同的,所以相对于字母数字的识别率,汉字的识别率不是很重要,另一方面汉字识别技术也不是很成熟;有的车牌识别软件干脆没有汉字识别功能。

第2节 拓展功能
多车牌识别

在同一副图像中有两个或者两个以上的车牌。多车牌识别软件可以对这些车牌全部成功识别。相对地,有些车牌识别软件只能对其中之一进行定位识别,有的是根据车牌距离中心点的距离选择,有的根据车牌在图像中的大小选择,有的甚至是随机选择。

车牌精确定位

在环境比较好的情况下,有时车牌识别软件无法正确识别字符。原因在于车牌本身受到一些干扰,其中包括固定螺丝和车牌边框。

对于蓝色车牌,较新的固定螺丝会反射光线;在图像中,螺丝呈现白色和字符颜色相近,干扰正常的识别程序。对于黄色车牌,比较旧的螺丝,或者悬空的螺丝孔,同样也会干扰正常的识别。

标准尺寸的车牌,车牌边框不会对车牌识别进行干扰。但是实际上,车牌并不都是按统一的标准制作的。有些车牌把边框向有文字的方向移动,腾出的空间用于标示汽车的品牌。但是这些非标准尺寸的车牌却给车牌定位带来了一定的干扰。

所以质量好的车牌识别软件,要对车牌进行精确定位,排除各种干扰。

国家标准

车牌识别依据的是《GA36-2007中华人民共和国机动车号牌》,互联网上有可供下载的pdf版本。

第2编 研发

以下部分为车牌识别研发人员参考资料。

第1章 开发环境

车牌识别实质上包括两部分的内容,一是车牌定位,二是字符识别。

车牌定位主要的工作是图像处理。目前来说图像处理的开发软件环境局限于c/c++。因为c/c++效率高,执行速度快。字符识别主要是人工智能。人工智能的开发环境较多,但是都不成熟。其中Prolog是目前比较好的人工智能语言之一。当考虑到统一开发环境时,一般字符识别也使用c/c++。

车牌识别硬件环境的选择比较丰富。除了选择主流的Intel和Amd的机器,高性能AMR机的也是比较好的选项。如果要嵌入到相机之中,AMR机是最好的选择。

第1节 软件环境
c c++的开发环境

 

c/c++语言是一种中级语言,它既有高级语言的简单性,又有和低级语言的相近的高效率。c/c++语言的高级语言特性,使得其描述车牌识别中比较复杂的逻辑较为方便。而其低级语言的特性,使得用c/c++语言开发的车牌识别程序执行速度快,内存开销小。

c/c++语言也十分容易移植,车牌识别的核心程序,在Windows系列操作系统上调试通过后,很容易移植到Linux系列操作系统上,而Linux系列操作系统也可以是在Arm机上运行的。

c是面向结构的,c++是面向对象的。这使得c/c++方便描述复杂的数据结构和算法。适合车牌识别中的字符识别操作。

c/c++语言的资料丰富,技术成熟。网上图像处理方面的程序较多是用c/c++编写,很容易把它们放到自我开的车牌识别程序中去。

c/c++开发环境也比较好,vc支持可视化编程,调试时变量跟踪异常方便,将c/c++不稳定性降到最低。

最为重要的是多数车牌识别程序都是基于c/c++的。

人工智能语言——Prolog

Prolog是目前为止中为重要的人工智能语言之一。他的语法中含有谓词,跟自然语言极为相似。使用Prolog描述完规则和事实后,Prolog内部进行演绎推理,自动给出结果;无需程序员关心内部操作。这样能够就大大加速了车牌识别的研发进程。

Prolog中的事实,用来描述对象和对象之间的关系。事实由谓词和对象组成。比如 Close(current_character,2). 表示当前识别的字符有两个封闭区域(车牌识别中“B”和“8”中的情况)。

Prolog中的规则由多个相关的简单句子组成。规则中结论放在前面,条件放在后面。例如:Q(character):-Anisomerous(character),Close(character,1). 表示如果字符只有一个封闭区域,而其不对称的话,那么这个字符是“Q”。

Prolog中的目标可以在事实和规则提交之后自动得到。

Prolog拥有和c语言的接口,Prolog可以融入c/c++的工程中。方便将整个车牌程序整合在一起。

Matlab

使用Matlab开发车牌识别比较快,但是最后发行软件的时候不容易脱离开Malab的环境。可以用Matlab研究车牌识别算法,但是开发车牌识别Malab并不是首选。

 

第2节 硬件环境
英特尔奔腾及其兼容机

因为车牌识别需要大量的并行计算,所以多核的CPU,占优势。如果不是通过系统,而是车牌识别软件本身分配给各个核心任务,效率会更高。

设计较好的车牌识别系统占用内存并不大,只有几兆。目前CPU Cache完全可以满足要求,整个识别过程,CPU不需要跟内存通信。

超线程技术也有助于车牌识别速度的提高。因为车牌识别程序并行计算的计算量较大,超线程技术也是针对有大量并行计算的软件设计的。

英特尔和AMD,都推出了64位的CPU,这一点对车牌识别可能并没有多大的帮助。

浮点计算对车牌识别软件的意义不大,因为浮点计算比较慢,设计良好的车牌识别软件,尽量使用整型运算。

从环保角度出发,CPU的功率越小越好。

 

arm机

Arm机是典型的精简指令系统。执行速度快,也是车牌识别的一个不错的硬件环境。

按性价比计算Arm机要比奔腾机好。如果算上周边芯片组成计算机系统,Arm机更占据成本优势。

Arm机基于Linux操作系统,车牌识别产品不会包含Windows的版本费用。Windows下面也有良好的Arm机调试环境。更可以移植已经在Windows环境下调试成功的软件。

同样地,64位的Arm机对车牌识别软件没有很大的帮助。

Arm机市面上较少,不容易购买,这是其一大缺点。

 

第2章 整体结构

第1节 整体结构分类
2.2.1.1是否回溯

举例来说,根据车牌的特征在整个图片中定位车牌的时候,如果车牌在图像中受到了环境的干扰,可能无法找到图片。为了解决这个问题,引用了回溯算法:当无法定位车牌的时候,增加对车牌错误的容忍程度,从新去寻找车牌。

有无回溯算法,对车牌识别的性能影响重大。无回溯算法的车牌识别软件速度快,但是定位成功率不高。适合视频触发的应用。有回溯算法的车牌识别软件定位成功率高,但是速度较慢。有时在成功定位车牌后,经过字符识别,发现字符识别率较低,重新定位车牌。这样速度便会更慢。有回溯算法的车牌识别软件一般应用于有触发线圈的场合。

2.2.1.2试探和计算

车牌识别中的定位操作,首先有一个判断某一区域是不是车牌的标准,然后对所有可能的区域依次根据这个标准判断。算法完成时,符合标准的区域便是车牌的位置。这是最原始的思路。这个算法在时间上并不占优势,但是这是车牌定位的基础,改进后的程序仍然沿用这个算法的绝大多数的函数。

上述算法,对所有的区域进行试探。在试探之前没有做计算,判断有没有必要对当前区域和标准进行比较。

另外一种算法就是“暴力”算法的改进形式。尽量避免没有必要的试探。或者根据已经试探失败的结果,去否定某一个较大区域试探的必要性。

2.2.1.3开发流程

实际上车牌识别代码量并不是很大,如果不停的手工输入;用不到一天的时间,即可大功告成。但是程序需要反复调试,才可以走向成熟。所以工作量是很大的。

另外车牌识别还有其本身的特点——理论并不成熟。这就给车牌识别带来了更多的工作量。一般来说,开发车牌识别程序,先按照最原始最朴素的思想编码,然后是不断地优化。这将贯彻到整个车牌识别的开发过程中去。

第2节 必备模块

车牌识别最基本的流程是:将采集后的图像二值化,然后依次经过车牌定位、字符分割、去除干扰,最后是字符识别。有时还会加入本节前面部分所叙述的思想(比如回溯)。

下面将分五章具体介绍每一个模块。

第3章 二值化

二值化是车牌识别的第一步。二值化前后的对比如图:

 

 

二值化的算法很简单,首先有一个亮度的阈值(threshold),对每一个像素的亮度和这个阈值做比较,根据比较结果得出车牌的前景和背景。用c/c++描述如下:

void CLPR::Binary(int threshold)
{
    int y;
    for(y=0;y<m_height;y+++)
    {
        int x;
        for(x=0;x<m_width;x++)
        {
            unsigned char red,green,blue;
            GetPixel(red,green,blue,x,y);
            int bright;
            bright=red+green;
            if(m_search_blue_plate)
            {
                if(bright<=threshold)
                    SetBinary(x,y,BACKGROUND);
                else
                    SetBinary(x,y,FOREGOUND);
            }
            else //we are searching yellow plate
            {
                if(bright>=threshold)
                    SetBinary(x,y,FOREGOUND);
                else
                    SetBinary(x,y,BACKGROUND);
            }
        }
    }
}

 

二值化算法虽然简单,但是阈值却不容易寻找。本章后面的部分,将重点介绍各种求解阈值的算法。

第1节 OTSU

OTSU算法的思想是:把输入图像首先转换成灰度图象,然后对图像进行直方图分析。如果直方图呈双峰分布。那么双峰之间的“谷”就是阈值。从统计学角度讲,阈值两边的距离最大。

由于车牌识别的特殊性,图象象素点的亮度为该象素点的红色分量和绿色分量的和,并且忽略蓝色分量。这一点对蓝色车牌和黄色车牌都是适用的。

OTSU算法仅对直方图呈双峰分布的图像有效。

全部代码如下:

void LPR::OTSU()
{
    //直方图统计
    {
        int index;
        for(index=0;index<m_bright_level_count;index++)
            m_pixel_number[index]=0;
    }
    {
        int y;
        for(y=0;y<=m_height;y++)
        {
            int x;
            for(x=0;x<=m_width;x++)
            {
                int bright;
                bright=Bright(x,y);
                m_pixel_number[bright]++;
            }
        }
    }
    //真正求阈值
    double sum;
    sum=0;
    int n;
    n=0;
    int k;
    for(k=0;k<=(m_bright_level_count-1);k++)
    {
        sum+=k*m_pixel_number[k];
        n+=m_pixel_number[k];
    }
    double c_sum;
    c_sum=0.0;
    double f_max;
    f_max=-1.0;
    int n1;
    n1=0;
    for(k=0;k<(m_bright_level_count-1);k++)
    {
        n1+=m_pixel_number[k];
        if (n1==0) 
            continue;
        int n2;
        n2=n-n1;
        if(n2==0) 
            break;
        c_sum+=(double)k*m_pixel_number[k];
        double m_1,m_2;
        m_1=c_sum/n1;
        m_2=(sum-c_sum)/n2;
        double sb;
        sb=(m_1-m_2)*(m_1-m_2)*(double)n1*(double)n2;
        if (f_max<sb)
        {
            f_max=sb;
            m_prepare_threhold=(int)(k+0.5);
        }
    }
}

 

第2节 Matlab算法

使用Matlab进行车牌识别,也是一个比较好的选择。在Matlab的环境中首先把输入的彩色图像使用命令rgb2gray转换成灰度图像。有了灰度图像就可以使用命令graythresh获得阈值了。最后使用命令im2bw对图像进行二值化。十分方便!代码如下:

I=imread('blood1.tif');

imhist(I);

% 人工观察灰度直方图,发现灰度120处有谷,确定阈值T=120

I1=im2bw(I,120/255);

% im2bw函数需要将灰度值转换到[0,1]范围内

figure,imshow(I1);

改进为

I=imread('blood1.tif');

imhist(I);

I1=graythresh(I);

% im2bw函数需要将灰度值转换到[0,1]范围内

figure,imshow(I1);

第4章 车牌定位

图像二值化,占用车牌识别中的大部分时间。在二值化之后,车牌识别将变得比较容易。

车牌定位为二值化后的第一步。下面分小节分别介绍各种车牌定位算法。

第1节 角点定位方法

车牌识别过程中,角点定位的基本思想是:

在所有的边界点中,如果某些点的曲率半径比较小,那么这些点叫做“角点”:如下图所示(角点用红点表示):

 

图中字符上和车牌的四角都有角点。但是这并不影响车牌的定位。

根据距离最大的四个角点,得到了车牌的四个角,从而定了车牌。从角点定位的原理看出,如果经过旋转后车牌并不会影响角点定位的成功率和速度。

该算法的实现可以采取遍历匹配的算法,实现如下:

void LPR::GetConere()
{
    int y;
    for(y=0;y<m_height;y++)
    {
        int x;
        for(x=0;x<m_width;x++)
        {
            if(Line(x,y,x+4,y)>=3)
            {
                if(Line(x,y,x,y+4)>=3)
                {
                    if(Line(x+1,y+1,x+4,y+4)<=1)
                        Add(x,y,LEFT_TOP_CONNER);
                }
                if(Line(x,y,x,y-4)>=3)
                {
                    if(Line(x+1,y-1,x+4,y-4)<=1)
                        Add(x,y,LEFT_DOWN_CONNER);
                }
            }
            if(Line(x,y,x-4,y)>=3)
            {
                if(Line(x,y,x,y+4)>=3)
                {
                    if(Line(x-1,y+1,x-4,y+4)<=1)
                        Add(x,y,RIGHT_TOP_CONNER);
                }
                if(Line(x,y,x,y-4)>=3)
                {
                    if(Line(x-1,y-1,x-4,y-4)<=1)
                        Add(x,y,RIGHT_DOWN_CONNER);
                }
            }
        }
    }
}

 

函数Line(x1,y1,x2,y2)返回过两点(x1,y1),(x2,y2)的直线,前景的象素个数。

注意这里的4,是检验角点的区域范围,如果区域过大,图像旋转时就会影响车牌定位的成功率。

第2节 上下定位方法

 

仔细观察二值化后的图像,在车牌的上边和下边各有一条较长的背景线(上图用红线表示)。根据这两条背景线可以准确的定位车牌。定位算法如下:

int LPR::HorizontalLine(int x,int y,int count)
{
    int ret;
    ret=0;
    int x_loop;
    for(x_loop=0;x_loop<count;x_loop++)
        if(!IsForegournd(x+x_loop,y))
            ret++;
    return ret;
}

 

IsForegournd(x,y)为询问点(x,y)是不是前景点的函数。

该算法比角点定位算法要快,但是不适合经过旋转后的车牌。虽然经过改进后也可以识别出旋转后的车牌,但是速度很慢,不能出现在成熟的产品中。

第3节 变化率定位法

 

图中有三条直线,如果沿着这三条直线扫描的话,会发现:黑色的扫描线前景占很大的比例,绿色的扫描线背景占很大的比例;红色的扫描线前景背景比例都不突出,但是前景背景交替较为频繁。这说明根据前景背景变化率可以定位车牌。

这种算法可以有效防止车牌旋转的干扰。

获得变化率的代码如下:

double CLPR::ScanLine(int x,int y,int count)
{
    bool current_foreground;
    current_foreground=IsForeground(x,y);
    int change_times;
    change_times=0;
    int loop;
    for(loop=1;loop<count;loop++)
    {
        if(current_foreground)
        {
            if(!IsForeground(x+loop,y))
            {
                change_times++;
                current_foreground=false;
            }
        }
        else
        {
            if(IsForeground(x+loop,y))
            {
                change_times++;
                current_foreground=true;
            }
        }
    }
    double ret;
    ret=change_times;
    ret/=(count+1);//do not div 0
    return ret;
}

 

第5章 字符分割

在车牌识别过程中,车牌定位后的工作便是分割字符。本章将分若干节介绍字符分割的算法。

第1节 连续点分割法

对于一个数字或者字母,前景的点是连续的。用填充算法对种子点填充即可得到整个字符。

当然汉字就不是了,所以这种分割算法仅仅适合数字或者字母。但是当整个车牌的数字和字母都得到之后,剩下的那个必定是汉字。

这个思想用c/c++描述如下:

void LPR::Scan()
{
    int y;
    for(y=m_plate_top;y<=m_plate_bottom;y++)
    {
        int x;
        for(x=m_plate_left;x<=m_plate_right;x++)
        {
            if(IsForeGround(x,y)&&!IsVisited(x,y))
            {
                Fill(x,y);
            }
        }
    }
}

 

Fill是种子填充,算法有多种。下面分别介绍。

2.5.1.1递归填充算法

递归填充算法的基本思想是,首先访问当前点,然后访问当前点的四个邻居。

每次访问时,要做一个记号,否则递归过程无法结束。

具体到车牌识别,每访问一个点,还要记录该点的坐标,从而得到当前字符点的集合。

void LPR::Fill(int x,int y)
{
    if(!IsForground(x,y))
        return ;
    if(IsVisited(x,y))
        return ;
    AddPixelToCharacter(x,y);
    MarkPixelVisited(x,y);
    Fill(x+1,y);
    Fill(x,y+1);
    Fill(x-1,y);
    Fill(x,y-1);
}

 

可以看出用c/c++描述的填充算法十分简单。

2.5.1.2递归扫描线算法

基本的递归填充算法,函数递归调用较为频繁,引起系统资源消耗巨大。人们后来又提出了改进了的“扫描线种子填充算法”。其基本思想是:在种子的左右两边水平扫描暂时不需要递归,以减少不必要的函数调用。水平扫描结束后,仅仅考虑扫描线两个端点即可。算法如下:

void LPR::Fill(int x,int y)
{
    int (*stack)[2];
    stack=(int (*)[2])new int [m_width*2];
    int stack_length;
    stack_length=0;
    stack[stack_length][0]=x;
    stack[stack_length][1]=y;
    stack_length++;
    while(true)
    {
        if(stack_length==0)
            break;
        x=stack[stack_length-1][0];
        y=stack[stack_length-1][1];
        stack_length--;
        int left_x;
        for(left_x=x-1;left_x>=0;left_x--)
            if(!IsForeGround(left_x,y)||IsVisited(left_x,y))
                break;
        left_x++;
        for(x=left_x;x<m_width;x++)
        {
            if(!IsForeGround(x,y)||IsVisited(left_x,y))
                break;

            Visit(x,y);
        }
        int right_x;
        right_x=x;
        if(right_x>=m_width)
            right_x=m_width-1;
        left_x--;
        if(left_x<0)
            left_x=0;
        int down_y;
        down_y=y+1;
        if(down_y<m_height)
        {
            for(x=left_x;x<=right_x;x++)
            {
                if(IsForeGround(x,down_y)&&!IsVisited(left_x,y))
                {
                    stack[stack_length][0]=x;
                    stack[stack_length][1]=down_y;
                    stack_length++;
                }
            }
        }
        int up_y;
        up_y=y-1;
        if(up_y>=0)
        {
            for(x=left_x;x<=right_x;x++)
            {
                if(IsForeGround(x,up_y)&&!IsVisited(left_x,y))
                {
                    stack[stack_length][0]=x;
                    stack[stack_length][1]=up_y;
                    stack_length++;
                }
            }
        }
    }
    delete [] (int*)stack;
}

 

虽然扫描线种子填充算法,比基本的种子填充算法复杂一些,但是在车牌识别测试后发现,扫描线种子填充算法比种子填充算法要快三分之一左右。

第2节 边界法

边界法和连续点分割法很相似,只不过连续的边界点,而不是所有的前景点。边界法需要事先得到前景像素中的边界点,这可能会花费一些时间。但是这样会加速得到连续点的递归操作。边界点如下图:

 

得到边界点的算法如下:

void CLPR::GetEdge()
{
    int y;
    for(y=0;y<m_height;y++)
    {
        int x;
        for(x=0;x<m_width;x++)
        {
            if(IsForeground(x,y))
            {
                if(!IsForeground(x-1,y))
                    SetEdge(x,y);
                else if(!IsForeground(x+1,y))
                    SetEdge(x,y);
                else if(!IsForeground(x,y-1))
                    SetEdge(x,y);
                else if(!IsForeground(x,y+1))
                    SetEdge(x,y);
            }
        }
    }
}

 

连接边界点的算法和连接前景点的算法完全相同。

第3节 根据尺寸分割

从理论上讲,图片和实物相比,尺寸上有了很大的变化。并不一定图片和实物几何意义上的相似。也就是说未必图像和实物成比例。但是实验证实,在一到两个像素范围内,在水平方向上,实物和图像基本上成比例。

请观察下图:

 

这个是来自于《GA36-2007中华人民共和国机动车号牌》的车牌尺寸说明。根据这幅图片,我们可以在一定位的车牌上,找到各个字符的坐标。请看下面的代码:

void GetCharacterPosition(int character_position[7],int plate_left,int plate_right)
{
    static const int mm[]=
    {
        3+45/2,
        character_position_mm[0]+12+45,
        character_position_mm[1]+12+10+12+45,
        character_position_mm[2]+12+45,
        character_position_mm[3]+12+45,
        character_position_mm[4]+12+45,
        character_position_mm[5]+12+45,
        character_position_mm[6]+45/2+1,
    };
    int index;
    for(index=0;index<sizeof(mm)/sizeof(mm)-1;index++)
        character_position[index]=plate_left+(-plate_left+plate_right)*mm[index]/mm[sizeof(mm)/sizeof(mm[0])-1];
}

 

上面的代码技巧性很大,需要读者认真揣摩。

第6章 去除干扰

干扰车牌识别的因素很多。比如车牌旋转、污染、固定螺丝和车牌边框等等。下面分小节分别介绍去除各种干扰的方法。

第1节 去除噪音

去除噪音的原理是:每一个字符,都是很大的一个连续块,但是噪音确是比较小的多个连续块。通过递归算法得到每个字符中各个块的大小,保留最大的块,其余块当作噪音删掉。算法很简单,这里不再列出代码。

第2节 去除螺丝干扰

去除螺丝的工作要和字符本身的特征联系在一起。对不同的字符、相同字符不同部位的螺丝,都要分别编码。工作量很大。下面分小节举几个例子:

2.6.2.10986等左上方螺丝

这种情况如下图所示:

 

在比较圆的9的上部出现了一个螺丝,比较明显。可以删掉,代码如下:

void CLPR::DeleteLeftUpSmallScrewCharacterRound()
{
    bool possible_screw;
    int possible_screw_start;
    int possible_screw_end;
    if(m_character_index==5)
    {
        possible_screw=false;
        {
            int seek;
            for(seek=0;seek<=1;seek++)
            {
                possible_screw_start=m_min_y+seek;
                int screw_right;
                if(ExpandHorizontal(screw_right,possible_screw_start,possible_screw_start,m_max_x,SUB))
                {
                    if(screw_right>=m_delta_x/2)
                    {
                        possible_screw=true;
                        break;
                    }
                }
            }
        }
        int delta_x;
        if(possible_screw)    
        {
            possible_screw=false;
            for(possible_screw_end=possible_screw_start+1;possible_screw_end<m_min_y+m_delta_y*2/5;possible_screw_end++)
            {
                delta_x=RecognizeCharacterLocateVeritcalSmallSideScrewGetDeltaX(true,possible_screw_end,true);
                int left;
                if(!ExpandHorizontal(left,possible_screw_end,possible_screw_end,m_min_x,ADD))
                    continue;
                if(left>m_delta_x/2)
                    continue;
                int right;
                if(!ExpandHorizontal(right,possible_screw_end,possible_screw_end,m_max_x,SUB))
                    continue;
                if(right>delta_x/2)
                    continue;
                if(possible_screw_end+4>m_center_y)
                    continue;
                int distance1;
                distance1=HorizontalDistance(possible_screw_end,possible_screw_end);
                int distance2;
                distance2=HorizontalDistance(possible_screw_end+2,possible_screw_end+2);
                int distance3;
                distance3=HorizontalDistance(possible_screw_end+4,possible_screw_end+4);
                if(!(distance1<=distance2&&distance2<=distance3&&distance1<distance3&&distance1>0))
                    continue;
                if(distance3<delta_x*3/4)
                    continue;
                int seek;
                for(seek=2;seek<=4;seek++)
                {
                    if(ChangeTimeHorizontal(possible_screw_end+seek,possible_screw_end+seek)==4)
                    {
                        possible_screw=true;
                        break;
                    }
                }
                if(possible_screw)
                    break;
            }
        }
        if(possible_screw)
        {
            int left;
            if(!ExpandHorizontal(left,possible_screw_end-1,possible_screw_end-1,m_min_x,ADD))
                possible_screw=false;
            else
            {
                if(left>=m_delta_x/2)
                    possible_screw=false;
            }
        }
        if(possible_screw)
        {
            bool known_letter;
            known_letter=false;
            if(!known_letter)
            {
                m_min_y=possible_screw_end;
                RecognizeCharacterAdjustVertical();
            }
        }
    }
}

 

以上代码拷贝自绿睿车牌识别工程,实际上是比较旧的版本。读者可以进行修改,并放入自己工程中。

2.6.2.2EFT5等右上方螺丝

这种情况如下图所示:

 

在比较平的5的上部出现了一个螺丝,十分明显。删掉螺丝的代码如下:

void CLPR::RightUpSmallSideScrewCharacterWide()
{
    if(!(m_character_index==1||m_character_index==4))
        return ;
    int possible_screw_start;
    possible_screw_start=RecognizeCharacterLocateVeritcalRightUpSmallSideScrewGetStart();
    if(possible_screw_start==-1)
        return ;
    bool possible_screw;
    int possible_screw_end;
    possible_screw=false;
    int delta_x;
    for(possible_screw_end=possible_screw_start+1;possible_screw_end<m_center_y;possible_screw_end++)
    {
        int distance;
        distance=HorizontalDistance(possible_screw_end,possible_screw_end);
        delta_x=RecognizeCharacterLocateVeritcalSmallSideScrewGetDeltaX(false,possible_screw_end,true);
        if(distance>delta_x*0.63)
        {
            int seek;
            for(seek=1;seek<=3;seek++)
            {
                int left;
                if(ExpandHorizontal(left,possible_screw_end-seek,possible_screw_end-seek,m_min_x,ADD))
                {
                    if(left>=delta_x/2)
                    {
                        possible_screw=true;
                        break;
                    }
                }
            }
        }
        if(possible_screw)
            break;
    }
    if(!possible_screw)
        return ;
    {
        int y;
        y=possible_screw_end-1;
        int left;
        if(ExpandHorizontal(left,y,y,m_min_x,ADD))
        {
            if(left<m_delta_x/2)
            {
                int distance;
                distance=HorizontalDistance(y,y);
                if(distance>delta_x*0.25)
                    return ;
            }
        }
    }
    if(possible_screw_end>m_locate_up_contrack_limit)
        return ;
    bool known_letter;
    known_letter=false;
    if(!known_letter)
    {
        m_min_y=possible_screw_end;
        RecognizeCharacterAdjustVertical();
    }
}

 

以上代码也是来自绿睿车牌识别的一个老版本。

第3节 去除边框

普通的车牌边框对车牌识别的准确度影响不大。但是某些车牌并不符合标准的车牌尺寸。比如下图:

 

对于没有经过旋转的车牌,去除边框的算法很容易。在车牌定位之后,就可以估算出车牌字符的大小。如果在字符宽度范围内,都是前景的颜色,没有发生前景背景交替,那么这个连续部分必定是车牌的边框。代码如下:

void CLPR::DeleteFrame()
{
    int y;
    for(y=m_plate_top;y<=m_plate_bottom;y++)
    {
        int x;
        for(x=m_plate_left;x<=m_plate_right;x++)
        {
            if(ScanLine(x,y,m_character_width*5/4)==0)
            {
                SetToBackground(x,y,x+m_character_width*5/4,y);
            }
        }
    }
}

 

SetToBackground(x1,y1,x2,y2)将x1,y1,x2,y2组成的矩形(这里是一条水平线)里的前景变成背景。这样就删除了边框。

如果车牌旋转角度不大,上述算法也同样适用,但需要在去除完边框后,再做一遍去除噪音点的操作。

如果车牌旋转角度很大,需要换用其它算法。

第4节 旋转

2.6.4.1根据车牌的旋转旋转字符

车牌是个刚体,也就是说它不能发生形变。车牌的旋转角度和字符的旋转角度相同。根据车牌的旋转角度逆向旋转就可以纠正字符。

对于较大的车牌,纠正旋转后的字符,识别率大大提高。对于较小的字符,纠正旋转过程可能丢失某些重要的信息,以致降低识别率。

角点车牌定位方法比较容易得到车牌自身的旋转角度。甚至车牌拉伸和斜切的参数也可以求出来。这样就可以对字符作更精细的恢复,减少字符识别的负担。

2.6.4.2在字体识别时容忍旋转

如果车牌定位本身不精确,很难得到字符本身的旋转信息。这就需要根据不同的字符,纠正旋转。但是一旦知道了字符值,也就没有任何必要做旋转纠正。所以字符识别需要增加对字符旋转的容忍能力。

第7章 字符识别

因为车牌字体是印刷字体。对于印刷字符识别,一般的思路是和模板比对。这样做最大的好处在于编程方便。

但是有两个坏处,第一速度较慢,第二识别率不高。如果用编程语言逐一描述字符的特点即可纠正这两个问题,当工作量很大。

第1节 模板匹配

车牌字符的字体可以在《GA36-2007中华人民共和国机动车号牌》找得到。下面程序有所有汉字的字体。

#define LPT_FONT_WIDTH 16
#define LPT_FONT_HEIGHT (LPT_FONT_WIDTH*2)

bool LPRFontGet(int character_index,int x,int y)
{
    static const int mask[][LPT_FONT_WIDTH*LPT_FONT_HEIGHT/(sizeof(int)*8)]=
    {
        {0x01800180,0x03800380,0xfffffffe,0x00000000,0x3ffc0000,0x3ddc3ffc,0x381c381c,0x381c381c,
        0x3ffc381c,0x01801ffc,0x19b80180,0x399c39b8,0x319c319c,0x718e718c,0x61e6618e,0x01e061e6},
        {0x07000700,0x7fce070c,0x7fdc7fdc,0x77007708,0xffe47700,0x770effee,0x77c8771c,0x7fc07fc0,
        0x071c0708,0x7fec071c,0x07cc7fec,0x070c070c,0xffeeffee,0x0706ffe6,0x07060707,0x07000704},
        {0x06600660,0x7e7f7e7f,0x06601e7f,0xfe7e7e7e,0x00003c62,0x3ffc3ffc,0x3ffc318c,0x3f9c3ffc,
        0x3ffc318c,0x0c603ffc,0x7ffe7c60,0x0c607ffe,0xffff0c60,0x0c20ffff,0x383c1e70,0x0000701e},
        {0x7ffc7ffc,0x06607ffc,0x66640660,0x766e666e,0x0664366c,0xffff0660,0xffffffff,0x00000000,
        0x1ff81ff8,0x18181ff8,0x18181818,0x1ff81ff8,0x18181818,0x18181818,0x1ff81ff8,0x18181818},
        {0x0c300c30,0x7fff7fff,0x0c307fff,0x7ffe7c3e,0x60067ff6,0x0ff06ff6,0x7ffe0000,0x01fe7ffe,
        0x33fc21f0,0x1fc43b9c,0x17701ee0,0x37cc379c,0x667036e4,0x671c6638,0x03e6438e,0x004001c0},
        {0x7fcc6008,0x7fdc7fdc,0x3c387818,0x0c001c18,0x0c1e0c00,0x0c1e0c1e,0x0c180c18,0x0c180c18,
        0x0c180c18,0x0c180c18,0x0c180c18,0x0c180c18,0x0f180f18,0x007c0738,0xffee00fe,0x7f007fc6},
        {0x03800380,0x03800380,0xffff0380,0xffffffff,0x03800380,0x03800380,0x7ffc0380,0x00007ffc,
        0x00000000,0x3ffc3ffc,0x301c301c,0x301c301c,0x301c301c,0x301c301c,0x3ffc3ffc,0x001c301c},
        {0x7ffc7ffc,0x718c7ffc,0x7dac718c,0x7fec7dbc,0x73cc77ec,0x7ffc738c,0x7ffc7ffc,0x01800180,
        0x7ffc7ffc,0x01807ffc,0xfffe0180,0xffffffff,0x32440000,0x666c766c,0xeeee666e,0x0000eee6},
        {0x07040000,0x0e1e060e,0x00080e1c,0xffc0ffc0,0xe1c6ffc4,0xe1dce1ce,0xe1c0e1c8,0xe1c4e1c0,
        0xffdcffcc,0x01cce1cc,0x01cc01cc,0x01cc01cc,0x00c600ce,0x00e600c6,0x007600e7,0x00000074},
        {0x1c301c30,0xfffe1c30,0xfffefffe,0x1c301c30,0x00001c30,0x01c001c0,0x0ffc01c0,0x0cc00ffc,
        0x0cc00cc0,0x3ccc3cdc,0x7c6e3cec,0x6e666e66,0x0e302e70,0x0e1c0e38,0x078e0e1c,0x01800786},
        {0x60c00000,0x7cce78c4,0x0cdc1ccc,0x0cc00cc0,0xfff60ff0,0xfccefcc6,0x6cc06ccc,0x6dc06cc0,
        0x6cec6dc4,0x6cec6cfc,0x6ccc6ccc,0x6ccc6ccc,0x6cc66cc6,0x66c66ec6,0x67e666c6,0x00206260},
        {0x0e000000,0x0c1c0e18,0x7f8c7f8c,0x61bf7fbf,0x61b761bf,0x3f373f37,0x00373f37,0x7fbf0037,
        0x7fb3ffbf,0x1f331f33,0x1f331f33,0x1b3f1f33,0x1b3f1b3f,0x5b071b07,0xfbc0db00,0x000079c0},
        {0x000c0000,0xffd8ffdc,0xe010e038,0xe187e187,0xeff7c187,0xcff7eff7,0xcdb7cdb7,0xcdb7cdb7,
        0xcff7cff7,0xc1b7cdf7,0xcd87c9b7,0xcf87cd87,0xdbf7dff7,0xc007c807,0xe007c007,0x00077007},
        {0x06180200,0xfe7efe18,0x6f00667e,0x39b63fa6,0x677e7c34,0x3c00437e,0x187e3c7e,0x7fe61866,
        0x007e7fe6,0x7f667f66,0x4b7e437e,0x5b185b18,0x087f4b18,0x3c180c7f,0x67983618,0x00186318},
        {0x00c00040,0x0ff01de0,0x07180ff8,0x3fff3ffe,0x3398339a,0x3ff83ff8,0x31983198,0x3ffc3ff8,
        0xffff0000,0xff80fffe,0x3ff80000,0x38183ff8,0x3ff83ff8,0x38183ff8,0x3ff83ff8,0x38003818},
        {0x06000200,0x3f3e3e3e,0x19b0333e,0x7fde7fdc,0x491849cc,0x7f7f4908,0x0c6c7f7f,0x4f0c0e6c,
        0x6dcc6f8c,0x3f0c3e8c,0x6dcc3f0c,0x6e0c6ecc,0x498c6b0c,0x4c4eccec,0x07000e06,0x01000300},
        {0x7bde79de,0xdb567bde,0xdb56db56,0x4bdecb52,0x68006bde,0x6bfc3800,0x48006bfc,0x4fff4800,
        0x4c184fff,0x4df84c18,0x4df84df8,0x4d804d80,0x6d804d80,0x6cc06dc0,0x0cf82cc0,0x00100c70},
        {0x00c000c0,0xfcce40c4,0xdcc8fccc,0xdff0dcc0,0xdcc0dff0,0xfccefcc7,0xddccddcc,0xdfe0ddc0,
        0xdee4dfe0,0xfcecfcec,0xdcfcfcfc,0xdcd4dcf4,0xdcd6dcd6,0xfcc6dcc6,0xdd80fdc6,0x0080dc80},
        {0x00e00040,0x3ffe0060,0x3ffe3ffe,0x3dae318e,0x31ae35ee,0x3ffc3ffc,0x35ec31cc,0x3dac3dec,
        0x3f8c318c,0x3ffc3ffc,0xffff0000,0x07ffffff,0x3ff00030,0x38003ff8,0x1f801800,0x03800f80},
        {0x38381838,0x38383838,0x7e387e38,0x38fe7e38,0x38fe38fe,0xfe3c3838,0xfe3cfe3c,0x00fc00fc,
        0x19fe18fe,0x183e18be,0x7e387e3e,0x18387e38,0x18381838,0x18381838,0x7f387f38,0x00387f38},
        {0x0e000000,0x0c000e00,0xffff0c3f,0x001cffdc,0x7f9c001c,0x7f9c7f9c,0x61bf61bf,0x618c61bf,
        0x7f8c7f8c,0x0e0c7f8c,0x7fbc0e2c,0x6fde6ffc,0x6ec66ece,0xee606ee2,0x4f60ce60,0x03000f00},
        {0x06040000,0x061c060c,0x1b100f18,0x31c03b80,0xe0e071e0,0x0f064f62,0x000c000e,0x43e063e0,
        0x4a604be0,0x4be84a60,0x4a6c4bec,0x4bec4a6c,0x4a6c4bec,0x42644a64,0x63664266,0x21606364},
        {0xe000e000,0xe718e018,0xe718e718,0xe318e318,0xe318e318,0xe318e318,0xe318e318,0xe318e318,
        0xe31ce31c,0xe31ce31c,0xe31ce31c,0xe31ce31c,0xe31ce31c,0xe30ee30c,0xe006e30e,0xe000e000},
        {0x03800380,0x3ffc3ffc,0x339c3ffc,0x3f9c339c,0x3ffc3ffc,0x03800380,0x7fffffff,0x00000000,
        0x3ffc3ffc,0x301c3ffc,0x339c339c,0x339c339c,0x01c0319c,0x1ce00dc0,0x703c3870,0x2004601e},
        {0x3ff83ff8,0x00003ff8,0x00000000,0x00000000,0x00000000,0x00000000,0xffffffff,0x018003ff,
        0x00c001c0,0x00e000c0,0x0c700860,0x18301c30,0x38181838,0x3f9c3c18,0x61fc7ffc,0x2000601c},
        {0x0c600420,0x7ffe0c60,0x7fe07ffe,0x60004460,0x78065806,0x7ff67ff6,0x1ff61836,0x5afe1ff6,
        0x5ff0dafe,0x7cfe5ff0,0x74fe74ff,0x32f637f6,0x32f632f6,0xf8167fd6,0x6e187c1a,0x00006718},
        {0x0c000c00,0x0c3e0c00,0x7ff60c3e,0x7ff67ff6,0x0c1e0c16,0x6cde4cde,0x6db66df6,0x2db66db6,
        0x0c360c36,0xfff6fff6,0x0e370c37,0x0e370e37,0x1b1f1f1f,0x31c73b87,0x60e771c7,0x40206067},
        {0x18381800,0x18381838,0x18381838,0x18381838,0x7fff1838,0x1838ffff,0x18381838,0x18381838,
        0x1ff81ff8,0x18381ff8,0x18381838,0x18381838,0x18381838,0x1ff81838,0x18381ff8,0x00381838},
        {0x03800180,0x7ffc7f80,0x03807ffc,0x3ffc0380,0x3ffc3ffc,0xffff0380,0xffffffff,0x3ffc0000,
        0x3ffc3ffc,0x301c301c,0x3ffc3ffc,0x301c301c,0x3ffc3ffc,0x301c301c,0x381c301c,0x0810381c},
        {0x01800100,0x03800380,0xfffe0380,0xe006fffe,0xe006e006,0x0000e006,0x00000000,0xffff0000,
        0xffffffff,0x03800380,0x03800380,0x03800380,0x03800380,0x03800380,0x03c00380,0x00c003c0},
        {0x40300020,0x3dfef830,0x0dfe0dfe,0x0c000c00,0x0ccc0ccc,0x7c600ccc,0x6fff7ff8,0x6c306fff,
        0x6c306c30,0x6dfe6dfe,0x6cbc6c30,0x65bc6dfc,0x67b667b6,0x66376636,0x63386630,0x00086338},
        {0x0d840c00,0x0d8e0d84,0x7fec0d8c,0x7fe07fe8,0x0d820d80,0x7ff70d82,0x7ff47ff6,0x18c059c4,
        0x7fe030e0,0x6ff47ff0,0x48cc48dc,0x0fc408c4,0x4fc60fc6,0x60c640c6,0x7fc27fc7,0x00003fc0},
        {0x03000100,0x7fe60304,0x7fec7fee,0x6660606c,0x6f606fe0,0x6fe66f62,0x6fe66fe7,0x6f646764,
        0x6fe46fe0,0x626c66e4,0xfff4260c,0xfff6fff6,0x0f060706,0x39c71f86,0x70f279e6,0x00006060},
        {0x0e380600,0x0e180e38,0x7ffc0e18,0x7fce7ffc,0x7fce0e0e,0xffce7fce,0xeecceecc,0x6ecc6ecc,
        0x7fcc7fcc,0x0e1c0e1c,0x0fec0edc,0x0f9c0fdc,0x0f0c0f9c,0x3f9c1f1c,0xf1fc7bdc,0x001c60dc},
        {0x7f980008,0x0c387f98,0x0c7c0c38,0x7f4c0c6c,0x61167f06,0x6d30611a,0x6d106d30,0x6d7e6d00,
        0x6d606d7e,0x6d6c6d60,0x6f3c6d3c,0x6f186f1c,0x0c300c38,0x36300c30,0x63807700,0x00004180},
        {0x18c00800,0x1ccc18ee,0x0ddc1dcc,0x0e000e08,0x7ffe7ffe,0x60067ffe,0x6fe66006,0x0ff00ff0,
        0x07000e00,0x03800380,0xffffffff,0x0180ffff,0x01800180,0x03800180,0x03e00380,0x00c001c0},
        {0x0c580408,0x7dfe0c5e,0x7e587ffe,0x37fc7618,0x3fbe3ffc,0xfdae3bbf,0x7ffc7ffc,0x038065bc,
        0xfffe7ffe,0x1ff80000,0x00001ff8,0x1ff81ff8,0x1ff80000,0x18181ff8,0x1ff81818,0x18181ff8}
    };
    if(x>=LPT_FONT_WIDTH)
        return false;
    if(y<0)
        return false;
    if(y>=LPT_FONT_HEIGHT)
        return false;
    int int_index;
    int_index=y*LPT_FONT_WIDTH+x;
    int bit_index;
    bit_index=int_index%(sizeof(int)*8);
    int_index/=(sizeof(int)*8);
    if(mask[character_index][int_index]&(1<<bit_index))
        return true;
    else
        return false;
        
}
void CChildView::OnPaint() 
{
    CPaintDC dc(this);
    int y;
    for(y=0;y<LPT_FONT_HEIGHT;y++)
    {
        int x;
        for(x=0;x<LPT_FONT_WIDTH;x++)
        {
            if(LPRFontGet(36,x,y))
                dc.SetPixel(x,y,RGB(64,128,128));
            else
                dc.SetPixel(x,y,RGB(200,200,200));
        }
    }
}

 

其中bool LPRFontGet(int character,int x,int y)的功能是取得字体。

character 的取值范围是0~36,分别代表:“京”“津”“冀”“晋”“蒙”“辽”“吉”“黑”“沪”“苏”“浙”“皖”“闽”“赣”“路”“豫”“鄂”“湘”“粤”“桂”“琼”“渝”“川”“贵”“云”“藏”“陕”“甘”“青”“宁”“新”“港”“澳”“使”“领”“学”和“警”。

x,y是字符上像素坐标取值范围:x[0~16],y[0~32]。

如果该点是字体上的点返回true否则返回false

void CChildView::OnPaint() 是应用LPRFontGet的一个例子。

第2节 字符特点

用编程语言逐一描述字符的特点,可以很好的提高在恶劣环境下的字符识别率,从而提高车牌识别的质量。但是这个算法编程任务繁重,所以只有在商业化运用时使用。另外这也是一种手工操作的“神经网络”识别,比自动化的“神经网络”效率高。

下面举个例子,例子来自于最新版的绿睿车牌识别的核心代码。

bool CLPR::LetterNumberRecognize5LeftDown()
{
    //判断是不是字符‘5’的左下角
    bool five_left_down;
    five_left_down=false;
    //扫描开始的地方
    int start_y;
    start_y=m_center_y+2;//m_center_y: 字符中间的y坐标
    //扫描结束的地方
    int end_y;
    end_y=m_max_y-2;//m_center_y: 字符最下面的y坐标
    //开始扫描
    int y;
    for(y=start_y;y<end_y;y++)
    {
        int left;
        left=Left(y);//在y行,最左端像素离边界的距离
        if(left>=m_delta_x*0.5)//m_delta_x 字符的宽度
        {
            five_left_down=true;
            break;
        }
    }
    return five_left_down;
}

 

整个过程都是在走逻辑很繁琐。但是识别效率还可以,还可以有效抵抗字符旋转等干扰。

转载于:https://www.cnblogs.com/zhaoleicpp/archive/2009/04/20/1439458.html

车牌识别系统源代码 本公司主要从事车牌识别算法开发,在车牌识别这行有一定知名度,有成熟的标清车牌识别源代码出售,并附源代码文档说明及VC++、BCB、VB、Delphi、C#等开发包。在市场有五年应用成功案例,代码具有极高商业价值。 源代码不含第三方控件或LIB,核心算法是C语言开发,基于VC6.0封装的DLL或OCX控件,基于Windows\Linux平台。 也可以移植到DSP平台,也可以我方出技术,投资方出资45W以上。车牌识别系统源代码需要有开发能力,对车牌识别很熟悉,有经济实力的公司。产品用在高速公路卡口、电子警察、收费站、智能停车场等众多需要车牌识别的领域。 开发环境:VC60软件功能如下: 1)全天候识别 (晴天、雨天、白天、夜晚...) 2)自动识别无严重污染的汽车牌照中的汉字、字母和数字部分; 3)自动识别汽车牌照的底色 (黄、黑、蓝); 4)具有识别训练功能,可适应不用的应用环境,提高识别效果。 5)识别速度:< 200 ms; 6)识别率:整牌照识别率大于 97% 7) 如不需要源码,可提供成熟的车牌识别SDK开发包,开发端口,支持多种语言,便于应用软件集成。 技术指标: 识别时间95%。 汉字识别正确率>95%、字符位识别率大于>95%、全车牌识别正确率>90%。 支持各种车牌识别:民用车牌(92式)、民用货车尾牌(双行)、民用车牌(2002个性化)、警车车牌、武警车牌、军用车牌(2004式)。 在可见光成像条件下车牌颜色(蓝、黄、黑、白)识别正确率>95%。 能够识别行驶速度可达200km/h高速行驶的车辆的车牌 。 支持视频自动触发及物理触发车辆检测 。 支持PCI、USB2.0、IEEE1394等接口的视频采集设备。 输出车辆照片、车牌照片、识别结果、车牌颜色、二值化图像 本车牌识别软件可以嵌入停车场管理系统,治安卡口,超速抓拍,电子警察,公路收费管理系统,地磅称重车牌识别系统,码头集装箱号码识别系统。如有需要,请电话或QQ联系洽谈。 咨询电话:13728687572罗经理 QQ:1713614711(备注:车牌识别
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值