赞
踩
#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#
前面几讲,我们已经实现了对车牌的检测,现在就来实现对检测出来的车牌进行文字识别。
环境配置:
操作系统:Ubuntu 64位
显卡:GTX 1080ti
Python:Python3.7
TensorFlow:2.3.0
因为只是教程,所以,还是使用CCPD2019数据集,但是需要我们进行预处理,以加快模型的训练速度。由前面的教程可知,我们EAST模型检测出来的是车牌的最小外接矩形,所以,我们先写个制作数据集的代码,目的是将CCPD2019数据集中车牌的最小外接矩形剪切出来并保存成图片文件,这样我们在训练的时候就不用重复操作这个预处理过程。首先导入CCPD的图片路径到列表中,代码如下,
- '''
- 从txt文本中获取图片路径
- '''
- def get_iamges_from_txt(data_path, filename):
- files = []
- with open(filename, "r") as fd:
- lines = fd.readlines()
- for line in lines:
- line = line.strip()
- if len(line) > 0:
- files.append(os.path.join(data_path, line))
- return files
然后,遍历这个列表,先根据文件名得到车牌的车牌号和四个顶点坐标。需要注意的是,我们不完全使用这四个顶点的坐标,因为正常情况下,这四个顶点是刚好把车牌截取出来,但是,由于EAST检测车牌的时候,多少会存在一点偏差,即,可能多截或少截了车牌的一部分内容。所以,我们也要模仿这种偏差,在不影响识别的前提下,随机多截或少截车牌内容,代码如下,
- def get_plate_poly(parts):
- text_polys = []
-
- # get plate poly
- polys_part = parts[3].split("_")
- poly = []
- for p in polys_part:
- poly.append(p.split("&"))
- text_polys.append(np.asarray(poly).astype(np.int32))
- return text_polys
-
- # 随机缩放车牌坐标
- def get_plate_poly_random_scale(parts, image):
- h, w, _ = image.shape
- text_polys = get_plate_poly(parts)
- text_poly = text_polys[0]
- rect_w = np.abs(text_poly[1][0] - text_poly[0][0])
- rect_h = np.abs(text_poly[3][1] - text_poly[0][1])
- min_scale_ratio = 0.05
- max_scale_ratio = 0.08
- 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)]
- 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)]
- 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)]
- 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)]
-
- if DEBUG:
- cv2.circle(image, tuple(p1), 10, (0,255,0), 4)
- random_polys = np.asarray([[p0, p1, p2, p3]]).astype(np.int32)
-
- return random_polys
-
- # 根据文件名获取车牌四个顶点的坐标,因为EAST检测出来的车牌不一定很完美,
- # 所以这里在识别的任务中,就模仿这种不完美,对车牌进行随机的缩放
- def get_plate_attribute(filename, image):
- parts = filename.split("-")
- if np.random.random() < 5./10:
- text_polys = get_plate_poly_random_scale(parts, image)
- else:
- text_polys = get_plate_poly(parts)
- number = get_plate_number(parts)
- # print("number_part:", number)
- return np.array(text_polys, dtype=np.float32), number

