OpenCV(项目)车牌识别4 -- 总结篇
Posted _睿智_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV(项目)车牌识别4 -- 总结篇相关的知识,希望对你有一定的参考价值。
目录
一、效果
1、成功案例
2、经典失败案例(单字符识别成类似字符)
3、其他失败案例
二、总结
前面字符提取比较成功的,大多数的识别都没太大问题,但是字符提取不到位,后面的识别工作自然也很难进行。这次的测试失败有很多都是前面的工作没有做好,尤其是第一步的车牌提取,个人感觉车牌提取是本次项目最困难的地方。
三、车牌识别总代码
# 车牌识别
import cv2 as cv
import numpy as np
import os
from matplotlib import pyplot as plt
from PIL import Image, ImageDraw, ImageFont
# 总文件夹
List = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '云','京','冀','吉','宁','川','新','晋','桂','沪','津','浙','渝','湘','琼',
'甘','皖','粤','苏','蒙','藏','豫','贵','赣','辽','鄂','闽','陕','青','鲁','黑']
# 车牌数字列表(10)
Num_List = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
# 车牌英文列表(24)
Eng_List = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
# 车牌汉字列表(31)
Chinese_List = ['云','京','冀','吉','宁','川','新','晋','桂','沪','津','浙','渝','湘','琼',
'甘','皖','粤','苏','蒙','藏','豫','贵','赣','辽','鄂','闽','陕','青','鲁','黑']
final_result = []
# 得到黑底白字(白色多则返回真)
def IsWhiteMore(binary):
white = black = 0
height, width = binary.shape
# 遍历每个像素
for i in range(height):
for j in range(width):
if binary[i,j]==0:
black+=1
else:
white+=1
if white >= black:
return True
else:
return False
# 限制图像大小(车牌)
def Limit(image):
height, width, channel = image.shape
# 设置权重
weight = width/300
# 计算输出图像的宽和高
last_width = int(width/weight)
last_height = int(height/weight)
image = cv.resize(image, (last_width, last_height))
return image
# 二-5、统计白色像素点(分别统计每一行、每一列)
def White_Statistic(image):
ptx = [] # 每行白色像素个数
pty = [] # 每列白色像素个数
height, width = image.shape
# 逐行遍历
for i in range(height):
num = 0
for j in range(width):
if(image[i][j]==255):
num = num+1
ptx.append(num)
# 逐列遍历
for i in range(width):
num = 0
for j in range(height):
if (image[j][i] == 255):
num = num + 1
pty.append(num)
return ptx, pty
# 二-6、绘制直方图
def Draw_Hist(ptx, pty):
# 依次得到各行、列
rows, cols = len(ptx), len(pty)
row = [i for i in range(rows)]
col = [j for j in range(cols)]
# 横向直方图
plt.barh(row, ptx, color='black', height=1)
# 纵 横
# plt.show()
# 纵向直方图
plt.bar(col, pty, color='black', width=1)
# 横 纵
# plt.show()
# 二-7-2、横向分割:上下边框
def Cut_X(ptx, rows):
# 横向切割(分为上下两张图,分别找其波谷,确定顶和底)
# 1、下半图波谷
min, r = 300, 0
for i in range(int(rows / 2)):
if ptx[i] < min:
min = ptx[i]
r = i
h1 = r # 添加下行(作为顶)
# 2、上半图波谷
min, r = 300, 0
for i in range(int(rows / 2), rows):
if ptx[i] < min:
min = ptx[i]
r = i
h2 = r # 添加上行(作为底)
return h1, h2
# 二-7-3、纵向分割:分割字符
def Cut_Y(pty, cols, h1, h2, binary):
global con, final_result
WIDTH = 33 # 经过测试,一个字符宽度约为32
w = w1 = w2 = 0 # 前谷 字符开始 字符结束
begin = False # 字符开始标记
last = 10 # 上一次的值
con = 0 # 计数(字符)
final_result = [] # 清空已识别的车牌
# 纵向切割(正式切割字符)
for j in range(int(cols)):
# 0、极大值判断
if pty[j] == max(pty):
if j < 30: # 左边(跳过)
w2 = j
if begin == True:
begin = False
continue
elif j > 270: # 右边(直接收尾)
if begin == True:
begin = False
w2 = j
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
# cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
Template_Match(b_copy)
con += 1
break
# 1、前谷(前面的波谷)
if pty[j] < 12 and begin == False: # 前谷判断:像素数量<12
last = pty[j]
w = j
# 2、字符开始(上升)
elif last < 12 and pty[j] > 20:
last = pty[j]
w1 = j
begin = True
# 3、字符结束
elif pty[j] < 13 and begin == True:
begin = False
last = pty[j]
w2 = j
width = w2 - w1
# 3-1、分割并显示(排除过小情况)
if 10 < width < WIDTH + 3: # 要排除掉干扰,又不能过滤掉字符”1“
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
# cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
Template_Match(b_copy)
con += 1
# 3-2、从多个贴合字符中提取单个字符
elif width >= WIDTH + 3:
# 统计贴合字符个数
num = int(width / WIDTH + 0.5) # 四舍五入
for k in range(num):
# w1和w2坐标向后移(用w3、w4代替w1和w2)
w3 = w1 + k * WIDTH
w4 = w1 + (k + 1) * WIDTH
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w3:w4]
# cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
Template_Match(b_copy)
con += 1
# 4、分割尾部噪声(距离过远默认没有字符了)
elif begin == False and (j - w2) > 30:
break
# 最后检查收尾情况
if begin == True:
w2 = 295
b_copy = binary.copy()
b_copy = b_copy[h1:h2, w1:w2]
# cv.imshow('binary%d-%d' % (count, con), b_copy)
cv.imwrite('car_characters/image%d-%d.jpg' % (count, con), b_copy)
Template_Match(b_copy)
# 显示识别结果(图像)
Show_Result_Image()
# 二-7、分割车牌图像(根据直方图)
def Cut_Image(ptx, pty, binary, dilate):
h1 = h2 = 0
#顶 底
begin = False #标记开始/结束
# 1、依次得到各行、列
rows, cols = len(ptx), len(pty)
row = [i for i in range(rows)]
col = [j for j in range(cols)]
# 2、横向分割:上下边框
h1, h2 = Cut_X(ptx, rows)
# cut_x = binary[h1:h2, :]
# cv.imshow('cut_x', cut_x)
# 3、纵向分割:分割字符
Cut_Y(pty, cols, h1, h2, binary)
# 显示文字(中文)(用的PIL,RGB正常显示,即和opencv的RGB相反)
def Text(image, text, p, color, size):
# cv2读取图片
# BGR转RGB:cv2和PIL中颜色的hex码的储存顺序不同
cv2_image = cv.cvtColor(image, cv.COLOR_RGB2BGR)
pil_image = Image.fromarray(cv2_image)
# PIL图片上打印汉字
draw = ImageDraw.Draw(pil_image) # 图片上打印
font = ImageFont.truetype("./simhei.ttf", size, encoding="utf-8") # 参数1:字体文件路径,参数2:字体大小
draw.text((p[0]-60, p[1]-20), text, color, font=font)
# PIL图片转cv2 图片
cv2_result = cv.cvtColor(np.array(pil_image), cv.COLOR_RGB2BGR)
# cv2.imshow("图片", cv2_result) # 汉字窗口标题显示乱码
# cv.imshow("photo", cv2_result) # 输出汉字
return cv2_result
# 显示识别结果(文字)
def Show_Result_Words(index):
print(List[index])
final_result.append(List[index])
print(final_result)
# 显示识别结果(图像)
def Show_Result_Image():
p = image_rect[0], image_rect[1]
w, h = image_rect[2] , image_rect[3]
# 框出车牌
cv.rectangle(img, (p[0],p[1]), (p[0]+w, p[1]+h), (0,0,255), 2)
# 输出字符(中文)
result = Text(img, str(final_result), p, (255,0,0), 16)
cv.imshow('result-%d'%count, result)
# cv.waitKey(0)
# 一、形态学提取车牌
def Get_Licenses(image):
global image_rect #待返回的矩形坐标
# 1、转灰度图
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
# cv.imshow('gray', gray)
# 2、顶帽运算
# gray = cv.equalizeHist(gray)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (17,17))
tophat = cv.morphologyEx(gray, cv.MORPH_TOPHAT, kernel)
# cv.imshow('tophat', tophat)
# 3、Sobel算子提取y方向边缘(揉成一坨)
y = cv.Sobel(tophat, cv.CV_16S, 1, 0)
absY = cv.convertScaleAbs(y)
# cv.imshow('absY', absY)
# 4、自适应二值化(阈值自己可调)
ret, binary = cv.threshold(absY, 75, 255, cv.THRESH_BINARY)
# cv.imshow('binary', binary)
# 5、开运算分割(纵向去噪,分隔)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (1, 15))
Open = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)
# cv.imshow('Open', Open)
# 6、闭运算合并,把图像闭合、揉团,使图像区域化,便于找到车牌区域,进而得到轮廓
kernel = cv.getStructuringElement(cv.MORPH_RECT, (41, 15))
close = cv.morphologyEx(Open, cv.MORPH_CLOSE, kernel)
# cv.imshow('close', close)
# 7、膨胀/腐蚀(去噪得到车牌区域)
# 中远距离车牌识别
kernel_x = cv.getStructuringElement(cv.MORPH_RECT, (25, 7))
kernel_y = cv.getStructuringElement(cv.MORPH_RECT, (1, 11))
# 近距离车牌识别
# kernel_x = cv.getStructuringElement(cv.MORPH_RECT, (79, 15))
# kernel_y = cv.getStructuringElement(cv.MORPH_RECT, (1, 31))
# 7-1、腐蚀、膨胀(去噪)
erode_y = cv.morphologyEx(close, cv.MORPH_ERODE, kernel_y)
# cv.imshow('erode_y', erode_y)
dilate_y = cv.morphologyEx(erode_y, cv.MORPH_DILATE, kernel_y)
# cv.imshow('dilate_y', dilate_y)
# 7-1、膨胀、腐蚀(连接)(二次缝合)
dilate_x = cv.morphologyEx(dilate_y, cv.MORPH_DILATE, kernel_x)
# cv.imshow('dilate_x', dilate_x)
erode_x = cv.morphologyEx(dilate_x, cv.MORPH_ERODE, kernel_x)
# cv.imshow('erode_x', erode_x)
# 8、腐蚀、膨胀:去噪
kernel_e = cv.getStructuringElement(cv.MORPH_RECT, (25, 9))
erode = cv.morphologyEx(erode_x, cv.MORPH_ERODE, kernel_e)
# cv.imshow('erode', erode)
kernel_d = cv.getStructuringElement(cv.MORPH_RECT, (25, 11))
dilate = cv.morphologyEx(erode, cv.MORPH_DILATE, kernel_d)
# cv.imshow('dilate', dilate)
# 9、获取外轮廓
img_copy = image.copy()
# 9-1、得到轮廓
contours, hierarchy = cv.findContours(dilate, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 9-2、画出轮廓并显示
cv.drawContours(img_copy, contours, -1, (255, 0, 255), 2)
# cv.imshow('Contours', img_copy)
# 10、遍历所有轮廓,找到车牌轮廓
i = 0
for contour in contours:
# 10-1、得到矩形区域:左顶点坐标、宽和高
rect = cv.boundingRect(contour)
# 10-2、判断宽高比例是否符合车牌标准,截取符合图片
if rect[2]>rect[3]*3 and rect[2]<rect[3]*7:
# 截取车牌并显示
print(rect)
image_rect = rect
img_copy = image.copy()
image = image[(rect[1]):(rect[1]+rect[3]), (rect[0]):(rect[0]+rect[2])] #高,宽
try:
# 限制大小(按照比例限制)
image = Limit(image)
cv.imshow('license plate%d-%d' % (count, i), image)
cv.imwrite('car_licenses/image%d-%d.jpg'%(count, i), image)
i += 1
return image
except:
pass
return image
# 二、直方图提取字符
def Get_Character(image):
# 清空
final_result = []
# 1、中值滤波
mid = cv.medianBlur(image, 5)
# 2、灰度化
gray = cv.cvtColor(mid, cv.COLOR_BGR2GRAY)
# 3、二值化
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU)
# 统一得到黑底白字
if(IsWhiteMore(binary)): #白色部分多则为真,意味着背景是白色,需要黑底白字
ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU | cv.THRESH_BINARY_INV)
cv.imshow('binary', binary)
# 4、膨胀(粘贴横向字符)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (7,1)) #横向连接字符
dilate = cv.dilate(binary, kernel)
# cv.imshow('dilate', dilate)
# 5、统计各行各列白色像素个数(为了得到直方图横纵坐标)
ptx, pty = White_Statistic(dilate)
# 6、绘制直方图(横、纵)
Draw_Hist(ptx, pty)
# 7、分割(横、纵)(横向分割边框、纵向分割字符)
Cut_Image(ptx, pty, binary, dilate)
# cv.waitKey(0)
# 三、模板匹配
# 原图和模板进行对比,越匹配,得分越大
def Template_Match(image):
# 单文件夹内的最佳得分
best_score = []
# 遍历所有文件夹(每一个文件夹匹配)
# (1) 汉字(首个位置只能是汉字(省))(为了节约时间)
if con == 0:
# 遍历34——65文件夹(汉字)
for i in range(34,65):
# 单个图片的得分
score = []
ForderPath = 'Template/' + List[i]
# 遍历单文件夹(每一个文件匹配)
for filename in os.listdir(ForderPath):
# 路径
path = 'Template/' + List[i] + '/' + filename
# 1、得到模板
template = cv.imdecode(np.fromfile(path, dtype=np.uint8), 1) #彩(类似imread)
gray = cv.cvtColor(template, cv.COLOR_RGB2GRAY) #灰
ret, template = cv.threshold(gray, 0, 255, cv.THRESH_OTSU) #二值
# 2、原图限定大小(和模板相似)
h, w = template.shape
image = cv.resize(image, (w, h))
# 3、模板匹配,得到得分(匹配度越高,得分越大)
result = cv.matchTemplate(image, template, cv.TM_CCOEFF)
score.append(result[0][0]) #得分(每张模板图)
# 4、保存子文件夹的最高得分(得分越高,匹配度越高)
best_score.append(max(score))
# 5、根据所有文件夹的最佳得分确定下标
index = best_score.index(max(best_score))+34
# (2) 字母(第二个位置只能为字母)
elif con == 1:
# 遍历10~34文件夹(字母文件夹)
for i in range(10,34):
# 单个图片的得分
score = []
ForderPath = 'Template/' + List[i]
# 遍历单文件夹(每一个文件匹配)
for filename in os.listdir(ForderPath):
# 路径
path = 'Template/' + List[i] + '/' + filename
# 模板
template = cv.imdecode(np.fromfile(path, dtype=np.uint8), 1) #彩(类似imread)
gray = cv.cvtColor(template, cv.COLOR_RGB2GRAY) #灰
ret, template = cv.threshold(gray, 0, 255, cv.THRESH_OTSU) #二值
h, w = template.shape
image = cv.resize(image, (w, h))
# 模板匹配,得到得分(匹配度越高,得分越大)
result = cv.matchTemplate(image, template, cv.TM_CCOEFF)
score.append(result[0][0]) #得分(每张模板图)
# 一个文件夹的最高得分(得分越高,匹配度越高)
best_score.append(max(score))
# 根据所有文件夹的最佳得分确定下标
index = best_score.index(max(best_score)) + 10
# (3) 数字+字母
else:
# 遍历0~34文件夹(数字+字母)
for i in range(34):
# 单个图片的得分
score = []
ForderPath = 'Template/' + List[i]
# 遍历单文件夹(每一个文件匹配)
for filename in os.listdir(ForderPath):
# 路径
path = 'Template/' + List[i] + '/' + filename
# 模板
template = cv.imdecode(np.fromfile(path, dtype=np.uint8), 1) #彩(类似imread)
gray = cv.cvtColor(template, cv.COLOR_RGB2GRAY) #灰
ret, template = cv.threshold(gray, 0, 255, cv.THRESH_OTSU) #二值
h, w = template.shape
image = cv.resize(image, (w, h))
# 模板匹配,得到得分(匹配度越高,得分越大)
result = cv.matchTemplate(image, template, cv.TM_CCOEFF)
score.append(result[0][0]) #得分(每张模板图)
# 一个文件夹的最高得分(得分越高,匹配度越高)
best_score.append(max(score))
# 根据所有文件夹的最佳得分确定下标
index = best_score.index(max(best_score))
# 显示结果(文字)(每识别一个显示一次)
Show_Result_Words(index)
if __name__ == '__main__':
global count, img
count=0 #计数:第几张图片
# cv.waitKey(0)
# 遍历文件夹中的每张图片(车)
for car in os.listdir('cars'):
# 1、获取路径
path = 'cars/'+'car'+str(count)+'.jpg'
# 2、获取图片
img = cv.imread(path)
image = img.copy()
# cv.imshow('image', image)
# 3、提取车牌
image = Get_Licenses(image) #形态学提取车牌
# 4、分割字符
Get_Character(image)
count += 1
cv.waitKey(0)
一张车牌约20秒,这些算法很多可能已经逐渐被淘汰了,这里只是作为学习用途,没有太高的实际应用价值。(后期进军深度学习/机器学习,可能会对这些进行优化)。有什么好的建议大家可以提出来,共同进步,谢谢~
以上是关于OpenCV(项目)车牌识别4 -- 总结篇的主要内容,如果未能解决你的问题,请参考以下文章