2016-09-07 08:53:40 qq_21949357 阅读数 4986
  • 车牌识别停车场管理系统(附SSM项目源代码)

          掌握基于腾讯人工智能(AI)的车牌识别技术,使用车牌识别技术实现一个完整的停车场管理系统,项目包括网页调用摄像头拍照,车牌拍照识别,上传车牌图片识别,用户管理,车辆管理(临时车与包月车),车辆出场,入场管理,停车费收费管理,按照临时车或包月车自动计算停车费,系统参数设置,修改用户密码及安全退出等功能,该系统采用Jsp技术,使用SSM框架,Mysql数据库,ajax技术及人工智能等相关技术实现。 项目开发技术:java,jsp,mysql,MyBatis,SpringMVC,jquery,ajax,json 项目运行环境:jdk1.7及以上版本,tomcat6.0及以上版本,mysql5.5及以上版本项目开发工具: 本项目开发工具是Eclipse,也支持myEclipse,Intellij Idea等其他版本开发工具 相关课程学习顺序 本校课程是培养JAVA软件工程师及JSP WEB网络应用程序开发,android工程师的全套课程,课程学习顺序如下:JAVA初级工程师:     1、计算机基础     2、HTML语言基础     3、C语言从入门到精通+贪吃蛇游戏     4、贪吃蛇游戏     5、SQL SERVER数据库基础     6、JAVA从入门到精通+推箱子游戏+QQ即时通讯软件     7、推箱子游戏;     8、仿QQ即时通讯软件;JAVA中级工程师:     9、SQLSERVER数据库高级     10、SQLSERVER从入门到精通(基础+高级)               11、JavaScript从入门到精通,     12、JSP从入门到精通+点餐系统,     13、JSP从入门到精通+在线视频学习教育平台,     14、JSP从入门到精通+大型电商平台;     15、XML从入门到精通,     16、数据结构(JAVA版),JAVA高级工程师:     17、Oracle数据库从入门到精通,     18、ajax+jquery从入门到精通,     19、EasyUI从入门到精通,SSH框架:     20、Struts2从入门到精通课程,     21、Hibernate从入门到精通课程,     22、Spring从入门到精通课程;     23、Echarts从入门到精通,     24、Excel基于POI的导入导出工作流框架:     25、Activiti流程框架从入门到精通     26、JBPM流程框架从入门到精通SSM框架:     27、MyBatis从入门到精通     28、Spring MVC从入门到精通面试题:     29、职业生涯规划及面试题集锦商业项目:     30、微信公众号在线支付系统     31、微信生活缴费在线支付系统     32、支付宝生活缴费在线支付系统     33、在线考试系统     34、手机订餐管理系统,     35、CRM客户关系管理系统     36、大型房地产CRM销售管理系统     37、CMPP2,CMPP3移动网关系统人工智能:     38、人脸识别在线考试系统     39、人脸识别系统项目实战     40、车牌识别系统项目实战     41、身份证识别系统项目实战     42、营业执照识别系统项目实战           43、名片识别管理系统

    892 人正在学习 去看看 赖国荣

MATLAB实现的车牌定位系统

看完《数字图像处理后》,做的图像识别的入门级项目,代码在: https://github.com/zhoulukuan/Plate-Location ,论文都可以在知网里找到,我就不贴了。具体项目的一些简介可以看github,大致上讲,可以分为颜色检测、区域操作、车牌检测算法和夜晚下的Retinex算法四个环节。

颜色检测

选用颜色检测的好处是在于定位比较精准。选中图片中特定的颜色区域,然后每个区域单独拿出来检测,可以提高检测速度。相对于对图片灰度化,然后进行算法增强与补偿的思路来说,颜色检测对图片信息的利用更加充分。缺点也很明显,就是对于某些强光或者异物(如污泥)引发的车牌颜色变化没有很好的检测能力。

颜色检测部分我较多地参考了:

陈昌涛.基于彩色和黑白纹理分析的车牌定位方法[D].重庆:重庆大学,2008

主要思路是在HSV模式下计算颜色与标准颜色相似度。从colorDetection.m文件中,可以看到,针对区域和非区域、白天和夜晚、各种颜色的参数都是不同。这里区域和非区域的差别可能比较难理解。具体来说,就是区域颜色检测是为了框出特定颜色区域,寻找车牌可能的所在区域的;而非区域则是为了检测特定的颜色边缘,为寻找某种颜色的边缘点做投影准备的。因此非区域阈值可以设置得窄一点,区域阈值可以设置大一点。

其实最主要的,设定区域和非区域参数的原因是区域的话需要进行一系列的形态学操作。我的步骤是:去除小区域——膨胀——进一步去除小区域。第一个去除小区域是排除大量干扰点,这里的面积阈值可以设置得比较小。随后的膨胀操作,是为了将车牌在颜色检测中得到的可能有断裂情况的车牌不同区域连成一体。最后,再次去除小区域的面积阈值可以设置大一些,是为了进一步过滤区域,减小工作量。不一步到位的原因是,若先去除区域,则可能将车牌断裂的小区域去除,区域不完整;若先膨胀,则可能引入大量不同区域连在一起造成的干扰。

区域操作

区域操作主要是我发现直接进行车牌检测干扰比较多,所以设置了很多检验条件(虽然好像也没什么卵用)。区域提取主要是依靠regionprops函数实现的。

一开始实际上我打算使用一个区域归并算法,顺便替代掉上面说的膨胀操作,因为膨胀可能会把车牌和周边区域连起来,引入干扰,这个算法在这里有提到:

陈寅鹏,丁晓青.复杂车辆图像中的车牌定位与字符分割方法[J].红外与激光工程

不过实际使用效果很差,大概因为我的样本库比较杂和刁钻,有很多倾斜度过高的图片,所以垂直重合率很低。我还是在代码里写了这个算法,真实的效果不得而知。
另外就是根据区域里蓝色点的比例之类的,区域大小之类的做做基本的排除工作了。

车牌检测

投影点选择

很多论文提到的思路都是差分绝对值投影,我个人感觉是不太靠谱的,因为对信息的利用率还是太低了。相比较而言,第一篇论文提到的提取白色区域和蓝色区域,计算边缘点然后做与,得到蓝白边缘点,然后计算蓝白交界点做投影就会更好一些。

边缘提取的话我用的是colorLP方法和OTSU阈值化方法,使用纵向sobel算子应该也可以,感觉差距不是很大。不过我只求了蓝色边缘点然后做投影,原因还是样本库太杂的问题。给我的样本库很多网上搜集的小图片,可能车牌大小还不到40*20这样子,打开一看都是像素点,白色的文字基本和蓝色背景混成一堆,颜色混叠了……

实际看了下,蓝色边缘和蓝白边缘区别不是很大的,不过真的图片尺寸理想的话,最好的还是用蓝白边缘交界。顺带一说,我感觉用白色边缘点膨胀然后与蓝色边缘点相与比较好,原论文的算法,检测出的点真的太少,可能这样会更准确一些?不过我感觉车牌纹理特征体现的不明显就是了……

计算具体区域

个人觉得这部分应该是整个项目最复杂的部分。简单说下思路:

水平方向,检测连续多波峰特征,指的是车牌纹理蓝色边缘点投影会连续出现多个高峰:
1、获取函数所有极大值,极大值最小间隙为图片宽/20,用极大值序列中第三高的峰值的0.6作为阈值。之所以选择第三高的峰值来获取阈值是因为在运行时发现区域两边会有几率出现0~2个干扰,如果干扰是峰值极大的情况,很可能会影响判断。而极大值最小间隙的存在是为了防止相近的峰被误检测。假设车牌最小宽度占总区域的1/3,则七个字符,每个字符高峰的距离为1/20。
2、获取大于阈值的所有峰值及其位置。依次检索每个峰,判断某个峰的位置和它前一个峰是否接近,若接近则认定检索峰和前面的峰属于同一个可能的“连续多波峰区域”;否则则认为检索峰属于新的“连续多波峰区域”。
比如我们检索到大于阈值的有8个峰,前7个峰都很接近,第8个峰离第7个较远,最后的区域划分是[1 7]和[8 8],代表1-7峰属于一个“连续多波峰区域”,而8峰则是一个单独的“连续多波峰区域”。
距离是否足够接近是由需要判断的峰和前面最后一个峰的距离与平均距离的比值判断的。
3、将包含最多峰数的区域视为车牌可能存在区域。若该区域所含峰数过少或宽度过短,则直接判断不含车牌;否则,从该区域的第一个峰向左检索,若某点接近零且该点的左面不含有大于阈值的峰,则该点视为车牌开始坐标;从该区域的最后一个峰向右检索,遇到的第一个接近零的点视为车牌结束坐标。接近零代表该点值小于峰值阈值的5%。
之所以开始坐标还要有左面不含有大于阈值的峰值要求,是因为在调试时发现,若左边的开始坐标仅以接近零为条件,程序会将车牌“XX·XXXXX”中的点作为开始位置。不过考虑中间的点确实有时候太小,会把车牌隔断,我后来把最大区域所含峰过少就判断不含车牌改成了,最大区域所含峰或者最大和次大区域所含峰之和过小,一定程度上也减小了这个问题。