得到车牌四个顶点的坐标以后,先求这个四个顶点的最小外接矩形,
- '''
- 求四边形的最小外接矩形
- '''
- def get_min_area_rect(poly):
- rect = cv2.minAreaRect(poly)
- box = cv2.boxPoints(rect)
- return box, rect[2]
然后再对顶点排序,p0对应左上角,p1对应右上角,p2对应右下角,p3对应左下角,并求出p2_p3这条边相对x轴的夹角,
- '''
- 对矩形的顶点进行排序,排序后的结果是,p0-左上角,p1-右上角,p2-右下角,p3-左下角
- 矩形与水平轴的夹角,即为p2_p3边到x轴的夹角,以逆时针为正,顺时针为负
- '''
- def sort_poly_and_get_angle(poly, image=None):
- # 先找到矩形最底部的顶点
- p_lowest = np.argmax(poly[:, 1])
-
- if np.count_nonzero(poly[:, 1] == poly[p_lowest, 1]) == 2:
- # 如果矩形底部的边刚好与x轴平行
- # 这种情况下,x坐标加y坐标之和最小的顶点就是左上角的顶点,即p0
- p0_index = np.argmin(np.sum(poly, axis=1))
- p1_index = (p0_index + 1) % 4
- p2_index = (p0_index + 2) % 4
- p3_index = (p0_index + 3) % 4
- return poly[[p0_index, p1_index, p2_index, p3_index]], 0.
- else:
- # 如果矩形底部与x轴有夹角
- # 找到最底部顶点的右边的第一个顶点
- p_lowest_right = (p_lowest - 1) % 4
- # 求最底部顶点与其右边第一个顶点组成的边与x轴的夹角
- angle = np.arctan(-(poly[p_lowest][1] - poly[p_lowest_right][1])/(poly[p_lowest][0] - poly[p_lowest_right][0] + 1e-5))
- # 下面的代码其实自己画个图就很好理解了
- if angle > np.pi/4:
- # 如果这个夹角大于45度,那么,最底部的顶点为p2顶点
- p2_index = p_lowest
- p1_index = (p2_index - 1) % 4
- p0_index = (p2_index - 2) % 4
- p3_index = (p2_index + 1) % 4
- # 这种情况下,p2-p3边与x轴的夹角就为-(np.pi/2 - angle)
- return poly[[p0_index, p1_index, p2_index, p3_index]], -(np.pi/2 - angle)
- else:
- # 如果这个夹角小于等于45度,那么,最底部的顶点为p3顶点
- p3_index = p_lowest
- p0_index = (p3_index + 1) % 4
- p1_index = (p3_index + 2) % 4
- p2_index = (p3_index + 3) % 4
- return poly[[p0_index, p1_index, p2_index, p3_index]], angle

然后就是截取车牌,因为车牌可能是斜的,所以,要先将图片根据我们上面求得的夹角旋转,经过旋转以后,车牌的最小外接矩形就会是水平的,这样我们就可以截取了,
# 将车牌裁剪出来,因为车牌有可能是斜的,所以要先将图片旋转到车牌的矩形框为水平时,再裁剪,
- # 将车牌裁剪出来,因为车牌有可能是斜的,所以要先将图片旋转到车牌的矩形框为水平时,再裁剪
- def crop_plate(image, angle, p0, p1, p2, p3):
- # DEBUG = True
- angle = -angle * (180 / math.pi)
- # print("angle:", angle)
- h, w, _ = image.shape
- rotate_mat = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1) # 按angle角度旋转图像
- h_new = int(w * np.fabs(np.sin(np.radians(angle))) + h * np.fabs(np.cos(np.radians(angle))))
- w_new = int(h * np.fabs(np.sin(np.radians(angle))) + w * np.fabs(np.cos(np.radians(angle))))
-
- rotate_mat[0, 2] += (w_new - w) / 2
- rotate_mat[1, 2] += (h_new - h) / 2
- rotated_image = cv2.warpAffine(image, rotate_mat, (w_new, h_new), borderValue=(255, 255, 255))
-
- # 旋转后图像的四点坐标
- [[p1[0]], [p1[1]]] = np.dot(rotate_mat, np.array([[p1[0]], [p1[1]], [1]]))
- [[p3[0]], [p3[1]]] = np.dot(rotate_mat, np.array([[p3[0]], [p3[1]], [1]]))
- [[p2[0]], [p2[1]]] = np.dot(rotate_mat, np.array([[p2[0]], [p2[1]], [1]]))
- [[p0[0]], [p0[1]]] = np.dot(rotate_mat, np.array([[p0[0]], [p0[1]], [1]]))
-
- if DEBUG:
- cv2.circle(rotated_image, tuple(p0), 10, (0,255,0), 4)
- cv2.circle(rotated_image, tuple(p1), 10, (0,0,255), 4)
- cv2.imshow('image', image)
- cv2.imshow('rotateImg2', rotated_image)
- cv2.waitKey(0)
-
- crop_image = rotated_image[int(p0[1]):int(p3[1]), int(p0[0]):int(p1[0])]
-
- return crop_image

