对于车牌分割我会从以下几个知识点来论述,最后对车牌分割的代码进行详解
1.图像边缘
图像边缘,即表示图像中一个区域的终结和另一个区域的开始,图像中相邻区域之间的像素集合构成了图像的边缘。所以,图像边缘可以理解为图像灰度发生空间突变的像素的集合。图像边缘有两个要素,即:方向和幅度。沿着边缘走向的像素值变化比较平缓;而沿着垂直于边缘的走向,像素值则变化得比较大。因此,根据这一变化特点,通常会采用一阶和二阶导数来描述和检测边缘。 综上,图像中的边缘检测可以通过对灰度值求导数来确定,而导数可以通过微分算子计算来实现。在数字图像处理中,通常是利用差分计算来近似代替微分运算。如下图所示。
2.Canny算子
Canny边缘检测算法的原理:
- 首先要对原始图像进行灰度化
- 对图像进行高斯滤波:这个应该都很清楚了,就是采用高斯核函数,根据其sigma 的不同对图像进行不同程度的模糊处理,虽然叫模糊处理,实质是滤除了高频的噪声。
- 根据Canny算法所采用的卷积算子,对灰度图像求梯度。
卷积算子为: - 对梯度幅值进行非极大值抑制。
通过上面的计算,就已经确定了每个像素的梯度和幅值,该点的像素梯度方向会根据邻域像素,计算8个方向的梯度直方图,由此确定该点像素的主方向。通常像素点局部最大值就是边缘点,垂直于该梯度方向,下一个点(可能点 s 和 r)为下一个边缘点(在两个像素点之间,可以通过邻域两个像素点进行插值来计算)
3.形态学(膨胀和腐蚀)
膨胀就是求局部最大值的操作。按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。(膨胀的处理效果)如下图所示。 腐蚀就是求局部最小值的操作。(腐蚀的处理效果)如下图所示。 数学公式对比:
4.开运算和闭运算
因为没有深入去了解基于数学基础是怎么实现的,这里只赘述了开运算和闭运算对图像有什么作用,以及试验的结果: 开运算(Opening Operation)其实就是先腐蚀后膨胀的过程:开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积,如下图所示。 闭运算(Closing Operation)是先膨胀后腐蚀的过程:闭运算能够排除小型黑洞。如下图所示。
5.车牌分割完整代码
import cv2
import numpy as np
from PIL import Image
import os.path
from skimage import io,data
def stretch(img):
maxi = float(img.max())
mini = float(img.min())
for i in range(img.shape[0]):
for j in range(img.shape[1]):
img[i,j] = (255/(maxi-mini)*img[i,j]-(255*mini)/(maxi-mini))
return img
def dobinaryzation(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
def find_rectangle(contour):
y,x = [],[]
for p in contour:
y.append(p[0][0])
x.append(p[0][1])
return [min(y),min(x),max(y),max(x)]
def locate_license(img,afterimg):
img,contours,hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
print(n)
block = []
for c in contours:
r = find_rectangle(c)
a = (r[2]-r[0])*(r[3]-r[1])
s = (r[2]-r[0])/(r[3]-r[1])
block.append([r,a,s])
block = sorted(block,key=lambda b:b[1])[-3:]
maxweight,maxindex = 0,-1
for i in range(len(block)):
b = afterimg[block[i][0][1]:block[i][0][3],block[i][0][0]:block[i][0][2]]
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 > maxweight:
maxindex = i
maxweight = w2
return block[maxindex][0]
def find_license(img):
m = 400*img.shape[0]/img.shape[1]
print(m)
img = cv2.resize(img,(400,int(m)),interpolation=cv2.INTER_CUBIC)
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
stretchedimg = stretch(gray_img)
r=16
h=w=r*2+1
kernel =np.zeros((h,w),np.uint8)
cv2.circle(kernel,(r,r),r,1,-1)
openingimg = cv2.morphologyEx(stretchedimg,cv2.MORPH_OPEN,kernel)
strtimg = cv2.absdiff(stretchedimg,openingimg)
binaryimg = dobinaryzation(strtimg)
canny = cv2.Canny(binaryimg,binaryimg.shape[0],binaryimg.shape[1])
kernel = np.ones((5,19),np.uint8)
closingimg = cv2.morphologyEx(canny,cv2.MORPH_CLOSE,kernel)
openingimg = cv2.morphologyEx(closingimg, cv2.MORPH_OPEN, kernel)
kernel = np.ones((11,5),np.uint8)
openingimg = cv2.morphologyEx(openingimg,cv2.MORPH_OPEN,kernel)
rect = locate_license(openingimg,img)
return rect,img
def cut_license(afterimg,rect):
rect[2]=rect[2]-rect[0]
rect[3]=rect[3]-rect[1]
rect_copy = tuple(rect.copy())
rect=[0,0,0,0]
mask = np.zeros(afterimg.shape[:2],np.uint8)
bgdmodel = np.zeros((1,65),np.float64)
fgdmodel = np.zeros((1,65),np.float64)
cv2.grabCut(afterimg,mask,rect_copy,bgdmodel,fgdmodel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img_show = afterimg*mask2[:,:,np.newaxis]
return img_show
def deal_license(licenseimg):
gray_img = cv2.cvtColor(licenseimg,cv2.COLOR_BGR2GRAY)
kernel = np.ones((3,3),np.float32)/9
gray_img = cv2.filter2D(gray_img,-1,kernel)
ret,thresh = cv2.threshold(gray_img,120,255,cv2.THRESH_BINARY)
return thresh
if __name__ == '__main__':
img = cv2.imread('car3.jpg',cv2.IMREAD_COLOR)
rect,afterimg = find_license(img)
cv2.rectangle(afterimg,(rect[0],rect[1]),(rect[2],rect[3]),(0,255,0),2)
cv2.imshow('afterimg',afterimg)
cutimg = cut_license(afterimg,rect)
cv2.imshow('cutimg',cutimg)
thresh = deal_license(cutimg)
cv2.imshow('thresh',thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
6.实验结果
以上是车牌区域分割的完整代码,分割效果如下: 从分割结果来看,还是不错的,那么接下来我们就仔细的剖析一下代码(说明:图片完全是我自己整理的笔记然后截图过来的,因为这是老师给我们布置的一次小实验,我是Word整理好的。图片解析的顺序也是根据函数的顺序依次进行排列的,有想要PDF或者Word的小伙伴可以直接私信我):
7.代码解析
1.预处理 图像拉伸 2.图像预处理 二值化处理 3.寻找矩形轮廓 4.对图像拉伸(涉及函数的调用)、开运算和闭运算、以及对图像Canny边缘检测 5.图像分割函数 6.分割得到的车牌区域二值化 到这里车牌分割的代码就解读完了,其实车牌分割还可以应用很多种方法,一般的车牌区域都是长方形区域,就可以根据查找车身的矩形框来做;还可以根据车牌的颜色来分割,一般的车牌都是蓝白和黄白,完全可以根据颜色实现分割。
7.实验不足与展望 首先对于车牌区域模糊的图像,处理的效果并不是很理想,还有对倾斜车身的这样的情况,车牌分割的效果也不是很理想,我看了很多的文章,有很多人用到了仿射变换的方法,这个我没有尝试。另外就是程序中,对图像进行开运算和闭运算,这个开和闭的顺序朋友们可以自己去尝试,也可以试试两次开运算一次闭运算,反之两次闭运算一次开运算。总的来说,需要改进的地方还很多~~
|