一、算法需求
基于模板匹配实现复杂环境下提取车辆照片中的车牌区域
二、问题分析
目前分析发现,在车辆照片中,车牌区域及其附近区域的特点如下:
1、车牌只存在于车头和车尾部分,车牌的下方是一个阴影区域(这是自然光照射所形成的
)
2、车牌在车尾时周围纹理比较单一,但车牌在车头时其上部分纹理较为复杂(不同车型的发动机出气口不同
)
3、车牌中的字符具备水平方向的边缘特征(车牌号都是水平排列的
)
要达成目前,首先裁剪出一个面积较大的模板(包含车牌周围的环境信息【这里所提到的特定1】
),对图像进行模板粗粒度匹配,得到车牌大概区域(通过该操作可以将图像的size缩减到原来的1/3,可以滤除掉车辆外的干扰因素【例如,地板上存在车牌,但其没有固定在车辆上】
)。然后对大模板裁剪出的小图进行分析,遴选出最具代表性的精准模板,然后进行多尺度模板匹配。
关键知识
模板匹配函数:matchTemplate
result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF)
参数含义:
image:原图像,待匹配图像,8bit整数型、32bit浮点型,可以是单通道或多通道;
template:模板图像,类型同原图像,尺寸必须小于源图像;
method:匹配方法;
mask:掩码;
result:返回结果,32bit浮点型,原图像为W×H,模板图像为w×h,生成的图像对象为(W−w+1)×(H−h+1);
匹配方法TemplateMatchModes有6种,可以用相应的cv2.TM_xxx传入Python接口:
1、cv::TM_SQDIFF:该方法使用平方差进行匹配,因此最佳的匹配结果在结果为0处,值越大匹配结果越差。
2、cv::TM_SQDIFF_NORMED:该方法使用归一化的平方差进行匹配,最佳匹配也在结果为0处。
3、cv::TM_CCORR:相关性匹配方法,该方法使用源图像与模板图像的卷积结果进行匹配,因此,最佳匹配位置在值最大处,值越小匹配结果越差。
4、cv::TM_CCORR_NORMED:归一化的相关性匹配方法,与相关性匹配方法类似,最佳匹配位置也是在值最大处。
5、cv::TM_CCOEFF:相关性系数匹配方法,该方法使用源图像与其均值的差、模板与其均值的差二者之间的相关性进行匹配,最佳匹配结果在值等于1处,最差匹配结果在值等于-1处,值等于0直接表示二者不相关。
6、cv::TM_CCOEFF_NORMED:归一化的相关性系数匹配方法,正值表示匹配的结果较好,负值则表示匹配的效果较差,也是值越大,匹配效果也好。
匹配方法的选取根据实际情况而定。
以上内容引用自 :https://blog.csdn.net/guduruyu/article/details/69231259
三、核心思路
1、读取图片为灰度图
2、根据相对尺寸,截取粗粒度模板(包含车牌周围的环境信息
)
3、进行简单模板匹配(利用高斯滤波降噪,再利用Cnny提取边缘信息进行匹配
),得到车牌粗区域
4、根据相对尺寸,截取精细模板(包含车牌邻域的空白区域信息
);该操作可能需要尝试很多遍
5、进行多尺度模板匹配(利用sobel算子提取车牌字符的横向梯度让车牌字符变得明显,同时滤除掉车头车牌上面的各种y方向的梯度线
)
四、具体实现
4.1 读取图片为灰度图
灰度图更利于我们分析车辆的特征,用于模板裁剪的车牌应轮廓完整,字迹清晰,并且车牌周围少有其他明显的干扰信息。
代码和执行效果如下所示:
import cv2,os
def all_gray(path_list):
for p in path_list:
img=cv2.imread(path+'/'+p,1)
print(path+"/"+p)
img=cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
cv2.imwrite(path+'/'+p,img)
path="images"
path_list=os.listdir(path)
#当jpg在x中才返回x,生成的path_list2只包含jpg文件
path_list2=[x for x in path_list if ".jpg" in x]
print(path_list)
print(path_list2)
all_gray(path_list2)
灰度图1:
灰度图2:
灰度图1和灰度图2相比,灰度图2更适合用于裁剪模板,灰度图1车牌区域显得很亮,而车牌周围区域显得很黑(这与大部分的车牌区域特征不合
),因此基于灰度图1裁剪出来的模板在其他车辆上适配度不高。
4.2 根据相对尺寸,截取粗粒度模板
粗粒度模板应包含车牌周围的环境信息(直接使用精细的车牌模块很难一次性截取到车牌区域,其针对大部分图像都存在错误匹配
),我们在选择模板时尽可能的扩大车牌区域信息(这里将车辆下方的阴影区域进行了保留
),基于更多的空间信息可以避免错误匹配。
截取代码和粗粒度模板如下所示:
import cv2
#保存面积较大的模板,更容易匹配到目标
def save_temp1():
path=r"Car/2.jpg"
img=cv2.imread(path,1)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
#cv2.imshow('grsy',gray)
gau_img=cv2.GaussianBlur(gray,(3,3),0)#高斯滤波
#根据相对尺寸,采集模板
hi,wi,c=img.shape
yr,xr,hr,wr=0.3,0.33,0.45,0.45
y,x,h,w=yr*hi,xr*wi,hr*hi,wr*wi
y,x,h,w=int(y),int(x),int(h),int(w)
print(y/hi,x/wi,h/hi,w/wi)
im = gau_img[y:y+h,x:x+w]
save_path = r'template1.jpg'
cv2.imwrite(save_path,im)
cv2.waitKey()
save_temp1()
粗粒度模板如下所示:
4.3 简单模板匹配
通过简单模板匹配可以将图像的size缩减到原来的1/3,可以滤除掉车辆外的干扰因素,确定车牌初步范围。
执行代码:
import glob
import cv2
def template_crop(templateo,imageo):
rate=5
image=cv2.resize(imageo,None,fx=1/rate,fy=1/rate)
template=cv2.resize(templateo,None,fx=1/rate,fy=1/rate)
tH,tW,c=template.shape
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
gray = cv2.Canny(image, 50, 200)
template = cv2.Canny(template, 50, 200)
gray=cv2.GaussianBlur(gray,(3,3),0)#高斯滤波
#cv2.imshow("template", template)
#cv2.imshow("gray", gray)
print(gray.shape, template.shape)
result = cv2.matchTemplate(gray, template, cv2.TM_CCOEFF)
(_, maxVal, _, maxLoc) = cv2.minMaxLoc(result)
print(imagePath,maxVal)
# 计算测试图片中模板所在的具体位置,即左上角和右下角的坐标值,并乘上对应的裁剪因子
(startX, startY) = (int(maxLoc[0] -30), int(maxLoc[1] ))
if startX<0:
startX=0
(endX, endY) = (int((maxLoc[0] + tW)+30 ), int((maxLoc[1] + tH) ))
crop=imageo[startY*rate:endY*rate,startX*rate:endX*rate]
crop=crop[50:-50]
return(crop)
# 绘制并显示结果
#cv2.rectangle(image, (startX, startY), (endX, endY), (0, 0, 255), 2)
#cv2.imshow("Image", image)
cv2.waitKey(0)
def get_template():
# 读取模板图片
template = cv2.imread('template1.jpg')
return template
template=get_template()
# 遍历所有的图片寻找模板
for imagePath in glob.glob("Car/*.jpg"):
image = cv2.imread(imagePath)
print(imagePath,image.shape)
print(template.shape)
img=template_crop(template,image)
print(img.shape)
sname=imagePath.replace('.jpg','.png')
cv2.imwrite(sname,img)
#cv2.imshow("imagimagee", image)
cv2.imshow("image", img)
cv2.waitKey(0)
代码执行效果:
4.4 根据相对尺寸,截取精细模板
通过4.3 我们已经得到了车牌的范围,排除了车牌周围大部分的干扰,但要想进一步确定车牌范围,还需在4.3的基础上裁剪出精细模板,尽可能的去除干扰。精细模板需根据实际匹配情况进行多次裁剪。
截取代码:
import cv2
#保存车牌模板,用于精准匹配到目标
def save_temp2():
path=r"Car/23.jpg"
img=cv2.imread(path,1)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
gau_img=cv2.GaussianBlur(gray,(3,3),0)#高斯滤波
#根据相对尺寸,采集模板
hi,wi,c=img.shape
yr,xr,hr,wr=0.35,0.4,0.15,0.45
y,x,h,w=yr*hi,xr*wi,hr*hi,wr*wi
y,x,h,w=int(y),int(x),int(h),int(w)
print(y/hi,x/wi,h/hi,w/wi)
im = gau_img[y:y+h,x:x+w]
save_path = r'template2.jpg'
cv2.imwrite(save_path,im)
save_temp2()
精细模板:
4.5 多尺度模板匹配
使用matchTemplate()只能检测和模板图像大小一样的目标,对于不同大小的目标,或者稍有差异的目标检测不行。我们所拍摄的车牌姿态信息较为丰富,距离远近各有不同。在单一尺度下进行精细的模板匹配,无法在所有的图片中都准确找到车牌。
针对这样的情况,对图像进行多尺度模板匹配(通过改变待检测图像的大小来实现多尺度的模板匹配算法
)。在该匹配过程中,任然发现有部分匹配错误(主要是将车牌上方的出气口或者logo文字识别为匹配区域
)。所以,对图像求水平方向的梯度,过滤掉大部分不符合车牌字符特征的纹理,基于梯度进行模板匹配。
运行代码:
import numpy as np
import argparse
import imutils
import glob
import cv2
args = {
'template': 'template2.jpg', 'images': "images",'visualize':False}
# 读取模板图片
template = cv2.imread(args["template"])
# 转换为灰度图片
template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
# 执行边缘检测
#template = cv2.Canny(template, 50, 200)
template = cv2.Sobel(template, cv2.CV_64F, dx=1, dy=0, ksize=3)
template = cv2.convertScaleAbs(template)
kernel=cv2.getStructuringElement(shape=cv2.MORPH_RECT,ksize=(7,7))
template=cv2.morphologyEx(src=template,op=cv2.MORPH_CLOSE,kernel=kernel,iterations=1)
(tH, tW) = template.shape[:2]
# 显示模板
cv2.imshow("Template", template)
print('args.get("visualize", False):====',args.get("visualize", True))
# 遍历所有的图片寻找模板
for imagePath in glob.glob(args["images"] + "/*.png"):
print(imagePath)
# 读取测试图片并将其转化为灰度图片
image = cv2.imread(imagePath)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
found = None
# 循环遍历不同的尺度
for scale in np.linspace(0.2, 1.0, 10)[::-1]:
# 根据尺度大小对输入图片进行裁剪
resized = imutils.resize(gray, width = int(gray.shape[1] * scale))
r = gray.shape[1] / float(resized.shape[1])
# 如果裁剪之后的图片小于模板的大小直接退出
if resized.shape[0] < tH or resized.shape[1] < tW:
break
# 首先进行高斯滤波,然后用Sobel算子计算水平方向的梯度,接着进行闭运算
resized=cv2.GaussianBlur(resized,(5,5),0)#高斯滤波
edged = cv2.Sobel(resized, cv2.CV_64F, dx=1, dy=0, ksize=3)
edged = cv2.convertScaleAbs(edged)
edged=cv2.morphologyEx(src=edged,op=cv2.MORPH_CLOSE,kernel=kernel,iterations=1)
#cv2.imshow("edged", edged)
#cv2.waitKey()
#模板匹配
result = cv2.matchTemplate(edged, template, cv2.TM_CCOEFF)
(_, maxVal, _, maxLoc) = cv2.minMaxLoc(result)
# 结果可视化
if args.get("visualize", False):
# 绘制矩形框并显示结果
clone = np.dstack([edged, edged, edged])
cv2.rectangle(clone, (maxLoc[0], maxLoc[1]), (maxLoc[0] + tW, maxLoc[1] + tH), (0, 0, 255), 2)
cv2.imshow("Visualize", clone)
cv2.waitKey(0)
# 如果发现一个新的关联值则进行更新
if found is None or maxVal > found[0]:
found = (maxVal, maxLoc, r)
# 计算测试图片中模板所在的具体位置,即左上角和右下角的坐标值,并乘上对应的裁剪因子
(_, maxLoc, r) = found
(startX, startY) = (int(maxLoc[0] * r), int(maxLoc[1] * r))
(endX, endY) = (int((maxLoc[0] + tW) * r), int((maxLoc[1] + tH) * r))
startX-=30
endX+=30
# 绘制并显示结果
cv2.rectangle(image, (startX, startY), (endX, endY), (0, 0, 255), 2)
cv2.imshow("Image", image)
save_name=imagePath.replace(".png",".bmp")
cv2.imwrite(save_name,image)
cv2.waitKey(0)
代码运行效果: