当前位置:   article > 正文

基于深度学习神经网络YOLOv4目标检测的车牌识别系统_yolo 车牌识别

yolo 车牌识别

第一步:YOLOv4介绍

YOLOv4是一种目标检测算法,它在精度和速度之间取得了最佳的平衡。它是YOLO(You Only Look Once)系列算法的最新版本,通过将目标检测任务转化为一个回归问题,实现了实时目标检测。YOLOv4采用了一系列的调优手段,使得其在目标检测任务中表现出色。

YOLOv4的框架原理主要包括以下几个方面:

  1. BackBone:YOLOv4使用了CSPDarknet53作为其主干网络,该网络结构具有较强的特征提取能力。
  2. 训练策略:YOLOv4采用了多尺度训练和数据增强等策略来提高模型的泛化能力和检测精度。
  3. 推理策略:YOLOv4使用了多尺度推理和后处理技术来提高检测速度和准确性。
  4. 检测头训练策略:YOLOv4使用了Mosaic数据增强和CIoU损失函数等策略来提高小目标的检测精度。
  5. 检测头推理策略:YOLOv4使用了YOLOv3和YOLOv4的检测头结合策略,提高了模型的检测能力。

总之,YOLOv4是一种高效准确的目标检测算法,具有较好的精度和速度表现。它在目标检测领域具有广泛的应用前景。

标注数据,YOLOv4的训练和测试步骤,各路大神都已经做了很多工作,我就不再写了,这里有几个写的比较好的博客可以参考:

【项目实践】YOLO V4万字原理详细讲解并训练自己的数据集(pytorch完整项目打包下载)-腾讯云开发者社区-腾讯云

YOLOv4 的各种新实现、配置、测试、训练资源汇总

第二步:YOLOv4网络结构

