Python-OpenCV车牌识别简易版(2)

  接上文,现在车牌的部分已经很清晰了,我们把车牌的部分的轮廓提取出来。

# 轮廓检测
# 【9】
contours,hierarchy = cv2.findContours(image,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
image1= rawImage.copy()
cv2.drawContours(image1,contours,-1,(0,0,255),5)
plt_show0(image1)

  【9】轮廓检测 contours,hierarchy =cv2.findContours(image,mode,method)

  cv2.findContours()函数可以从二值图像中提取轮廓,并返回一个包含轮廓信息的列表和一个包含轮廓层次结构的numpy数组。

  mode: 轮廓检索模式,决定了返回的轮廓列表中包含哪些轮廓。有四种模式可选:

  cv2.RETR_EXTERNAL: 只返回最外层的轮廓,忽略内部的轮廓;cv2.RETR_LIST: 返回所有的轮廓,不建立任何层次关系;cv2.RETR_CCOMP: 返回所有的轮廓,并将它们组织成两层结构,顶层是外部轮廓,次层是内部轮廓;cv2.RETR_TREE: 返回所有的轮廓,并建立一个完整的轮廓层次结构,可以表示图像中任意嵌套的轮廓。

  method: 轮廓近似方法,决定了返回的轮廓点的数量和位置。有两种方法可选:

  cv2.CHAIN_APPROX_NONE: 返回轮廓上的所有点,不做任何近似。cv2.CHAIN_APPROX_SIMPLE: 只返回轮廓上的拐点,去除冗余的点,例如一条直线上的点。

  cv2.drawContours(image1,contours,-1,(0,0,255),5)

  contours必须是一个由点坐标组成的数组。

​ 这个系统兼顾了蓝色车牌和绿色车牌的识别,所以我们先设定关于颜色的掩膜。

#筛选出车牌的轮廓(结合了颜色和形状)
#要绘制轮廓,先把原图复制一份
image2= rawImage.copy()
#得到关于颜色的掩膜
# 原图转换为HSV色彩空间,凡是用颜色筛选都要这样做
hsv = cv2.cvtColor(image2, cv2.COLOR_BGR2HSV)

# 定义蓝色和绿色的颜色范围
lower_blue = (100, 43, 46)
upper_blue = (124, 255, 255)
lower_green = (35, 43, 46)
upper_green = (77, 255, 255)
# 根据颜色范围提取蓝色和绿色区域的掩膜
# 【10】
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_green = cv2.inRange(hsv, lower_green, upper_green)
plt_show(mask_blue)
plt_show(mask_green)
a4e399880481e616d162d3d427fa9e8 9f86a207fd00a161f6864d0e4a3d3dc

  【10】mask = cv2.inRange(image, lower, upper)

  inRange()用于提取图像中指定颜色范围内的像素。

  cv2.inRange函数的作用是根据指定的下限和上限阈值,将输入图像中的像素值限制在这个范围内。它会将满足条件的像素设置为255(白色),不满足条件的像素设置为0(黑色),从而形成一个二值图像。这种二值图像常用于图像分割、目标提取等任务中。

  上图还是有很多噪音和孔洞,我们利用开运算,让图片更加有效和紧凑。

# 对掩膜开运算去除噪点和孔洞,分离相连的物体
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) #(5,5是试出来的比较好的)
mask_blue = cv2.morphologyEx(mask_blue, cv2.MORPH_OPEN, kernel)
mask_green = cv2.morphologyEx(mask_green, cv2.MORPH_OPEN, kernel)
plt_show(mask_blue)
plt_show(mask_green)
d66431927d2591b8b1f9f9856ecb0d4 bbf5bb5b76a395a59826a9cf2f848dc

  得到两个掩膜后将其合并。

# 合并两种颜色的掩膜,因为是二值图,用或方法即可
mask = cv2.bitwise_or(mask_blue, mask_green)
# 在原图上应用掩膜,用与方法,得到颜色过滤后的图像
filtered = cv2.bitwise_and(image2, image2, mask=mask)
plt_show0(filtered)
f1c6b53640b3e8be62dd7c6f5440978

  现在车牌已经非常明显了,我们可以按照轮廓面积、矩形的长款比、矩形和轮廓的覆盖率、矩形和掩膜的覆盖率等来筛选出车牌所在的区域。

# 通过循环来筛选车牌位置
# 定义一个空列表,用来存储可能的车牌区域
plates = []

# 遍历所有的轮廓
for contour in contours:
# 计算轮廓面积
# 【11】
area = cv2.contourArea(contour)
# 计算轮廓的最小外接矩形
# 【12】
x, y, w, h = cv2.boundingRect(contour)
# 计算矩形的长宽比
ratio = w / h
# 如果长宽比太小或太大,忽略该轮廓【pass】
if ratio < 2 or ratio > 6:
image_pass = image2[y:y+h,x:x+w]
plt_show(image_pass)
print('长宽不通过')
continue
# 计算矩形和轮廓的覆盖率
rect_area = w * h
coverage = area / rect_area
# 如果覆盖率太小,忽略该轮廓【pass】
if coverage < 0.5:
image_pass = image2[y:y+h,x:x+w]
plt_show(image_pass)
print('矩形和轮廓的覆盖率不通过')
continue
# 计算掩膜在矩形区域内的覆盖率
# 【13】
mask_area = cv2.countNonZero(mask[y:y+h, x:x+w])#掩膜的面积
mask_coverage = mask_area / rect_area
# 如果掩膜覆盖率太小,忽略该轮廓
if mask_coverage < 0.5:
image_pass = image2[y:y+h,x:x+w]
plt_show(image_pass)
print('掩膜覆盖率不通过')
continue
# 将符合条件的矩形区域添加到列表中
plates.append((x, y, w, h))

# 如果没有找到任何可能的车牌区域,打印提示信息
if len(plates) == 0:
print("No plates found.")
# 如果找到了多个可能的车牌区域,打印提示信息
elif len(plates) > 1:
print("Multiple plates found.")
# 如果只找到了一个可能的车牌区域,绘制矩形框并显示结果
else:
# 获取唯一的车牌区域
x, y, w, h = plates[0]
# 在原图上绘制矩形框
cv2.rectangle(image2, (x, y), (x+w, y+h), (0, 255, 0), 1)
# 显示结果
plt_show0(image2)
#单独保存车牌为test1.png
image3 = image2[y:y+h,x:x+w]
cv2.imwrite('./car_license/test1.png',image3)

  【11】计算面积area = cv2.contourArea(contour, oriented = False)

  contour 表示输入的单个轮廓,是一个由点坐标组成的数组。

  cv2.contourArea函数的作用是根据轮廓的点坐标,使用格林公式计算轮廓的面积,格林公式是一个将平面曲线积分转化为沿曲线的线积分的公式。

  【12】最小外接矩形 x, y, w, h = cv2.boundingRect(contour)

  contour 表示输入的单个轮廓,是一个由点坐标组成的数组,可以使用OpenCV的findContours函数获取。

  x, y, w, h 表示输出的矩形的左上角坐标 (x, y) ,以及宽度 w 和高度 h 。

  cv2.boundingRect函数的作用是根据轮廓的点坐标,找到一个能够包围轮廓的最小矩形。这个矩形是水平的,不会发生旋转。

  如果想要找到一个可能旋转的最小矩形,可以使用cv2.minAreaRect函数。

  【13】统计图像非零像素的个数count = cv2.countNonZero(src)

  例子:结合掩膜可以统计任何颜色的数目

  hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)  #定义蓝色的颜色范围

  lower_blue = np.array([100, 50, 50]) # 下界阈值

  upper_blue = np.array([130, 255, 255]) # 上界阈值  #应用颜色范围阈值

  mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)  #统计非零像素值的个数

  count = cv2.countNonZero(mask_blue)

第2章 车牌字符分割

  先看看提取出来的车牌。

license = cv2.imread('./car_license/test1.png')
plt_show0(license)
e6bce8f1a31cb3bbbeb5f83e21e31f7

  还是老规矩,先高斯去噪,再转为灰度,再阈值化。

#高斯去噪
licence_GB = cv2.GaussianBlur(license,(1,3),0)
plt_show0(licence_GB)
#灰度
licence_gray = cv2.cvtColor(licence_GB,cv2.COLOR_BGR2GRAY)
plt_show(licence_gray)
#自适应阈值
ret , img = cv2.threshold(licence_gray,0,255,cv2.THRESH_OTSU)
plt_show(img)
986a39df046eaea3202438c586a08e6 eef4425b67b121c14e8497f6eb96315 6e359c09c87b15320397184d54ba124
#创建一个两行一列的矩形卷积核,来闭运算,将分开的小块连起来
#这个卷积核的大小要根据实际情况来自己调整
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(2,1))
img_close = cv2.dilate(img,kernel)
plt_show(img_close)
#轮廓检测
number_contours , hierarchy = cv2.findContours(img_close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
img1 = license.copy()
cv2.drawContours(img1,number_contours,-1,(0,255,0),1)
plt_show0(img1)
ba28e2ec315a24ae5c383649abe829b 346c874505a6208e9757c868675a72f

  然后根据轮廓的位置和大小进行筛选,并分割处每个字符。

# 根据轮廓的位置和大小进行筛选
chars = []
for cnt in number_contours:
x, y, w, h = cv2.boundingRect(cnt)
# 去除过小或过大的轮廓
if w < 5 or h < 10 or w > 50 or h > 50:
continue
#去除过宽或过高的轮廓
ratio = w / h
if ratio < 0.25 or ratio > 0.7:
continue
# 保存符合条件的轮廓
chars.append((x, y, w, h))

# 根据轮廓的水平位置进行排序
chars = sorted(chars, key=lambda x: x[0])
print(chars)

# 分割每个字符
i = 0
for x, y, w, h in chars:
i = i+1
# 提取字符的图像
char = license[y:y+h, x:x+w]
# 调整字符的大小为64x64
char = cv2.resize(char, (64, 64))
plt_show(char)
#重新命名
cv2.imwrite('./words/test2_'+str(i)+'.png',char)

  至此,我们就可以看到每一个字符都被分割了出来,下一章,我们进行模式匹配来按顺序识别字符。