得到车牌图片以后,保存即可。
这样,我们就得到了两个文件夹和一个TXT文本文件,如下图所示,
其中,train和val文件夹下保存的是车牌图片,train.txt和val.txt文件则是这些车牌图片的索引和其对应的车牌号,用逗号隔开,如下图所示,
数据集做好以后,我们先来看CRNN的论文,我们模型就是基于它的思想。
论文链接:https://arxiv.org/abs/1507.05717
直接看网络结构,
如上图所示,模型主要由三部分构成:卷积层、循环层和转录层。
卷积层主要做特征提取的工作。循环层则由一个双向循环神经网络构成,用来预测标签分布。最后一层转录层则有CTC来完成。知道这个模型的结构就可以了,我们不完全按照它的来,直接用DenseNet+CTC就可以完成文字的识别,当然也可以用DenseNet+BiRNN+CTC,我们两个模型都实现。
数据增强部分,我们对图片做随机缩放、随机改变亮度和随机小角度旋转的操作,代码如下,
- '''
- 随机缩放图片
- '''
- def random_scale_image(image):
- random_scale = np.array([0.5, 0.75, 1., 1.25, 1.5])
- rd_scale = np.random.choice(random_scale)
- x_scale_variation = np.random.randint(-10, 10) / 100.
- y_scale_variation = np.random.randint(-10, 10) / 100.
- x_scale = rd_scale + x_scale_variation
- y_scale = rd_scale + y_scale_variation
- image = cv2.resize(image, dsize=None, fx=x_scale, fy=y_scale)
- return image
-
- '''
- 随机改变亮度
- '''
- def random_brightness(image):
- random_delta = np.random.randint(60, 140) / 100.
- random_bias = np.random.randint(10, 30)
- image = np.uint8(np.clip((image*random_delta+random_bias), 0, 255))
- return image
-
- '''
- 随机旋转
- '''
- def random_rotate(images):
- h, w, _ = images.shape
- random_angle = np.random.randint(-15,15)
- random_scale = np.random.randint(8,10) / 10.0
- mat_rotate = cv2.getRotationMatrix2D(center=(w*0.5, h*0.5), angle=random_angle, scale=random_scale)
- images = cv2.warpAffine(images, mat_rotate, (w, h))
- return images

为了能进行批量训练,将输入图片的大小固定,但不是直接将图片缩放至固定大小(避免车牌图片的严重变形),而是按比例进行缩放,CRNN模型要求输入图片的高度是固定的,宽度随意。所以,我们先根据原车牌图片的按比例缩放至固定高度,然后再对宽度进行填充,代码如下,
- def resize(image, FLAGS):
- h, w, _ = image.shape
- scale = h / float(FLAGS.input_size_h)
- new_w = int(np.around(w / scale))
- new_w = np.where(new_w > FLAGS.input_size_w, FLAGS.input_size_w, new_w)
- black_image = np.zeros((FLAGS.input_size_h, FLAGS.input_size_w, 3), dtype=np.uint8)
- resize_image = cv2.resize(image, (new_w, FLAGS.input_size_h))
- # print("black_image:", black_image.shape, " image:", image.shape, " resize_image:", resize_image.shape)
- new_h, new_w, _ = resize_image.shape
- start_w = np.where(new_w+10>FLAGS.input_size_w, 0, 10)
- black_image[:new_h,start_w:new_w+start_w,:] = resize_image[:new_h,:new_w,:]
- # cv2.imshow("resize_image", resize_image)
- # cv2.imshow("black_image", black_image)
- # cv2.waitKey(0)
- return black_image
跟语音识别一样,文字识别也需要将所有待识别的文字(外加一个空格符,当然也可以不用空格符)放到一个文件中,一行一个字符,如下图所示,
我们先将上面的文件以字典的形式导入,这样我们后面才能将车牌号转成向量的形式,代码如下,
- '''
- 导入车牌包含的字符,以字典的形式返回
- '''
- def get_char_vector(filename):
- char_list = []
-
- with open(filename, 'r', encoding='utf-8') as fd:
- lines = fd.readlines()
- for line in lines:
- char_list.append(line.strip("\n"))
- vector = dict(zip(char_list, range(len(char_list))))
- return vector, char_list
接着将数据集中所有图片路径和对应的车牌号导入列表中,这里车牌号是以向量的形式保存,代码如下,
- def get_images(filename, char_vector):
- root_dir,_ = os.path.split(filename)
- images_list = []
- labels_list = []
- with open(filename, "r") as fd:
- lines = fd.readlines()
- for line in lines:
- line = line.strip().split(",")
- if len(line) == 2:
- images_list.append(os.path.join(root_dir, line[0]))
- # print("line[1]:", line[1])
- labels_list.append([char_vector[i] for i in line[1]])
- # break
- # print("labels_list:", labels_list)
- randnum = random.randint(0,100)
- random.seed(randnum)
- random.shuffle(images_list)
- random.seed(randnum)
- random.shuffle(labels_list)
- return np.asarray(images_list), np.asarray(labels_list)

