1.问题描述
- 对于一张已经检测并且经过矫正的车牌,为了能够识别出车牌上的字母,先将每个字符检测出来,然后对每个字符进行识别。
- 本博客主要是讲述如何将字符检测出来,暂时不涉及字符的识别。利用的是传统的图像处理方法,基于连通区域的算法。
2.解决方法概述
- 对于一张车牌图片,首先转灰度图,然后利用阈值将图像变成二值图像。也就是像素值只有0和1(或者0和255)的图像。
- 因为直接二值化后会有很多噪音,会出现字符之间会有连接在一起的情况,或者字符与图像边缘连接在一起的情况,合理利用形态学的开操作,去掉大部分的噪音。
- 将连通区域全部检测出,根据车牌字符的一些特征(比如字符的宽度不会超过字整张图片的1/7等),过滤掉一些不合理的区域,留下合理的字符区域。
3.处理以及代码展示
3.1导入库
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os
def imgs_show(images):
fig, axes = plt.subplots(2,2)
for i in range(4):
axes[i//2][i%2].imshow(images[i], cmap="gray")
plt.show()
3.2 选择一组图片
图像选择了几张,源自于CCPD数据集,光照模糊程度各不相同
img_names = ["浙JP303U.jpg", "皖A3H921.jpg", "皖A12J89.jpg", "皖A86552.jpg"]
img_datas = []
for img_name in img_names:
img_path = os.path.join("/Users/fulei/Datasets/CCPD/gener_just_plate_images/", img_name)
img_data = cv2.imread(img_path)[:, :, ::-1]
img_datas.append(img_data)
imgs_show(img_datas)
由于图像处理的代码都放在一起的,所以放在文章末尾。
3.3 对图像的二值化处理
- 转灰度图,基于OTSU自动选择出合适的阈值,将图像变成二值化图像
3.4 开操作除去噪音
- 先是利用利用 2x4的核对整张图像进行一次开操作,目的是去掉字符与字符之间的连接
- 在利用3x3的核对图像的上下1/4的部分进行开操作去掉字符与边缘之间的连接
3.5 将所有的连通区域找出
3.6 根据检测过滤规则
- 字符的高度必须大于图片高度的1/3
- 字符的宽度必须小于图像高度的1/7
3.7 剩余代码
def img_char_detection(image, step=1):
"""
字符分割算法
"""
# 1. 对图像转灰度图再进行二值化
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
threshold, img_bin = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
if step == 1:
return img_bin
# 2. 对图像进行形态学处理 (由于要用到联通分量处理,必须将单个字符与其他字符或者噪音区域分开)
# 2.1 基于开操作断开字符之间的连接
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 4))
img_bin_open = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN, kernel, iterations=1)
if step == 2:
return img_bin_open
# 2.2 基于开操作将字符与上下边缘之间的连接断开,只处理上四分之一和下四分之一区域
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
img_bin_close = img_bin_open.copy()
temp_height = img_bin_close.shape[0] // 4
img_bin_close[:temp_height] = cv2.morphologyEx(img_bin_close[:temp_height], cv2.MORPH_OPEN, kernel, iterations=1)
img_bin_close[-temp_height:] = cv2.morphologyEx(img_bin_close[-temp_height:], cv2.MORPH_OPEN, kernel, iterations=1)
if step == 3:
return img_bin_close
# 3 用形态学操作处理之后就少了很多噪音,现在基于轮廓找到联通区域(并且得到其外界矩形)
image_ = image.copy()
contours, hierarchy = cv2.findContours(img_bin_close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(image_, (x, y), (x+w, y+h), (0xff, 0, 0), 2)
if step == 4:
return image_
# 3.1 增加一些额外的判断条件,过滤掉一些边框
# 字符宽度必须小于 整张图片的宽度的七分之一
# 字符高度必须大于 整张图片的三分之一
image_ = image.copy()
img_h, img_w, img_c = image_.shape
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
if w > img_w / 7 or h < img_h / 3 :
continue
cv2.rectangle(image_, (x, y), (x+w, y+h), (0xff, 0, 0), 2)
if step == 5:
return image_
def images_handle(images, num_step=5):
for i in range(1, num_step+1):
print("*"*30)
temp_iamges = []
for image in images:
temp_iamges.append(img_char_detection(image, i))
imgs_show(temp_iamges)
images_handle(img_datas)
4.总结
- 基于连通区域的算法在处理这类问题的时候往往效果并不是最理想的,特别是对噪音的一些过滤细节方面要很好地处理。
- 另外最后的字符过滤规则为了避免错选可以多加一些过滤规则,例如字符的重心区域会在图像的中间等等,但是这里测试的几张效果出来了就没有进一步讨论了。