注:这是依然一个简单的车牌识别demo
1.前言
在上一篇Python+Opencv简易车牌识别(一):基于HSV颜色空间的图像分割中,我们讲了如何仅基于颜色来进行简单粗暴的车牌分割。今天我们考虑对图像进行一些更复杂的预处理,来使我们的程序能够识别更复杂情境下的车牌。
原图依旧如下:
完整代码于文章末尾给出。
2.分析
2.1.读取图片
程序开始,读取图片。在这里我们将图片进行等比例放缩,宽度固定为400像素,以防止图片分辨率过高影响处理的性能。然后将图片转化为灰度图以供进一步的处理。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
#读取图片
img = cv2.imread(r'D:\car1.png')
m = 400 * img.shape[0] / img.shape[1]
#调整图像尺寸
img = cv2.resize(img, (400, int(m)), interpolation = cv2.INTER_AREA)
#转化为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
这一阶段得到的灰度图gray_img长这样:
2.2.图像分割
2.2.1.顶帽
在这一阶段,我们采用开运算与闭运算这两种形态学运算的方法来对图像进行分割。
开运算:
- 是一个基于几何运算的滤波器
- 本质是先腐蚀运算,再膨胀运算
- 能够除去孤立的小点,毛刺,并保持总体的位置和形状不发生改变
闭运算:
- 是一个基于几何运算的滤波器
- 本质是先膨胀运算,再腐蚀运算
- 能够填平小孔,弥合小裂缝,并保持总体的位置和形状不发生改变
现在,我们先计算一个顶帽。所谓顶帽:
- 值为原图像 - 开运算
- 开运算能放大裂缝或者局部亮度较低区域
- 因此从原图中减去开运算后的图,能够突出比轮廓周围更明亮的区域
- 可以用来分离比邻近点亮一些的亮斑。在一幅图像具有大幅的背景,而微小物品比较有规律的情况下,可以使用顶帽进行背景提取
#结构元素(kernel)为半径为16的圆
r = 16
h = w = r * 2 + 1
kernel = np.zeros((h, w), np.uint8)
cv2.circle(kernel, (r, r), r, 1, -1)
#开运算
open_img = cv2.morphologyEx(gray_img, cv2.MORPH_OPEN, kernel)
#顶帽
hat_img = cv2.absdiff(gray_img, open_img)
这一阶段得到的顶帽hat_img长这样:
可以看到背景变暗,而车牌、车标等较亮区域得到了增强。
2.2.2.边缘检测
我们上一步增强了图像中几个较亮的区域,其中便包括了我们感兴趣的车牌。现在我们用Canny算子进行边缘检测。
注意在边缘检测之前,要先对图像进行二值化处理。
#二值化函数
def binaryzation(img):
maxi = float(img.max())
mini = float(img.min())
x = maxi - ((maxi - mini) / 2)
ret, thresh = cv2.threshold(img, x, 255, cv2.THRESH_BINARY)
return thresh
#图像二值化
binary_img = binaryzation(hat_img)
#canny边缘检测
canny = cv2.Canny(binary_img, binary_img.shape[0], binary_img.shape[1])
这一阶段得到的边缘检测结果canny长这样:
2.2.3.填充边缘
边缘检测后能够得到线条形式的图像边缘。现在我们利用闭运算来填平小孔,对边缘内部进行填充:
#闭运算核
kernel = np.ones((5, 19), np.uint8)
#闭运算
close_img = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, kernel)
这一阶段得到的闭运算图像close_img长这样:
2.2.4.噪声消除
可以看到经过上一步的边缘填充后,我们车牌的主体区域已经被标记出来了,但除此之外图像中还存在着许多小的噪点,因此我们进行连续两次开运算尝试把它们消去:
#开运算
open_img = cv2.morphologyEx(close_img, cv2.MORPH_OPEN, kernel)
#更换结构元素
kernel = np.ones((11, 5), np.uint8)
#开运算
open_img = cv2.morphologyEx(open_img, cv2.MORPH_OPEN, kernel)
这一阶段得到的开运算图像open_img长这样:
可见噪声已被大大消除,图像被分割成了8个区域。接下来我们要做的是从这8个区块中找到我们想要的车牌。
2.3.车牌定位
2.3.1.位置提取
在上一节中我们从可视化结果的角度,明确了图片被分割成了8个区域,那么如何获得这8个区域的位置特征(如坐标)呢?
先给出代码:
#找到能够包围给定区块的最小矩形的左下角坐标(min(x),min(y))与右上角坐标(max(x),max(y))
def find_rectangle(contour):
x = []
y = []
for p in contour:
y.append(p[0][0])
x.append(p[0][1])
return [min(y), min(x), max(y), max(x)]
#提取轮廓
contours, hierarchy = cv2.findContours(open_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#存储每个块的矩形坐标
block = []
for c in contours:
r = find_rectangle(c)
block.append(r)
首先是这个findContours函数,其功能为提取区域的边缘。而在cv2.RETR_EXTERNAL模式下,会返回边缘"顶点"坐标构成的数组,例如对于矩形会有四个边缘顶点,三角形会有三个边缘顶点。
然后,对于每个区域,我们尝试找到一个最小的矩形来将其覆盖,这就是find_rectangle方法的作用。
2.3.2.基于HSV颜色空间的筛选
上一篇我们采用的也是基于颜色空间筛选的思想来分割车牌。在这里,我们先将分割出来的图像区域b转化成hsv颜色空间,然后利用"掩膜"来筛选出区域中位于上下界范围内的像素点。最后,对比8个区域颜色范围的满足情况,最符合蓝色颜色区间的区域便是车牌区域。
max_weight = 0
max_index = -1
#遍历分割出来的各个区域
for i in range(len(block)):
b = img[block[i][1] : block[i][3], block[i][0] : block[i][2]]
#转化为hsv颜色空间
hsv = cv2.cvtColor(b, cv2.COLOR_BGR2HSV)
#蓝色下界
lower = np.array([100, 50, 50])
#蓝色上界
upper = np.array([140, 255, 255])
#利用掩膜进行筛选
mask = cv2.inRange(hsv, lower, upper)
#计算当前区域的满足情况
w1 = 0
for m in mask:
print(m)
w1 += m / 255
w2 = 0
for n in w1:
w2 += n
if w2 > max_weight:
max_index = i
max_weight = w2
现在我们看看选出来的"最可能为车牌"的区域长啥样:
cv2.imshow('ovo', img[block[max_index][1] : block[max_index][3], block[max_index][0] : block[max_index][2]])
cv2.waitKey(0)
2.3.3.标注
最后给选出的区域上框就好了:
#最可能为车牌的区域对应的矩形坐标
rect = block[max_index]
#画框
cv2.rectangle(img, (rect[0], rect[1]), (rect[2], rect[3]),(0, 255, 0), 2)
cv2.imshow('ovo', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.完整代码
# -*- coding: utf-8 -*-
import cv2
import numpy as np
#二值化
def binaryzation(img):
maxi = float(img.max())
mini = float(img.min())
x = maxi - ((maxi - mini) / 2)
ret, thresh = cv2.threshold(img, x, 255, cv2.THRESH_BINARY)
return thresh
#找到能够包围给定区块的最小矩形的左下角坐标(min(x),min(y))与右上角坐标(max(x),max(y))
def find_rectangle(contour):
x = []
y = []
for p in contour:
y.append(p[0][0])
x.append(p[0][1])
return [min(y), min(x), max(y), max(x)]
#读取图片
img = cv2.imread(r'D:\car1.png')
m = 400 * img.shape[0] / img.shape[1]
#调整图像尺寸
img = cv2.resize(img, (400, int(m)), interpolation = cv2.INTER_AREA)
#转化为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#结构元素(kernel)为半径为16的圆
r = 16
h = w = r * 2 + 1
kernel = np.zeros((h, w), np.uint8)
cv2.circle(kernel, (r, r), r, 1, -1)
#开运算
open_img = cv2.morphologyEx(gray_img, cv2.MORPH_OPEN, kernel)
#顶帽
hat_img = cv2.absdiff(gray_img, open_img)
#图像二值化
binary_img = binaryzation(hat_img)
#canny边缘检测
canny = cv2.Canny(binary_img, binary_img.shape[0], binary_img.shape[1])
#闭运算核
kernel = np.ones((5, 19), np.uint8)
#闭运算
close_img = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, kernel)
#开运算
open_img = cv2.morphologyEx(close_img, cv2.MORPH_OPEN, kernel)
#更换结构元素
kernel = np.ones((11, 5), np.uint8)
#开运算
open_img = cv2.morphologyEx(open_img, cv2.MORPH_OPEN, kernel)
#提取轮廓
contours, hierarchy = cv2.findContours(open_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#存储每个块的矩形坐标
block = []
for c in contours:
r = find_rectangle(c)
block.append(r)
max_weight = 0
max_index = -1
#遍历分割出来的各个区域
for i in range(len(block)):
b = img[block[i][1] : block[i][3], block[i][0] : block[i][2]]
#转化为hsv颜色空间
hsv = cv2.cvtColor(b, cv2.COLOR_BGR2HSV)
#蓝色下界
lower = np.array([100, 50, 50])
#蓝色上界
upper = np.array([140, 255, 255])
#利用掩膜进行筛选
mask = cv2.inRange(hsv, lower, upper)
#计算当前区域的满足情况
w1 = 0
for m in mask:
w1 += m / 255
w2 = 0
for n in w1:
w2 += n
if w2 > max_weight:
max_index = i
max_weight = w2
#最可能为车牌的区域对应的矩形坐标
rect = block[max_index]
#画框
cv2.rectangle(img, (rect[0], rect[1]), (rect[2], rect[3]),(0, 255, 0), 2)
cv2.imshow('ovo', img)
cv2.waitKey(0)
cv2.destroyAllWindows()