我们这里以tf.keras.utils.Sequence类来操作数据集,对这个类不熟悉的话,可以看看我另一篇博客(https://blog.csdn.net/rookie_wei/article/details/100013787),导入数据的代码我就不多解释了,重点来看__getitem__函数,代码如下,
- def __getitem__(self, index):
- if self.subset == "train":
- batch_filename = self.train_filelist[index * self.FLAGS.batch_size : (index + 1) * self.FLAGS.batch_size]
- batch_label = self.train_labellist[index * self.FLAGS.batch_size : (index + 1) * self.FLAGS.batch_size]
- else:
- batch_filename = self.valid_filelist[index * self.FLAGS.batch_size : (index + 1) * self.FLAGS.batch_size]
- batch_label = self.valid_labellist[index * self.FLAGS.batch_size : (index + 1) * self.FLAGS.batch_size]
-
- images = []
- labels = []
- input_lengths = []
- label_lenghts = []
- for filename, label in zip(batch_filename, batch_label):
- image = preprocess(filename, self.FLAGS)
- images.append(image)
- labels.append(label)
- # ctc 输入长度
- input_lengths.append([self.FLAGS.ctc_input_lengths])
- # 文本长度
- label_lenghts.append([len(label)])
-
- images = np.asarray(images)
- labels = np.asarray(labels)
- input_lengths = np.asarray(input_lengths)
- label_lenghts = np.asarray(label_lenghts)
- inputs = {
- 'input_image': images,
- 'labels': labels,
- 'input_length': input_lengths,
- 'label_length': label_lenghts,
- }
- outputs = {'ctc': np.zeros([self.FLAGS.batch_size])}
-
- return (inputs, outputs)

主要是看返回的inputs和outputs的形式,之所以这样返回数据,是因为求CTC loss时,需要我们传入这些数据。
网络模型就非常简单了,先来看DenseNet的,代码如下,
- def densenet169(FLAGS, num_classes):
- input_shape = (FLAGS.input_size_h, None, 3)
- input = tf.keras.layers.Input(shape=input_shape, name='input_image')
- x = tf.keras.applications.densenet.preprocess_input(input)
- x = tf.keras.applications.DenseNet169(include_top=False, weights='imagenet')(x)
- x = tf.keras.layers.Dropout(0.2)(x)
- x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1.1e-5)(x)
- x = tf.keras.layers.Activation('relu')(x)
- x = tf.keras.layers.Permute((2, 1, 3), name='permute')(x)
- x = tf.keras.layers.TimeDistributed(tf.keras.layers.Flatten(), name='flatten')(x)
- y_pred = tf.keras.layers.Dense(num_classes, name='out', activation='softmax')(x)
- model = tf.keras.Model(inputs=input, outputs=y_pred)
- model.summary()
- return model, y_pred, input
这里直接用keras定义好的DenseNet169,如果想在DenseNet后再加一个双向循环神经网络也可以用下面的代码,
- def densenet169_BiGRU(FLAGS, num_classes):
- input_shape = (FLAGS.input_size_h, None, 3)
- input = tf.keras.layers.Input(shape=input_shape, name='input_image')
- x = tf.keras.applications.densenet.preprocess_input(input)
- x = tf.keras.applications.DenseNet169(include_top=False, weights='imagenet')(x)
- x = tf.keras.layers.Dropout(0.2)(x)
- x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1.1e-5)(x)
- x = tf.keras.layers.Activation('relu')(x)
- x = tf.keras.layers.Permute((2, 1, 3), name='permute')(x)
- x = tf.keras.layers.TimeDistributed(tf.keras.layers.Flatten(), name='flatten')(x)
- x = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(512, return_sequences=True, implementation=2), name='blstm')(x)
- y_pred = tf.keras.layers.Dense(num_classes, name='out', activation='softmax')(x)
- model = tf.keras.Model(inputs=input, outputs=y_pred)
- model.summary()
- return model, y_pred, input
如果你不想用整个DenseNet,只想用它到某些层的特征,就可以用下面的代码,
- def densenet88(FLAGS, num_classes):
- input_shape = (FLAGS.input_size_h, None, 3)
- input = tf.keras.layers.Input(shape=input_shape, name='input_image')
- basemodel = tf.keras.applications.DenseNet121(input_tensor=input, include_top=False, weights='imagenet')
- x = basemodel.get_layer('pool4_pool').output
- x = tf.keras.layers.Dropout(0)(x)
- x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1.1e-5)(x)
- x = tf.keras.layers.Activation('relu')(x)
- x = tf.keras.layers.Permute((2, 1, 3), name='permute')(x)
- x = tf.keras.layers.TimeDistributed(tf.keras.layers.Flatten(), name='flatten')(x)
- y_pred = tf.keras.layers.Dense(num_classes, name='out', activation='softmax')(x)
- model = tf.keras.Model(inputs=input, outputs=y_pred)
- model.summary()
- return model, y_pred, input
其中,num_classes是车牌号包含的字符个数加一,这个1是预留给CTC的Blank的。定义完DenseNet后,我们来看CTC的定义,代码如下,
- def ctc_lambda_func(args):
- y_pred, labels, input_length, label_length = args
- return tf.keras.backend.ctc_batch_cost(labels, y_pred, input_length, label_length)
-
- def get_models(FLAGS, num_classes):
- densenet_model, y_pred, input = densenet(FLAGS, num_classes)
-
- labels = tf.keras.layers.Input(name='labels', shape=[None], dtype='float32')
- input_length = tf.keras.layers.Input(name='input_length', shape=[1], dtype='int64')
- label_length = tf.keras.layers.Input(name='label_length', shape=[1], dtype='int64')
-
- ctc_loss = tf.keras.layers.Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([y_pred, labels, input_length, label_length])
-
- ctc_model = tf.keras.Model(inputs=[input, labels, input_length, label_length], outputs=ctc_loss)
- ctc_model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adam', metrics=['accuracy'])
- ctc_model.summary()
- return densenet_model, ctc_model

