OpenCV基于边缘检测的车牌提取和字符分割
上一篇博客是基于颜色信息的车牌提取,这一篇博客是基于边缘检测的车牌提取。其实无论是基于颜色信息还是基于边缘检测,都是先找到目标区域(车牌)的一些特征,将特征用白色标记出来,背景颜色是黑色。然后用形态学方法(腐蚀和膨胀),将车牌的矩形区域弄出来,再用轮廓提取,将车牌的矩形区域提取出来。
本文主要参考了以下这一篇博客,该博客是用C++编写的算法,我参考其方法用Python实现了一遍,其中字符分割的算法实现有一些不同。
参考的博客(C++编写的算法)
在代码中,我详细地注释了每一个步骤流程以及一些注意事项,看注释应该比较容易理解,代码可以直接使用。
注意把cv2.imread()函数中的图片路径修改一下,原始图片我放在了博客的最后。
#基于边缘检测的车牌提取
import cv2
import numpy as np
from imutils.perspective import four_point_transform
#读取车辆原始图像
car_original = cv2.imread('C:\\Users\\Administrator\\Desktop\\car_edge.jpg')
car_gray = cv2.cvtColor(car_original,cv2.COLOR_BGR2GRAY) #灰度化
#cv2.imshow('car_gray',car_gray)
car_blur = cv2.GaussianBlur(car_gray,(3,3),0) #高斯模糊
car_edge = cv2.Canny(car_blur,250,300) #Canny边缘检测
#cv2.imshow('car_edge',car_edge)
#接下来进行形态学处理
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,1)) #设定核的形状大小,由于车牌的字符是横向排列的,设定是横向膨胀
car_dilation_2 = cv2.dilate(car_edge,kernel,iterations=2) #膨胀两次,保证字符区域全部连通起来,膨胀必须一步到位,否则再次腐蚀可能会将图像回复原装
#cv2.imshow('car_dilation_2',car_dilation_2)
car_erosion_4 = cv2.erode(car_dilation_2,kernel,iterations=4) #再腐蚀四次,尽可能多的去除小块碎片
#cv2.imshow('car_erosion_4',car_erosion_4)
car_dilation_4 = cv2.dilate(car_erosion_4,kernel,iterations=2) #再膨胀两次,保证膨胀总次数和腐蚀总次数相同
#cv2.imshow('car_dilation_4',car_dilation_4)
#再进行Y方向的膨胀腐蚀
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5)) #设定核的形状大小,纵向
car_erosion_6 = cv2.erode(car_dilation_4,kernel,iterations=2) #再沿Y方向腐蚀两次,尽可能多的去除小块碎片
#cv2.imshow('car_erosion_6',car_erosion_6)
car_dilation_6 = cv2.dilate(car_erosion_6,kernel,iterations=2) #再膨胀两次,保证膨胀总次数和腐蚀总次数相同
#cv2.imshow('car_dilation_6',car_dilation_6) #经过六次腐蚀和六次膨胀,最终得到理想的结果
#查找轮廓
contours,hierarchy = cv2.findContours(car_dilation_6,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
x,y,w,h = cv2.boundingRect(c) #轮廓的边界矩形,x,y是矩形左上角的坐标,w,h是矩形的宽度和高度
if w>60 and h>20: #如果当前的这个轮廓区域足够大,它一定是车牌号矩形区域。
x_goal = x
y_goal = y
w_goal = w
h_goal = h
goal = c
car_goal = cv2.imread('C:\\Users\\Administrator\\Desktop\\car_edge.jpg')
cv2.rectangle(car_goal,(x_goal,y_goal),(x_goal+w_goal,y_goal+h_goal),(0,255,0),2) #把车牌号在原始图像上标记出来
#cv2.imshow('car_goal',car_goal)
license_plate = car_original[y_goal:y_goal+h_goal,x_goal:x_goal+w_goal,:] #把车牌提取出来
cv2.imshow('license_plate',license_plate) #仅显示车牌区域
'''
传统的字符分割方法主要有:直接分割法、基于识别基础上的分割法、自适应分割线聚类法
直接分割法简单,但是其局限是分割点的确定需要比较高的准确性
基于识别基础上的分割法,把识别和分割结合起来,但是需要识别的高准确性,根据识别和分类的耦合程度又有不同的划分
自适应分割线聚类法,建立一个分类器,用来判断图像的每一列是否是分割线,是根据训练样本来进行自适应学习的神经网络分类器,但是对于粘连字符训练困难
还有直接把字符组成的单词当作一个整体来识别的,例如运用马尔科夫数学模型等方法进行处理,主要用于印刷体文本识别
'''
#把车牌放大
license_plate_large = cv2.resize(license_plate,None,fx=3,fy=3,interpolation=cv2.INTER_CUBIC) #interpolation:插值方法
cv2.imshow('license_plate_large',license_plate_large) #显示放大之后的车牌
license_plate_gray = cv2.cvtColor(license_plate_large,cv2.COLOR_BGR2GRAY) #灰度化
img_blur = cv2.GaussianBlur(license_plate_gray,(5,5),0) #高斯模糊
canny_edge = cv2.Canny(img_blur,80,200) #Canny边缘检测
cv2.imshow('canny_edge',canny_edge)
'''
#使用Otsu算法进行二值化
ret,threshold = cv2.threshold(img_blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('threshold',threshold) #仅显示车牌区域
'''
(x,y)=canny_edge.shape #返回的分别是矩阵的行数和列数,x是行数,y是列数
#将第一列和最后一列设置成黑色,这样pointCount的第一个和最后一个值都是0,则[上个为0当前不为0,即为开始;上个不为0当前为0,即为结束]不会出问题
canny_edge[:,0] = 0
canny_edge[:,y-1] = 0
pointCount=np.zeros(y,dtype=np.uint8)#每列白色的个数
#i是列数,j是行数
for i in range(0,y):
for j in range(0,x):
if(canny_edge[j,i]==255):
pointCount[i]=pointCount[i]+1
#start和end里面存放的是每个字符在图像中的起始列和结束列,即垂直分割
start = []
end = []
# 对图像进行垂直分割
for index in range(1, y):
# 上个为0当前不为0,即为开始
if ((pointCount[index-1] == 0) & (pointCount[index] != 0)):
start.append(index)
# 上个不为0当前为0,即为结束
elif ((pointCount[index-1] != 0) & (pointCount[index] == 0)):
end.append(index)
#将分割之后的字符显示出来
imgArr=np.zeros(x)
imgArr=imgArr.reshape((-1,1)) #转置,行向量转成列向量
for idx in range(0,len(start)):
if idx in [0,1,3,4,5,6,7]: #将车牌字符里面的小圆圈点去掉,只保留车牌里面有用的字符
temp_img=canny_edge[:,start[idx]:end[idx]]
imgArr=np.hstack((imgArr,temp_img)) #矩阵拼接,将各个字符拼接一下显示出来
(xx,yy)=imgArr.shape
imgArr=imgArr[:,1:yy]
cv2.imshow('vertical_cut',imgArr)
#根据类似的分割算法实现,在垂直分割之后,还可以对每个字符进行水平分割,在此不再赘述。
#垂直分割和水平分割之后,即可以得到每个字符,再用相关的算法识别字符,在此字符识别暂时不写了。
cv2.waitKey(0)
下图是代码中使用的原始图片car_edge.jpg