一.前言
-
需要源码请留言,环境配置请留言,项目运行时报错请留言,运行环境python3.7、pycharm。
-
仅供交流学习,如做商业用途,依法追究责任。
-
结课设计…顺利过。项目主要是利用OpenCV的SVM支持向量机,来进行车牌字符的训练,并进行字符的分割、字符识别等。使用Python内置模块Tkinter来实现系统的图形界面。将识别到的车牌与数据库中的黑名单车牌比对,实现正常车牌的快速放行以及黑名单车牌的及时报警。
-
图形界面预览:
-
车牌识别的方法有很多种,如支持向量机、机器学习Tensorflow等。其原理类似,都是对训练集进行训练,使用测试集进行测试从而实现车牌识别功能。
-
一. 前言
-
二. 设计思路和设计构图
-
1 设计思路
-
2 设计构图
-
3 监测报警功能的实现
-
-
三. 车牌识别算法的设计与实现
-
1 图像预处理
-
2 边缘检测和轮廓检测
-
3 车牌定位
-
4 车牌图像矩形矫正
-
5 训练样本集
-
6 车牌区域的预处理
-
7 字符分割
-
8 字符识别
-
9 算法流程图
-
-
四. 监测报警功能的实现
-
1 监测功能
-
2 报警功能
-
-
五. 程序主界面效果图
二.设计思路和设计构图
1. 设计思路
- 第一步对拍摄到的车牌进行预处理操作,使用高斯滤波GaussianBlur()方法来缓解由照相机或其他因素,如天气、磨损等外部因素对车牌图像造成的影响,平滑操作能够防止产生过多的垂直边缘。
- 第二步进行车牌的定位,将图像转换成HSV格式,利用颜色掩码来筛选出蓝色区域、黄色区域、绿色区域。再使用minAreaRect()方法计算出由findContours()方法所提取到的图像中存在的所有的外部轮廓的最小外接矩形,使用车牌固定的长宽比阈值,作最终的验证,筛选出车牌所在的区域,并将车牌所在的区域信息存储下来,从而实现车牌的定位。
- 第三步使用OpenCV自带的支持向量机SVM来完成对数字、字符以及省份缩写的训练。
- 第四步将第二步定位到的车牌实例分割,并对分割得到的车牌实例进行预处理操作和举行矫正操作。
- 第五步对第四步得到的车牌图像依照次序进行字符的分割。
- 第六步使用训练好的SVM对按序分割得到的字符进行识别,车牌识别功能到此成功实现。
2. 设计构图
3. 监测报警功能的实现
- 对于监测报警功能的实现,需要使用MySQL模拟交管部门的数据库。Python程序可以连接数据库,并通过接口执行数据库的写入和查询操作。在实时检测过程中,会将识别到的每一条车牌信息写入数据表allvehicle中,此为监测功能。若出现与数据表blacklist中相匹配的车牌,程序报警,提示执法人员,此为报警功能。
- 一.回到目录
三. 车牌识别算法的设计实现
1. 图像预处理
- 在系统的图像识别中,图像噪音的处理方式直接影响了识别算法的准确性。因此在对图像进行处理之前,必须先进行图像的预处理操作。图像平滑目的是消除拍摄到的图像中存在的各种噪音,如salt-and-pepper。系统使用的是高斯平滑技术,用高斯核来代替等过滤系数组成的框形过滤器。具体实现为调用GaussianBlur()方法,传入核的宽度和高度(blur, blur),本文blur选定为5,实际操作时需要自行调校blur的值,来实现较优的平滑效果。对拍摄到的原始图像进行处理时,只需要对其进行高斯平滑操作,平滑图像中的噪音。下方为设定不同blur值的滤波效果:
(1)读取原图:
(2)blur设置为5时的滤波效果:
(3)blur=3和blur=7时的滤波效果:
- 一.回到目录
2. 边缘检测与轮廓检测
- 图像边缘可以描述为图像中一个部分的结束和下一个部分的开始,轮廓可能是边缘的一部分。如图所示,车牌的矩形边界就是一个完整的轮廓,而车牌的一条边界是图像的边缘。轮廓检测建立在边缘检测的基础之上。
- 边缘检测流程为:
(1)使用高斯滤波的方法,平滑拍摄的到图像中的噪音,滤除由于环境等因素产生的噪声。
(2)计算梯度,用于边缘检测和轮廓检测。
(3)应用非极大值抑制,将杂散响应消除。
(4)双阈值去除所有不符合要求的边缘。
(5)抑制孤立的弱边缘,留下车牌区域的垂直边缘。 - 实现非极大值抑制的较为准确的一种方法是:线性插值法。
- NE和E为真实的相邻像素,P1和P2为非真实存在的亚像素,只是为了与P点的像素的值作比较。计算P1和P2的梯度值,计算出P点的梯度值。只有P点的像素梯度比P1和P2的像素梯度都大,P点像素对应的像素值才是真正的极大值。非极大值抑制需选择合适的阈值,如果选择的阈值不合适,那么过多的边缘将被抑制掉,从而造成车牌定位的失败。但如果对变换后的图像赋予一个固定的阈值,那么一些微弱的边缘会由于噪声干扰等一并被滤除,同样会影响后续车牌的定位。综合以上考量,系统采用双阈值代替单阈值的方式,既能够很好的消除噪声,同时也能使得图像边缘连续。检测到的所有的边缘中含有大量的垂直边缘,这些垂直边缘是由车牌中的字符产生的,去除已检测到的边缘中的弱边缘,完成边缘检测。
- 轮廓检测是提取出图像中闭合的边缘。OpenCV模块提供了findContours()方法,用于检测出图像中的所有轮廓。由于车牌的长宽比、面积是固定的,故系统使用长宽比和面积对得到的这些区域进行区分:
(1)首先使用findContours()方法找到图像中存在的所有外部轮廓,包括车牌区域的外部轮廓。
(2)使用minAreaRect()方法计算出所得到的外部轮廓的最小外接矩形,存储下来。 - 主要代码为:
contours,hierarchy=cv2.findContours(img_contours,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
- 一.回到目录
3. 车牌定位
- 车牌定位即寻找ROI区域,即蓝色车牌区域。车牌定位的方法有:
(1)基于边缘检测和轮廓检测的通过车牌形状定位的方法。
(2)基于HSV的通过车牌颜色固定定位的方法。
(3)基于数学形态法的对拍摄到的图像进行形态学操作的定位方法。
(4)基于车牌区域水平方向的纹理的定位方法。 - 图像预处理之后,根据我国车牌的颜色只有蓝色、绿色、黄色三种颜色进行精确定位:将拍摄到的RGB格式的车牌图像转换成HSV格式车牌图像,设置H、S、V各自的阈值,生成对应蓝黄绿颜色的掩码。最后将HSV格式的原图与蓝黄绿颜色的掩码相与,将得到的图像转换成灰度图,进行开操作,再根据车牌的长度与宽度的比值是固定的,使用面积和长宽比阈值完成最终定位。具体操作为:平滑噪音,根据车牌颜色信息精确定位,使用inRange()方法生成对应颜色、对应阈值的掩码图像。对于inRange()方法中阈值的确定,以蓝色车牌为例,色度的范围为0-179,由于我国的蓝色车牌的蓝色的色度值较大,以车牌冀TGK857为例,从上方的图片中截取一部分蓝色区域,选取的区域如图中的白色虚线框所示:
- 接着,在Jupyter中将截取的BGR格式的蓝色区域转换成HSV格式,并以数组形式输出读入的蓝色区域的像素信息,如图所示:
- 输出的三维数组中的最内层数组对应的是H、S、V的值,通过多次上述操作,发现蓝色车牌的色度H的值的范围大致在100-130之间,饱和度S的值的范围在110-255之间,亮度V的值的范围在110-255之间。故inRange()方法中参数设置为np.array([100, 110, 110])和np.array([130, 255, 255])的效果最好。黄色车牌、绿色车牌的识别过程中的inRange()方法中的参数也是这种方法获得的。使用inRange()方法获得车牌对应的蓝色掩码图、黄色掩码图、绿色掩码图,无论拍摄到的车牌图像是蓝色、黄色或绿色车牌,都调用bitwise_and()方法将原图与三色掩码图mask逐位相与,实现了车牌通过颜色定位的归一化,具体实现为:cv2.bitwise_and(hsv, hsv, mask=mask_blue + mask_yellow + mask_green)。接着使用轮廓查询方法找到图像中存在的所有的外部轮廓,使用生成最小外接矩形法计算出所得到的所有的外部轮廓的最小外接矩形,存储下来,使用面积和长宽比阈值,作最后的验证,筛选出车牌区域,并将车牌区域信息存储下来,从而实现车牌的定位功能。
- 下面对原图实现车牌定位,描述过程如下:
(1)将BGR转换为HSV,
(2)掩码参数设置过小时得到的蓝色掩码图像:cv2.inRange(img_hsv,np.array([20, 30, 30]), np.array([50, 175, 175])),如图所示。
(3)掩码参数设置过大时得到的蓝色掩码图像:cv2.inRange(img_hsv,np.array([111, 110, 110]), np.array([130, 255, 255])),如图所示。
(4)期望掩码参数下的蓝色掩码图像:cv2.inRange(img_hsv,np.array([100, 110, 110]), np.array([130, 255, 255])),如图所示。
- 如上图所示,无论掩码参数设置过大或过小,都会对蓝色掩码图像带来影响,在bitwise_and过程中,可能会使车牌字符膨胀变形,容易产生错识误识。
(5)黄色掩码图像:
(6)绿色掩码图像:
(7)bitwise_and操作:
(8)掩码参数过大对字符的膨胀效果:
(9)将图4-15转换成灰度图像:
(10)通过n×n的二维数组对上图所示的灰度图像做闭运算和开运算操作,若n取值过大,则会使车牌区域膨胀的太大,需要再次进行车牌的定位。若n取值过小,则字符之间的缝隙仍然存在,车牌区域不能一体化。经过反复测试,n取值为20时,对车牌区域的整体化效果最好,如图所示:
(11)检测轮廓,使用findContours()方法得到所有的轮廓信息,存储在一个四维数组中,将四维数组中存放的轮廓信息使用drawContours()方法标识出来,如一图所示。再对所有标识出来的轮廓进行面积和长宽比的筛选,调用minAreaRect()方法得到最终的车牌位置信息,车牌位置以(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)的格式返回,并保存在列表变量car_contours中,如二图所示为返回的冀TGK857的车牌位置信息。
(12)通过上一步得到的车牌区域位置信息,进行车牌的矫正,并按照矫正后的车牌位置信息调用img_color()方法,得到车牌的颜色字段和车牌位置信息,如图所示。
- 主要代码为:
width, height = ant[1]
ration=width/height
if 2 < ration < 5.5:
car_contours.append(ant)
box = cv2.boxPoints(ant)
- 一.回到目录
4. 车牌图像矩形矫正
- 车牌图像矩形矫正,简单说就是在2D平面内对定位到的车牌图像做变换。因为车牌的位置与摄像头之间的角度存在着变化,一般所拍摄的车牌图像都不是理想状态下的矩形。所以要对定位到的车牌做车牌矫正,调用矩形矫正方法img_Transform(),具体实现过程为:判断变量car_contours中存储的旋转角度的值,如果介于-1和1之间,将角度变量angle设为1。否则angle中存储的旋转角度就是car_contours中第三个字段的值,即-89.2500991821289。将定位到的车牌区域的h、w增加5个像素,避免车牌边缘被排除。调用boxPoints()方法得到车牌矩形的四个顶点的信息,判断矩形的角度,若为正角度,则向生成仿射变换矩阵方法getAffineTransform()中传入两个参数pts1和pts2,其中pts1为矩形变换前的三个顶点的坐标np.float32([左下角, 右下角, 右上角]),pts2是矩形变换到目标区域的的三个目标顶点的坐标np.float32([目标左下角, 目标右下角, 目标右上角]),对于冀TGK857的实例来说,pts1的值为:[[262.8244 471.00055] [731.07184 477.12946] [733.02985 327.5395 ]],pts2的值为:[[262.8244 477.12946] [731.07184 477.12946] [733.02985 327.5395 ]]。getAffineTransform()方法会生成一个仿射变换矩阵M,这个仿射变换矩阵的选取是OpenCV模块根据传入的pts1和pts2的参数信息自动求解的。接着调用仿射变换方法warpAffine(),传入要变换的图像oldimg,仿射变换矩阵M,变换后的图像大小(pic_width, pic_hight)三个参数,实现对车牌图像的矩形矫正。
- 主要代码为:
car_rect = (car_rect[0], (car_rect[1][0] + 5, car_rect[1][1] + 5), angle)
- 一.回到目录
5. 训练样本集
- 系统使用OpenCV-Python的4.2.0.32版本自带的SVM,在识别中文、英文及数字上,使用SVM。在数据的转换问题上,使用支持向量机的核函数进行处理,借助核函数处理后的转换结果,在即将输出的情况之中找出一个最可能的情况进行输出。简单来说,SVM实现了将一种输入数据转换成另一种需要的数据的工作。算法定义一个SVM类,继承父类StatModel,在SVM类中重写SVM的初始化函数:
def __init__(self, C=1, gamma=0.5):
self.model = cv2.ml.SVM_create()
self.model.setGamma(gamma)
self.model.setC(C)
self.model.setKernel(cv2.ml.SVM_RBF)
self.model.setType(cv2.ml.SVM_C_SVC)
- 在创建SVM的实例对象时,使用默认参数C=1, gamma=0.5来创建对象model。其中C是惩罚系数,C的值设置过大或过小,向量机的泛化能力会变差。gamma值决定了支持向量的个数和训练预测的速度。当把C的值设置较大值如10的时候,分类器就会尽可能地在训练数据上少犯错误,而实际上这是对系统资源与性能的一种浪费。把C的值设置为0.1的时候,分类器就会疏忽训练数据的准确性,从而导致训练的模型不准确。gamma的值设置为0.1时,SVM模型泛化能力退化,接近线性模型的泛化能力。gamma的值设置为5时,模型基本上能够拟合任何非线性数据。
- 要训练的汉字“冀”的数据集如图所示。
- 当C=1, gamma=0.5的时候,训练的模型如图所示。
- 当C=10, gamma=5的时候,训练的模型如图所示。
- 当C=0.1, gamma=0.1的时候,训练的模型如下图所示。
- 分类问题的大致流程为:
(1)输入车牌识别训练样本,并制作车牌字符的类别标签。
(2)设置车牌识别训练集的练习参数。
(3)对SVM的车牌样本进行训练。
(4)对目标车牌图像依据训练好的数据进行猜测,并返回输出的结果。
(5)获取车牌识别训练样本的支撑向量。 - 训练样本的支撑向量如下图所示:
- 系统借助支撑向量完成字符的识别功能,测试发现,当C设置为1,gamma设置为0.5时,识别效果最好。
- 训练数据集主要代码:
self.model = SVM(C=1, gamma=0.5)
self.modelchinese = SVM(C=1, gamma=0.5)
- 一.回到目录
6. 车牌区域的预处理
- 灰度化就是把摄像头拍摄的RGB类型的图像上每一个像素点的R、G、B三个通道的相同位置像素点的像素值相等。灰度共有256个灰度级别,255是白色,0是黑色。使拍摄到的车牌图像的R、G、B三个通道的像素值相等,即灰度化。接着调用resize()方法将传入的图像转换成固定大小,再通过各种形态学操作对得到的灰度图像进行进一步的处理,如礼帽操作。先开运算消除图像中的毛刺,后闭运算使图像变为一个整体可分的轮廓。接着进行二值化,将灰度值转换成只有255和0的像素值。二值化过程中,需要选取一个最佳阈值。最佳阈值的选取方法有试错法、Otsu’s算法。本项目使用Otsu’s算法。OpenCV的cv2.threshold() 方法已经实现了这一过程。在寻找最佳阈值时,只需调用OpenCV模块的threshold()方法,此时需要传递参数cv2.THRESH_OTSU,这个参数的含义是使用Otsu’s算法来计算最佳阈值,并返回最佳阈值。冀TGK857实例对应的二值化最佳阈值为142,即当阈值取142时,类间方差最大。以下为预处理的效果图:
- 灰度化,如下图所示。
- 二值化的过程中,因为黄、绿车牌的字符相对底色来说较暗,对于黄色车牌的和绿色车牌需要bitwise_not操作才会达到像蓝色车牌的二值化的效果,如下图所示。
- 一.回到目录
7. 字符分割
- 要正确高效率的识别车牌字符,最重要的是对定位到的车牌区域上的中文、英文及数字的准确分割。对中文、英文及数字的分割的目的是把经过上述操作定位到的车牌区域中的每个字符依照次序的从大图像中分割出只包含单个字符的小图像,对分割下来的包含单个字符的图像依照分割的顺序进行字符识别。分割得到的车牌字符与分割得到的车牌底色之间的灰度差异明显,所以基于投影的对中文、英文及数字的分割方法比较常见。系统使用的对中文、英文及数字的分割算法是基于投影的字符分割法。在二值化过程中计算每一列上目标像素的数目,得到表现为一系列波峰和波谷的车牌图像的垂直投影曲线。波谷对应着对中文、英文及数字的分割的位置,而波峰的位置对应着相应的字符。分割算法首先构建直方图,调用波峰查找方法find_wave()找出波峰,对波峰过滤,去除车牌上的分隔点。对于find_wave()中阈值和直方图的选取,直方图由np.sum(gray_img, axis=0)得到,其中gray_img是去除白边的蓝色车牌区域,阈值由y_min = np.min(y_histogram)、y_average = np.sum(y_histogram) / y_histogram.shape[0]、y_threshold = (y_min + y_average) / 5三条语句获得,除5操作是由于类似U、0这类字符的分割操作要求阈值偏小,否则会造成分割不完整。接着调用分割字符的seperate_card()方法,分割图片,从而得到逐个字符图片。然后去除车牌上的铆钉的干扰,使用常量扩充边缘的方法copyMakeBorder(),将分割后的字符使用常量扩充边界,如下图为不扩充边界的字符区域。
- 若不使用常量扩充边界的方法,会产生错识或者多识。接着使用重写后的resize()方法将识别后的图像调整为固定格式,以下通过实例实现车牌字符分割,具体过程如下:
(1)精确车牌区域,如下图所示。
(2)去除白边,如下图所示。
(3)分割汉字冀,如下图所示。
(4)调整大小,如下图所示。
(5)分割字符T,等等,此处不再列举。如下图所示。
- 主要代码:
wave_peaks = img_math.find_waves(x_threshold, x_histogram)
part_cards = img_math.seperate_card(gray_img, wave_peaks)
- 一.回到目录
8. 字符识别
- 调用modelchinese.predict()方法,接着对分割得到的第一个字符即省份的缩写进行识别,调用model.predict()方法,对分割得到的英文或数字字符进行识别,并判断最后一个字符是否为车牌边缘。直到完成对最后一个字符的识别。
- 定位及识别结果,如下图所示。
- 字符识别主要代码:
res = self.modelchinese.predict(part_card)
res = self.model.predict(part_card)
r = self.model.predict(samples)
return r[1].ravel()
9. 算法流程图
- 算法流程图如下图所示。
- 一.回到目录
四. 监测报警功能的实现
1. 监测功能
- 由程序进行车牌的识别,将识别到的车牌写入数据表allvehicle中。
- 主要代码为:
sql = “INSERT INTO ALLVEHICLE(TIME, \COLOR, TEXT, SOURCE) VALUES (’%s’, ‘%s’, ‘%s’, ‘%s’)” % (TIME, COLOR, TEXT, SOURCE)
2. 报警功能
- 将程序识别到的车牌信息记录下来,与数据表blacklist中存储的黑名单信息进行匹配,返回True,则报警。返回False,则放行。
- 主要代码为:
sql0 = "SELECt * FROM BLACKLIST WHERe TEXT1 like (’%s’) or TEXT2 like (’%s’) " % (TEXT1, TEXT2) - 报警功能的实现如下图所示:
- 一.回到目录
五. 程序主界面效果图
- 一.回到目录
声明:所用车牌照片为网络照片,若侵犯您的权益,请联系留言删除。