可以看到,CTC模型的输入格式刚好跟我们在做数据处理时的输出一致。
接下来就是训练了,也是比较简单,直接用fit函数即可,先来试试DenseNet169+CTC的,训练代码如下,
- def main(_):
-
- train_ds = Plate_Dataset(FLAGS, "train")
- valid_ds = Plate_Dataset(FLAGS, "valid")
-
- num_classes = train_ds.get_num_classes() + 1
- checkpoint_path = os.path.join(FLAGS.checkpoint_path, FLAGS.densenet_model_net, "plate-densenet-{epoch:04d}.ckpt")
- checkpoint = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path, save_freq=100,
- monitor='val_loss', save_best_only=False, save_weights_only=True)
-
- lr_schedule = lambda epoch: FLAGS.learning_rate * 0.4**epoch
- learning_rate = np.array([lr_schedule(i) for i in range(10)])
- changelr = tf.keras.callbacks.LearningRateScheduler(lambda epoch: float(learning_rate[epoch]))
- earlystop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=2, verbose=1)
- tensorboard = tf.keras.callbacks.TensorBoard(log_dir=FLAGS.tensorboard_dir, write_graph=True, update_freq=100)
-
- _, model = get_models(FLAGS, num_classes)
-
- checkpoint_dir = os.path.dirname(checkpoint_path)
- latest_ckpt = tf.train.latest_checkpoint(checkpoint_dir)
- latest_epoch = 0
- if latest_ckpt:
- print("---------------load_weights--------------------")
- model.load_weights(latest_ckpt)
- epoch_index_start = latest_ckpt.rfind("-")
- epoch_index_end = latest_ckpt.rfind(".ckpt")
- latest_epoch = int(latest_ckpt[epoch_index_start+1:epoch_index_end])
- print("latest_epoch:", latest_epoch)
-
- steps_per_epoch = train_ds.get_lenght()
- validation_steps = 100
- model.fit(train_ds,
- epochs = FLAGS.epochs,
- initial_epoch = latest_epoch,
- validation_data = valid_ds,
- steps_per_epoch = steps_per_epoch,
- validation_steps = validation_steps,
- callbacks = [checkpoint, earlystop, changelr, tensorboard])
- h5_path = os.path.join("models", "plate-"+FLAGS.densenet_model_net+".h5")
- model.save(h5_path)

运行结果,
可以看到,验证集的准确率高达0.9837。
准确率是否真有上面的那么好呢?我们随机从验证集中拷贝一些图片来测试看看,测试代码如下,
- def pading_plate_width(image):
- h, w, _ = image.shape
- new_w = np.where(FLAGS.input_size_w > w+20, FLAGS.input_size_w, w+20)
- new_image = np.zeros((h, new_w, 3), dtype=np.uint8)
- start_w = 10
- new_image[:h,start_w:w+start_w,:] = image[:h,:w,:]
- return new_image
-
- '''
- 随机旋转,这里不旋转一下识别效果反而降低,可能是训练的时候大部分都旋转的原因
- '''
- def random_rotate(images):
- h, w, _ = images.shape
- random_angle = np.random.randint(-15,15)
- random_scale = np.random.randint(8,10) / 10.0
- mat_rotate = cv2.getRotationMatrix2D(center=(w*0.5, h*0.5), angle=random_angle, scale=random_scale)
- images = cv2.warpAffine(images, mat_rotate, (w, h))
- return images
-
- def resize(image):
- image = random_rotate(image)
- h, w, _ = image.shape
- new_w = np.around(w / (h/FLAGS.input_size_h)).astype(np.int32)
- image = cv2.resize(image, (new_w, FLAGS.input_size_h))
- image = pading_plate_width(image)
- # cv2.imshow("image", image)
- image = image[np.newaxis,:,:,:]
- return image
-
- def get_images(data_path):
- files = []
- for ext in ['jpg', 'png', 'jpeg']:
- files.extend(glob.glob(os.path.join(data_path, '*.{}'.format(ext))))
- return files
-
- def decode(pred, char_list, num_classes):
- plate_char_list = []
- pred_text = pred.argmax(axis=2)[0]
- # print("pred_text:", pred_text, " char_list len:", len(char_list))
- for i in range(len(pred_text)):
- 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])):
- plate_char_list.append(char_list[pred_text[i]])
- return u''.join(plate_char_list)
-
- def main(_):
- _,char_list = get_char_vector(FLAGS.char_filename)
-
- num_classes = len(char_list) + 1
- model,_,_ = densenet(FLAGS, num_classes)
-
- h5_path = os.path.join("models", "plate-"+FLAGS.densenet_model_net+".h5")
- if os.path.exists(h5_path):
- print("---------------load_weights--------------------")
- model.load_weights(h5_path)
-
- image_filenames = get_images("test_images/input")
- for filename in image_filenames:
- image = cv2.imread(filename)
- _, y_label = get_plate_attribute(filename, image)
- resized_image = resize(image)
- y_pred = model.predict(resized_image)
- pre_plate = decode(y_pred, char_list, num_classes)
- print("y_label:", y_label, " y_pred:", pre_plate)
- # cv2.imshow("image", image)
- # cv2.waitKey(0)

