时间: 2020-09-03 00:08:26 人气: 2271 评论: 0
这是《使用腾讯云GPU学习深度学习》系列文章的第五篇,以车牌识别和简单OCR为例,谈了谈如何进行字母、数字的识别以及定位。本系列文章主要介绍如何使用腾讯云GPU服务器进行深度学习运算,前面主要介绍原理部分,后期则以实践为主。
往期内容:
上一节,我们简要介绍了一些与深度学习相关的数据预处理方法。其中我们特别提到,使用 基于深度学习的 Spatial Transform 方法,可以让“草书” 字体的手写数字同样也可以被高效识别。
但无论是工整书写的 Tensorflow 官网上的 MNIST 教程,还是上节提到“草书”数字,都是 单一的数字识别问题。 但是,在实际生活中,遇到数字、字母识别问题时,往往需要识别一组数字。这时候一个简单的深度神经网络可能就做不到了。本节内容,就是在讨论遇到这种情况时,应该如何调整深度学习模型。
固定长度的字符、数字识别,比较常见的应用场景包括:
识别验证码的方法,这篇文章 有详细介绍。不过该文章使用的是版本较早的 Keras1,实际使用时会有一些问题。如果想尝试,根据Jupyter 的提示更改就好,最终效果也是相当不错:
我们这里要识别的内容,是中华人民共和国机动车车牌。相比上面例子的 4 位验证码,车牌长度更长,达到了 7 位,并且内容也更加丰富,第一位是各省的汉字简称,第二位是 A-Z 的大写字母,3-7位则是数字、字母混合。
由于车牌涉及个人隐私,我们使用了用户 szad670401 在 Github 上开源的一个车牌生成器,随机的生成一些车牌的图片,用于模型训练。当然这个项目同样提供了完整的 MXNet 深度学习框架编写的代码,我们接下来会用 Keras 再写一个。
首先做些准备工作,从 szad670401 的开源项目中获取必要的文件:
### 从 szad670401 github 项目下载车牌生成器以及字体文件 !git clone https://github.com/szad670401/end-to-end-for-chinese-plate-recognition !cp -r end-to-end-for-chinese-plate-recognition/* ./ !sed 's/for i in range(batchSize):/l_plateStr = []\n l_plateImg = []\n for i in range(batchSize):/g' ./genplate.py | sed 's/cv2.imwrite(outputPath/l_plateStr.append(plateStr)\n l_plateImg.append(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n #cv2.imwrite(outputPath/g' | sed 's/img);/img);\n return l_plateStr,l_plateImg/g' >genplateRev.py
来看看生成器的效果:
from keras.models import Model from keras.callbacks import ModelCheckpoint from keras.layers import Conv2D, MaxPool2D, Flatten, Dropout, Dense, Input from keras.optimizers import Adam from keras.backend.tensorflow_backend import set_session from keras.utils.vis_utils import model_to_dot import tensorflow as tf import matplotlib import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from IPython.display import SVG from genplate import * %matplotlib inline np.random.seed(5) config = tf.ConfigProto() config.gpu_options.allow_growth=True set_session(tf.Session(config=config)) chars = ["京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "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" ]; M_strIdx = dict(zip(chars, range(len(chars)))) n_generate = 100 rows = 20 cols = int(n_generate/rows) G = GenPlate("./font/platech.ttf",'./font/platechar.ttf',"./NoPlates") l_plateStr,l_plateImg = G.genBatch(100,2,range(31,65),"./plate",(272,72)) l_out = [] for i in range(rows): l_tmp = [] for j in range(cols): l_tmp.append(l_plateImg[i*cols+j]) l_out.append(np.hstack(l_tmp)) fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(111) ax.imshow( np.vstack(l_out), aspect="auto" )
看来 szad670401 开源的车牌生成器,随机生成的车牌确实达到了以假乱真的效果。于是我们基于这个生成器,再自己写一个生成器,用于深度神经网络的数据输入:
def gen(batch_size=32): while True: l_plateStr,l_plateImg = G.genBatch(batch_size, 2, range(31,65),"./plate",(272,72)) X = np.array(l_plateImg, dtype=np.uint8) ytmp = np.array(list(map(lambda x: [M_strIdx[a] for a in list(x)], l_plateStr)), dtype=np.uint8) y = np.zeros([ytmp.shape[1],batch_size,len(chars)]) for batch in range(batch_size): for idx,row_i in enumerate(ytmp[batch]): y[idx,batch,row_i] = 1 yield X, [yy for yy in y]
因为是固定长度,所以我们有个想法,就是既然我们知道识别七次,那就可以用七个模型按照顺序识别。这个思路没有问题,但实际上根据之前卷积神经网络的原理,实际上卷积神经网络在扫描整张图片的过程中,已经对整个图像的内容以及相对位置关系有所了解,所以,七个模型的卷积层实际上是可以共享的。我们实际上可以用一个 一组卷积层+7个全链接层 的架构,来对应输入的车牌图片:
adam = Adam(lr=0.001) input_tensor = Input((72, 272, 3)) x = input_tensor for i in range(3): x = Conv2D(32*2**i, (3, 3), activation='relu')(x) x = Conv2D(32*2**i, (3, 3), activation='relu')(x) x = MaxPool2D(pool_size=(2, 2))(x) x = Flatten()(x) x = Dropout(0.25)(x) n_class = len(chars) x = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(7)] model = Model(inputs=input_tensor, outputs=x) model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy']) SVG(model_to_dot(model=model, show_layer_names=True, show_shapes=True).create(prog='dot', format='svg'))
训练模型:
best_model = ModelCheckpoint("chepai_best.h5", monitor='val_loss', verbose=0, save_best_only=True) model.fit_generator(gen(32), steps_per_epoch=2000, epochs=5, validation_data=gen(32), validation_steps=1280, callbacks=[best_model] )
Epoch 1/5 2000/2000 [==============================] - 547s - loss: 11.1077 - c1_loss: 1.3878 - c2_loss: 0.7512 - c3_loss: 1.1270 - c4_loss: 1.3997 - c5_loss: 1.7955 - c6_loss: 2.3060 - c7_loss: 2.3405 - c1_acc: 0.6157 - c2_acc: 0.7905 - c3_acc: 0.6831 - c4_acc: 0.6041 - c5_acc: 0.50