|
|
|
|
import numpy as np
|
|
|
|
|
import cv2
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
from matplotlib import font_manager
|
|
|
|
|
import ddddocr
|
|
|
|
|
import threadsafe_tkinter as tk
|
|
|
|
|
from tkinter import filedialog
|
|
|
|
|
font_path = "C:/Users/Lenovo/Downloads/OPPOSans3.0/OPPOSans-Regular.ttf"
|
|
|
|
|
|
|
|
|
|
# 动态添加字体到 matplotlib 的字体列表
|
|
|
|
|
font = font_manager.FontProperties(fname=font_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Get_license():
|
|
|
|
|
|
|
|
|
|
#图像拉伸函数
|
|
|
|
|
def stretch(self, 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(self, img):
|
|
|
|
|
# 使用OpenCV的自动阈值方法
|
|
|
|
|
ret, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
|
|
|
|
return thresh
|
|
|
|
|
|
|
|
|
|
#寻找矩形的轮廓
|
|
|
|
|
def find_rectangle(self, 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(self, img, afterimg):
|
|
|
|
|
|
|
|
|
|
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
|
|
|
|
|
|
#找出最大的三个区域
|
|
|
|
|
block = []
|
|
|
|
|
for c in contours:
|
|
|
|
|
#找出轮廓的左上点和右下点
|
|
|
|
|
#由此计算它的面积和长度比
|
|
|
|
|
r = self.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])
|
|
|
|
|
|
|
|
|
|
#选出面积最大的3个区域
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 计算mask中白色像素(即HSV范围内的像素)的数量
|
|
|
|
|
weight = np.sum(mask == 255) # 或者简单地使用 np.count_nonzero(mask)
|
|
|
|
|
|
|
|
|
|
# 选出最大权值的区域
|
|
|
|
|
if weight > maxweight:
|
|
|
|
|
maxindex = i
|
|
|
|
|
maxweight = weight
|
|
|
|
|
|
|
|
|
|
return block[maxindex][0]
|
|
|
|
|
|
|
|
|
|
#预处理函数
|
|
|
|
|
def find_license(self, img):
|
|
|
|
|
|
|
|
|
|
m = 400 * img.shape[0] / img.shape[1]
|
|
|
|
|
|
|
|
|
|
#压缩图像
|
|
|
|
|
img = cv2.resize(img, (400, int(m)), interpolation=cv2.INTER_CUBIC)
|
|
|
|
|
|
|
|
|
|
#BGR转换为灰度图像
|
|
|
|
|
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
|
|
|
|
|
|
|
|
#灰度拉伸
|
|
|
|
|
stretchedimg = self.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 = self.dobinaryzation(strtimg)
|
|
|
|
|
|
|
|
|
|
#canny边缘检测
|
|
|
|
|
canny = cv2.Canny(binaryimg, binaryimg.shape[0], binaryimg.shape[1])
|
|
|
|
|
|
|
|
|
|
'''消除小的区域,保留大块的区域,从而定位车牌'''
|
|
|
|
|
#进行闭运算
|
|
|
|
|
kernel = np.ones((5, 17), np.uint8)
|
|
|
|
|
closingimg = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, kernel)
|
|
|
|
|
|
|
|
|
|
#进行开运算
|
|
|
|
|
openingimg = cv2.morphologyEx(closingimg, cv2.MORPH_OPEN, kernel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#消除小区域,定位车牌位置
|
|
|
|
|
rect = self.locate_license(openingimg, img)
|
|
|
|
|
|
|
|
|
|
return rect, img
|
|
|
|
|
#图像分割函数
|
|
|
|
|
def cut_license(self, afterimg, rect):
|
|
|
|
|
|
|
|
|
|
#转换为宽度和高度
|
|
|
|
|
rect[2] = rect[2] - rect[0]
|
|
|
|
|
rect[3] = rect[3] - rect[1]
|
|
|
|
|
rect_copy = tuple(rect.copy())
|
|
|
|
|
#创建掩膜
|
|
|
|
|
mask = np.zeros(afterimg.shape[:2], np.uint8)
|
|
|
|
|
#创建背景模型 大小只能为13*5,行数只能为1,单通道浮点型
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Segmentation():
|
|
|
|
|
def __init__(self, cutimg):
|
|
|
|
|
|
|
|
|
|
#1、读取图像,并把图像转换为灰度图像并显示
|
|
|
|
|
|
|
|
|
|
img_gray = cv2.cvtColor(cutimg, cv2.COLOR_BGR2GRAY) #转换了灰度化
|
|
|
|
|
|
|
|
|
|
#2、将灰度图像二值化,设定阈值是150
|
|
|
|
|
self.img_thre = img_gray
|
|
|
|
|
cv2.threshold(img_gray, 150
|
|
|
|
|
, 255, cv2.THRESH_BINARY_INV, self.img_thre)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#3、保存黑白图片
|
|
|
|
|
cv2.imwrite('thre_res.jpg', self.img_thre)
|
|
|
|
|
|
|
|
|
|
#4、分割字符
|
|
|
|
|
self.white = [] #记录每一列的白色像素总和
|
|
|
|
|
self.black = [] #黑色
|
|
|
|
|
self.height = self.img_thre.shape[0]
|
|
|
|
|
self.width = self.img_thre.shape[1]
|
|
|
|
|
self.white_max = 0
|
|
|
|
|
self.black_max = 0
|
|
|
|
|
#计算每一列的黑白色像素总和
|
|
|
|
|
for i in range(self.width):
|
|
|
|
|
white_count = 0 #这一列白色总数
|
|
|
|
|
black_count = 0 #这一列黑色总数
|
|
|
|
|
for j in range(self.height):
|
|
|
|
|
if self.img_thre[j][i] == 255:
|
|
|
|
|
white_count += 1
|
|
|
|
|
if self.img_thre[j][i] == 0:
|
|
|
|
|
black_count += 1
|
|
|
|
|
self.white_max = max(self.white_max, white_count)
|
|
|
|
|
self.black_max = max(self.black_max, black_count)
|
|
|
|
|
self.white.append(white_count)
|
|
|
|
|
self.black.append(black_count)
|
|
|
|
|
|
|
|
|
|
self.arg = False #False表示白底黑字;True表示黑底白字
|
|
|
|
|
if self.black_max > self.white_max:
|
|
|
|
|
self.arg = True
|
|
|
|
|
|
|
|
|
|
def heibai(self):
|
|
|
|
|
return self.img_thre
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_end(self, start_):
|
|
|
|
|
end_ = start_ + 1
|
|
|
|
|
for m in range(start_ + 1, self.width - 1):
|
|
|
|
|
if (self.black[m] if self.arg else self.white[m]) > (
|
|
|
|
|
0.85 * self.black_max if self.arg else 0.85 * self.white_max):
|
|
|
|
|
end_ = m
|
|
|
|
|
break
|
|
|
|
|
return end_
|
|
|
|
|
|
|
|
|
|
def display(self):
|
|
|
|
|
#img_list = []
|
|
|
|
|
n = 1
|
|
|
|
|
plt.figure()
|
|
|
|
|
img_num = 0
|
|
|
|
|
while n < self.width - 2:
|
|
|
|
|
n += 1
|
|
|
|
|
if (self.white[n] if self.arg else self.black[n]) > (
|
|
|
|
|
0.15 * self.white_max if self.arg else 0.15 * self.black_max):
|
|
|
|
|
#上面这些判断用来辨别是白底黑字还是黑底白字
|
|
|
|
|
|
|
|
|
|
start = n
|
|
|
|
|
end = self.find_end(start)
|
|
|
|
|
n = end
|
|
|
|
|
|
|
|
|
|
if end - start > 5:
|
|
|
|
|
cj = self.img_thre[1:self.height, start:end]
|
|
|
|
|
img_num += 1
|
|
|
|
|
cj = cv2.cvtColor(cj, cv2.COLOR_RGB2BGR)
|
|
|
|
|
|
|
|
|
|
plt.figure(2)
|
|
|
|
|
plt.subplot(2, 4, img_num)
|
|
|
|
|
plt.title('{}'.format(img_num))
|
|
|
|
|
plt.imshow(cj)
|
|
|
|
|
plt.show()
|
|
|
|
|
return self.img_thre
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
|
|
|
|
def select_image():
|
|
|
|
|
# 弹出文件选择对话框,限制文件类型为图片
|
|
|
|
|
file_path = filedialog.askopenfilename()
|
|
|
|
|
if file_path:
|
|
|
|
|
# 将文件路径显示在标签上
|
|
|
|
|
img = cv2.imread(file_path)
|
|
|
|
|
img1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
|
|
|
|
# 绘图
|
|
|
|
|
plt.figure(1)
|
|
|
|
|
plt.suptitle('车牌识别', fontproperties=font)
|
|
|
|
|
plt.subplot(2, 3, 1)
|
|
|
|
|
plt.title('原始图像', fontproperties=font)
|
|
|
|
|
plt.imshow(img1)
|
|
|
|
|
|
|
|
|
|
# 预处理图像
|
|
|
|
|
license = Get_license()
|
|
|
|
|
rect, afterimg = license.find_license(img)
|
|
|
|
|
afterimg = cv2.cvtColor(afterimg, cv2.COLOR_RGB2BGR)
|
|
|
|
|
|
|
|
|
|
plt.subplot(2, 3, 2)
|
|
|
|
|
plt.title('预处理后图像', fontproperties=font)
|
|
|
|
|
plt.imshow(afterimg)
|
|
|
|
|
|
|
|
|
|
# 车牌号打框
|
|
|
|
|
cv2.rectangle(afterimg, (rect[0], rect[1]), (rect[2], rect[3]), (0, 255, 0), 1)
|
|
|
|
|
x1, y1, x2, y2 = int(rect[0]), int(rect[1]), int(rect[2]), int(rect[3])
|
|
|
|
|
|
|
|
|
|
plt.subplot(2, 3, 3)
|
|
|
|
|
plt.title('车牌框出', fontproperties=font)
|
|
|
|
|
plt.imshow(afterimg)
|
|
|
|
|
|
|
|
|
|
# 背景去除
|
|
|
|
|
cutimg = license.cut_license(afterimg, rect)
|
|
|
|
|
plt.subplot(2, 3, 4)
|
|
|
|
|
plt.title('车牌背景去除', fontproperties=font)
|
|
|
|
|
plt.imshow(cutimg)
|
|
|
|
|
# print(int(_rect[0]), int(_rect[3]), int(_rect[2]), int(_rect[1]))
|
|
|
|
|
|
|
|
|
|
# 开始分割车牌
|
|
|
|
|
# cutimg = cutimg[140:165, 151:240]
|
|
|
|
|
cutimg = cutimg[y1 + 3:y2 - 3, x1 - 1:x2 - 3]
|
|
|
|
|
# cutimg = cutimg[int(_rect[0]):int(_rect[3]),int(_rect[2]):int(_rect[1])]
|
|
|
|
|
|
|
|
|
|
height, width = cutimg.shape[:2]
|
|
|
|
|
cutimg1 = cv2.resize(cutimg, (2 * width, 2 * height), interpolation=cv2.INTER_CUBIC)
|
|
|
|
|
plt.subplot(2, 3, 5)
|
|
|
|
|
plt.title('分割车牌与背景', fontproperties=font)
|
|
|
|
|
plt.imshow(cutimg)
|
|
|
|
|
|
|
|
|
|
# 字符切割
|
|
|
|
|
seg = Segmentation(cutimg)
|
|
|
|
|
plt.subplot(2, 3, 6)
|
|
|
|
|
img_hei = seg.heibai()
|
|
|
|
|
img_hei = cv2.cvtColor(img_hei, cv2.COLOR_RGB2BGR)
|
|
|
|
|
plt.title('车牌二值化处理', fontproperties=font)
|
|
|
|
|
plt.imshow(img_hei)
|
|
|
|
|
seg.display()
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
# 打印车牌
|
|
|
|
|
ocr = ddddocr.DdddOcr()
|
|
|
|
|
with open('thre_res.jpg', 'rb') as f:
|
|
|
|
|
image = f.read()
|
|
|
|
|
res = ocr.classification(image)
|
|
|
|
|
print(res)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 创建根窗口
|
|
|
|
|
root = tk.Tk()
|
|
|
|
|
root.title('车牌识别程序')
|
|
|
|
|
root.geometry('600x450')
|
|
|
|
|
# 创建一个标签用于显示图片路径
|
|
|
|
|
label = tk.Label(root, text='未选择图片')
|
|
|
|
|
label.pack(pady=20) # 使用pack布局管理器,并添加一些垂直填充
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def exit_app():
|
|
|
|
|
# 退出应用程序的函数
|
|
|
|
|
root.destroy()
|
|
|
|
|
# 创建一个按钮,点击时会调用select_image函数
|
|
|
|
|
button = tk.Button(root, text='选择图片', command=select_image)
|
|
|
|
|
button.pack()
|
|
|
|
|
button_exit = tk.Button(root, text='退出', command=exit_app)
|
|
|
|
|
button_exit.pack(pady=30)
|
|
|
|
|
# 启动事件循环
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|