垂直方向:
1、获取函数的最大值及其位置,设置最大值5%为阈值。
2、从最大值位置向左检索,若某点接近零且该点的左面不含有大于阈值的峰,则该点视为开始点;从最大值位置向右检索,若某点接近零且该点的右面不含有大于阈值的峰,则该点视为结束点。
波峰要求同样是为了防止检索到的车牌不完整而设置的。
3、若垂直方向的宽度比例过短,则直接判断不含车牌;否则,结合水平方向的宽度进行车牌判断。

含有车牌条件:
宽度大于40,高度大于20,长宽比在0.7~6之间。这个阈值和各种论文比起来很大了,主要原因当然还是样本库……写到这里真是满满的怨念。

夜晚下的Retinex算法

最后就是考虑到夜晚下车牌太暗所希望做的补偿算法,这个具体参考: https://github.com/Vespa314/Retinex ,将的很清楚,代码也写的很棒,我的代码也是用了这里的。不过不知道为什么,好像这个代码使用后得到的图像再转成HSV模式时,部分图像会提示函数错误,而且还是工具箱的函数使用错误,让我有点摸不着头脑,也是我目前没有找出来的一个bug。

关于白天和夜晚的衔接,我的也比较粗暴。直接先检测白天的条件,没有的话再检测夜晚……主要是关于白天和夜晚不是很好判断。我最早使用的是灰度化图片的平均值,标准差平均值,OTSU阈值和灰度平均值比值来判断的,效果其实还是不错的。更加复杂精准的方法,我看过用模式的聚类来做的,也属于我知识的空白区吧,以后有机会再做改进。最后使用的这个方法也是勉勉强强能用,夜晚识别率也只有一半左右,没办法识别强光。

总结

想想,自己做的还是比较惨淡的。很多更加有效精准的方法不能用,碍于样本库的多样复杂,检测率一直没办法提高。有时候还确实有卡着的感觉,好像忙活了半天也就改改参数,没什么实质化的进步。感觉自己还是对特征挖掘不够充分,掌握方法不多,也很不足,说到底还是个刚入门的。希望自己以后继续深入学习能找到改进的方法吧。

2019-10-09 15:32:38 weixin_43397593 阅读数 114
  • 车牌识别停车场管理系统(附SSM项目源代码)

          掌握基于腾讯人工智能(AI)的车牌识别技术,使用车牌识别技术实现一个完整的停车场管理系统,项目包括网页调用摄像头拍照,车牌拍照识别,上传车牌图片识别,用户管理,车辆管理(临时车与包月车),车辆出场,入场管理,停车费收费管理,按照临时车或包月车自动计算停车费,系统参数设置,修改用户密码及安全退出等功能,该系统采用Jsp技术,使用SSM框架,Mysql数据库,ajax技术及人工智能等相关技术实现。 项目开发技术:java,jsp,mysql,MyBatis,SpringMVC,jquery,ajax,json 项目运行环境:jdk1.7及以上版本,tomcat6.0及以上版本,mysql5.5及以上版本项目开发工具: 本项目开发工具是Eclipse,也支持myEclipse,Intellij Idea等其他版本开发工具 相关课程学习顺序 本校课程是培养JAVA软件工程师及JSP WEB网络应用程序开发,android工程师的全套课程,课程学习顺序如下:JAVA初级工程师:     1、计算机基础     2、HTML语言基础     3、C语言从入门到精通+贪吃蛇游戏     4、贪吃蛇游戏     5、SQL SERVER数据库基础     6、JAVA从入门到精通+推箱子游戏+QQ即时通讯软件     7、推箱子游戏;     8、仿QQ即时通讯软件;JAVA中级工程师:     9、SQLSERVER数据库高级     10、SQLSERVER从入门到精通(基础+高级)               11、JavaScript从入门到精通,     12、JSP从入门到精通+点餐系统,     13、JSP从入门到精通+在线视频学习教育平台,     14、JSP从入门到精通+大型电商平台;     15、XML从入门到精通,     16、数据结构(JAVA版),JAVA高级工程师:     17、Oracle数据库从入门到精通,     18、ajax+jquery从入门到精通,     19、EasyUI从入门到精通,SSH框架:     20、Struts2从入门到精通课程,     21、Hibernate从入门到精通课程,     22、Spring从入门到精通课程;     23、Echarts从入门到精通,     24、Excel基于POI的导入导出工作流框架:     25、Activiti流程框架从入门到精通     26、JBPM流程框架从入门到精通SSM框架:     27、MyBatis从入门到精通     28、Spring MVC从入门到精通面试题:     29、职业生涯规划及面试题集锦商业项目:     30、微信公众号在线支付系统     31、微信生活缴费在线支付系统     32、支付宝生活缴费在线支付系统     33、在线考试系统     34、手机订餐管理系统,     35、CRM客户关系管理系统     36、大型房地产CRM销售管理系统     37、CMPP2,CMPP3移动网关系统人工智能:     38、人脸识别在线考试系统     39、人脸识别系统项目实战     40、车牌识别系统项目实战     41、身份证识别系统项目实战     42、营业执照识别系统项目实战           43、名片识别管理系统

    892 人正在学习 去看看 赖国荣

前言:本案例的车牌图像来源于互联网,如有侵权请尽快联系我,立删。

一、概述

在智能交通系统中,汽车牌照识别发挥了巨大的作用。其实现是将图像处理技术与计算机软件技术相连接在一起,以准确识别出车牌牌照的字符为目的,将识别出的数据传送至交通实时管理系统,以最终实现交通监管的功能。在车牌自动识别系统中,从汽车图像的获取到车牌字符处理是一个复杂的过程,主要分为四个阶段:图像获取、车牌定位、字符分割以及字符识别。本文主要通过OpenCV的各种图像处理方法实现车牌定位以及字符分割。(字符识别可参考我的另一篇博客车牌识别

二、车牌图像分析

我国汽车牌照一般由七个字符和一个点组成(参考下图),车牌字符的高度和宽度是固定的,分别为90mm和45mm,七个字符之间的距离也是固定的12mm,中间分割符圆点的直径是10mm,但是真实车牌图像会因为透视原因造成字符间的距离变化。在民用车牌中,字符排列位置遵循以下规律:第一个字符通常是我国各省区的简称,共31个,用汉字表示;第二个字符通常是发证机关的代码号,最后五个字符由英文字母和数字组合而成,字母是24个大写字母(除去 I 和 O)的组合,数字用"0-9"之间的数字表示。

标准车牌
从图像处理角度看,汽车牌照具有以下几个特征:

  1. 车牌的几何特征,即车牌形状统一为高宽比固定的矩形;
  2. 车牌的灰度分布呈现出连续的波谷-波峰-波谷分布,这是因为我国车牌颜色单一,字符直线排列;
  3. 车牌直方图呈现出双峰状的特点,即车牌直方图中可以看到双个波峰;
  4. 车牌具有强边缘信息,这是因为车牌的字符相对集中在车牌的中心,而车牌边缘无字符,因此车牌的边缘信息感较强;
  5. 车牌的字符颜色和车牌背景颜色对比鲜明。目前,我国国内的车牌大致可分为蓝底白字和黄底黑字,特殊用车采用白底黑字或黑底白字,有时辅以红色字体等。为简化处理,本文只考虑蓝底白字的车牌。

三、车牌定位

1. 基本处理

调整尺寸和转灰度图
为了确保输入的车牌图像不能太大或太小,需要对图像进行尺寸调整,一般的照片高宽比是3:4,此次我们限制图像最大宽度为400像素,函数如下:

def resize_img(img):
    """ resize图像 """
    h, w = img.shape[:-1]
    scale = 400 / max(h, w)
    img_resized = cv.resize(img, None, fx=scale, fy=scale, 
                            interpolation=cv.INTER_CUBIC)
    # print(img_resized.shape)
    return img_resized

因图像后续处理输入要求,在此需将图像转为灰度图

img_gray = cv.cvtColor(img_resized, cv.COLOR_BGR2GRAY)
cv.imshow('Gray', img_gray)

效果展示:
在这里插入图片描述

2. 图像降噪

噪声是由一种或者多种原因造成的灰度值的随机变化,通常需要平滑技术(也常称为滤波或者降噪技术)进行抑制或者去除。图像降噪即通过滤波器增强图像中某个波段或频率并阻塞(或降低)其他频率波段。常见的图像滤波方式有均值滤波、高斯滤波、中值滤波、双边滤波等。一般采用高斯滤波来对图像进行降噪。(本案例车牌图像质量较好,此次没有进行此操作)

  img_gaussian = cv.GaussianBlur(img_gray, (3, 3), 0) 
  cv.imshow("Gaussian_Blur2",  img_gaussian)

在这里插入图片描述

3. 灰度拉伸

图像拉伸主要用来改善图像显示的对比度,道路提取流程中往往首先要对图像进行拉伸的预处理。图像拉伸主要有三种方式:灰度拉伸、直方图均衡化和直方图规定化,此次使用灰度拉伸,将灰度值拉伸到整个0-255的区间,那么其对比度显然是大幅增强的。可以用如下的公式来将某个像素的灰度值映射到更大的灰度空间:
在这里插入图片描述
其中Imin,Imax是原始图像的最小灰度值和最大灰度值,MIN和MAX是要拉伸到的灰度空间的灰度最小值和最大值。

def stretching(img):
    """ 灰度拉伸 """
    maxi = float(img.max())
    mini = float(img.min())

    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            img[i, j] = 255 / (maxi - mini) * img[i, j] - (255 * mini) / (maxi - mini)
    img_stretched = img
    return img_stretched

效果展示:
在这里插入图片描述

4. 图像差分

图像差分,就是把两幅图像的对应像素值相减,以削弱图像的相似部分,突出显示图像的变化部分。在进行差分前,需要对图像进行开运算,即先腐蚀后膨胀。

# 进行开运算,去除噪声
r = 14
h = w = r * 2 + 1
kernel = np.zeros((h, w), np.uint8)
cv.circle(kernel, (r, r), r, 1, -1)
# 开运算
img_opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)

