一、前言及思路简析
目前车牌识别系统在各小区门口随处可见,识别效果貌似都还可以。查阅资料后,发现整个过程又可以细化为车牌定位、畸变校正、车牌分割和内容识别四部分。本篇随笔主要介绍车牌定位及畸变校正两部分,在python环境下通过opencv实现。
1.1 车牌定位
目前主流的车牌定位方法从大的方面来说可以分为两类:一种是基于车牌的背景颜色特征;另一种基于车牌的轮廓形状特征。基于颜色特征的又可分为两类:一种在RGB空间识别,另一种在HSV空间识别。经测试后发现,单独使用任何一种方法,效果均不太理想。目前比较普遍的做法是几种定位方法同时使用,或用一种识别,另一种验证。本文主要通过颜色特征对车牌进行定位,以HSV空间的H分量为主,以RGB空间的R分量和B分量为辅,后续再用车牌的长宽比例排除干扰。
1.2 畸变校正
在车牌的图像采集过程中,相机镜头通常都不是垂直于车牌的,所以待识别图像中车牌或多或少都会有一定程度的畸变,这给后续的车牌内容识别带来了一定的困难。因此需要对车牌进行畸变校正,消除畸变带来的不利影响。
二、代码实现
2.1 车牌定位
2.1.1 通过颜色特征选定可疑区域
取了不同光照环境下车牌的图像,截取其背景颜色,利用opencv进行通道分离和颜色空间转换,经试验后,总结出车牌背景色的以下特征:
(1)在HSV空间下,H分量的值通常都在115附近徘徊,S分量和V分量因光照不同而差异较大(opencv中H分量的取值范围是0到179,而不是图像学中的0到360;S分量和V分量的取值范围是到255);
(2)在RGB空间下,R分量通常较小,一般在30以下,B分量通常较大,一般在80以上,G分量波动较大;
(3)在HSV空间下对图像进行补光和加饱和度处理,即将图像的S分量和V分量均置为255,再进行色彩空间转换,由HSV空间转换为RGB空间,发现R分量全部变为0,B分量全部变为255(此操作会引入较大的干扰,后续没有使用)。
根据以上特征可初步筛选出可疑的车牌区域。随后对灰度图进行操作,将可疑位置的像素值置为255,其他位置的像素值置为0,即根据特征对图像进行了二值化。二值化图像中,可疑区域用白色表示,其他区域均为黑色。随后可通过膨胀腐蚀等操作对图像进一步处理。
for i in range(img_h): for j in range(img_w): # 普通蓝色车牌,同时排除透明反光物质的干扰 if ((img_HSV[:, :, 0][i, j]-115)**2 < 15**2) and (img_B[i, j] > 70) and (img_R[i, j] < 40): img_gray[i, j] = 255 else: img_gray[i, j] = 0
2.1.2 寻找车牌外围轮廓
选定可疑区域并将图像二值化后,一般情况下,图像中就只有车牌位置的像素颜色为白,但在一些特殊情况下还会存在一些噪声。如上图所示,由于图像右上角存在蓝色支架,与车牌颜色特征相符,因此也被当做车牌识别了出来,由此引入了噪声。
经过观察可以发现,车牌区域与噪声之间存在较大的差异,且车牌区域特征比较明显:
(1)根据我国常规车牌的形状可知,车牌的形状为扁平矩形,长宽比约为3:1;
(2)车牌区域面积远大于噪声区域,一般为图像中最大的白色区域。
可以通过cv2.findContours()函数寻找二值化后图像中白色区域的轮廓。
注意:在opencv2和opencv4中,cv2.findContours()的返回值有两个,而在opencv3中,返回值有3个。视opencv版本不同,代码的写法也会存在一定的差异。
# 检测所有外轮廓,只留矩形的四个顶点 # opencv4.0, opencv2.x contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # opencv3.x _, contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
在本例中,因为二值化图像中共有三块白色区域(车牌及两处噪声),因此返回值contours为长度为3的list。list内装有3个array,每个array内各存放着一块白色区域的轮廓信息。每个array的shape均为(n, 1, 2),即每个array存放着对应白色区域轮廓上n个点的坐标。
目前得到了3个array,即3组轮廓信息,但我们并不清楚其中哪个是车牌区域对应的那一组轮廓信息。此时可以根据车牌的上述特征筛选出车牌区域的轮廓。
#形状及大小筛选校验 det_x_max = 0 det_y_max = 0 num = 0 for i in range(len(contours)): x_min = np.min(contours[i][ :, :, 0]) x_max = np.max(contours[i][ :, :, 0]) y_min = np.min(contours[i][ :, :, 1]) y_max = np.max(contours[i][ :, :, 1]) det_x = x_max - x_min det_y = y_max - y_min if (det_x / det_y > 1.8) and (det_x > det_x_max ) and (det_y > det_y_max ): det_y_max = det_y det_x_max = det_x num = i # 获取最可疑区域轮廓点集 points = np.array(contours[num][:, 0])
最终得到的points的shape为(n, 2),即存放了n个点的坐标,这n个点均分布在车牌的边缘上。
2.1.3 车牌区域定位
获取车牌轮廓上的点集后,可用cv2.minAreaRect()获取点集的最小外接矩形。返回值rect内包含该矩形的中心点坐标、高度宽度及倾斜角度等信息,使用cv2.boxPoints()可获取该矩形的四个顶点坐标。
# 获取最小外接矩阵,中心点坐标,宽高,旋转角度 rect = cv2.minAreaRect(points) # 获取矩形四个顶点,浮点型 box = cv2.boxPoints(rect) # 取整 box = np.int0(box)
但我们并不清楚这四个坐标点各对应着矩形的哪一个顶点,因此无法充分地利用这些坐标信息。
可以从坐标值的大小特征入手,将四个坐标与矩形的四个顶点匹配起来:在opencv的坐标体系下,纵坐标最小的是top_point,纵坐标最大的是bottom_point, 横坐标最小的是left_point,横坐标最大的是right_point。
# 获取四个顶点坐标 left_point_x =