Skip to content

Commit 7d76916

Browse files
Merge pull request avinashkranjan#1989 from SyedImtiyaz-1/ChinesePlate
Chinese Plate Scanner Added
2 parents 3747bc0 + 4fcce05 commit 7d76916

File tree

17 files changed

+440
-0
lines changed

17 files changed

+440
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# -*- coding:utf-8 -*-
2+
# author: DuanshengLiu
3+
from tensorflow.keras import layers, losses, models
4+
import numpy as np
5+
import cv2
6+
import os
7+
8+
9+
def cnn_train():
10+
char_dict = {"京": 0, "沪": 1, "津": 2, "渝": 3, "冀": 4, "晋": 5, "蒙": 6, "辽": 7, "吉": 8, "黑": 9, "苏": 10,
11+
"浙": 11, "皖": 12, "闽": 13, "赣": 14, "鲁": 15, "豫": 16, "鄂": 17, "湘": 18, "粤": 19, "桂": 20,
12+
"琼": 21, "川": 22, "贵": 23, "云": 24, "藏": 25, "陕": 26, "甘": 27, "青": 28, "宁": 29, "新": 30,
13+
"0": 31, "1": 32, "2": 33, "3": 34, "4": 35, "5": 36, "6": 37, "7": 38, "8": 39, "9": 40,
14+
"A": 41, "B": 42, "C": 43, "D": 44, "E": 45, "F": 46, "G": 47, "H": 48, "J": 49, "K": 50,
15+
"L": 51, "M": 52, "N": 53, "P": 54, "Q": 55, "R": 56, "S": 57, "T": 58, "U": 59, "V": 60,
16+
"W": 61, "X": 62, "Y": 63, "Z": 64}
17+
18+
# 读取数据集
19+
path = 'home/cnn_datasets/' # 车牌号数据集路径(车牌图片宽240,高80)
20+
pic_name = sorted(os.listdir(path))
21+
n = len(pic_name)
22+
X_train, y_train = [], []
23+
for i in range(n):
24+
print("正在读取第%d张图片" % i)
25+
img = cv2.imdecode(np.fromfile(path + pic_name[i], dtype=np.uint8), -1) # cv2.imshow无法读取中文路径图片,改用此方式
26+
label = [char_dict[name] for name in pic_name[i][0:7]] # 图片名前7位为车牌标签
27+
X_train.append(img)
28+
y_train.append(label)
29+
X_train = np.array(X_train)
30+
y_train = [np.array(y_train)[:, i] for i in range(7)] # y_train是长度为7的列表,其中每个都是shape为(n,)的ndarray,分别对应n张图片的第一个字符,第二个字符....第七个字符
31+
32+
# cnn模型
33+
Input = layers.Input((80, 240, 3)) # 车牌图片shape(80,240,3)
34+
x = Input
35+
x = layers.Conv2D(filters=16, kernel_size=(3, 3), strides=1, padding='same', activation='relu')(x)
36+
x = layers.MaxPool2D(pool_size=(2, 2), padding='same', strides=2)(x)
37+
for i in range(3):
38+
x = layers.Conv2D(filters=32 * 2 ** i, kernel_size=(3, 3), padding='valid', activation='relu')(x)
39+
x = layers.Conv2D(filters=32 * 2 ** i, kernel_size=(3, 3), padding='valid', activation='relu')(x)
40+
x = layers.MaxPool2D(pool_size=(2, 2), padding='same', strides=2)(x)
41+
x = layers.Dropout(0.5)(x)
42+
x = layers.Flatten()(x)
43+
x = layers.Dropout(0.3)(x)
44+
Output = [layers.Dense(65, activation='softmax', name='c%d' % (i + 1))(x) for i in range(7)] # 7个输出分别对应车牌7个字符,每个输出都为65个类别类概率
45+
model = models.Model(inputs=Input, outputs=Output)
46+
model.summary()
47+
model.compile(optimizer='adam',
48+
loss='sparse_categorical_crossentropy', # y_train未进行one-hot编码,所以loss选择sparse_categorical_crossentropy
49+
metrics=['accuracy'])
50+
51+
# 模型训练
52+
print("开始训练cnn")
53+
model.fit(X_train, y_train, epochs=35) # 总loss为7个loss的和
54+
model.save('cnn.h5')
55+
print('cnn.h5保存成功!!!')
56+
57+
58+
def cnn_predict(cnn, Lic_img):
59+
characters = ["京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫",
60+
"鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2",
61+
"3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M",
62+
"N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
63+
Lic_pred = []
64+
for lic in Lic_img:
65+
lic_pred = cnn.predict(lic.reshape(1, 80, 240, 3)) # 预测形状应为(1,80,240,3)
66+
lic_pred = np.array(lic_pred).reshape(7, 65) # 列表转为ndarray,形状为(7,65)
67+
if len(lic_pred[lic_pred >= 0.8]) >= 4: # 统计其中预测概率值大于80%以上的个数,大于等于4个以上认为识别率高,识别成功
68+
chars = ''
69+
for arg in np.argmax(lic_pred, axis=1): # 取每行中概率值最大的arg,将其转为字符
70+
chars += characters[arg]
71+
chars = chars[0:2] + '·' + chars[2:]
72+
Lic_pred.append((lic, chars)) # 将车牌和识别结果一并存入Lic_pred
73+
return Lic_pred
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# End-to-end-for-chinese-plate-recognition
2+
3+
Chinese license plate recognition software based on U-Net, OpenCV, and CNN, where U-Net and OpenCV are used for license plate localization and correction, and CNN is used for license plate recognition. Both U-Net and CNN are implemented using TensorFlow's Keras.
4+
5+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/lic.png)
6+
### Other than that, there are no major issues, and normal recognition works well.
7+
### Here are some sample result images:
8+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/0.png)
9+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/1.png)
10+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/2.png)
11+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/3.png)
12+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/4.png)
13+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/5.png)
14+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/6.png)
15+
![](https://github.com/duanshengliu/End-to-end-for-chinese-plate-recognition/blob/master/test_pic/7.png)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# -*- coding:utf-8 -*-
2+
# author: DuanshengLiu
3+
import cv2
4+
import numpy as np
5+
from tkinter import *
6+
from tkinter.filedialog import askopenfilename
7+
from PIL import Image, ImageTk
8+
from tensorflow import keras
9+
from core import locate_and_correct
10+
from Unet import unet_predict
11+
from CNN import cnn_predict
12+
13+
14+
class Window:
15+
def __init__(self, win, ww, wh):
16+
self.win = win
17+
self.ww = ww
18+
self.wh = wh
19+
self.win.geometry("%dx%d+%d+%d" % (ww, wh, 200, 50)) # 界面启动时的初始位置
20+
self.win.title("车牌定位,矫正和识别软件---by DuanshengLiu")
21+
self.img_src_path = None
22+
23+
self.label_src = Label(self.win, text='原图:', font=('微软雅黑', 13)).place(x=0, y=0)
24+
self.label_lic1 = Label(self.win, text='车牌区域1:', font=('微软雅黑', 13)).place(x=615, y=0)
25+
self.label_pred1 = Label(self.win, text='识别结果1:', font=('微软雅黑', 13)).place(x=615, y=85)
26+
self.label_lic2 = Label(self.win, text='车牌区域2:', font=('微软雅黑', 13)).place(x=615, y=180)
27+
self.label_pred2 = Label(self.win, text='识别结果2:', font=('微软雅黑', 13)).place(x=615, y=265)
28+
self.label_lic3 = Label(self.win, text='车牌区域3:', font=('微软雅黑', 13)).place(x=615, y=360)
29+
self.label_pred3 = Label(self.win, text='识别结果3:', font=('微软雅黑', 13)).place(x=615, y=445)
30+
31+
self.can_src = Canvas(self.win, width=512, height=512, bg='white', relief='solid', borderwidth=1) # 原图画布
32+
self.can_src.place(x=50, y=0)
33+
self.can_lic1 = Canvas(self.win, width=245, height=85, bg='white', relief='solid', borderwidth=1) # 车牌区域1画布
34+
self.can_lic1.place(x=710, y=0)
35+
self.can_pred1 = Canvas(self.win, width=245, height=65, bg='white', relief='solid', borderwidth=1) # 车牌识别1画布
36+
self.can_pred1.place(x=710, y=90)
37+
self.can_lic2 = Canvas(self.win, width=245, height=85, bg='white', relief='solid', borderwidth=1) # 车牌区域2画布
38+
self.can_lic2.place(x=710, y=175)
39+
self.can_pred2 = Canvas(self.win, width=245, height=65, bg='white', relief='solid', borderwidth=1) # 车牌识别2画布
40+
self.can_pred2.place(x=710, y=265)
41+
self.can_lic3 = Canvas(self.win, width=245, height=85, bg='white', relief='solid', borderwidth=1) # 车牌区域3画布
42+
self.can_lic3.place(x=710, y=350)
43+
self.can_pred3 = Canvas(self.win, width=245, height=65, bg='white', relief='solid', borderwidth=1) # 车牌识别3画布
44+
self.can_pred3.place(x=710, y=440)
45+
46+
self.button1 = Button(self.win, text='选择文件', width=10, height=1, command=self.load_show_img) # 选择文件按钮
47+
self.button1.place(x=680, y=wh - 30)
48+
self.button2 = Button(self.win, text='识别车牌', width=10, height=1, command=self.display) # 识别车牌按钮
49+
self.button2.place(x=780, y=wh - 30)
50+
self.button3 = Button(self.win, text='清空所有', width=10, height=1, command=self.clear) # 清空所有按钮
51+
self.button3.place(x=880, y=wh - 30)
52+
self.unet = keras.models.load_model('unet.h5')
53+
self.cnn = keras.models.load_model('cnn.h5')
54+
print('正在启动中,请稍等...')
55+
cnn_predict(self.cnn, [np.zeros((80, 240, 3))])
56+
print("已启动,开始识别吧!")
57+
58+
59+
def load_show_img(self):
60+
self.clear()
61+
sv = StringVar()
62+
sv.set(askopenfilename())
63+
self.img_src_path = Entry(self.win, state='readonly', text=sv).get() # 获取到所打开的图片
64+
img_open = Image.open(self.img_src_path)
65+
if img_open.size[0] * img_open.size[1] > 240 * 80:
66+
img_open = img_open.resize((512, 512), Image.ANTIALIAS)
67+
self.img_Tk = ImageTk.PhotoImage(img_open)
68+
self.can_src.create_image(258, 258, image=self.img_Tk, anchor='center')
69+
70+
def display(self):
71+
if self.img_src_path == None: # 还没选择图片就进行预测
72+
self.can_pred1.create_text(32, 15, text='请选择图片', anchor='nw', font=('黑体', 28))
73+
else:
74+
img_src = cv2.imdecode(np.fromfile(self.img_src_path, dtype=np.uint8), -1) # 从中文路径读取时用
75+
h, w = img_src.shape[0], img_src.shape[1]
76+
if h * w <= 240 * 80 and 2 <= w / h <= 5: # 满足该条件说明可能整个图片就是一张车牌,无需定位,直接识别即可
77+
lic = cv2.resize(img_src, dsize=(240, 80), interpolation=cv2.INTER_AREA)[:, :, :3] # 直接resize为(240,80)
78+
img_src_copy, Lic_img = img_src, [lic]
79+
else: # 否则就需通过unet对img_src原图预测,得到img_mask,实现车牌定位,然后进行识别
80+
img_src, img_mask = unet_predict(self.unet, self.img_src_path)
81+
img_src_copy, Lic_img = locate_and_correct(img_src, img_mask) # 利用core.py中的locate_and_correct函数进行车牌定位和矫正
82+
83+
Lic_pred = cnn_predict(self.cnn, Lic_img) # 利用cnn进行车牌的识别预测,Lic_pred中存的是元祖(车牌图片,识别结果)
84+
if Lic_pred:
85+
img = Image.fromarray(img_src_copy[:, :, ::-1]) # img_src_copy[:, :, ::-1]将BGR转为RGB
86+
self.img_Tk = ImageTk.PhotoImage(img)
87+
self.can_src.delete('all') # 显示前,先清空画板
88+
self.can_src.create_image(258, 258, image=self.img_Tk,
89+
anchor='center') # img_src_copy上绘制出了定位的车牌轮廓,将其显示在画板上
90+
for i, lic_pred in enumerate(Lic_pred):
91+
if i == 0:
92+
self.lic_Tk1 = ImageTk.PhotoImage(Image.fromarray(lic_pred[0][:, :, ::-1]))
93+
self.can_lic1.create_image(5, 5, image=self.lic_Tk1, anchor='nw')
94+
self.can_pred1.create_text(35, 15, text=lic_pred[1], anchor='nw', font=('黑体', 28))
95+
elif i == 1:
96+
self.lic_Tk2 = ImageTk.PhotoImage(Image.fromarray(lic_pred[0][:, :, ::-1]))
97+
self.can_lic2.create_image(5, 5, image=self.lic_Tk2, anchor='nw')
98+
self.can_pred2.create_text(40, 15, text=lic_pred[1], anchor='nw', font=('黑体', 28))
99+
elif i == 2:
100+
self.lic_Tk3 = ImageTk.PhotoImage(Image.fromarray(lic_pred[0][:, :, ::-1]))
101+
self.can_lic3.create_image(5, 5, image=self.lic_Tk3, anchor='nw')
102+
self.can_pred3.create_text(40, 15, text=lic_pred[1], anchor='nw', font=('黑体', 28))
103+
104+
else: # Lic_pred为空说明未能识别
105+
self.can_pred1.create_text(47, 15, text='未能识别', anchor='nw', font=('黑体', 27))
106+
107+
def clear(self):
108+
self.can_src.delete('all')
109+
self.can_lic1.delete('all')
110+
self.can_lic2.delete('all')
111+
self.can_lic3.delete('all')
112+
self.can_pred1.delete('all')
113+
self.can_pred2.delete('all')
114+
self.can_pred3.delete('all')
115+
self.img_src_path = None
116+
117+
def closeEvent(): # 关闭前清除session(),防止'NoneType' object is not callable
118+
keras.backend.clear_session()
119+
sys.exit()
120+
121+
122+
if __name__ == '__main__':
123+
win = Tk()
124+
ww = 1000 # 窗口宽设定1000
125+
wh = 600 # 窗口高设定600
126+
Window(win, ww, wh)
127+
win.protocol("WM_DELETE_WINDOW", Window.closeEvent)
128+
win.mainloop()
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# -*- coding:utf-8 -*-
2+
# author: DuanshengLiu
3+
import numpy as np
4+
import os
5+
import cv2
6+
from tensorflow.keras import layers, losses, models
7+
8+
9+
def unet_train():
10+
height = 512
11+
width = 512
12+
path = 'D:/desktop/unet_datasets/'
13+
input_name = os.listdir(path + 'train_image')
14+
n = len(input_name)
15+
print(n)
16+
X_train, y_train = [], []
17+
for i in range(n):
18+
img = cv2.imread(path + 'train_image/%d.png' % i)
19+
label = cv2.imread(path + 'train_label/%d.png' % i)
20+
X_train.append(img)
21+
y_train.append(label)
22+
X_train = np.array(X_train)
23+
y_train = np.array(y_train)
24+
25+
26+
def Conv2d_BN(x, nb_filter, kernel_size, strides=(1, 1), padding='same'):
27+
x = layers.Conv2D(nb_filter, kernel_size, strides=strides, padding=padding)(x)
28+
x = layers.BatchNormalization(axis=3)(x)
29+
x = layers.LeakyReLU(alpha=0.1)(x)
30+
return x
31+
32+
def Conv2dT_BN(x, filters, kernel_size, strides=(2, 2), padding='same'):
33+
x = layers.Conv2DTranspose(filters, kernel_size, strides=strides, padding=padding)(x)
34+
x = layers.BatchNormalization(axis=3)(x)
35+
x = layers.LeakyReLU(alpha=0.1)(x)
36+
return x
37+
38+
inpt = layers.Input(shape=(height, width, 3))
39+
conv1 = Conv2d_BN(inpt, 8, (3, 3))
40+
conv1 = Conv2d_BN(conv1, 8, (3, 3))
41+
pool1 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv1)
42+
43+
conv2 = Conv2d_BN(pool1, 16, (3, 3))
44+
conv2 = Conv2d_BN(conv2, 16, (3, 3))
45+
pool2 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv2)
46+
47+
conv3 = Conv2d_BN(pool2, 32, (3, 3))
48+
conv3 = Conv2d_BN(conv3, 32, (3, 3))
49+
pool3 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv3)
50+
51+
conv4 = Conv2d_BN(pool3, 64, (3, 3))
52+
conv4 = Conv2d_BN(conv4, 64, (3, 3))
53+
pool4 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv4)
54+
55+
conv5 = Conv2d_BN(pool4, 128, (3, 3))
56+
conv5 = layers.Dropout(0.5)(conv5)
57+
conv5 = Conv2d_BN(conv5, 128, (3, 3))
58+
conv5 = layers.Dropout(0.5)(conv5)
59+
60+
convt1 = Conv2dT_BN(conv5, 64, (3, 3))
61+
concat1 = layers.concatenate([conv4, convt1], axis=3)
62+
concat1 = layers.Dropout(0.5)(concat1)
63+
conv6 = Conv2d_BN(concat1, 64, (3, 3))
64+
conv6 = Conv2d_BN(conv6, 64, (3, 3))
65+
66+
convt2 = Conv2dT_BN(conv6, 32, (3, 3))
67+
concat2 = layers.concatenate([conv3, convt2], axis=3)
68+
concat2 = layers.Dropout(0.5)(concat2)
69+
conv7 = Conv2d_BN(concat2, 32, (3, 3))
70+
conv7 = Conv2d_BN(conv7, 32, (3, 3))
71+
72+
convt3 = Conv2dT_BN(conv7, 16, (3, 3))
73+
concat3 = layers.concatenate([conv2, convt3], axis=3)
74+
concat3 = layers.Dropout(0.5)(concat3)
75+
conv8 = Conv2d_BN(concat3, 16, (3, 3))
76+
conv8 = Conv2d_BN(conv8, 16, (3, 3))
77+
78+
convt4 = Conv2dT_BN(conv8, 8, (3, 3))
79+
concat4 = layers.concatenate([conv1, convt4], axis=3)
80+
concat4 = layers.Dropout(0.5)(concat4)
81+
conv9 = Conv2d_BN(concat4, 8, (3, 3))
82+
conv9 = Conv2d_BN(conv9, 8, (3, 3))
83+
conv9 = layers.Dropout(0.5)(conv9)
84+
outpt = layers.Conv2D(filters=3, kernel_size=(1, 1), strides=(1, 1), padding='same', activation='relu')(conv9)
85+
86+
model = models.Model(inpt, outpt)
87+
model.compile(optimizer='adam',
88+
loss='mean_squared_error',
89+
metrics=['accuracy'])
90+
model.summary()
91+
92+
print("开始训练u-net")
93+
model.fit(X_train, y_train, epochs=100, batch_size=15)
94+
model.save('unet.h5')
95+
print('unet.h5保存成功!!!')
96+
97+
98+
def unet_predict(unet, img_src_path):
99+
img_src = cv2.imdecode(np.fromfile(img_src_path, dtype=np.uint8), -1)
100+
if img_src.shape != (512, 512, 3):
101+
img_src = cv2.resize(img_src, dsize=(512, 512), interpolation=cv2.INTER_AREA)[:, :, :3]
102+
img_src = img_src.reshape(1, 512, 512, 3)
103+
img_mask = unet.predict(img_src)
104+
img_src = img_src.reshape(512, 512, 3)
105+
img_mask = img_mask.reshape(512, 512, 3)
106+
img_mask = img_mask / np.max(img_mask) * 255
107+
img_mask[:, :, 2] = img_mask[:, :, 1] = img_mask[:, :, 0]
108+
img_mask = img_mask.astype(np.uint8)
109+
110+
return img_src, img_mask
Binary file not shown.

0 commit comments

Comments
 (0)