效果展示:
在这里插入图片描述
再对开运算前后图像进行差分,使用cv.absdiff函数
效果展示:
在这里插入图片描述

5. 二值化

图像二值化处理就是将图像上点的灰度置为0或255,即整个图像呈现出明显的黑白效果。将256个亮度等级的灰度图像通过适当的阀值选取而获得仍然可以反映图像整体和局部特征的二值化图像。

def binarization(img):
    """ 二值化处理函数 """
    maxi = float(img.max())
    mini = float(img.min())
    x = maxi - ((maxi - mini) / 2)
    # 二值化, 返回阈值ret和二值化操作后的图像img_binary
    ret, img_binary = cv.threshold(img, x, 255, cv.THRESH_BINARY)
    # img_binary = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 2)
    return img_binary

二值化后可以明显看到车牌区域,效果展示:
在这里插入图片描述

6. 边缘检测

边缘检测的目的是找到图像中亮度变化剧烈的像素点构成的集合,表现出来往往是轮廓。边缘检测有很多检测器,其中最常用的是canny边检测器,不容易受到噪声的影响。

def canny(img):
    """ canny边缘检测 """
    img_canny = cv.Canny(img, img.shape[0], img.shape[1])
    return img_canny

效果展示:
在这里插入图片描述

7. 形态学处理

开运算和闭运算是形态学常用的图像处理方式,开运算可以消除亮度较高的细小区域,在纤细点处分离物体,对于较大物体,可以在不明显改变其面积的情况下平滑其边界。闭运算具有填充白色物体内细小黑色空洞的区域、连接临近物体、平滑边界等作用。

def opening_closing(img):
    """ 开闭运算,保留车牌区域,消除其他区域,从而定位车牌 """
    # 进行闭运算
    kernel = np.ones((5, 23), np.uint8)
    img_closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
    cv.imshow("Closing", img_closing)

    # 进行开运算
    img_opening1 = cv.morphologyEx(img_closing, cv.MORPH_OPEN, kernel)
    cv.imshow("Opening_1", img_opening1)

    # 再次进行开运算
    kernel = np.ones((11, 6), np.uint8)
    img_opening2 = cv.morphologyEx(img_opening1, cv.MORPH_OPEN, kernel)
    return img_opening2

三次运算效果展示:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

8. 定位车牌

先对上一步的图像 ‘img_opening2’ 检测轮廓,使用的是cv.findContours,该函数会返回图像的轮廓信息,然后对轮廓信息进行大小,高宽比,颜色筛选出最符合车牌的矩形轮廓,从而定位车牌区域。

def find_rectangle(contour):
    """ 寻找矩形轮廓 """
    y, x = [], []
    for p in contour:
        y.append(p[0][0])
        x.append(p[0][1])
    return [min(y), min(x), max(y), max(x)]


def locate_license(original, img):
    """ 定位车牌号 """
    _, contours, hierarchy = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    img_cont = original.copy()
    img_cont = cv.drawContours(img_cont, contours, -1, (255, 0, 0), 6)
    cv.imshow("Contours", img_cont)
    # 计算轮廓面积及高宽比
    block = []
    for c in contours:
        # 找出轮廓的左上点和右下点,由此计算它的面积和长度比
        r = find_rectangle(c)    # 里面是轮廓的左上点和右下点
        a = (r[2] - r[0]) * (r[3] - r[1])   # 面积
        s = (r[2] - r[0]) / (r[3] - r[1])   # 长度比
        block.append([r, a, s])
    # 选出面积最大的五个区域
    block = sorted(block, key=lambda b: b[1])[-5:]

    # 使用颜色识别判断找出最像车牌的区域
    maxweight, maxindex=0, -1
    for i in range(len(block)):
        # print('block', block[i])
        if 2 <= block[i][2] <=4 and 1000 <= block[i][1] <= 20000:    # 对矩形区域高宽比及面积进行限制 
            b = original[block[i][0][1]: block[i][0][3], block[i][0][0]: block[i][0][2]]
            # BGR转HSV
            hsv = cv.cvtColor(b, cv.COLOR_BGR2HSV)
            lower = np.array([100, 50, 50])
            upper = np.array([140, 255, 255])
            # 根据阈值构建掩膜
            mask = cv.inRange(hsv, lower, upper)
            # 统计权值
            w1 = 0
            for m in mask:
                w1 += m / 255
                print(w1)

            w2 = 0
            for n in w1:
                w2 += n

            # 选出最大权值的区域
            if w2 > maxweight:
                maxindex = i
                maxweight = w2

    rect = block[maxindex][0]
    return rect

在原图中框出车牌,效果展示:
在这里插入图片描述在这里插入图片描述

四、字符分割

1. 去除上下边缘

将车牌区域从图像中裁剪出来,如下图:

在这里插入图片描述
车牌的上下边界通常都是不规范的,其中拉铆螺母的位置也会干扰字符分割,我们需要去除边缘没用的部分。

def find_waves(threshold, histogram):
    """ 根据设定的阈值和图片直方图,找出波峰,用于分隔字符 """
    up_point = -1    # 上升点
    is_peak = False
    if histogram[0] > threshold:
        up_point = 0
        is_peak = True
    wave_peaks = []
    for i, x in enumerate(histogram):
        if is_peak and x < threshold:
            if i - up_point > 2:
                is_peak = False
                wave_peaks.append((up_point, i))
        elif not is_peak and x >= threshold:
            is_peak = True
            up_point = i
    if is_peak and up_point != -1 and i - up_point > 4:
        wave_peaks.append((up_point, i))
    return wave_peaks
 
 
