车牌识别是计算机视频图像识别技术在车辆牌照识别中的一种应用,通常来讲如果结合opencv进行车牌识别主要分为四个大步骤,分别为:
- 图像采集
- 车牌定位
- 分割车牌字符
- 字符识别
当然,如果结合了机器学习可能步骤会变得更为精简,但是从opencv基础方法开始也不失为一种学习进步,此案例仅仅从蓝牌车牌入手,作为学习交流用,暂不打算花时间研究绿牌、黄牌车等车牌识别。
图像采集我们直接掠过,现在假设我们已经完成了图像采集,得到了包含车牌的图片。我们直接从车牌定位开始。
*** 文中的车辆、车牌均来自网络,与现实中任何事务无关。***
现在我们目的是从一张图片中识别出车牌位置,并将车牌位置的矩形单独取出来形成一张图片。为了达到这样的目的我们需要完成以下几步:
- 尺寸调整:将较大尺寸图片处理成较小尺寸图片,避免图像处理过慢
- 色域分析:取出蓝色色彩范围
- 色域叠加:将色域分析出的蓝色范围在图片中进行叠加,更加方便识别
- 调整为灰度图像:牢记我们最终目的是进行车牌识别,调整为灰度图像更方便后续使用
- 高斯过滤:主要过滤噪点等干扰
- 形态学处理:通常使用一次侵蚀和膨胀作为基本操作来执行形态转换,以方便后面的图像边缘检测
- 二值化:将灰度图像的某个值以下的全部设置为0,该值以上的全部设置为255,更加方便后续的图片边缘检测
- 边缘检测:检测图片中的边缘
- 特征找牌:根据车牌宽高比的特征取出车牌
当然,为什么要进行以上几步才找出车牌这个仁者见仁智者见智,就类似如果要吃饭首先要淘米,然后放入锅中煮一样,总而言之有更好更简洁高效的方式也是可以的。
刚才说到,尺寸调整的目的是避免因图片尺寸过大而引起计算机处理速度过慢,在这里需要重点注意的是在调整尺寸的时候需要注意保持好图片的宽高比,假设我们需要做的是将图片的宽度变为500px,并同时假设我们手中的图片是19201080px,那么我们最终处理好的图片应该是889500,即500/1080*1920
以下代码来自网络,在python opencv中保持宽高比地处理图片:
点击查看代码
def resize_keep_aspectratio(image_src, dst_size):
src_h, src_w = image_src.shape[:2]
# print(src_h, src_w)
dst_h, dst_w = dst_size
# 判断应该按哪个边做等比缩放
h = dst_w * (float(src_h) / src_w) # 按照w做等比缩放
w = dst_h * (float(src_w) / src_h) # 按照h做等比缩放
h = int(h)
w = int(w)
if h <= dst_h:
image_dst = cv2.resize(image_src, (dst_w, int(h)))
else:
image_dst = cv2.resize(image_src, (int(w), dst_h))
h_, w_ = image_dst.shape[:2]
return image_dst
我们使用img = self.resize_keep_aspectratio(image, [500, 500])即可进行调用,其中虽然传递为[500,500],实际上改方法会根据宽高比自动调整。
色域分析&叠加色域分析的关键函数是调用cv2.inRange方法,围绕该方法我们写出以下代码:
点击查看代码
# hsv提取蓝色部分
def hsv_color_find(img):
img_copy = img.copy()
"""
提取图中的蓝色部分 hsv范围可以自行优化
"""
hsv = cv2.cvtColor(img_copy, cv2.COLOR_BGR2HSV)
low_hsv = np.array([100, 80, 80])
high_hsv = np.array([124, 255, 255])
# 设置HSV的阈值
mask = cv2.inRange(hsv, lowerb=low_hsv, upperb=high_hsv)
cv2.imshow("hsv_color_find", mask)
# 将掩膜与图像层逐像素相加
res = cv2.bitwise_and(img_copy, img_copy, mask=mask)
cv2.imshow("hsv_color_find2", res)
return res
其中low_hsv以及high_hsv是根据经验设定的在hsv色域取出蓝色的值,最终效果如下:
原图:
进行色域分析后的图:
进行色域分析后进行叠加的图:
点击查看代码
# RGB->灰度
gray_img = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
点击查看代码
gray_img_ = cv2.GaussianBlur(gray_img, (5, 5), 0, 0, cv2.BORDER_DEFAULT)
其中(5, 5)也是经验值,可以自行调整
经过灰度处理和高斯过滤后结果如下:
点击查看代码
kernel = np.ones((23, 23), np.uint8)
# 使用侵蚀和膨胀作为基本操作来执行高级形态转换。任何操作都可以就地完成.在多通道图像的情况下,每个通道都是独立处理的.
img_opening = cv2.morphologyEx(gray_img, cv2.MORPH_OPEN, kernel)
# 计算两个数组的加权和
img_opening = cv2.addWeighted(gray_img, 1, img_opening, -1, 0)
进行形态学处理的主要方法是cv2.morphologyEx,里面包括了两个重要的参数,对于这两个重要的参数可以通过搜索引擎得知其用途。
处理过后效果如下:
ret2, img_thresh2 = cv2.threshold(img_opening, 0, 255, cv2.THRESH_BINARY)
效果如下:
点击查看代码
# # 使用开运算和闭运算让图像边缘成为一个整体
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
img_edge1 = cv2.morphologyEx(img_edge, cv2.MORPH_CLOSE, kernel)
img_edge2 = cv2.morphologyEx(img_edge1, cv2.MORPH_OPEN, kernel)
img_edge3 = cv2.morphologyEx(img_thresh2, cv2.MORPH_CLOSE, kernel)
img_edge4 = cv2.morphologyEx(img_edge3, cv2.MORPH_CLOSE, kernel)
contours, hierarchy = cv2.findContours(img_edge4, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
真正检测边缘的代码实际上就是
cv2.findContours
但是在真正进行检测的时候还做了几次开运算和闭运算,这样可以使碎片化的二值化图像形成一个整体,以便后续进行整体识别。
识别过后针对边缘画出的边缘图像如下:
从上一步得知,我们进行边缘检测得到的边缘多达9个,我们需要从这些边缘中找出车牌号的那块,OpenCV自带的cv2.contourArea()函数可以实现计算点集(轮廓)所围区域的面积,cv2.minAreaRect()函数可以计算出点集的最小外包旋转矩形,cv2.boxPoints()函数可以根据旋转矩形的中心的坐标、尺寸和旋转角度,计算出旋转矩形的四个顶点。
我们可以通过测量知道中国的蓝牌和黑牌是440×140,黄牌前牌尺寸同,后牌为440×220;摩托车及轻便摩托车前牌是220×95,后牌是220×140。我们可以通如下代码筛选:
点击查看代码
## 先筛选面积较大的地方
for contour in contours:
if cv2.contourArea(contour) > 2000:
temp_contours.append(contour)
for temp_contour in temp_contours:
rect_tupple = cv2.minAreaRect(temp_contour)
rect_width, rect_height = rect_tupple[1]
if rect_width < rect_height:
rect_width, rect_height = rect_height, rect_width
aspect_ratio = rect_width / rect_height
if aspect_ratio > 1.5 and aspect_ratio < 4.65: #此处可根据具体的情况针对比例进行具体的调整
car_plate1.append(temp_contour)
rect_vertices = cv2.boxPoints(rect_tupple)
#最后进行矩阵和投影变换,将斜着的矩形摆正
rect = cv2.minAreaRect(car_plate)
# 计算最小区域的坐标
box = cv2.boxPoints(rect)
wh = np.int0(rect[1])
# 变换矩阵
if 0<rect[2]<=45:
mat = cv2.getPerspectiveTransform(np.float32([[box[0][0], box[0][1]], [box[1][0], box[1][1]], [box[2][0], box[2][1]], [box[3][0], box[3][1]]]),np.float32([[0, wh[1]], [0, 0], [wh[0], 0], [wh[0], wh[1]]]))
# 投影变换
lic = cv2.warpPerspective(img, mat, (wh[0], wh[1]))
if 45 < rect[2] <= 90:
mat = cv2.getPerspectiveTransform(np.float32(
[[box[0][0], box[0][1]], [box[1][0], box[1][1]], [box[2][0], box[2][1]],
[box[3][0], box[3][1]]]), np.float32([[0, 0], [wh[1], 0], [wh[1], wh[0]], [0, wh[0]]]))
# 投影变换
lic = cv2.warpPerspective(img, mat, (wh[1], wh[0]))
cv2.imshow("card_img", lic)
得到的结果如下:
至此,我们完成了车牌的提取,得到这张图片后我们可以即可以进行后续的操作!