第三步:代码展示

  1. import colorsys
  2. import os
  3. import time
  4. import numpy as np
  5. import torch
  6. import torch.nn as nn
  7. import cv2
  8. from PIL import ImageDraw, ImageFont, Image
  9. from nets.yolo import YoloBody
  10. from utils.utils import (cvtColor, get_anchors, get_classes, preprocess_input,
  11. resize_image, show_config)
  12. from utils.utils_bbox import DecodeBox, DecodeBoxNP
  13. '''
  14. 训练自己的数据集必看注释!
  15. '''
  16. class YOLO(object):
  17. _defaults = {
  18. #--------------------------------------------------------------------------#
  19. # 使用自己训练好的模型进行预测一定要修改model_path和classes_path!
  20. # model_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
  21. #
  22. # 训练好后logs文件夹下存在多个权值文件,选择验证集损失较低的即可。
  23. # 验证集损失较低不代表mAP较高,仅代表该权值在验证集上泛化性能较好。
  24. # 如果出现shape不匹配,同时要注意训练时的model_path和classes_path参数的修改
  25. #--------------------------------------------------------------------------#
  26. "model_path" : 'logs\ep160-loss0.020-val_loss0.018.pth',
  27. "classes_path" : 'model_data/voc_classes.txt',
  28. #---------------------------------------------------------------------#
  29. # anchors_path代表先验框对应的txt文件,一般不修改。
  30. # anchors_mask用于帮助代码找到对应的先验框,一般不修改。
  31. #---------------------------------------------------------------------#
  32. "anchors_path" : 'model_data/yolo_anchors.txt',
  33. "anchors_mask" : [[6, 7, 8], [3, 4, 5], [0, 1, 2]],
  34. #---------------------------------------------------------------------#
  35. # 输入图片的大小,必须为32的倍数。
  36. #---------------------------------------------------------------------#
  37. "input_shape" : [416, 416],
  38. #---------------------------------------------------------------------#
  39. # 只有得分大于置信度的预测框会被保留下来
  40. #---------------------------------------------------------------------#
  41. "confidence" : 0.5,
  42. #---------------------------------------------------------------------#
  43. # 非极大抑制所用到的nms_iou大小
  44. #---------------------------------------------------------------------#
  45. "nms_iou" : 0.3,
  46. #---------------------------------------------------------------------#
  47. # 该变量用于控制是否使用letterbox_image对输入图像进行不失真的resize,
  48. # 在多次测试后,发现关闭letterbox_image直接resize的效果更好
  49. #---------------------------------------------------------------------#
  50. "letterbox_image" : False,
  51. #-------------------------------#
  52. # 是否使用Cuda
  53. # 没有GPU可以设置成False
  54. #-------------------------------#
  55. "cuda" : True,
  56. }
  57. @classmethod
  58. def get_defaults(cls, n):
  59. if n in cls._defaults:
  60. return cls._defaults[n]
  61. else:
  62. return "Unrecognized attribute name '" + n + "'"
  63. #---------------------------------------------------#
  64. # 初始化YOLO
  65. #---------------------------------------------------#
  66. def __init__(self, **kwargs):
  67. self.__dict__.update(self._defaults)
  68. for name, value in kwargs.items():
  69. setattr(self, name, value)
  70. self._defaults[name] = value
  71. #---------------------------------------------------#
  72. # 获得种类和先验框的数量
  73. #---------------------------------------------------#
  74. self.class_names, self.num_classes = get_classes(self.classes_path)
  75. self.anchors, self.num_anchors = get_anchors(self.anchors_path)
  76. self.bbox_util = DecodeBox(self.anchors, self.num_classes, (self.input_shape[0], self.input_shape[1]), self.anchors_mask)
  77. #---------------------------------------------------#
  78. # 画框设置不同的颜色
  79. #---------------------------------------------------#
  80. hsv_tuples = [(x / self.num_classes, 1., 1.) for x in range(self.num_classes)]
  81. self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
  82. self.colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), self.colors))
  83. self.generate()
  84. show_config(**self._defaults)
  85. #---------------------------------------------------#
  86. # 生成模型
  87. #---------------------------------------------------#
  88. def generate(self, onnx=False):
  89. #---------------------------------------------------#
  90. # 建立yolo模型,载入yolo模型的权重
  91. #---------------------------------------------------#
  92. self.net = YoloBody(self.anchors_mask, self.num_classes)
  93. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  94. self.net.load_state_dict(torch.load(self.model_path, map_location=device))
  95. self.net = self.net.eval()
  96. print('{} model, anchors, and classes loaded.'.format(self.model_path))
  97. if not onnx:
  98. if self.cuda:
  99. self.net = nn.DataParallel(self.net)
  100. self.net = self.net.cuda()
  101. #---------------------------------------------------#
  102. # 检测图片
  103. #---------------------------------------------------#
  104. def detect_image(self, image, crop = True, count = False):
  105. #---------------------------------------------------#
  106. # 计算输入图片的高和宽
  107. #---------------------------------------------------#
  108. image_shape = np.array(np.shape(image)[0:2])
  109. #---------------------------------------------------------#
  110. # 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
  111. # 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  112. #---------------------------------------------------------#
  113. image = cvtColor(image)
  114. #---------------------------------------------------------#
  115. # 给图像增加灰条,实现不失真的resize
  116. # 也可以直接resize进行识别
  117. #---------------------------------------------------------#
  118. image_data = resize_image(image, (self.input_shape[1],self.input_shape[0]), self.letterbox_image)
  119. #---------------------------------------------------------#
  120. # 添加上batch_size维度
  121. #---------------------------------------------------------#
  122. image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
  123. with torch.no_grad():
  124. images = torch.from_numpy(image_data)
  125. if self.cuda:
  126. images = images.cuda()
  127. #---------------------------------------------------------#
  128. # 将图像输入网络当中进行预测!
  129. #---------------------------------------------------------#
  130. outputs = self.net(images)
  131. outputs = self.bbox_util.decode_box(outputs)
  132. #---------------------------------------------------------#
  133. # 将预测框进行堆叠,然后进行非极大抑制
  134. #---------------------------------------------------------#
  135. results = self.bbox_util.non_max_suppression(torch.cat(outputs, 1), self.num_classes, self.input_shape,
  136. image_shape, self.letterbox_image, conf_thres = self.confidence, nms_thres = self.nms_iou)
  137. if results[0] is None:
  138. return image
  139. top_label = np.array(results[0][:, 6], dtype = 'int32')
  140. top_conf = results[0][:, 4] * results[0][:, 5]
  141. top_boxes = results[0][:, :4]
  142. #---------------------------------------------------------#
  143. # 设置字体与边框厚度
  144. #---------------------------------------------------------#
  145. font = ImageFont.truetype(font='model_data/simhei.ttf', size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
  146. thickness = int(max((image.size[0] + image.size[1]) // np.mean(self.input_shape), 1))
  147. #---------------------------------------------------------#
  148. # 计数
  149. #---------------------------------------------------------#
  150. if count:
  151. print("top_label:", top_label)
  152. classes_nums = np.zeros([self.num_classes])
  153. for i in range(self.num_classes):
  154. num = np.sum(top_label == i)
  155. if num > 0:
  156. print(self.class_names[i], " : ", num)
  157. classes_nums[i] = num
  158. print("classes_nums:", classes_nums)
  159. #---------------------------------------------------------#
  160. # 是否进行目标的裁剪
  161. #---------------------------------------------------------#
  162. if crop:
  163. for i, c in list(enumerate(top_label)):
  164. top, left, bottom, right = top_boxes[i]
  165. top = max(0, np.floor(top).astype('int32'))
  166. left = max(0, np.floor(left).astype('int32'))
  167. bottom = min(image.size[1], np.floor(bottom).astype('int32'))
  168. right = min(image.size[0], np.floor(right).astype('int32'))
  169. dir_save_path = "img_crop"
  170. if not os.path.exists(dir_save_path):
  171. os.makedirs(dir_save_path)
  172. crop_image = image.crop([left, top, right, bottom])
  173. crop_image.save(os.path.join(dir_save_path, "crop_" + str(i) + ".png"), quality=95, subsampling=0)
  174. print("save crop_" + str(i) + ".png to " + dir_save_path)
  175. #---------------------------------------------------------#
  176. # 图像绘制
  177. #---------------------------------------------------------#
  178. resdict = {}
  179. for i, c in list(enumerate(top_label)):
  180. predicted_class = self.class_names[int(c)]
  181. box = top_boxes[i]
  182. score = top_conf[i]
  183. top, left, bottom, right = box
  184. top = max(0, np.floor(top).astype('int32'))
  185. left = max(0, np.floor(left).astype('int32'))
  186. bottom = min(image.size[1], np.floor(bottom).astype('int32'))
  187. right = min(image.size[0], np.floor(right).astype('int32'))
  188. label = '{}'.format(predicted_class)
  189. draw = ImageDraw.Draw(image)
  190. label_size = draw.textsize(label, font)
  191. label = label.encode('utf-8')
  192. print(label, top, left, bottom, right)
  193. if top - label_size[1] >= 0:
  194. text_origin = np.array([left, top - label_size[1]])
  195. else:
  196. text_origin = np.array([left, top + 1])
  197. for i in range(thickness):
  198. draw.rectangle([left + i, top + i, right - i, bottom - i], outline=self.colors[c])
  199. draw.rectangle([tuple(text_origin), tuple(text_origin + label_size)], fill=self.colors[c])
  200. draw.text(text_origin, str(label,'UTF-8'), fill=(0, 0, 0), font=font)
  201. resdict[left] = str(label,'UTF-8')
  202. del draw
  203. sorted_by_key = dict(sorted(resdict.items(), key=lambda item: item[0]))
  204. res = ''
  205. cnt = 0
  206. for key in sorted_by_key:
  207. if cnt == 0:
  208. cnt += 1
  209. continue
  210. res += sorted_by_key[key]
  211. print(res)
  212. return image,res
  213. def get_FPS(self, image, test_interval):
  214. image_shape = np.array(np.shape(image)[0:2])
  215. #---------------------------------------------------------#
  216. # 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
  217. # 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  218. #---------------------------------------------------------#
  219. image = cvtColor(image)
  220. #---------------------------------------------------------#
  221. # 给图像增加灰条,实现不失真的resize
  222. # 也可以直接resize进行识别
  223. #---------------------------------------------------------#
  224. image_data = resize_image(image, (self.input_shape[1],self.input_shape[0]), self.letterbox_image)
  225. #---------------------------------------------------------#
  226. # 添加上batch_size维度
  227. #---------------------------------------------------------#
  228. image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
  229. with torch.no_grad():
  230. images = torch.from_numpy(image_data)
  231. if self.cuda:
  232. images = images.cuda()
  233. #---------------------------------------------------------#
  234. # 将图像输入网络当中进行预测!
  235. #---------------------------------------------------------#
  236. outputs = self.net(images)
  237. outputs = self.bbox_util.decode_box(outputs)
  238. #---------------------------------------------------------#
  239. # 将预测框进行堆叠,然后进行非极大抑制
  240. #---------------------------------------------------------#
  241. results = self.bbox_util.non_max_suppression(torch.cat(outputs, 1), self.num_classes, self.input_shape,
  242. image_shape, self.letterbox_image, conf_thres=self.confidence, nms_thres=self.nms_iou)
  243. t1 = time.time()
  244. for _ in range(test_interval):
  245. with torch.no_grad():
  246. #---------------------------------------------------------#
  247. # 将图像输入网络当中进行预测!
  248. #---------------------------------------------------------#
  249. outputs = self.net(images)
  250. outputs = self.bbox_util.decode_box(outputs)
  251. #---------------------------------------------------------#
  252. # 将预测框进行堆叠,然后进行非极大抑制
  253. #---------------------------------------------------------#
  254. results = self.bbox_util.non_max_suppression(torch.cat(outputs, 1), self.num_classes, self.input_shape,
  255. image_shape, self.letterbox_image, conf_thres=self.confidence, nms_thres=self.nms_iou)
  256. t2 = time.time()
  257. tact_time = (t2 - t1) / test_interval
  258. return tact_time
  259. def detect_heatmap(self, image, heatmap_save_path):
  260. import cv2
  261. import matplotlib.pyplot as plt
  262. def sigmoid(x):
  263. y = 1.0 / (1.0 + np.exp(-x))
  264. return y
  265. #---------------------------------------------------------#
  266. # 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
  267. # 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  268. #---------------------------------------------------------#
  269. image = cvtColor(image)
  270. #---------------------------------------------------------#
  271. # 给图像增加灰条,实现不失真的resize
  272. # 也可以直接resize进行识别
  273. #---------------------------------------------------------#
  274. image_data = resize_image(image, (self.input_shape[1],self.input_shape[0]), self.letterbox_image)
  275. #---------------------------------------------------------#
  276. # 添加上batch_size维度
  277. #---------------------------------------------------------#
  278. image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
  279. with torch.no_grad():
  280. images = torch.from_numpy(image_data)
  281. if self.cuda:
  282. images = images.cuda()
  283. #---------------------------------------------------------#
  284. # 将图像输入网络当中进行预测!
  285. #---------------------------------------------------------#
  286. outputs = self.net(images)
  287. plt.imshow(image, alpha=1)
  288. plt.axis('off')
  289. mask = np.zeros((image.size[1], image.size[0]))
  290. for sub_output in outputs:
  291. sub_output = sub_output.cpu().numpy()
  292. b, c, h, w = np.shape(sub_output)
  293. sub_output = np.transpose(np.reshape(sub_output, [b, 3, -1, h, w]), [0, 3, 4, 1, 2])[0]
  294. score = np.max(sigmoid(sub_output[..., 4]), -1)
  295. score = cv2.resize(score, (image.size[0], image.size[1]))
  296. normed_score = (score * 255).astype('uint8')
  297. mask = np.maximum(mask, normed_score)
  298. plt.imshow(mask, alpha=0.5, interpolation='nearest', cmap="jet")
  299. plt.axis('off')
  300. plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
  301. plt.margins(0, 0)
  302. plt.savefig(heatmap_save_path, dpi=200, bbox_inches='tight', pad_inches = -0.1)
  303. print("Save to the " + heatmap_save_path)
  304. plt.show()
  305. def convert_to_onnx(self, simplify, model_path):
  306. import onnx
  307. self.generate(onnx=True)
  308. im = torch.zeros(1, 3, *self.input_shape).to('cpu') # image size(1, 3, 512, 512) BCHW
  309. input_layer_names = ["images"]
  310. output_layer_names = ["output"]
  311. # Export the model
  312. print(f'Starting export with onnx {onnx.__version__}.')
  313. torch.onnx.export(self.net,
  314. im,
  315. f = model_path,
  316. verbose = False,
  317. opset_version = 12,
  318. training = torch.onnx.TrainingMode.EVAL,
  319. do_constant_folding = True,
  320. input_names = input_layer_names,
  321. output_names = output_layer_names,
  322. dynamic_axes = None)
  323. # Checks
  324. model_onnx = onnx.load(model_path) # load onnx model
  325. onnx.checker.check_model(model_onnx) # check onnx model
  326. # Simplify onnx
  327. if simplify:
  328. import onnxsim
  329. print(f'Simplifying with onnx-simplifier {onnxsim.__version__}.')
  330. model_onnx, check = onnxsim.simplify(
  331. model_onnx,
  332. dynamic_input_shape=False,
  333. input_shapes=None)
  334. assert check, 'assert check failed'
  335. onnx.save(model_onnx, model_path)
  336. print('Onnx model save as {}'.format(model_path))
  337. def get_map_txt(self, image_id, image, class_names, map_out_path):
  338. f = open(os.path.join(map_out_path, "detection-results/"+image_id+".txt"),"w")
  339. image_shape = np.array(np.shape(image)[0:2])
  340. #---------------------------------------------------------#
  341. # 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
  342. # 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  343. #---------------------------------------------------------#
  344. image = cvtColor(image)
  345. #---------------------------------------------------------#
  346. # 给图像增加灰条,实现不失真的resize
  347. # 也可以直接resize进行识别
  348. #---------------------------------------------------------#
  349. image_data = resize_image(image, (self.input_shape[1],self.input_shape[0]), self.letterbox_image)
  350. #---------------------------------------------------------#
  351. # 添加上batch_size维度
  352. #---------------------------------------------------------#
  353. image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
  354. with torch.no_grad():
  355. images = torch.from_numpy(image_data)
  356. if self.cuda:
  357. images = images.cuda()
  358. #---------------------------------------------------------#
  359. # 将图像输入网络当中进行预测!
  360. #---------------------------------------------------------#
  361. outputs = self.net(images)
  362. outputs = self.bbox_util.decode_box(outputs)
  363. #---------------------------------------------------------#
  364. # 将预测框进行堆叠,然后进行非极大抑制
  365. #---------------------------------------------------------#
  366. results = self.bbox_util.non_max_suppression(torch.cat(outputs, 1), self.num_classes, self.input_shape,
  367. image_shape, self.letterbox_image, conf_thres = self.confidence, nms_thres = self.nms_iou)
  368. if results[0] is None:
  369. return
  370. top_label = np.array(results[0][:, 6], dtype = 'int32')
  371. top_conf = results[0][:, 4] * results[0][:, 5]
  372. top_boxes = results[0][:, :4]
  373. for i, c in list(enumerate(top_label)):
  374. predicted_class = self.class_names[int(c)]
  375. box = top_boxes[i]
  376. score = str(top_conf[i])
  377. top, left, bottom, right = box
  378. if predicted_class not in class_names:
  379. continue
  380. f.write("%s %s %s %s %s %s\n" % (predicted_class, score[:6], str(int(left)), str(int(top)), str(int(right)),str(int(bottom))))
  381. f.close()
  382. return
  383. class YOLO_ONNX(object):
  384. _defaults = {
  385. #--------------------------------------------------------------------------#
  386. # 使用自己训练好的模型进行预测一定要修改onnx_path和classes_path!
  387. # onnx_path指向logs文件夹下的权值文件,classes_path指向model_data下的txt
  388. #
  389. # 训练好后logs文件夹下存在多个权值文件,选择验证集损失较低的即可。
  390. # 验证集损失较低不代表mAP较高,仅代表该权值在验证集上泛化性能较好。
  391. # 如果出现shape不匹配,同时要注意训练时的onnx_path和classes_path参数的修改
  392. #--------------------------------------------------------------------------#
  393. "onnx_path" : 'model_data/models.onnx',
  394. "classes_path" : 'model_data/coco_classes.txt',
  395. #---------------------------------------------------------------------#
  396. # anchors_path代表先验框对应的txt文件,一般不修改。
  397. # anchors_mask用于帮助代码找到对应的先验框,一般不修改。
  398. #---------------------------------------------------------------------#
  399. "anchors_path" : 'model_data/yolo_anchors.txt',
  400. "anchors_mask" : [[6, 7, 8], [3, 4, 5], [0, 1, 2]],
  401. #---------------------------------------------------------------------#
  402. # 输入图片的大小,必须为32的倍数。
  403. #---------------------------------------------------------------------#
  404. "input_shape" : [416, 416],
  405. #---------------------------------------------------------------------#
  406. # 只有得分大于置信度的预测框会被保留下来
  407. #---------------------------------------------------------------------#
  408. "confidence" : 0.5,
  409. #---------------------------------------------------------------------#
  410. # 非极大抑制所用到的nms_iou大小
  411. #---------------------------------------------------------------------#
  412. "nms_iou" : 0.3,
  413. #---------------------------------------------------------------------#
  414. # 该变量用于控制是否使用letterbox_image对输入图像进行不失真的resize,
  415. # 在多次测试后,发现关闭letterbox_image直接resize的效果更好
  416. #---------------------------------------------------------------------#
  417. "letterbox_image" : True
  418. }
  419. @classmethod
  420. def get_defaults(cls, n):
  421. if n in cls._defaults:
  422. return cls._defaults[n]
  423. else:
  424. return "Unrecognized attribute name '" + n + "'"
  425. #---------------------------------------------------#
  426. # 初始化YOLO
  427. #---------------------------------------------------#
  428. def __init__(self, **kwargs):
  429. self.__dict__.update(self._defaults)
  430. for name, value in kwargs.items():
  431. setattr(self, name, value)
  432. self._defaults[name] = value
  433. import onnxruntime
  434. self.onnx_session = onnxruntime.InferenceSession(self.onnx_path)
  435. # 获得所有的输入node
  436. self.input_name = self.get_input_name()
  437. # 获得所有的输出node
  438. self.output_name = self.get_output_name()
  439. #---------------------------------------------------#
  440. # 获得种类和先验框的数量
  441. #---------------------------------------------------#
  442. self.class_names, self.num_classes = self.get_classes(self.classes_path)
  443. self.anchors, self.num_anchors = self.get_anchors(self.anchors_path)
  444. self.bbox_util = DecodeBoxNP(self.anchors, self.num_classes, (self.input_shape[0], self.input_shape[1]), self.anchors_mask)
  445. #---------------------------------------------------#
  446. # 画框设置不同的颜色
  447. #---------------------------------------------------#
  448. hsv_tuples = [(x / self.num_classes, 1., 1.) for x in range(self.num_classes)]
  449. self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
  450. self.colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), self.colors))
  451. show_config(**self._defaults)
  452. def get_classes(self, classes_path):
  453. with open(classes_path, encoding='utf-8') as f:
  454. class_names = f.readlines()
  455. class_names = [c.strip() for c in class_names]
  456. return class_names, len(class_names)
  457. def get_anchors(self, anchors_path):
  458. '''loads the anchors from a file'''
  459. with open(anchors_path, encoding='utf-8') as f:
  460. anchors = f.readline()
  461. anchors = [float(x) for x in anchors.split(',')]
  462. anchors = np.array(anchors).reshape(-1, 2)
  463. return anchors, len(anchors)
  464. def get_input_name(self):
  465. # 获得所有的输入node
  466. input_name=[]
  467. for node in self.onnx_session.get_inputs():
  468. input_name.append(node.name)
  469. return input_name
  470. def get_output_name(self):
  471. # 获得所有的输出node
  472. output_name=[]
  473. for node in self.onnx_session.get_outputs():
  474. output_name.append(node.name)
  475. return output_name
  476. def get_input_feed(self,image_tensor):
  477. # 利用input_name获得输入的tensor
  478. input_feed={}
  479. for name in self.input_name:
  480. input_feed[name]=image_tensor
  481. return input_feed
  482. #---------------------------------------------------#
  483. # 对输入图像进行resize
  484. #---------------------------------------------------#
  485. def resize_image(self, image, size, letterbox_image, mode='PIL'):
  486. if mode == 'PIL':
  487. iw, ih = image.size
  488. w, h = size
  489. if letterbox_image:
  490. scale = min(w/iw, h/ih)
  491. nw = int(iw*scale)
  492. nh = int(ih*scale)
  493. image = image.resize((nw,nh), Image.BICUBIC)
  494. new_image = Image.new('RGB', size, (128,128,128))
  495. new_image.paste(image, ((w-nw)//2, (h-nh)//2))
  496. else:
  497. new_image = image.resize((w, h), Image.BICUBIC)
  498. else:
  499. image = np.array(image)
  500. if letterbox_image:
  501. # 获得现在的shape
  502. shape = np.shape(image)[:2]
  503. # 获得输出的shape
  504. if isinstance(size, int):
  505. size = (size, size)
  506. # 计算缩放的比例
  507. r = min(size[0] / shape[0], size[1] / shape[1])
  508. # 计算缩放后图片的高宽
  509. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  510. dw, dh = size[1] - new_unpad[0], size[0] - new_unpad[1]
  511. # 除以2以padding到两边
  512. dw /= 2
  513. dh /= 2
  514. # 对图像进行resize
  515. if shape[::-1] != new_unpad: # resize
  516. image = cv2.resize(image, new_unpad, interpolation=cv2.INTER_LINEAR)
  517. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
  518. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
  519. new_image = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(128, 128, 128)) # add border
  520. else:
  521. new_image = cv2.resize(image, (w, h))
  522. return new_image
  523. def detect_image(self, image):
  524. image_shape = np.array(np.shape(image)[0:2])
  525. #---------------------------------------------------------#
  526. # 在这里将图像转换成RGB图像,防止灰度图在预测时报错。
  527. # 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
  528. #---------------------------------------------------------#
  529. image = cvtColor(image)
  530. image_data = self.resize_image(image, self.input_shape, True)
  531. #---------------------------------------------------------#
  532. # 添加上batch_size维度
  533. # h, w, 3 => 3, h, w => 1, 3, h, w
  534. #---------------------------------------------------------#
  535. image_data = np.expand_dims(np.transpose(preprocess_input(np.array(image_data, dtype='float32')), (2, 0, 1)), 0)
  536. input_feed = self.get_input_feed(image_data)
  537. outputs = self.onnx_session.run(output_names=self.output_name, input_feed=input_feed)
  538. feature_map_shape = [[int(j / (2 ** (i + 3))) for j in self.input_shape] for i in range(len(self.anchors_mask))][::-1]
  539. for i in range(len(self.anchors_mask)):
  540. outputs[i] = np.reshape(outputs[i], (1, len(self.anchors_mask[i]) * (5 + self.num_classes), feature_map_shape[i][0], feature_map_shape[i][1]))
  541. outputs = self.bbox_util.decode_box(outputs)
  542. #---------------------------------------------------------#
  543. # 将预测框进行堆叠,然后进行非极大抑制
  544. #---------------------------------------------------------#
  545. results = self.bbox_util.non_max_suppression(np.concatenate(outputs, 1), self.num_classes, self.input_shape,
  546. image_shape, self.letterbox_image, conf_thres = self.confidence, nms_thres = self.nms_iou)
  547. if results[0] is None:
  548. return image
  549. top_label = np.array(results[0][:, 6], dtype = 'int32')
  550. top_conf = results[0][:, 4] * results[0][:, 5]
  551. top_boxes = results[0][:, :4]
  552. #---------------------------------------------------------#
  553. # 设置字体与边框厚度
  554. #---------------------------------------------------------#
  555. font = ImageFont.truetype(font='model_data/simhei.ttf', size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
  556. thickness = int(max((image.size[0] + image.size[1]) // np.mean(self.input_shape), 1))
  557. #---------------------------------------------------------#
  558. # 图像绘制
  559. #---------------------------------------------------------#
  560. for i, c in list(enumerate(top_label)):
  561. predicted_class = self.class_names[int(c)]
  562. box = top_boxes[i]
  563. score = top_conf[i]
  564. top, left, bottom, right = box
  565. top = max(0, np.floor(top).astype('int32'))
  566. left = max(0, np.floor(left).astype('int32'))
  567. bottom = min(image.size[1], np.floor(bottom).astype('int32'))
  568. right = min(image.size[0], np.floor(right).astype('int32'))
  569. label = '{} {:.2f}'.format(predicted_class, score)
  570. draw = ImageDraw.Draw(image)
  571. label_size = draw.textsize(label, font)
  572. label = label.encode('utf-8')
  573. print(label, top, left, bottom, right)
  574. if top - label_size[1] >= 0:
  575. text_origin = np.array([left, top - label_size[1]])
  576. else:
  577. text_origin = np.array([left, top + 1])
  578. for i in range(thickness):
  579. draw.rectangle([left + i, top + i, right - i, bottom - i], outline=self.colors[c])
  580. draw.rectangle([tuple(text_origin), tuple(text_origin + label_size)], fill=self.colors[c])
  581. draw.text(text_origin, str(label,'UTF-8'), fill=(0, 0, 0), font=font)
  582. del draw
  583. return image

第四步:运行

运行界面:

输入图片:

识别效果:

第五步:整个工程的内容(包括训练代码和数据)

代码的下载路径(新窗口打开链接)基于深度学习神经网络YOLOv4目标检测的车牌识别系统

有问题可以私信或者留言,有问必答

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/723989
推荐阅读
相关标签
  

闽ICP备14008679号