def remove_upanddown_border(img):
    """ 去除车牌上下无用的边缘部分,确定上下边界 """
    plate_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    ret, plate_binary_img = cv.threshold(plate_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    row_histogram = np.sum(plate_binary_img, axis=1)    # 数组的每一行求和
    row_min = np.min(row_histogram)
    row_average = np.sum(row_histogram) / plate_binary_img.shape[0]
    row_threshold = (row_min + row_average) / 2
    wave_peaks = find_waves(row_threshold, row_histogram)
    # 挑选跨度最大的波峰
    wave_span = 0.0
    for wave_peak in wave_peaks:
        span = wave_peak[1] - wave_peak[0]
        if span > wave_span:
            wave_span = span
            selected_wave = wave_peak
    plate_binary_img = plate_binary_img[selected_wave[0]:selected_wave[1], :]
    #cv.imshow("plate_binary_img", plate_binary_img)
    return  plate_binary_img

效果展示:
在这里插入图片描述

2. 分割并保存字符

从左往右开始检测匹配字符,若宽度(end - start)大于5则认为是字符,将其裁剪并保存下来。

def find_end(start, arg, black, white, width, black_max, white_max):
    end = start + 1
    for m in range(start + 1, width - 1):
        if (black[m] if arg else white[m]) > (0.95*black_max if arg else 0.95*white_max):
            end = m
            break
    return end


def char_segmentation(thresh):
    """ 分割字符 """
    white, black = [], []    # list记录每一列的黑/白色像素总和
    height, width = thresh.shape
    white_max = 0    # 仅保存每列,取列中白色最多的像素总数
    black_max = 0    # 仅保存每列,取列中黑色最多的像素总数
    # 计算每一列的黑白像素总和
    for i in range(width):
        line_white = 0    # 这一列白色总数
        line_black = 0    # 这一列黑色总数
        for j in range(height):
            if thresh[j][i] == 255:
                line_white += 1
            if thresh[j][i] == 0:
                line_black += 1
        white_max = max(white_max, line_white)
        black_max = max(black_max, line_black)
        white.append(line_white)
        black.append(line_black)
        # print('white_max', white_max)
        # print('black_max', black_max)
    # arg为true表示黑底白字,False为白底黑字
    arg = True
    if black_max < white_max:
        arg = False

    # 分割车牌字符
    n = 1
    while n < width - 2:
        n += 1
        # 判断是白底黑字还是黑底白字  0.05参数对应上面的0.95 可作调整
        if (white[n] if arg else black[n]) > (0.05 * white_max if arg else 0.05 * black_max):  # 这点没有理解透彻
            start = n
            end = find_end(start, arg, black, white, width, black_max, white_max)
            n = end
            if end - start > 5 or end > (width * 3 / 7):
                cropImg = thresh[0:height, start-1:end+1]
                # 对分割出的数字、字母进行resize并保存
                cropImg = cv.resize(cropImg, (34, 56))
                cv.imwrite(save_path + '\\{}.bmp'.format(n), cropImg)
                cv.imshow('Char_{}'.format(n), cropImg)

最终分割的字符保存至文件夹中,效果展示:
在这里插入图片描述

五、测试其它图片

测试另外两张汽车正视图,字符分割效果较好,展示如下:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述在这里插入图片描述

六、总结

案例思路总结起来是先对图像做预处理,包含调整图像尺寸、转换灰度图、图像降噪、灰度拉伸、图像差分、图像二值化、canny边缘检测、形态学开闭运算,从而定位车牌区域,然后裁剪车牌,去除上下无用边缘部分,最后进行字符分割并将其保存至特定文件夹。

本案例对汽车正视图的车牌定位以及字符分割的效果较为成功,如果图像中车牌有一定的倾斜度以及透视变形,则其中还需对车牌进行倾斜矫正以及透视变换,图像处理也将更为复杂。后续的字符识别只要拥有足够的数据集训练,其过程也与手写数字识别案例一样简单。

七、附上完整代码

import cv2 as cv
import numpy as np

img_path = 'data\\img\\test_005.jpg'
save_path = 'Chars\\test'


def resize_img(img, max_size):
    """ resize图像 """
    h, w = img.shape[0:2]
    scale = max_size / max(h, w)
    img_resized = cv.resize(img, None, fx=scale, fy=scale, 
                            interpolation=cv.INTER_CUBIC)
    # print(img_resized.shape)
    return img_resized


def stretching(img):
    """ 图像拉伸 """
    maxi = float(img.max())
    mini = float(img.min())

    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            img[i, j] = 255 / (maxi - mini) * img[i, j] - (255 * mini) / (maxi - mini)
    img_stretched = img
    return img_stretched


def absdiff(img):
    """ 对开运算前后图像做差分 """
    # 进行开运算,用来去除噪声
    r = 15
    h = w = r * 2 + 1
    kernel = np.zeros((h, w), np.uint8)
    cv.circle(kernel, (r, r), r, 1, -1)
    # 开运算
    img_opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
    # 获取差分图
    img_absdiff = cv.absdiff(img, img_opening)
    cv.imshow("Opening", img_opening)
    return img_absdiff  


def binarization(img):
    """ 二值化处理函数 """
    maxi = float(img.max())
    mini = float(img.min())
    x = maxi - ((maxi - mini) / 2)
    # 二值化, 返回阈值ret和二值化操作后的图像img_binary
    ret, img_binary = cv.threshold(img, x, 255, cv.THRESH_BINARY)
    # img_binary = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 5, 2)
    # 返回二值化后的黑白图像
    return img_binary


def canny(img):
    """ canny边缘检测 """
    img_canny = cv.Canny(img, img.shape[0], img.shape[1])
    return img_canny


def opening_closing(img):
    """ 开闭运算,保留车牌区域,消除其他区域,从而定位车牌 """
    # 进行闭运算
    kernel = np.ones((5, 23), np.uint8)
    img_closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
    cv.imshow("Closing", img_closing)

    # 进行开运算
    img_opening1 = cv.morphologyEx(img_closing, cv.MORPH_OPEN, kernel)
    cv.imshow("Opening_1", img_opening1)

    # 再次进行开运算
    kernel = np.ones((11, 6), np.uint8)
    img_opening2 = cv.morphologyEx(img_opening1, cv.MORPH_OPEN, kernel)
    return img_opening2


def find_rectangle(contour):
    """ 寻找矩形轮廓 """
    y, x = [], []
    for p in contour:
        y.append(p[0][0])
        x.append(p[0][1])
    return [min(y), min(x), max(y), max(x)]


def locate_license(original, img):
    """ 定位车牌号 """
    _, contours, hierarchy = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    img_cont = original.copy()
    img_cont = cv.drawContours(img_cont, contours, -1, (255, 0, 0), 6)
    cv.imshow("Contours", img_cont)
    # 计算轮廓面积及高宽比
    block = []
    for c in contours:
        # 找出轮廓的左上点和右下点,由此计算它的面积和长度比
        r = find_rectangle(c)    # 里面是轮廓的左上点和右下点
        a = (r[2] - r[0]) * (r[3] - r[1])   # 面积
        s = (r[2] - r[0]) / (r[3] - r[1])   # 长度比
        block.append([r, a, s])
    # 选出面积最大的五个区域
    block = sorted(block, key=lambda bl: bl[1])[-5:]

    # 使用颜色识别判断找出最像车牌的区域
    maxweight, maxindex=0, -1
    for i in range(len(block)):
        # print('block', block[i])
        if 2 <= block[i][2] <=4 and 1000 <= block[i][1] <= 20000:    # 对矩形区域高宽比及面积进行限制 
            b = original[block[i][0][1]: block[i][0][3], block[i][0][0]: block[i][0][2]]
            # BGR转HSV
            hsv = cv.cvtColor(b, cv.COLOR_BGR2HSV)
            lower = np.array([100, 50, 50])
            upper = np.array([140, 255, 255])
            # 根据阈值构建掩膜
            mask = cv.inRange(hsv, lower, upper)
            # 统计权值
            w1 = 0
            for m in mask:
                w1 += m / 255

            w2 = 0
            for n in w1:
                w2 += n

            # 选出最大权值的区域
            if w2 > maxweight:
                maxindex = i
                maxweight = w2

    rect = block[maxindex][0]
    return rect


def preprocessing(img):
    # resize图像至300 * 400
    img_resized = resize_img(img, 400)
    cv.imshow('Original', img_resized)
    # 转灰度图
    img_gray = cv.cvtColor(img_resized, cv.COLOR_BGR2GRAY)
    cv.imshow('Gray', img_gray)
    # 高斯滤波
    # img_gaussian = cv.GaussianBlur(img_gray, (3,3), 0)
    # cv.imshow("Gaussian_Blur", img_gaussian)
    # 灰度拉伸,提升图像对比度
    img_stretched = stretching(img_gray)
    cv.imshow('Stretching', img_stretched)
    # 差分开运算前后图像
    img_absdiff = absdiff(img_stretched)
    cv.imshow("Absdiff", img_absdiff)
    # 图像二值化
    img_binary = binarization(img_absdiff)
    cv.imshow('Binarization', img_binary)
    # 边缘检测
    img_canny = canny(img_binary)
    cv.imshow("Canny", img_canny)
    # 开闭运算,保留车牌区域,消除其他区域
    img_opening2 = opening_closing(img_canny)
    cv.imshow("Opening_2", img_opening2)
    # 定位车牌号所在矩形区域
    rect = locate_license(img_resized, img_opening2)
    print("rect:", rect)  
    # 框出并显示车牌
    img_copy = img_resized.copy()
    cv.rectangle(img_copy, (rect[0], rect[1]), (rect[2], rect[3]), (0, 255, 0), 2)
    cv.imshow('License', img_copy)
    return rect, img_resized


def cut_license(original, rect):
    """ 裁剪车牌 """
    license_img = original[rect[1]:rect[3], rect[0]:rect[2]]
    return license_img


def find_waves(threshold, histogram):
    """ 根据设定的阈值和图片直方图,找出波峰,用于分隔字符 """
    up_point = -1    # 上升点
    is_peak = False
    if histogram[0] > threshold:
        up_point = 0
        is_peak = True
    wave_peaks = []
    for i, x in enumerate(histogram):
        if is_peak and x < threshold:
            if i - up_point > 2:
                is_peak = False
                wave_peaks.append((up_point, i))
        elif not is_peak and x >= threshold:
            is_peak = True
            up_point = i
    if is_peak and up_point != -1 and i - up_point > 4:
        wave_peaks.append((up_point, i))
    return wave_peaks
 
 
def remove_upanddown_border(img):
    """ 去除车牌上下无用的边缘部分,确定上下边界 """
    plate_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    ret, plate_binary_img = cv.threshold(plate_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    row_histogram = np.sum(plate_binary_img, axis=1)    # 数组的每一行求和
    row_min = np.min(row_histogram)
    row_average = np.sum(row_histogram) / plate_binary_img.shape[0]
    row_threshold = (row_min + row_average) / 2
    wave_peaks = find_waves(row_threshold, row_histogram)
    # 挑选跨度最大的波峰
    wave_span = 0.0
    selected_wave = []
    for wave_peak in wave_peaks:
        span = wave_peak[1] - wave_peak[0]
        if span > wave_span:
            wave_span = span
            selected_wave = wave_peak
    plate_binary_img = plate_binary_img[selected_wave[0]:selected_wave[1], :]
    return  plate_binary_img


def find_end(start, arg, black, white, width, black_max, white_max):
    end = start + 1
    for m in range(start + 1, width - 1):
        if (black[m] if arg else white[m]) > (0.95*black_max if arg else 0.95*white_max):
            end = m
            break
    return end


def char_segmentation(thresh):
    """ 分割字符 """
    white, black = [], []    # list记录每一列的黑/白色像素总和
    height, width = thresh.shape
    white_max = 0    # 仅保存每列,取列中白色最多的像素总数
    black_max = 0    # 仅保存每列,取列中黑色最多的像素总数
    # 计算每一列的黑白像素总和
    for i in range(width):
        line_white = 0    # 这一列白色总数
        line_black = 0    # 这一列黑色总数
        for j in range(height):
            if thresh[j][i] == 255:
                line_white += 1
            if thresh[j][i] == 0:
                line_black += 1
        white_max = max(white_max, line_white)
        black_max = max(black_max, line_black)
        white.append(line_white)
        black.append(line_black)
        # print('white_max', white_max)
        # print('black_max', black_max)
    # arg为true表示黑底白字,False为白底黑字
    arg = True
    if black_max < white_max:
        arg = False

    # 分割车牌字符
    n = 1
    while n < width - 2:
        n += 1
        # 判断是白底黑字还是黑底白字  0.05参数对应上面的0.95 可作调整
        if (white[n] if arg else black[n]) > (0.05 * white_max if arg else 0.05 * black_max):  # 这点没有理解透彻
            start = n
            end = find_end(start, arg, black, white, width, black_max, white_max)
            n = end
            if end - start > 5 or end > (width * 3 / 7):
                cropImg = thresh[0:height, start-1:end+1]
                # 对分割出的数字、字母进行resize并保存
                cropImg = cv.resize(cropImg, (34, 56))
                cv.imwrite(save_path + '\\{}.bmp'.format(n), cropImg)
                cv.imshow('Char_{}'.format(n), cropImg)


def main():
    # 读取图像
    image = cv.imread(img_path)
    # 图像预处理,返回img_resized和定位的车牌矩形区域
    rect, img_resized = preprocessing(image)
    # 裁剪出车牌
    license_img = cut_license(img_resized, rect)
    cv.imshow('License', license_img)
    # 去除车牌上下无用边缘
    plate_b_img = remove_upanddown_border(license_img)
    cv.imshow('plate_binary', plate_b_img)
    # 字符分割,保存至文件夹
    char_segmentation(plate_b_img)
    cv.waitKey(0)
    cv.destroyAllWindows()


if __name__ == '__main__':
    main()
2019-01-24 17:26:03 lixiaoyu101 阅读数 3932
  • 车牌识别停车场管理系统(附SSM项目源代码)

          掌握基于腾讯人工智能(AI)的车牌识别技术,使用车牌识别技术实现一个完整的停车场管理系统,项目包括网页调用摄像头拍照,车牌拍照识别,上传车牌图片识别,用户管理,车辆管理(临时车与包月车),车辆出场,入场管理,停车费收费管理,按照临时车或包月车自动计算停车费,系统参数设置,修改用户密码及安全退出等功能,该系统采用Jsp技术,使用SSM框架,Mysql数据库,ajax技术及人工智能等相关技术实现。 项目开发技术:java,jsp,mysql,MyBatis,SpringMVC,jquery,ajax,json 项目运行环境:jdk1.7及以上版本,tomcat6.0及以上版本,mysql5.5及以上版本项目开发工具: 本项目开发工具是Eclipse,也支持myEclipse,Intellij Idea等其他版本开发工具 相关课程学习顺序 本校课程是培养JAVA软件工程师及JSP WEB网络应用程序开发,android工程师的全套课程,课程学习顺序如下:JAVA初级工程师:     1、计算机基础     2、HTML语言基础     3、C语言从入门到精通+贪吃蛇游戏     4、贪吃蛇游戏     5、SQL SERVER数据库基础     6、JAVA从入门到精通+推箱子游戏+QQ即时通讯软件     7、推箱子游戏;     8、仿QQ即时通讯软件;JAVA中级工程师:     9、SQLSERVER数据库高级     10、SQLSERVER从入门到精通(基础+高级)               11、JavaScript从入门到精通,     12、JSP从入门到精通+点餐系统,     13、JSP从入门到精通+在线视频学习教育平台,     14、JSP从入门到精通+大型电商平台;     15、XML从入门到精通,     16、数据结构(JAVA版),JAVA高级工程师:     17、Oracle数据库从入门到精通,     18、ajax+jquery从入门到精通,     19、EasyUI从入门到精通,SSH框架:     20、Struts2从入门到精通课程,     21、Hibernate从入门到精通课程,     22、Spring从入门到精通课程;     23、Echarts从入门到精通,     24、Excel基于POI的导入导出工作流框架:     25、Activiti流程框架从入门到精通     26、JBPM流程框架从入门到精通SSM框架:     27、MyBatis从入门到精通     28、Spring MVC从入门到精通面试题:     29、职业生涯规划及面试题集锦商业项目:     30、微信公众号在线支付系统     31、微信生活缴费在线支付系统     32、支付宝生活缴费在线支付系统     33、在线考试系统     34、手机订餐管理系统,     35、CRM客户关系管理系统     36、大型房地产CRM销售管理系统     37、CMPP2,CMPP3移动网关系统人工智能:     38、人脸识别在线考试系统     39、人脸识别系统项目实战     40、车牌识别系统项目实战     41、身份证识别系统项目实战     42、营业执照识别系统项目实战           43、名片识别管理系统

    892 人正在学习 去看看 赖国荣

车牌识别分三步:车牌定位,车牌字符分割,车牌字符识别。
本篇就车牌定位进行讲述。车牌定位顾名思义——找出车牌的位置。如何实现,又分三步:图像预处理,数学形态学粗定位,长宽比例精确定位。

首先,对图像预处理:

  1. 彩色图像转灰度图
  2. 高斯滤波,中值滤波
  3. 边缘化检测
  4. 二值化操作
#-*- coding: utf-8 -*-
import cv2
import numpy as np
 
def Process(img):

	# 高斯平滑
	gaussian = cv2.GaussianBlur(img, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
	#cv2.GaussianBlur(src,ksize,sigmaX[,sigmaxY[,borderType]]])高斯滤波函数
	#src: 输入图像
	#ksize: 高斯内核大小,元组类型
	#sigmaX: 高斯核函数在X方向上的标准偏差
	#sigmaY: 高斯核函数在Y方向上的标准偏差,如果sigmaY是0,则函数会自动将sigmaY的值设置为与sigmaX相同的值,如果sigmaX和sigmaY都是0,这两个值将由ksize[0]和ksize[1]计算而来。建议将size、sigmaX和sigmaY都指定出来。
	#borderType: 推断图像外部像素的某种便捷模式,有默认值cv2.BORDER_DEFAULT,如果没有特殊需要不用更改,具体可以参考borderInterpolate()函数。
	
	# 中值滤波
	median = cv2.medianBlur(gaussian, 5)
	#cv2.medianBlur(src,ksize)
	#src: 输入图像
	#ksize: 高斯内核大小,元组类型

	# Sobel算子
	# 梯度方向: x
	sobel = cv2.Sobel(median, cv2.CV_8U, 1, 0, ksize=3)
	# 利用Sobel方法可以进行sobel边缘检测
  	# img表示源图像,即进行边缘检测的图像
	# 图像深度是cv2.CV_8U、cv2.CV_16U、cv2.CV_16S、cv2.CV_32F以及cv2.CV_64F其中的某一个
	# 第三和第四个参数分别是对X和Y方向的导数(即dx,dy),对于图像来说就是差分,这里1表示对X求偏导(差分),0表示不对Y求导(差分)。其中,X还可以求2次导。
	# 注意:对X求导就是检测X方向上是否有边缘。
	# 第五个参数ksize是指核的大小。
	# 这里说明一下,这个参数的前四个参数都没有给谁赋值,而ksize则是被赋值的对象
	# 实际上,这是可省略的参数,而前四个是不可省的参数。注意其中的不同点值化
	
	ret, binary = cv2.threshold(sobel, 170, 255, cv2.THRESH_BINARY)
	#灰度值小于175的点置0,灰度值大于175的点置255
	#最后的参数是阈值类型,对应一个公式,一般用这个就可以

然后,数学形态学处理: 形态学处理的核心就是定义结构元素,一般情况下对二值化图像进行的操作。

	# 膨胀和腐蚀操作的核函数
	element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
	element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 7))
	#第一个参数定义结构元素,如椭圆(MORPH_ELLIPSE)、交叉形结构(MORPH_CROSS)和矩形(MORPH_RECT)
	#第二个参数就是指和函数的size,9×1
  
	# 膨胀一次,让轮廓突出
	dilation = cv2.dilate(binary, element2, iterations=1)
	# 腐蚀一次,去掉细小杂点
	erosion = cv2.erode(dilation, element1, iterations=1)
	# 再次膨胀,让轮廓更明显
	dilation2 = cv2.dilate(erosion, element2, iterations=3)
	
	#存储中间图片 
    cv2.imwrite("binary.png", binary)
    cv2.imwrite("dilation.png", dilation)
    cv2.imwrite("erosion.png", erosion)
    cv2.imwrite("dilation2.png", dilation2)
    
	return dilation2

最后:查找轮廓,精确定位

  1. 查找筛选轮廓
  2. 车牌长宽比
  3. 颜色判断(没大必要)
def GetRegion(img):
	regions = []
	# 查找轮廓
	_, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
	#三个输入参数:输入图像(二值图像),轮廓检索方式,轮廓近似方法
	#轮廓检索方式:cv2.RETR_EXTERNAL	只检测外轮廓。cv2.RETR_LIST	检测的轮廓不建立等级关系。cv2.RETR_CCOMP	建立两个等级的轮廓,上面一层为外边界,里面一层为内孔的边界信息。cv2.RETR_TREE	建立一个等级树结构的轮廓
	#轮廓近似方法:cv2.CHAIN_APPROX_NONE	存储所有边界点。cv2.CHAIN_APPROX_SIMPLE	压缩垂直、水平、对角方向,只保留端点。cv2.CHAIN_APPROX_TX89_L1	使用teh-Chini近似算法。cv2.CHAIN_APPROX_TC89_KCOS	使用teh-Chini近似算法
	#三个返回值:图像,轮廓,轮廓的层析结构

    #筛选面积小的
	for contour in contours:
	    #计算该轮廓的面积
		area = cv2.contourArea(contour)
		#面积小的都筛选掉
		if (area < 2000):
			continue
		#轮廓近似,作用很小
		epslion = 1e-3 * cv2.arcLength(contour, True)
		approx = cv2.approxPolyDP(contour, epslion, True)
		#epsilon,是从轮廓到近似轮廓的最大距离。是一个准确率参数,好的epsilon的选择可以得到正确的输出。True决定曲线是否闭合。
		# 找到最小的矩形,该矩形可能有方向
		rect = cv2.minAreaRect(contour)
		# box是四个点的坐标
		box = cv2.boxPoints(rect)
		box = np.int0(box)
		## 计算高和宽
		height = abs(box[0][1] - box[2][1])
		width = abs(box[0][0] - box[2][0])
		#车牌正常情况下长高比在2-5之间
		ratio =float(width) / float(height)
		if (ratio < 5 and ratio > 2):
			regions.append(box)
	return regions
	
def detect(img):
	# 灰度化
	gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
	# 预处理及形态学处理,得到可以查找矩形的图片
	prc = Process(gray)
	#得到车牌轮廓
	regions = GetRegion(prc)
	print('[INFO]:Detect %d license plates' % len(regions))
	#用绿线画出这些找到的轮廓
	for box in regions:
		cv2.drawContours(img, [box], 0, (0, 255, 0), 2)
        #五个输入参数:原始图像,轮廓,轮廓的索引(当设置为-1时,绘制所有轮廓),画笔颜色,画笔大小
        #一个返回值:返回绘制了轮廓的图像
	cv2.imshow('Result', img)
    #保存结果文件名
	cv2.imwrite('result.jpg', img)
	cv2.waitKey(0)
	cv2.destroyAllWindows()
 
if __name__ == '__main__':
    #输入的参数为图片的路径
	img = cv2.imread('1.jpg')
	detect(img)
2018-12-06 23:25:36 weixin_43958974 阅读数 6625
  • 车牌识别停车场管理系统(附SSM项目源代码)

          掌握基于腾讯人工智能(AI)的车牌识别技术,使用车牌识别技术实现一个完整的停车场管理系统,项目包括网页调用摄像头拍照,车牌拍照识别,上传车牌图片识别,用户管理,车辆管理(临时车与包月车),车辆出场,入场管理,停车费收费管理,按照临时车或包月车自动计算停车费,系统参数设置,修改用户密码及安全退出等功能,该系统采用Jsp技术,使用SSM框架,Mysql数据库,ajax技术及人工智能等相关技术实现。 项目开发技术:java,jsp,mysql,MyBatis,SpringMVC,jquery,ajax,json 项目运行环境:jdk1.7及以上版本,tomcat6.0及以上版本,mysql5.5及以上版本项目开发工具: 本项目开发工具是Eclipse,也支持myEclipse,Intellij Idea等其他版本开发工具 相关课程学习顺序 本校课程是培养JAVA软件工程师及JSP WEB网络应用程序开发,android工程师的全套课程,课程学习顺序如下:JAVA初级工程师:     1、计算机基础     2、HTML语言基础     3、C语言从入门到精通+贪吃蛇游戏     4、贪吃蛇游戏     5、SQL SERVER数据库基础     6、JAVA从入门到精通+推箱子游戏+QQ即时通讯软件     7、推箱子游戏;     8、仿QQ即时通讯软件;JAVA中级工程师:     9、SQLSERVER数据库高级     10、SQLSERVER从入门到精通(基础+高级)               11、JavaScript从入门到精通,     12、JSP从入门到精通+点餐系统,     13、JSP从入门到精通+在线视频学习教育平台,     14、JSP从入门到精通+大型电商平台;     15、XML从入门到精通,     16、数据结构(JAVA版),JAVA高级工程师:     17、Oracle数据库从入门到精通,     18、ajax+jquery从入门到精通,     19、EasyUI从入门到精通,SSH框架:     20、Struts2从入门到精通课程,     21、Hibernate从入门到精通课程,     22、Spring从入门到精通课程;     23、Echarts从入门到精通,     24、Excel基于POI的导入导出工作流框架:     25、Activiti流程框架从入门到精通     26、JBPM流程框架从入门到精通SSM框架:     27、MyBatis从入门到精通     28、Spring MVC从入门到精通面试题:     29、职业生涯规划及面试题集锦商业项目:     30、微信公众号在线支付系统     31、微信生活缴费在线支付系统     32、支付宝生活缴费在线支付系统     33、在线考试系统     34、手机订餐管理系统,     35、CRM客户关系管理系统     36、大型房地产CRM销售管理系统     37、CMPP2,CMPP3移动网关系统人工智能:     38、人脸识别在线考试系统     39、人脸识别系统项目实战     40、车牌识别系统项目实战     41、身份证识别系统项目实战     42、营业执照识别系统项目实战           43、名片识别管理系统

    892 人正在学习 去看看 赖国荣

车牌识别系统主要包括车牌定位字符分割字符识别三个核心模块。

车牌定位是利用车牌的颜色和形状特征确认并获取汽车的车牌位置;
字符分割是将获取到的车牌切割成单个字符;
字符识别目前主要有基于模板匹配算法和基于人工神经网络算法对切割的字符进行识别。

本节内容主要讲解车牌定位,主要内容有:


读取图像
预处理
边缘检测
形态学操作
定位
裁剪

主函数代码如下 :

// main.m
close all;
clear all 
clc
[fn,pn,fi] = uigetfile('*.jpg','请选择所要识别的图片');        %1.读取图像
I = imread([pn fn]); % 读取图像 参数为图像名称和图像路径
 figure('name','原始图像'), imshow(I);                   
title('原始图像'); %显示原始图像 
Ia = Yuchuli_Caitu(I);                                     %2.进行预处理
Ib = Bianyuanjiance_Tu(Ia);                                  %3.边缘检测
Ic = Xingtaixue_Tu(Ib);                    %4.形态学操作:腐蚀、平滑、擦除
bw = Ic;
[PY2,PY1,PX2,PX1]=Chepai_Dingwei(bw);                 %5.获取车牌边界信息
%获取车牌上下左右边界的位置信息
 dw = I(PY1:PY2-8,PX1:PX2,:);                                %6.裁剪车牌                     
 figure('name','定位剪切后的彩色车牌图像'),imshow(dw);
 title('定位剪切后的彩色车牌图像');
1.读取图像

读取原始图像,这里采用的是[fn,pn,fi] = uigetfile函数,可用于直接弹出对话框的选择图片,比较方便。
fn表示图片的名字,pn表示图片的路径,fi表示选择的文件类型

// 读取原始图像
[fn,pn,fi] = uigetfile('*.jpg','请选择所要识别的图片');     
I = imread([pn fn]); % 读取图像 参数为图像名称和图像路径
figure('name','原始图像'), imshow(I);          
title('原始图像'); %显示原始图像 
2.预处理

灰度处理

I1 = imadjust(I1,[0.3,0.7],[]);
% 表示I显示在J中的像素值范围为0.3到0.8,而且0.3在J中对应0.2,0.8在J中对应0.9; I中小于0.3的也都在J中用0.2表示,高于0.8的像素在J中都用0.9表示;

滤波处理,滤波处理采用中值滤波

I1 = medfilt2(I1);

// 预处理
function Ia = Yuchuli_Caitu(I)
I1 = rgb2gray(I);                                    %1.RGB图像转灰度图像
I1 = imadjust(I1,[0.3,0.7],[]);
figure('name','预处理后的图片'),subplot(231),imshow(I1);
title('灰度处理后的灰度图');
subplot(122),imhist(I1);
title('灰度处理后的灰度图直方图');
%-------中值滤波
 I1 = medfilt2(I1);                                          %2.中值滤波    
 subplot(232),imshow(I1);title('中值滤波');
 Ia = I1;
end
3.边缘检测

采用roberts算子边缘检测

I2 = edge(Ia,‘roberts’,0.25,‘both’);
%边缘检测算法,强度小于阈值0.15的边缘被省略掉,'both’两个方向检测(缺省默认)

//边缘检测
function Ib = Bianyuanjiance_Tu(Ia)
I2 = edge(Ia,'roberts',0.25,'both');                          %3.边缘检测
%figure('name','边缘检测'),
subplot(233),imshow(I2);title('robert算子边缘检测') 
Ib = I2;
end
4.形态学操作

腐蚀imrode、平滑imclose、擦除bwareaopen

// 腐蚀、平滑、擦除
function Ic = Xingtaixue_Tu(Ib)
se = [1;1;1];%设置结构元素对象
I3 = imerode(Ib,se);                                         %4.腐蚀图像
% 腐蚀Imerode(X,SE).其中X是待处理的图像,SE是结构元素对象
%figure('name','腐蚀后图像'),
subplot(234),imshow(I3);title('腐蚀后的图像');
se = strel('rectangle',[20,20]);
%strel(shape,parameters):shape为形状参数;parameters为控制形状大小参数
 I4 = imclose(I3,se);                                        %5.平滑处理
 %25*25的矩形对图像进行闭运算(先膨胀后腐蚀)有平滑边界作用
 %该函数功能是对灰度图像执行形态学闭运算
 %figure('name','平滑处理'),
 subplot(235),imshow(I4);title('平滑图像的轮廓');
 I5 = bwareaopen(I4,2000);                                      %6.擦除
 % 从二进制图像中移除所有少于2000像素的连接对象,消失的是连续的白色像素数量少于2000的字符
%figure('name','擦除'),
subplot(236),imshow(I5);title('擦除');
Ic = I5;
end
5.定位

采用的是对蓝色像素点的采集判断,因为就中国地区而言绝大部分车牌的底色的蓝色的,虽然算法很快很识别出大区域蓝色,但缺点在于对蓝色车辆的车牌识别不是很好。

//定位:适用非蓝色车辆
function [PY2,PY1,PX2,PX1]=Chepai_Dingwei(bw)
%并得到车牌的上边界PY1、下边界PY2、左边界PX1、右边界 PX2
[y,x,z]=size(bw);% size(A) 反回矩阵行数/列数/片数          %1.读取bw的size
myI=double(bw);                                  %2.将bw数据转换成双精度型

 %====================== Y 方向====================    %3.获取车牌上下边界
 Blue_y=zeros(y,1); %创建列向量y*1                    
 % zeros(M,N) 表示的是M*N列的全0矩阵
for i=1:y                                        %% a.统计每行蓝色像素点数
    for j=1:x
         if(myI(i,j,1)==1) %针对预处理后图像的判断蓝色像素
        % if((I(i,j,1)<=48)&&((I(i,j,2)<=100)&&(I(i,j,2)>=40))&&((I(i,j,3)<=200)&&(I(i,j,3)>=80))) 
            % 此蓝色像素点的判断语句,是针对原始图像;如用的话,需加输入参数I
             Blue_y(i,1)= Blue_y(i,1)+1;     % 蓝色像素点统计                    
        end  
    end       
end
 [temp MaxY]=max(Blue_y); % Y方向车牌区域确定 [temp MaxY]临时变量MaxY
 %temp表示最多的点数,MaxY表示最多点数所在的行
 
  if temp<=20    %若蓝色像素小于这个值,则代表  2048*1536  照相
           msgbox('车牌定位出错','warning'); % 弹出对话框函数msgbox       
           pause;
  end
 
 PY1=MaxY;                                         %% b.寻找车牌上边界PY1
 Y_threshlow=5;  %提取彩图的质量因子,阈值
 while ((Blue_y(PY1,1)>=Y_threshlow)&&(PY1>1)) %如果蓝色像素点数大于阈值
         PY1=PY1-1;                            %则上移
 end    
 PY2=MaxY;                                         %% c.寻找车牌下边界PY2
 while ((Blue_y(PY2,1)>=Y_threshlow)&&(PY2<y)) %并且保证不跳出图像自身大小
         PY2=PY2+1;
 end

  %====================== X 方向====================   %4.获取车牌左右边界
 Blue_x=zeros(1,x);   %创建行向量1*x                      %% 步骤参考Y方向
 for j=1:x
      for i=PY1:PY2  % 因为确定了上下界范围,因此只需要处理PY1:PY2范围
          if(myI(i,j,1)==1) %针对预处理后图像的判断蓝色像素
        % if((I(i,j,1)<=48)&&((I(i,j,2)<=100)&&(I(i,j,2)>=40))&&((I(i,j,3)<=200)&&(I(i,j,3)>=80))) 
            % 此蓝色像素点的判断语句,是针对原始图像;如用的话,需加输入参数I
             Blue_x(1,j)= Blue_x(1,j)+1; % 蓝色像素点统计             
          end 
      end   
 end

 PX1 = 1;                                          %% a. 寻找车牌左侧位置
 X_threshlow = 5;  %提取彩图的质量因子,阈值
 while ((Blue_x(1,PX1)<X_threshlow)&&(PX1<x))
        PX1 = PX1+1;
 end    
 PX2 = x;                                          %% b. 寻找车牌右侧位置
 while ((Blue_x(1,PX2)<3)&&(PX2>PX1))
         PX2 = PX2-1;
 end
   %======================修正====================      %5.对车牌边界修正
X_firrectify=5;    %干扰因子
Y_firrectify=fix((PY2-PY1)/5); %取整,车牌区域修正系数
%fit为取整函数
PY1=PY1-Y_firrectify;%对车牌区域的修正,向上
PY2=PY2+Y_firrectify;%对车牌区域的修正,向下
PX1=PX1-X_firrectify;% 对车牌区域的修正
PX2=PX2+X_firrectify;% 对车牌区域的修正,
end
6.裁剪

对定位好的车牌进行裁剪,以便接下来的字符分割操作

 dw = I(PY1:PY2-8,PX1:PX2,:);                                   %6.裁剪车牌                     
 figure('name','定位剪切后的彩色车牌图像'),imshow(dw);
 title('定位剪切后的彩色车牌图像')

图像处理过程
原始图像
在这里插入图片描述
在这里插入图片描述
留言就不一一回复了
有问题和需要源码的小伙伴们可以加我微信:Abox_0226
在这里插入图片描述
获取更多MATLAB以及GUI学习资料。
欢迎扫描下方二维码关注我的微信公众号:阿不小奇谈;
在这里插入图片描述

2015-02-07 17:06:56 u011630458 阅读数 10061
  • 车牌识别停车场管理系统(附SSM项目源代码)

          掌握基于腾讯人工智能(AI)的车牌识别技术,使用车牌识别技术实现一个完整的停车场管理系统,项目包括网页调用摄像头拍照,车牌拍照识别,上传车牌图片识别,用户管理,车辆管理(临时车与包月车),车辆出场,入场管理,停车费收费管理,按照临时车或包月车自动计算停车费,系统参数设置,修改用户密码及安全退出等功能,该系统采用Jsp技术,使用SSM框架,Mysql数据库,ajax技术及人工智能等相关技术实现。 项目开发技术:java,jsp,mysql,MyBatis,SpringMVC,jquery,ajax,json 项目运行环境:jdk1.7及以上版本,tomcat6.0及以上版本,mysql5.5及以上版本项目开发工具: 本项目开发工具是Eclipse,也支持myEclipse,Intellij Idea等其他版本开发工具 相关课程学习顺序 本校课程是培养JAVA软件工程师及JSP WEB网络应用程序开发,android工程师的全套课程,课程学习顺序如下:JAVA初级工程师:     1、计算机基础     2、HTML语言基础     3、C语言从入门到精通+贪吃蛇游戏     4、贪吃蛇游戏     5、SQL SERVER数据库基础     6、JAVA从入门到精通+推箱子游戏+QQ即时通讯软件     7、推箱子游戏;     8、仿QQ即时通讯软件;JAVA中级工程师:     9、SQLSERVER数据库高级     10、SQLSERVER从入门到精通(基础+高级)               11、JavaScript从入门到精通,     12、JSP从入门到精通+点餐系统,     13、JSP从入门到精通+在线视频学习教育平台,     14、JSP从入门到精通+大型电商平台;     15、XML从入门到精通,     16、数据结构(JAVA版),JAVA高级工程师:     17、Oracle数据库从入门到精通,     18、ajax+jquery从入门到精通,     19、EasyUI从入门到精通,SSH框架:     20、Struts2从入门到精通课程,     21、Hibernate从入门到精通课程,     22、Spring从入门到精通课程;     23、Echarts从入门到精通,     24、Excel基于POI的导入导出工作流框架:     25、Activiti流程框架从入门到精通     26、JBPM流程框架从入门到精通SSM框架:     27、MyBatis从入门到精通     28、Spring MVC从入门到精通面试题:     29、职业生涯规划及面试题集锦商业项目:     30、微信公众号在线支付系统     31、微信生活缴费在线支付系统     32、支付宝生活缴费在线支付系统     33、在线考试系统     34、手机订餐管理系统,     35、CRM客户关系管理系统     36、大型房地产CRM销售管理系统     37、CMPP2,CMPP3移动网关系统人工智能:     38、人脸识别在线考试系统     39、人脸识别系统项目实战     40、车牌识别系统项目实战     41、身份证识别系统项目实战     42、营业执照识别系统项目实战           43、名片识别管理系统

    892 人正在学习 去看看 赖国荣

简介

  按照在哪里跌倒就在哪里爬起来的精神,本章继续做车牌号的检测识别。所有步骤分为3步完成:车牌号定位,车牌号字符分割、字符识别。
本章为第一部分:车牌号定位。


效果演示

  正式开始讲解之前,先看下车牌号定位出来的效果演示。注:本文所有图片均来源于网络。
                 
  如图所示,定位到车牌号之后,将车牌号用黄色框选起来,同时将该车牌复制为新图片显示出来。


代码及实现原理讲解


图像灰阶/二值化

  首先也是常用的操作,将图像灰阶化,然后从像素值为255一侧开始,以累积像素占总像素5%的的地方作为二值化的阀值,进而获得对应的二值化图像。
对应代码如下:
void pic_gray(Mat& mat1, Mat& mat2){
	IplImage pI = mat1;
	uchar* ptr;
	CvScalar s;
 
	int width = mat1.rows;
	int height = mat1.cols;
 
	mat2 = cv::Mat(width, height, CV_8UC1, 1);
	ptr = mat2.ptr(0);
	for(int i = 0; i < width; i++){
		for(int j=0; j<height; j++){  
			s = cvGet2D(&pI,i,j);
			int grayScale = (int)(s.val[0]*0.299 + s.val[1]*0.587 + s.val[2]*0.114);
			ptr[i*height+j] = grayScale;
		}
	}
}
int histogram_Calculate(Mat& mat1, int number){
	Mat gray_hist;
	int histSize = 255;
	float range[] = { 0, 255 } ;
	const float* histRange = { range };
	bool uniform = true;
	bool accumulate = false;
	int width, height;
	int i, j;
	uchar* ptr = mat1.ptr(0);
	long int pixel_all = 0, pixel_Calc = 0;
 
	calcHist(&mat1, 1, 0, Mat(), gray_hist, 1, &histSize, &histRange, uniform, accumulate);
 
	width = gray_hist.rows;
	height = gray_hist.cols;
 
	for(i=0; i<=width; i++){
		pixel_all += ptr[i];
	}
 
	for(i=0; i<=width; i++){
		pixel_Calc += ptr[255 - i];
		if(((pixel_Calc * 100) / pixel_all) > number){
			i = 255 - i;
			break;	
		}
	}
	return i;
}
 
void pic_Thresholding(Mat& mat1, int threshold){
	uchar* ptr = mat1.ptr(0);
	int width = mat1.rows;
	int height = mat1.cols;
 
	for(int i = 0; i < width; i++){
		for(int j=0;j<height;j++){
			if(ptr[i*height+j] > 125){
				ptr[i*height+j] = 255;  
			}else{
				ptr[i*height+j] = 0;    
			}
		}
	}
}
 
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <math.h>
#include <string.h>
#include <opencv/cv.h>
#include <stdio.h>
#include "lib/normal.h"
 
#define DEBUG
 
#ifdef DEBUG
#define DE(format, ...) printf(format, ## __VA_ARGS__)
#else
#define DE(format, ...) while(0)
#endif
 
int main(int argc,char *argv[]){
	int threshold = 0;
	Mat img = cv::imread(argv[1]);
	double x_beta;
	int width = img.rows;
	int height = img.cols;
	int** selection_1, selection_Number_1;
	int** address_1, address_Number_1;
	int i, j, color_num, box_flag;
	Mat img_3, img_4, img_5;
	Mat img_2;
	char str[2];
	Point s1, s2;
	Scalar color = Scalar( 0, 255, 255);
 
	namedWindow("img");
	imshow("img",img);
	pic_gray(img, img_2);
 
	threshold = histogram_Calculate(img_2, 5);
	DE("threshold:%d\n",threshold);
 
	pic_Thresholding(img_2, threshold);
 
	namedWindow("tmp");
	imshow("tmp", img_2);
 
	waitKey(0);
	return 0;
}


  首先pic_gray来讲源图像img,转化为灰阶图像img_2;接着用histogram_Calculate函数,以5%的比例算出二值化的阀值threshold,最后用pic_Thresholding,
二值化图像。
  效果演示如下:
                         


图像分割

  车牌是使用是蓝底白字,而这个二值化图像img_2中,蓝色的背景被去除了,留下了白色的字。而根据车牌的特点,每一行中蓝色和白色的交替应该至少是7次。
转化在这个img_2的二值图像中,则表示在车牌所在的行中,像素值的跳变至少是7次。所以根据这个特性,可以将图像中,可能是车牌号所在位置的图像分割出来。
使用的代买如下:
int** selection_Function_1(Mat& mat1, int* number){
    int **a, i, j, flag, num = 0, enter_flag = 0;
    int width = mat1.rows;
    int height = mat1.cols;
    uchar* ptr = mat1.ptr(0);
 
    a = (int**)malloc(width * sizeof(int*));
 
    for(i=0; i<width; i++){
        flag = 0;
        for(j=0; j< height-1; j++){
            if(ptr[i*height + j] != ptr[i*height + j +1]){
                flag += 1;  
            }
        }
        if((flag >= 7) && (enter_flag == 0)){
            a[num] = (int* )malloc(2 * sizeof(int));
            a[num][0] = i;
            enter_flag = 1;
        }else if((enter_flag != 0) && (flag < 7)){
            if(i - a[num][0] < 8){
                continue;   
            }
            a[num][1] = i - 1;
            num ++;
            enter_flag = 0;
        }
    }
    *number = num;
    return a;
}
void pic_cutting(Mat& mat1, Mat& pic_cutting, int** selection, int number){
    int real_height = mat1.cols;
    IplImage pI_1 = mat1;
    IplImage pI_2;
    IplImage pI_3;
    CvScalar s;
 
    pic_cutting = cv::Mat(selection[number][1] - selection[number][0], real_height, CV_8UC3, 1);
    pI_2 = pic_cutting;
 
    for(int i = selection[number][0]; i < selection[number][1]; i++){
        for(int j=0; j<real_height; j++){
            s = cvGet2D(&pI_1, i, j);
            cvSet2D(&pI_2, i-selection[number][0], j, s);
        }
    }
}
 
int main(int argc,char *argv[]){
    ................
    selection_1 = selection_Function_1(img_2, &selection_Number_1);
    for(i=0; i< selection_Number_1; i++){
        DE("selection_1[%d]:%d, %d\n", i, selection_1[i][0], selection_1[i][1]);    
    }
 
    for(i=0; i<selection_Number_1; i++){
        pic_cutting(img, img_3, selection_1, i);
        sprintf(str, "%d", i);
        namedWindow(str);
        imshow(str, img_3);
    }
    waitKey(0);
    return 0;
}


  首先使用函数selection_Function_1,将二值图像img_2中连续出现了至少七次跳变行的图像行开始位置与结束位置保存到二维数组selection_1,img_2中一共有
多少次出现满足连续出现至少七次跳变行图像的统计保存在selection_Number_1中。
  接着函数pic_cutting用img源图像长度作为新图像长度,用selection_1中保存的行开始,结束位置作为新图像宽度,从源图像中将对应位置的图像复制到img_3
中。显示效果如下:
   
  从显示效果途中,我们看到原图像被分割为了6张图片,车牌在第五张图片中。


图片筛选

  从车牌号特性知道,车牌的背景为蓝色。这里就是检测分割出来的图片中,蓝色占的数量,将不满足要求的图片抛弃掉。
接着将筛选出来的图片,再一次灰阶、二值化。
代码如下:
int choice_Color(Mat& mat1, int color_Start, int color_End){
    int width = mat1.rows;
    int height = mat1.cols;
    uchar* ptr = mat1.ptr(0);
    IplImage pI_1;
    int flag[width];
    int num, i, j, num_width = 0;
    CvScalar s;
 
    pI_1 = mat1;
    cvCvtColor(&pI_1, &pI_1, CV_BGR2HSV);
    for(i=0; i<width; i++){
        num = 0;
        for(j=0; j<height; j++){
            s = cvGet2D(&pI_1, i, j);
            if((s.val[0] >= color_Start) && (s.val[0] <= color_End)){
                num += 1;   
            }
        }
        if(num > 20){
            flag[i] = 1;
            num_width += 1;
        }else{
            flag[i] = 0;
        }
        num = 0;
    }
    return num_width;
}
 
int main(int ragc, char** argv){
        ..........
       for(i=0; i<selection_Number_1; i++){
        pic_cutting(img, img_3, selection_1, i);
        color_num = choice_Color(img_3, 110, 120);   //蓝色为H:110--120
        DE("color_num:%d\n", color_num);
        if(color_num > 5){
            IplImage pI_1 = img_3;
            cvCvtColor(&pI_1, &pI_1, CV_HSV2BGR);
 
            pic_gray(img_3, img_3);
            threshold = histogram_Calculate(img_3, 3);
            pic_Thresholding(img_3, threshold);
 
            sprintf(str, "%d", i);
            namedWindow(str);
            imshow(str, img_3);
        }
    }
    waitKey(0);
    return 0;
}
  使用choice_Color将分割出来的每张图片img_3都依次转化为HSV,然后根据H来检测出图像中蓝色像素超过20的行数,并将该数据返回到color_num中。
接着判断如果color_num 大于了5行就表示该图像不能抛弃。
演示效果如下:
         
  因为该图像中,蓝色干扰背景相当多,所以该步骤,只筛选丢弃了最后一张图片。
没有更多推荐了,返回首页