运行结果,
最后,我们自己写个代码,在CCDP数据集的训练集和测试集中验证一下模型的准确率,代码如下,
- def pading_plate_width(image):
- h, w, _ = image.shape
- new_w = np.where(FLAGS.input_size_w > w+20, FLAGS.input_size_w, w+20)
- new_image = np.zeros((h, new_w, 3), dtype=np.uint8)
- start_w = 10
- new_image[:h,start_w:w+start_w,:] = image[:h,:w,:]
- return new_image
-
- '''
- 随机旋转,这里不旋转一下识别效果反而降低,可能是训练的时候大部分都旋转的原因
- '''
- def random_rotate(images):
- h, w, _ = images.shape
- random_angle = np.random.randint(-15,15)
- random_scale = np.random.randint(8,10) / 10.0
- mat_rotate = cv2.getRotationMatrix2D(center=(w*0.5, h*0.5), angle=random_angle, scale=random_scale)
- images = cv2.warpAffine(images, mat_rotate, (w, h))
- return images
-
- def resize(image):
- image = random_rotate(image)
- h, w, _ = image.shape
- new_w = np.around(w / (h/FLAGS.input_size_h)).astype(np.int32)
- image = cv2.resize(image, (new_w, FLAGS.input_size_h))
- image = pading_plate_width(image)
- # cv2.imshow("image", image)
- image = image[np.newaxis,:,:,:]
- return image
-
- def decode(pred, char_list, num_classes):
- plate_char_list = []
- pred_text = pred.argmax(axis=2)[0]
- # print("pred_text:", pred_text, " char_list len:", len(char_list))
- for i in range(len(pred_text)):
- 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])):
- plate_char_list.append(char_list[pred_text[i]])
- return u''.join(plate_char_list)
-
- def get_plate_number(inputs, char_list):
- plate_char_list = []
- chars = inputs["labels"][0]
- for c in chars:
- plate_char_list.append(char_list[c])
- return u''.join(plate_char_list)
-
- def get_accuracy(model, ds, char_list, num_classes):
- error = 0
- total = 0
- for (inputs, _) in ds:
- y_label = get_plate_number(inputs, char_list)
- image = inputs["input_image"][0]
-
- image = resize(image)
- y_pred = model.predict(image)
- pre_plate = decode(y_pred, char_list, num_classes)
- # print("y_label:", y_label, " y_pred:", pre_plate, " total:", total)
- # cv2.waitKey(0)
- if y_label != pre_plate:
- error += 1
- total += 1
- if total == 10000:
- break
- accuracy = 1 - float(error)/total
- return accuracy
-
- def main(_):
- train_ds = Plate_Dataset(FLAGS, "train")
- valid_ds = Plate_Dataset(FLAGS, "valid")
- _,char_list = get_char_vector(FLAGS.char_filename)
-
- num_classes = len(char_list) + 1
- model,_,_ = densenet(FLAGS, num_classes)
- h5_path = os.path.join("models", "plate-"+FLAGS.densenet_model_net+".h5")
- if os.path.exists(h5_path):
- print("---------------load_weights--------------------")
- model.load_weights(h5_path)
-
- accuracy = get_accuracy(model, train_ds, char_list, num_classes)
- print("train accuracy:", accuracy)
-
- accuracy = get_accuracy(model, valid_ds, char_list, num_classes)
- print("valid accuracy:", accuracy)

运行结果,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。