Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 54bfbed

Browse files
Chinese Plate Scanner Added
1 parent cc6dc44 commit 54bfbed

File tree

17 files changed

+444
-0
lines changed

17 files changed

+444
-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: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
print("正在读取第%d张图片" % i)
19+
img = cv2.imread(path + 'train_image/%d.png' % i)
20+
label = cv2.imread(path + 'train_label/%d.png' % i)
21+
X_train.append(img)
22+
y_train.append(label)
23+
X_train = np.array(X_train)
24+
y_train = np.array(y_train)
25+
26+
27+
def Conv2d_BN(x, nb_filter, kernel_size, strides=(1, 1), padding='same'):
28+
x = layers.Conv2D(nb_filter, kernel_size, strides=strides, padding=padding)(x)
29+
x = layers.BatchNormalization(axis=3)(x)
30+
x = layers.LeakyReLU(alpha=0.1)(x)
31+
return x
32+
33+
def Conv2dT_BN(x, filters, kernel_size, strides=(2, 2), padding='same'):
34+
x = layers.Conv2DTranspose(filters, kernel_size, strides=strides, padding=padding)(x)
35+
x = layers.BatchNormalization(axis=3)(x)
36+
x = layers.LeakyReLU(alpha=0.1)(x)
37+
return x
38+
39+
inpt = layers.Input(shape=(height, width, 3))
40+
conv1 = Conv2d_BN(inpt, 8, (3, 3))
41+
conv1 = Conv2d_BN(conv1, 8, (3, 3))
42+
pool1 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv1)
43+
44+
conv2 = Conv2d_BN(pool1, 16, (3, 3))
45+
conv2 = Conv2d_BN(conv2, 16, (3, 3))
46+
pool2 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv2)
47+
48+
conv3 = Conv2d_BN(pool2, 32, (3, 3))
49+
conv3 = Conv2d_BN(conv3, 32, (3, 3))
50+
pool3 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv3)
51+
52+
conv4 = Conv2d_BN(pool3, 64, (3, 3))
53+
conv4 = Conv2d_BN(conv4, 64, (3, 3))
54+
pool4 = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(conv4)
55+
56+
conv5 = Conv2d_BN(pool4, 128, (3, 3))
57+
conv5 = layers.Dropout(0.5)(conv5)
58+
conv5 = Conv2d_BN(conv5, 128, (3, 3))
59+
conv5 = layers.Dropout(0.5)(conv5)
60+
61+
convt1 = Conv2dT_BN(conv5, 64, (3, 3))
62+
concat1 = layers.concatenate([conv4, convt1], axis=3)
63+
concat1 = layers.Dropout(0.5)(concat1)
64+
conv6 = Conv2d_BN(concat1, 64, (3, 3))
65+
conv6 = Conv2d_BN(conv6, 64, (3, 3))
66+
67+
convt2 = Conv2dT_BN(conv6, 32, (3, 3))
68+
concat2 = layers.concatenate([conv3, convt2], axis=3)
69+
concat2 = layers.Dropout(0.5)(concat2)
70+
conv7 = Conv2d_BN(concat2, 32, (3, 3))
71+
conv7 = Conv2d_BN(conv7, 32, (3, 3))
72+
73+
convt3 = Conv2dT_BN(conv7, 16, (3, 3))
74+
concat3 = layers.concatenate([conv2, convt3], axis=3)
75+
concat3 = layers.Dropout(0.5)(concat3)
76+
conv8 = Conv2d_BN(concat3, 16, (3, 3))
77+
conv8 = Conv2d_BN(conv8, 16, (3, 3))
78+
79+
convt4 = Conv2dT_BN(conv8, 8, (3, 3))
80+
concat4 = layers.concatenate([conv1, convt4], axis=3)
81+
concat4 = layers.Dropout(0.5)(concat4)
82+
conv9 = Conv2d_BN(concat4, 8, (3, 3))
83+
conv9 = Conv2d_BN(conv9, 8, (3, 3))
84+
conv9 = layers.Dropout(0.5)(conv9)
85+
outpt = layers.Conv2D(filters=3, kernel_size=(1, 1), strides=(1, 1), padding='same', activation='relu')(conv9)
86+
87+
model = models.Model(inpt, outpt)
88+
model.compile(optimizer='adam',
89+
loss='mean_squared_error',
90+
metrics=['accuracy'])
91+
model.summary()
92+
93+
print("开始训练u-net")
94+
model.fit(X_train, y_train, epochs=100, batch_size=15)#epochs和batch_size看个人情况调整,batch_size不要过大,否则内存容易溢出
95+
#我11G显存也只能设置15-20左右,我训练最终loss降低至250左右,acc约95%左右
96+
model.save('unet.h5')
97+
print('unet.h5保存成功!!!')
98+
99+
100+
def unet_predict(unet, img_src_path):
101+
img_src = cv2.imdecode(np.fromfile(img_src_path, dtype=np.uint8), -1) # 从中文路径读取时用
102+
# img_src=cv2.imread(img_src_path)
103+
if img_src.shape != (512, 512, 3):
104+
img_src = cv2.resize(img_src, dsize=(512, 512), interpolation=cv2.INTER_AREA)[:, :, :3] # dsize=(宽度,高度),[:,:,:3]是防止图片为4通道图片,后续无法reshape
105+
img_src = img_src.reshape(1, 512, 512, 3) # 预测图片shape为(1,512,512,3)
106+
107+
img_mask = unet.predict(img_src) # 归一化除以255后进行预测
108+
img_src = img_src.reshape(512, 512, 3) # 将原图reshape为3维
109+
img_mask = img_mask.reshape(512, 512, 3) # 将预测后图片reshape为3维
110+
img_mask = img_mask / np.max(img_mask) * 255 # 归一化后乘以255
111+
img_mask[:, :, 2] = img_mask[:, :, 1] = img_mask[:, :, 0] # 三个通道保持相同
112+
img_mask = img_mask.astype(np.uint8) # 将img_mask类型转为int型
113+
114+
return img_src, img_mask
Binary file not shown.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /