当前位置:   article > 正文

TensorFlow入门教程(29)车牌识别之使用DenseNet+CTC模型进行文字识别(五)_tensorflow rnn 车架号

tensorflow rnn 车架号

#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#

1、概述

前面几讲,我们已经实现了对车牌的检测,现在就来实现对检测出来的车牌进行文字识别。

环境配置:

操作系统:Ubuntu 64位

显卡:GTX 1080ti

Python:Python3.7

TensorFlow:2.3.0

 

2、车牌数据集制作

因为只是教程,所以,还是使用CCPD2019数据集,但是需要我们进行预处理,以加快模型的训练速度。由前面的教程可知,我们EAST模型检测出来的是车牌的最小外接矩形,所以,我们先写个制作数据集的代码,目的是将CCPD2019数据集中车牌的最小外接矩形剪切出来并保存成图片文件,这样我们在训练的时候就不用重复操作这个预处理过程。首先导入CCPD的图片路径到列表中,代码如下,

  1. '''
  2. 从txt文本中获取图片路径
  3. '''
  4. def get_iamges_from_txt(data_path, filename):
  5.     files = []
  6.     with open(filename, "r"as fd:
  7.         lines = fd.readlines()
  8.         for line in lines
  9.             line = line.strip()   
  10.             if len(line> 0:  
  11.                 files.append(os.path.join(data_path, line))
  12.     return files

然后,遍历这个列表,先根据文件名得到车牌的车牌号和四个顶点坐标。需要注意的是,我们不完全使用这四个顶点的坐标,因为正常情况下,这四个顶点是刚好把车牌截取出来,但是,由于EAST检测车牌的时候,多少会存在一点偏差,即,可能多截或少截了车牌的一部分内容。所以,我们也要模仿这种偏差,在不影响识别的前提下,随机多截或少截车牌内容,代码如下,

  1. def get_plate_poly(parts):
  2.     text_polys = []
  3.     
  4.     # get plate poly
  5.     polys_part = parts[3].split("_")
  6.     poly = []
  7.     for p in polys_part:
  8.         poly.append(p.split("&"))
  9.     text_polys.append(np.asarray(poly).astype(np.int32))
  10.     return text_polys
  11. # 随机缩放车牌坐标
  12. def get_plate_poly_random_scale(parts, image):
  13.     h, w, _ = image.shape
  14.     text_polys = get_plate_poly(parts)
  15.     text_poly = text_polys[0]
  16.     rect_w = np.abs(text_poly[1][0] - text_poly[0][0])
  17.     rect_h = np.abs(text_poly[3][1] - text_poly[0][1])
  18.     min_scale_ratio = 0.05
  19.     max_scale_ratio = 0.08
  20.     p0 = [np.clip(text_poly[0][0]+np.random.randint(-int(rect_w*min_scale_ratio), int(rect_w*max_scale_ratio)), 0, w), np.clip(text_poly[0][1]+np.random.randint(-int(rect_h*min_scale_ratio), int(rect_h*max_scale_ratio)), 0, h)]
  21.     p1 = [np.clip(text_poly[1][0]+np.random.randint(-int(rect_w*max_scale_ratio), int(rect_w*min_scale_ratio)), 0, w), np.clip(text_poly[1][1]+np.random.randint(-int(rect_h*min_scale_ratio), int(rect_h*max_scale_ratio)), 0, h)]
  22.     p2 = [np.clip(text_poly[2][0]+np.random.randint(-int(rect_w*max_scale_ratio), int(rect_w*min_scale_ratio)), 0, w), np.clip(text_poly[2][1]+np.random.randint(-int(rect_h*max_scale_ratio), int(rect_h*min_scale_ratio)), 0, h)]
  23.     p3 = [np.clip(text_poly[3][0]+np.random.randint(-int(rect_w*min_scale_ratio), int(rect_w*max_scale_ratio)), 0, w), np.clip(text_poly[3][1]+np.random.randint(-int(rect_h*max_scale_ratio), int(rect_h*min_scale_ratio)), 0, h)]
  24.     if DEBUG:
  25.         cv2.circle(image, tuple(p1), 10, (0,255,0), 4)    
  26.     random_polys = np.asarray([[p0, p1, p2, p3]]).astype(np.int32)
  27.     
  28.     return random_polys
  29. # 根据文件名获取车牌四个顶点的坐标,因为EAST检测出来的车牌不一定很完美,
  30. # 所以这里在识别的任务中,就模仿这种不完美,对车牌进行随机的缩放
  31. def get_plate_attribute(filename, image):    
  32.     parts = filename.split("-")    
  33.     if np.random.random() < 5./10:
  34.         text_polys = get_plate_poly_random_scale(parts, image)    
  35.     else:        
  36.         text_polys = get_plate_poly(parts)    
  37.     number = get_plate_number(parts)
  38.     # print("number_part:"number)
  39.     return np.array(text_polys, dtype=np.float32), number

得到车牌四个顶点的坐标以后,先求这个四个顶点的最小外接矩形,

  1. '''
  2. 求四边形的最小外接矩形
  3. '''
  4. def get_min_area_rect(poly):
  5.     rect = cv2.minAreaRect(poly)
  6.     box = cv2.boxPoints(rect)
  7.     return box, rect[2]

然后再对顶点排序,p0对应左上角,p1对应右上角,p2对应右下角,p3对应左下角,并求出p2_p3这条边相对x轴的夹角,

  1. '''
  2. 对矩形的顶点进行排序,排序后的结果是,p0-左上角,p1-右上角,p2-右下角,p3-左下角
  3. 矩形与水平轴的夹角,即为p2_p3边到x轴的夹角,以逆时针为正,顺时针为负
  4. '''
  5. def sort_poly_and_get_angle(poly, image=None):    
  6.     # 先找到矩形最底部的顶点
  7.     p_lowest = np.argmax(poly[:, 1])
  8.     if np.count_nonzero(poly[:, 1== poly[p_lowest, 1]) == 2:
  9.         # 如果矩形底部的边刚好与x轴平行
  10.         # 这种情况下,x坐标加y坐标之和最小的顶点就是左上角的顶点,即p0
  11.         p0_index = np.argmin(np.sum(poly, axis=1))
  12.         p1_index = (p0_index + 1) % 4
  13.         p2_index = (p0_index + 2) % 4
  14.         p3_index = (p0_index + 3) % 4
  15.         return poly[[p0_index, p1_index, p2_index, p3_index]], 0.
  16.     else:        
  17.         # 如果矩形底部与x轴有夹角
  18.         # 找到最底部顶点的右边的第一个顶点
  19.         p_lowest_right = (p_lowest - 1) % 4    
  20.         # 求最底部顶点与其右边第一个顶点组成的边与x轴的夹角                
  21.         angle = np.arctan(-(poly[p_lowest][1] - poly[p_lowest_right][1])/(poly[p_lowest][0] - poly[p_lowest_right][0+ 1e-5))        
  22.         # 下面的代码其实自己画个图就很好理解了
  23.         if angle > np.pi/4:    
  24.             # 如果这个夹角大于45度,那么,最底部的顶点为p2顶点        
  25.             p2_index = p_lowest
  26.             p1_index = (p2_index - 1) % 4
  27.             p0_index = (p2_index - 2) % 4
  28.             p3_index = (p2_index + 1) % 4
  29.             # 这种情况下,p2-p3边与x轴的夹角就为-(np.pi/2 - angle)
  30.             return poly[[p0_index, p1_index, p2_index, p3_index]], -(np.pi/2 - angle)
  31.         else:
  32.             # 如果这个夹角小于等于45度,那么,最底部的顶点为p3顶点
  33.             p3_index = p_lowest
  34.             p0_index = (p3_index + 1) % 4
  35.             p1_index = (p3_index + 2) % 4
  36.             p2_index = (p3_index + 3) % 4
  37.             return poly[[p0_index, p1_index, p2_index, p3_index]], angle

然后就是截取车牌,因为车牌可能是斜的,所以,要先将图片根据我们上面求得的夹角旋转,经过旋转以后,车牌的最小外接矩形就会是水平的,这样我们就可以截取了,

# 将车牌裁剪出来,因为车牌有可能是斜的,所以要先将图片旋转到车牌的矩形框为水平时,再裁剪,

  1. # 将车牌裁剪出来,因为车牌有可能是斜的,所以要先将图片旋转到车牌的矩形框为水平时,再裁剪
  2. def crop_plate(image, angle, p0, p1, p2, p3):
  3.     # DEBUG = True
  4.     angle = -angle * (180 / math.pi)
  5.     # print("angle:", angle)    
  6.     h, w, _ = image.shape
  7.     rotate_mat = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1)  # 按angle角度旋转图像
  8.     h_new = int(w * np.fabs(np.sin(np.radians(angle))) + h * np.fabs(np.cos(np.radians(angle))))
  9.     w_new = int(h * np.fabs(np.sin(np.radians(angle))) + w * np.fabs(np.cos(np.radians(angle))))
  10.     rotate_mat[02+= (w_new - w) / 2
  11.     rotate_mat[12+= (h_new - h) / 2
  12.     rotated_image = cv2.warpAffine(image, rotate_mat, (w_new, h_new), borderValue=(255255255))
  13.     
  14.     # 旋转后图像的四点坐标
  15.     [[p1[0]], [p1[1]]] = np.dot(rotate_mat, np.array([[p1[0]], [p1[1]], [1]]))
  16.     [[p3[0]], [p3[1]]] = np.dot(rotate_mat, np.array([[p3[0]], [p3[1]], [1]]))
  17.     [[p2[0]], [p2[1]]] = np.dot(rotate_mat, np.array([[p2[0]], [p2[1]], [1]]))
  18.     [[p0[0]], [p0[1]]] = np.dot(rotate_mat, np.array([[p0[0]], [p0[1]], [1]]))
  19.     if DEBUG:
  20.         cv2.circle(rotated_image, tuple(p0), 10, (0,255,0), 4)
  21.         cv2.circle(rotated_image, tuple(p1), 10, (0,0,255), 4)
  22.         cv2.imshow('image',  image)
  23.         cv2.imshow('rotateImg2',  rotated_image)
  24.         cv2.waitKey(0)
  25.     crop_image = rotated_image[int(p0[1]):int(p3[1]), int(p0[0]):int(p1[0])]
  26.     return crop_image

得到车牌图片以后,保存即可。

这样,我们就得到了两个文件夹和一个TXT文本文件,如下图所示,

其中,train和val文件夹下保存的是车牌图片,train.txt和val.txt文件则是这些车牌图片的索引和其对应的车牌号,用逗号隔开,如下图所示,

3、CRNN

数据集做好以后,我们先来看CRNN的论文,我们模型就是基于它的思想。

论文链接:https://arxiv.org/abs/1507.05717

直接看网络结构,

如上图所示,模型主要由三部分构成:卷积层、循环层和转录层。

卷积层主要做特征提取的工作。循环层则由一个双向循环神经网络构成,用来预测标签分布。最后一层转录层则有CTC来完成。知道这个模型的结构就可以了,我们不完全按照它的来,直接用DenseNet+CTC就可以完成文字的识别,当然也可以用DenseNet+BiRNN+CTC,我们两个模型都实现。

4、数据增强

数据增强部分,我们对图片做随机缩放、随机改变亮度和随机小角度旋转的操作,代码如下,

  1. '''
  2. 随机缩放图片
  3. '''
  4. def random_scale_image(image):
  5.     random_scale = np.array([0.50.751., 1.251.5])    
  6.     rd_scale = np.random.choice(random_scale)
  7.     x_scale_variation = np.random.randint(-1010/ 100.
  8.     y_scale_variation = np.random.randint(-1010/ 100.
  9.     x_scale = rd_scale + x_scale_variation
  10.     y_scale = rd_scale + y_scale_variation
  11.     image = cv2.resize(image, dsize=None, fx=x_scale, fy=y_scale)
  12.     return image
  13. '''
  14. 随机改变亮度
  15. '''
  16. def random_brightness(image):        
  17.     random_delta = np.random.randint(60140/ 100.
  18.     random_bias = np.random.randint(1030)
  19.     image = np.uint8(np.clip((image*random_delta+random_bias), 0255))                                   
  20.     return image
  21. '''
  22. 随机旋转
  23. '''
  24. def random_rotate(images):
  25.     h, w, _ = images.shape
  26.     random_angle =  np.random.randint(-15,15)
  27.     random_scale =  np.random.randint(8,10/ 10.0
  28.     mat_rotate = cv2.getRotationMatrix2D(center=(w*0.5, h*0.5), angle=random_angle, scale=random_scale)
  29.     images = cv2.warpAffine(images, mat_rotate, (w, h))
  30.     return images

为了能进行批量训练,将输入图片的大小固定,但不是直接将图片缩放至固定大小(避免车牌图片的严重变形),而是按比例进行缩放,CRNN模型要求输入图片的高度是固定的,宽度随意。所以,我们先根据原车牌图片的按比例缩放至固定高度,然后再对宽度进行填充,代码如下,

  1. def resize(image, FLAGS):
  2.     h, w, _ = image.shape
  3.     scale = h / float(FLAGS.input_size_h)
  4.     new_w = int(np.around(w / scale))
  5.     new_w = np.where(new_w > FLAGS.input_size_w, FLAGS.input_size_w, new_w)
  6.     black_image = np.zeros((FLAGS.input_size_h, FLAGS.input_size_w, 3), dtype=np.uint8)
  7.     resize_image = cv2.resize(image, (new_w, FLAGS.input_size_h))    
  8.     # print("black_image:", black_image.shape, " image:", image.shape, " resize_image:", resize_image.shape)
  9.     new_h, new_w, _ = resize_image.shape
  10.     start_w = np.where(new_w+10>FLAGS.input_size_w, 010)
  11.     black_image[:new_h,start_w:new_w+start_w,:] = resize_image[:new_h,:new_w,:]
  12.     # cv2.imshow("resize_image", resize_image)
  13.     # cv2.imshow("black_image", black_image)
  14.     # cv2.waitKey(0)
  15.     return black_image

5、导入字符数据

跟语音识别一样,文字识别也需要将所有待识别的文字(外加一个空格符,当然也可以不用空格符)放到一个文件中,一行一个字符,如下图所示,

我们先将上面的文件以字典的形式导入,这样我们后面才能将车牌号转成向量的形式,代码如下,

  1. '''
  2. 导入车牌包含的字符,以字典的形式返回
  3. '''
  4. def get_char_vector(filename):    
  5.     char_list = []
  6.    
  7.     with open(filename, 'r', encoding='utf-8'as fd:
  8.         lines = fd.readlines()
  9.         for line in lines:
  10.             char_list.append(line.strip("\n"))
  11.     vector = dict(zip(char_list, range(len(char_list))))    
  12.     return vector, char_list

接着将数据集中所有图片路径和对应的车牌号导入列表中,这里车牌号是以向量的形式保存,代码如下,

  1. def get_images(filename, char_vector):
  2.     root_dir,_ = os.path.split(filename)
  3.     images_list = []
  4.     labels_list = []
  5.     with open(filename, "r"as fd:
  6.         lines = fd.readlines()
  7.         for line in lines:
  8.             line = line.strip().split(",")
  9.             if len(line== 2:
  10.                 images_list.append(os.path.join(root_dir, line[0]))
  11.                 # print("line[1]:"line[1])
  12.                 labels_list.append([char_vector[i] for i in line[1]])
  13.                 # break
  14.     # print("labels_list:", labels_list)
  15.     randnum = random.randint(0,100)
  16.     random.seed(randnum)
  17.     random.shuffle(images_list)
  18.     random.seed(randnum)
  19.     random.shuffle(labels_list)
  20.     return np.asarray(images_list), np.asarray(labels_list)

6、操作数据集

我们这里以tf.keras.utils.Sequence类来操作数据集,对这个类不熟悉的话,可以看看我另一篇博客(https://blog.csdn.net/rookie_wei/article/details/100013787),导入数据的代码我就不多解释了,重点来看__getitem__函数,代码如下,

  1.     def __getitem__(selfindex):
  2.         if self.subset == "train"
  3.             batch_filename = self.train_filelist[index * self.FLAGS.batch_size : (index + 1* self.FLAGS.batch_size]
  4.             batch_label = self.train_labellist[index * self.FLAGS.batch_size : (index + 1* self.FLAGS.batch_size]
  5.         else:
  6.             batch_filename = self.valid_filelist[index * self.FLAGS.batch_size : (index + 1* self.FLAGS.batch_size]
  7.             batch_label = self.valid_labellist[index * self.FLAGS.batch_size : (index + 1* self.FLAGS.batch_size]
  8.         
  9.         images = []
  10.         labels = []
  11.         input_lengths = []
  12.         label_lenghts = []
  13.         for filename, label in zip(batch_filename, batch_label):
  14.             image = preprocess(filename, self.FLAGS)
  15.             images.append(image)
  16.             labels.append(label)
  17.             # ctc 输入长度
  18.             input_lengths.append([self.FLAGS.ctc_input_lengths])
  19.             # 文本长度
  20.             label_lenghts.append([len(label)])
  21.         images = np.asarray(images)
  22.         labels = np.asarray(labels)
  23.         input_lengths = np.asarray(input_lengths)
  24.         label_lenghts = np.asarray(label_lenghts)
  25.         inputs = {
  26.                     'input_image': images,
  27.                     'labels': labels,
  28.                     'input_length'input_lengths,
  29.                     'label_length': label_lenghts,
  30.                 }
  31.         outputs = {'ctc': np.zeros([self.FLAGS.batch_size])}
  32.        
  33.         return (inputs, outputs)

主要是看返回的inputs和outputs的形式,之所以这样返回数据,是因为求CTC loss时,需要我们传入这些数据。

7、网络模型

网络模型就非常简单了,先来看DenseNet的,代码如下,

  1. def densenet169(FLAGS, num_classes):
  2.     input_shape = (FLAGS.input_size_h, None, 3)
  3.     input = tf.keras.layers.Input(shape=input_shape, name='input_image')   
  4.     x = tf.keras.applications.densenet.preprocess_input(input
  5.     x = tf.keras.applications.DenseNet169(include_top=False, weights='imagenet')(x)
  6.     x = tf.keras.layers.Dropout(0.2)(x)
  7.     x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1.1e-5)(x)
  8.     x = tf.keras.layers.Activation('relu')(x)
  9.     x = tf.keras.layers.Permute((213), name='permute')(x)
  10.     x = tf.keras.layers.TimeDistributed(tf.keras.layers.Flatten(), name='flatten')(x)    
  11.     y_pred = tf.keras.layers.Dense(num_classes, name='out', activation='softmax')(x)
  12.     model = tf.keras.Model(inputs=input, outputs=y_pred)
  13.     model.summary()
  14.     return model, y_pred, input

这里直接用keras定义好的DenseNet169,如果想在DenseNet后再加一个双向循环神经网络也可以用下面的代码,

  1. def densenet169_BiGRU(FLAGS, num_classes):
  2.     input_shape = (FLAGS.input_size_h, None, 3)
  3.     input = tf.keras.layers.Input(shape=input_shape, name='input_image')
  4.     x = tf.keras.applications.densenet.preprocess_input(input)
  5.     x = tf.keras.applications.DenseNet169(include_top=False, weights='imagenet')(x)
  6.     x = tf.keras.layers.Dropout(0.2)(x)
  7.     x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1.1e-5)(x)
  8.     x = tf.keras.layers.Activation('relu')(x)
  9.     x = tf.keras.layers.Permute((213), name='permute')(x)
  10.     x = tf.keras.layers.TimeDistributed(tf.keras.layers.Flatten(), name='flatten')(x)
  11.     x = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(512return_sequences=True, implementation=2), name='blstm')(x)      
  12.     y_pred = tf.keras.layers.Dense(num_classes, name='out', activation='softmax')(x)
  13.     model = tf.keras.Model(inputs=input, outputs=y_pred)
  14.     model.summary()
  15.     return model, y_pred, input

如果你不想用整个DenseNet,只想用它到某些层的特征,就可以用下面的代码,

  1. def densenet88(FLAGS, num_classes):    
  2.     input_shape = (FLAGS.input_size_h, None, 3)    
  3.     input = tf.keras.layers.Input(shape=input_shape, name='input_image')    
  4.     basemodel = tf.keras.applications.DenseNet121(input_tensor=input, include_top=False, weights='imagenet')    
  5.     x = basemodel.get_layer('pool4_pool').output 
  6.     x = tf.keras.layers.Dropout(0)(x)
  7.     x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1.1e-5)(x)
  8.     x = tf.keras.layers.Activation('relu')(x)
  9.     x = tf.keras.layers.Permute((213), name='permute')(x)
  10.     x = tf.keras.layers.TimeDistributed(tf.keras.layers.Flatten(), name='flatten')(x)     
  11.     y_pred = tf.keras.layers.Dense(num_classes, name='out', activation='softmax')(x)
  12.     model = tf.keras.Model(inputs=input, outputs=y_pred)
  13.     model.summary()    
  14.     return model, y_pred, input

其中,num_classes是车牌号包含的字符个数加一,这个1是预留给CTC的Blank的。定义完DenseNet后,我们来看CTC的定义,代码如下,

  1. def ctc_lambda_func(args):
  2.     y_pred, labels, input_length, label_length = args
  3.     return tf.keras.backend.ctc_batch_cost(labels, y_pred, input_length, label_length)
  4. def get_models(FLAGS, num_classes):
  5.     densenet_model, y_pred, input = densenet(FLAGS, num_classes)
  6.     labels = tf.keras.layers.Input(name='labels', shape=[None], dtype='float32')
  7.     input_length = tf.keras.layers.Input(name='input_length', shape=[1], dtype='int64')
  8.     label_length = tf.keras.layers.Input(name='label_length', shape=[1], dtype='int64')
  9.     ctc_loss = tf.keras.layers.Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([y_pred, labels, input_length, label_length])
  10.     ctc_model = tf.keras.Model(inputs=[input, labels, input_length, label_length], outputs=ctc_loss)
  11.     ctc_model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adam', metrics=['accuracy'])
  12.     ctc_model.summary()
  13.     return densenet_model, ctc_model

可以看到,CTC模型的输入格式刚好跟我们在做数据处理时的输出一致。

8、训练

接下来就是训练了,也是比较简单,直接用fit函数即可,先来试试DenseNet169+CTC的,训练代码如下,

  1. def main(_):
  2.         
  3.     train_ds = Plate_Dataset(FLAGS, "train")
  4.     valid_ds = Plate_Dataset(FLAGS, "valid")
  5.     
  6.     num_classes = train_ds.get_num_classes() + 1
  7.     checkpoint_path = os.path.join(FLAGS.checkpoint_path, FLAGS.densenet_model_net, "plate-densenet-{epoch:04d}.ckpt")
  8.     checkpoint = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path, save_freq=100,
  9.                         monitor='val_loss', save_best_only=False, save_weights_only=True)
  10.     lr_schedule = lambda epoch: FLAGS.learning_rate * 0.4**epoch
  11.     learning_rate = np.array([lr_schedule(i) for i in range(10)])
  12.     changelr = tf.keras.callbacks.LearningRateScheduler(lambda epoch: float(learning_rate[epoch]))
  13.     earlystop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=2, verbose=1)
  14.     tensorboard = tf.keras.callbacks.TensorBoard(log_dir=FLAGS.tensorboard_dir, write_graph=True, update_freq=100)
  15.     
  16.     _, model = get_models(FLAGS, num_classes)
  17.                     
  18.     checkpoint_dir = os.path.dirname(checkpoint_path)
  19.     latest_ckpt = tf.train.latest_checkpoint(checkpoint_dir)
  20.     latest_epoch = 0
  21.     if latest_ckpt:
  22.         print("---------------load_weights--------------------")
  23.         model.load_weights(latest_ckpt)
  24.         epoch_index_start = latest_ckpt.rfind("-")
  25.         epoch_index_end = latest_ckpt.rfind(".ckpt")
  26.         latest_epoch = int(latest_ckpt[epoch_index_start+1:epoch_index_end])
  27.         print("latest_epoch:", latest_epoch)
  28.     steps_per_epoch = train_ds.get_lenght()   
  29.     validation_steps =  100
  30.     model.fit(train_ds,     
  31.         epochs = FLAGS.epochs,
  32.         initial_epoch = latest_epoch,
  33.         validation_data = valid_ds,
  34.         steps_per_epoch = steps_per_epoch,
  35.         validation_steps = validation_steps,
  36.         callbacks = [checkpoint, earlystop, changelr, tensorboard])
  37.     h5_path = os.path.join("models""plate-"+FLAGS.densenet_model_net+".h5")
  38.     model.save(h5_path)

运行结果,

可以看到,验证集的准确率高达0.9837。

9、验证模型

准确率是否真有上面的那么好呢?我们随机从验证集中拷贝一些图片来测试看看,测试代码如下,

  1. def pading_plate_width(image):
  2.     h, w, _ = image.shape
  3.     new_w = np.where(FLAGS.input_size_w > w+20, FLAGS.input_size_w, w+20)
  4.     new_image = np.zeros((h, new_w, 3), dtype=np.uint8)
  5.     start_w = 10
  6.     new_image[:h,start_w:w+start_w,:] = image[:h,:w,:]
  7.     return new_image
  8. '''
  9. 随机旋转,这里不旋转一下识别效果反而降低,可能是训练的时候大部分都旋转的原因
  10. '''
  11. def random_rotate(images):
  12.     h, w, _ = images.shape
  13.     random_angle =  np.random.randint(-15,15)
  14.     random_scale =  np.random.randint(8,10/ 10.0
  15.     mat_rotate = cv2.getRotationMatrix2D(center=(w*0.5, h*0.5), angle=random_angle, scale=random_scale)
  16.     images = cv2.warpAffine(images, mat_rotate, (w, h))
  17.     return images
  18. def resize(image):
  19.     image = random_rotate(image)
  20.     h, w, _ = image.shape    
  21.     new_w = np.around(w / (h/FLAGS.input_size_h)).astype(np.int32)    
  22.     image = cv2.resize(image, (new_w, FLAGS.input_size_h))
  23.     image = pading_plate_width(image)
  24.     # cv2.imshow("image", image)    
  25.     image = image[np.newaxis,:,:,:]
  26.     return image
  27. def get_images(data_path):
  28.     files = []  
  29.     for ext in ['jpg''png''jpeg']:
  30.         files.extend(glob.glob(os.path.join(data_path, '*.{}'.format(ext))))    
  31.     return files
  32. def decode(pred, char_list, num_classes):
  33.     plate_char_list = []
  34.     pred_text = pred.argmax(axis=2)[0]
  35.     # print("pred_text:", pred_text, " char_list len:", len(char_list))
  36.     for i in range(len(pred_text)):
  37.         if pred_text[i] != num_classes - 1 and ((not (i > 0 and pred_text[i] == pred_text[i - 1])) or (i > 1 and pred_text[i] == pred_text[i - 2])):
  38.             plate_char_list.append(char_list[pred_text[i]])
  39.     return u''.join(plate_char_list)  
  40. def main(_):
  41.     _,char_list = get_char_vector(FLAGS.char_filename)
  42.     
  43.     num_classes = len(char_list) + 1
  44.     model,_,_ = densenet(FLAGS, num_classes)
  45.     
  46.     h5_path = os.path.join("models""plate-"+FLAGS.densenet_model_net+".h5")    
  47.     if os.path.exists(h5_path):
  48.         print("---------------load_weights--------------------")
  49.         model.load_weights(h5_path)
  50.     image_filenames = get_images("test_images/input")    
  51.     for filename in image_filenames:
  52.         image = cv2.imread(filename)
  53.         _, y_label = get_plate_attribute(filename, image)
  54.         resized_image = resize(image)
  55.         y_pred = model.predict(resized_image)
  56.         pre_plate = decode(y_pred, char_list, num_classes)
  57.         print("y_label:", y_label, " y_pred:", pre_plate)
  58.         # cv2.imshow("image", image)
  59.         # cv2.waitKey(0)

运行结果,

10、验证模型在CCDP数据集的准确率

最后,我们自己写个代码,在CCDP数据集的训练集和测试集中验证一下模型的准确率,代码如下,

  1. def pading_plate_width(image):
  2.     h, w, _ = image.shape
  3.     new_w = np.where(FLAGS.input_size_w > w+20, FLAGS.input_size_w, w+20)
  4.     new_image = np.zeros((h, new_w, 3), dtype=np.uint8)
  5.     start_w = 10
  6.     new_image[:h,start_w:w+start_w,:] = image[:h,:w,:]
  7.     return new_image
  8. '''
  9. 随机旋转,这里不旋转一下识别效果反而降低,可能是训练的时候大部分都旋转的原因
  10. '''
  11. def random_rotate(images):
  12.     h, w, _ = images.shape
  13.     random_angle =  np.random.randint(-15,15)
  14.     random_scale =  np.random.randint(8,10/ 10.0
  15.     mat_rotate = cv2.getRotationMatrix2D(center=(w*0.5, h*0.5), angle=random_angle, scale=random_scale)
  16.     images = cv2.warpAffine(images, mat_rotate, (w, h))
  17.     return images
  18. def resize(image):
  19.     image = random_rotate(image)
  20.     h, w, _ = image.shape    
  21.     new_w = np.around(w / (h/FLAGS.input_size_h)).astype(np.int32)    
  22.     image = cv2.resize(image, (new_w, FLAGS.input_size_h))
  23.     image = pading_plate_width(image)
  24.     # cv2.imshow("image", image)    
  25.     image = image[np.newaxis,:,:,:]
  26.     return image
  27. def decode(pred, char_list, num_classes):
  28.     plate_char_list = []
  29.     pred_text = pred.argmax(axis=2)[0]
  30.     # print("pred_text:", pred_text, " char_list len:", len(char_list))
  31.     for i in range(len(pred_text)):
  32.         if pred_text[i] != num_classes - 1 and ((not (i > 0 and pred_text[i] == pred_text[i - 1])) or (i > 1 and pred_text[i] == pred_text[i - 2])):
  33.             plate_char_list.append(char_list[pred_text[i]])
  34.     return u''.join(plate_char_list)  
  35. def get_plate_number(inputs, char_list):
  36.     plate_char_list = []
  37.     chars = inputs["labels"][0]
  38.     for c in chars:
  39.         plate_char_list.append(char_list[c])
  40.     return u''.join(plate_char_list)
  41. def get_accuracy(model, ds, char_list, num_classes):
  42.     error = 0
  43.     total = 0
  44.     for (inputs, _) in ds:
  45.         y_label = get_plate_number(inputs, char_list)
  46.         image = inputs["input_image"][0]
  47.      
  48.         image = resize(image)
  49.         y_pred = model.predict(image)
  50.         pre_plate = decode(y_pred, char_list, num_classes)
  51.         # print("y_label:", y_label, " y_pred:", pre_plate, " total:", total)
  52.         # cv2.waitKey(0)
  53.         if y_label != pre_plate:
  54.             error += 1
  55.         total += 1
  56.         if total == 10000:
  57.             break
  58.     accuracy = 1 - float(error)/total
  59.     return accuracy
  60. def main(_):
  61.     train_ds = Plate_Dataset(FLAGS, "train")
  62.     valid_ds = Plate_Dataset(FLAGS, "valid")
  63.     _,char_list = get_char_vector(FLAGS.char_filename)
  64.     
  65.     num_classes = len(char_list) + 1
  66.     model,_,_ = densenet(FLAGS, num_classes)
  67.     h5_path = os.path.join("models""plate-"+FLAGS.densenet_model_net+".h5")    
  68.     if os.path.exists(h5_path):
  69.         print("---------------load_weights--------------------")
  70.         model.load_weights(h5_path)
  71.     accuracy = get_accuracy(model, train_ds, char_list, num_classes)
  72.     print("train accuracy:", accuracy)
  73.     accuracy = get_accuracy(model, valid_ds, char_list, num_classes)
  74.     print("valid accuracy:", accuracy)

运行结果,

10、完整代码

https://mianbaoduo.com/o/bread/YZWcl5xu

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号