You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

hyperlpr.py 15 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import os
  2. import cv2
  3. import numpy as np
  4. from .table_chs import chars
  5. class LPR:
  6. def __init__(self, folder):
  7. """
  8. Init the recognition instance.
  9. :param model_detection: opencv cascade model which detecting license plate.
  10. :param model_finemapping: finemapping model which deskew the license plate
  11. :param model_rec: CNN based sequence recognition model trained with CTC loss.
  12. """
  13. charLocPath = os.path.join(folder, "cascade/char/char_single.xml")
  14. detectorPath = os.path.join(folder, "cascade/detector/detector_ch.xml")
  15. detectorPathDB = os.path.join(folder, "cascade/detector/cascade_double.xml")
  16. modelRecognitionPath = [os.path.join(folder, "dnn/SegmenationFree-Inception.prototxt"),
  17. os.path.join(folder, "dnn/SegmenationFree-Inception.caffemodel")]
  18. modelFineMappingPath = [os.path.join(folder, "dnn/HorizonalFinemapping.prototxt"),
  19. os.path.join(folder, "dnn/HorizonalFinemapping.caffemodel")]
  20. mini_ssd_path = [os.path.join(folder, "dnn/mininet_ssd_v1.prototxt"),
  21. os.path.join(folder, "dnn/mininet_ssd_v1.caffemodel")]
  22. refine_net_path = [os.path.join(folder, "dnn/refinenet.prototxt"),
  23. os.path.join(folder, "dnn/refinenet.caffemodel")]
  24. self.detector = cv2.CascadeClassifier(detectorPath)
  25. self.detectorDB = cv2.CascadeClassifier(detectorPathDB)
  26. self.charLoc = cv2.CascadeClassifier(charLocPath)
  27. self.modelRecognition = cv2.dnn.readNetFromCaffe(*modelRecognitionPath)
  28. self.ssd_detection = cv2.dnn.readNetFromCaffe(*mini_ssd_path)
  29. self.refine_net = cv2.dnn.readNetFromCaffe(*refine_net_path)
  30. def detect_ssd(self, im):
  31. """
  32. Detect the approximate location of plate via single shot detector based on modified mobilenet.
  33. :param im: input image (BGR) .
  34. :return: [[cropped,x1,y2,x2,y2] ,... ]
  35. """
  36. _im = im.copy()
  37. pixel_means = [0.406, 0.456, 0.485]
  38. pixel_stds = [0.225, 0.224, 0.229]
  39. pixel_scale = 255.0
  40. rows, cols, c = im.shape
  41. im_tensor = np.zeros((1, 3, im.shape[0], im.shape[1]))
  42. im = im.astype(np.float32)
  43. for i in range(3):
  44. im_tensor[0, i, :, :] = (im[:, :, 2 - i] / pixel_scale - pixel_means[2 - i]) / pixel_stds[2 - i]
  45. self.ssd_detection.setInput(im_tensor)
  46. # print(im_tensor.shape)
  47. cropped_images = []
  48. cvOut = self.ssd_detection.forward()
  49. for detection in cvOut[0, 0, :, :]:
  50. score = float(detection[2])
  51. if score > 0.5:
  52. x1 = int(detection[3] * cols)
  53. y1 = int(detection[4] * rows)
  54. x2 = int(detection[5] * cols)
  55. y2 = int(detection[6] * rows)
  56. x1 = max(x1, 0)
  57. y1 = max(y1, 0)
  58. x2 = min(x2, im.shape[1]-1)
  59. y2 = min(y2, im.shape[0]-1)
  60. cropped = _im[y1:y2, x1:x2]
  61. cropped_images.append([cropped, [x1, y1, x2, y2]])
  62. return cropped_images
  63. def detect_traditional(self, image_gray, resize_h=720, en_scale=1.1, minSize=30, DB=True):
  64. """
  65. Detect the approximate location of plate via opencv build-in cascade detection.
  66. :param image_gray: input single channel image (gray) .
  67. :param resize_h: adjust input image size to a fixed size.
  68. :param en_scale: the ratio of image between every scale of images in cascade detection.
  69. :param minSize: minSize of plate increase this parameter can increase the speed of detection.
  70. :return: the results.
  71. """
  72. if DB:
  73. watches = self.detectorDB.detectMultiScale(image_gray, en_scale, 3, minSize=(minSize*4, minSize))
  74. else:
  75. watches = self.detector.detectMultiScale(image_gray, en_scale, 3, minSize=(minSize*4, minSize))
  76. cropped_images = []
  77. for (x, y, w, h) in watches:
  78. x -= w * 0.14
  79. w += w * 0.28
  80. y -= h * 0.15
  81. h += h * 0.35
  82. x1 = int(x)
  83. y1 = int(y)
  84. x2 = int(x+w)
  85. y2 = int(y+h)
  86. x1 = max(x1, 0)
  87. y1 = max(y1, 0)
  88. x2 = min(x2, image_gray.shape[1]-1)
  89. y2 = min(y2, image_gray.shape[0]-1)
  90. cropped = image_gray[y1:y2, x1:x2]
  91. cropped_images.append([cropped, [x1, y1, x2, y2]])
  92. return cropped_images
  93. def loose_crop(self, image, box, aspect_ratio, padding_ratio=1.7):
  94. """
  95. Crop the image with an extend rectangle.
  96. :param image: input image (BGR).
  97. :param box: origin bounding box.
  98. :param aspect_ratio: the aspect ratio that need to keep.
  99. :param padding_ratio: padding ratio of origin rectangle.
  100. :return: the cropped image
  101. """
  102. x1, y1, x2, y2 = box
  103. cx, cy = ((x2 + x1) // 2, (y2 + y1) // 2)
  104. if (x2 - x1) / (y2 - y1) > aspect_ratio:
  105. _w = int((x2 - x1) * padding_ratio)
  106. _h = int(_w / aspect_ratio)
  107. else:
  108. _h = int((y2 - y1) * padding_ratio)
  109. _w = int(_h * aspect_ratio)
  110. x1, y1, x2, y2 = cx - _w // 2, cy - _h // 2, cx + _w // 2, cy + _h // 2
  111. x1 = int(max(x1, 0))
  112. y1 = int(max(y1, 0))
  113. cropped = image[y1:y2, x1:x2]
  114. return cropped
  115. def decode_ctc(self, y_pred):
  116. """
  117. Decode the results from the last layer of recognition model.
  118. :param y_pred: the feature map output last feature map.
  119. :return: decode results.
  120. """
  121. results = ""
  122. confidence = 0.0
  123. y_pred = y_pred.T
  124. table_pred = y_pred
  125. res = table_pred.argmax(axis=1)
  126. for i, one in enumerate(res):
  127. if one < len(chars) and (i == 0 or (one != res[i-1])):
  128. results += chars[one]
  129. confidence += table_pred[i][one]
  130. confidence /= len(results)
  131. return results, confidence
  132. def fit_ransac(self, pts, zero_add=0):
  133. """
  134. fit a line and use RANSAC algorithm to reject outlier.
  135. :param pts: input pts
  136. :return: The line border around the image on the location of the point
  137. """
  138. if len(pts) >= 2:
  139. [vx, vy, x, y] = cv2.fitLine(pts, cv2.DIST_HUBER, 0, 0.01, 0.01)
  140. lefty = int((-x * vy / vx) + y)
  141. righty = int(((136 - x) * vy / vx) + y)
  142. return lefty + 30 + zero_add, righty + 30 + zero_add
  143. return 0, 0
  144. def fine_mapping(self, image_rgb):
  145. """
  146. fit plate upper and lower with multi-threshold method to segment single character.
  147. :param image_rgb:
  148. :return: fined image.
  149. """
  150. line_upper = []
  151. line_lower = []
  152. line_experiment = []
  153. if image_rgb.ndim == 3:
  154. gray_image = cv2.cvtColor(image_rgb, cv2.COLOR_BGR2GRAY)
  155. else:
  156. gray_image = image_rgb
  157. for k in np.linspace(-50, 0, 16):
  158. binary_niblack = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 17, k)
  159. if cv2.__version__[0] == "4":
  160. contours, hierarchy = cv2.findContours(binary_niblack.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  161. else:
  162. imagex, contours, hierarchy = cv2.findContours(binary_niblack.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  163. for contour in contours:
  164. bdbox = cv2.boundingRect(contour)
  165. threshold1 = bdbox[3] / float(bdbox[2])
  166. threshold2 = bdbox[3] * bdbox[2]
  167. if (threshold1 > 0.7 and 100 < threshold2 < 1200) or (threshold1 > 3 and threshold2 < 100):
  168. line_upper.append([bdbox[0], bdbox[1]])
  169. line_lower.append([bdbox[0] + bdbox[2], bdbox[1] + bdbox[3]])
  170. line_experiment.append([bdbox[0], bdbox[1]])
  171. line_experiment.append([bdbox[0] + bdbox[2], bdbox[1] + bdbox[3]])
  172. rgb = cv2.copyMakeBorder(image_rgb, 30, 30, 0, 0, cv2.BORDER_REPLICATE)
  173. leftyA, rightyA = self.fit_ransac(np.array(line_lower), 3)
  174. leftyB, rightyB = self.fit_ransac(np.array(line_upper), -3)
  175. rows, cols = rgb.shape[:2]
  176. pts_map1 = np.float32([[cols - 1, rightyA], [0, leftyA], [cols - 1, rightyB], [0, leftyB]])
  177. pts_map2 = np.float32([[136, 36], [0, 36], [136, 0], [0, 0]])
  178. mat = cv2.getPerspectiveTransform(pts_map1, pts_map2)
  179. image = cv2.warpPerspective(rgb, mat, (136, 36), flags=cv2.INTER_CUBIC)
  180. return image
  181. def fine_mapping_by_selecting(self, image_rgb, line_upper, line_lower):
  182. """
  183. fit plate upper and lower with detecting character bounding box
  184. :param image_rgb: input image
  185. :param line_upper: padding of upper
  186. :param line_lower: padding of lower
  187. :return: fined image.
  188. """
  189. rgb = cv2.copyMakeBorder(image_rgb, 30, 30, 0, 0, cv2.BORDER_REPLICATE)
  190. leftyA, rightyA = self.fit_ransac(np.array(line_lower), 3)
  191. leftyB, rightyB = self.fit_ransac(np.array(line_upper), -3)
  192. rows, cols = rgb.shape[:2]
  193. pts_map1 = np.float32([[cols - 1, rightyA], [0, leftyA], [cols - 1, rightyB], [0, leftyB]])
  194. pts_map2 = np.float32([[136, 36], [0, 36], [136, 0], [0, 0]])
  195. mat = cv2.getPerspectiveTransform(pts_map1, pts_map2)
  196. image = cv2.warpPerspective(rgb, mat, (136, 36), flags=cv2.INTER_CUBIC)
  197. return image
  198. def to_refine(self, image, pts, scale=3.0):
  199. """
  200. refine the image by input points.
  201. :param image: input image
  202. :param pts: points
  203. """
  204. x1, y1, x2, y2, x3, y3, x4, y4 = pts.ravel()
  205. cx, cy = int(128 // 2), int(48 // 2)
  206. cw = 64
  207. ch = 24
  208. tx1 = cx - cw // 2
  209. ty1 = cy - ch // 2
  210. tx2 = cx + cw // 2
  211. ty2 = cy - ch // 2
  212. tx3 = cx + cw // 2
  213. ty3 = cy + ch // 2
  214. tx4 = cx - cw // 2
  215. ty4 = cy + ch // 2
  216. target_pts = np.array([[tx1, ty1], [tx2, ty2], [tx3, ty3], [tx4, ty4]]).astype(np.float32) * scale
  217. org_pts = np.array([[x1, y1], [x2, y2], [x3, y3], [x4, y4]]).astype(np.float32)
  218. if cv2.__version__[0] == "4":
  219. mat_, _ = cv2.estimateAffine2D(org_pts, target_pts)
  220. else:
  221. mat_ = cv2.estimateRigidTransform(org_pts, target_pts, True)
  222. dsize = (int(120 * scale), int(48 * scale))
  223. warped = cv2.warpAffine(image, mat_, dsize)
  224. return warped
  225. def affine_crop(self, image, pts):
  226. """
  227. crop a image by affine transform.
  228. :param image: input image
  229. :param pts: points
  230. """
  231. x1, y1, x2, y2, x3, y3, x4, y4 = pts.ravel()
  232. target_pts = np.array([[0, 0], [136, 0], [136, 36], [0, 36]]).astype(np.float32)
  233. org_pts = np.array([[x1, y1], [x2, y2], [x3, y3], [x4, y4]]).astype(np.float32)
  234. mat = cv2.getPerspectiveTransform(org_pts, target_pts)
  235. dsize = (136, 36)
  236. warped = cv2.warpPerspective(image, mat, dsize)
  237. return warped
  238. def finetune(self, image_, stage=2):
  239. """
  240. cascade fine tune a image by regress four corner of plate.
  241. :param image_: input image
  242. :param stages: cascade stage
  243. """
  244. tof = image_.copy()
  245. image = cv2.resize(tof, (120, 48))
  246. blob = cv2.dnn.blobFromImage(image, size=(120, 48), swapRB=False, mean=(127.5, 127.5, 127.5), scalefactor=0.0078125, crop=False)
  247. self.refine_net.setInput(blob)
  248. h, w, c = image_.shape
  249. pts = (self.refine_net.forward("conv6-3").reshape(4, 2) * np.array([w, h])).astype(np.int)
  250. g = self.to_refine(image_, pts)
  251. blob = cv2.dnn.blobFromImage(g, size=(120, 48), swapRB=False, mean=(127.5, 127.5, 127.5), scalefactor=0.0078125, crop=False)
  252. self.refine_net.setInput(blob)
  253. h, w, c = g.shape
  254. pts = (self.refine_net.forward("conv6-3").reshape(4, 2) * np.array([w, h])).astype(np.int)
  255. cropped = self.affine_crop(g, pts)
  256. return cropped
  257. def segmentation_free_recognition(self, src):
  258. """
  259. return: ctc decode results
  260. """
  261. temp = cv2.resize(src, (160, 40))
  262. temp = temp.transpose(1, 0, 2)
  263. blob = cv2.dnn.blobFromImage(temp, 1/255.0, (40, 160), (0, 0, 0), False, False)
  264. self.modelRecognition.setInput(blob)
  265. y_pred = self.modelRecognition.forward()[0]
  266. y_pred = y_pred[:, 2:, :]
  267. y_pred = np.squeeze(y_pred)
  268. return self.decode_ctc(y_pred)
  269. def plate_recognition(self, image, minSize=30, charSelectionDeskew=True, DB=True, mode='ssd'):
  270. """
  271. the simple pipline consists of detection . deskew , fine mapping alignment, recognition.
  272. :param image: the input BGR image from imread used by opencv
  273. :param minSize: the minSize of plate
  274. :param charSelectionDeskew: use character detection when fine mapping stage which will reduce the False Accept Rate as far as possible.
  275. :return: will return [ [plate1 string ,confidence1, location1 ],
  276. [plate2 string ,confidence2, location2 ] ....
  277. ]
  278. usage:
  279. import cv2
  280. import numpy as np
  281. from hyperlpr import LPR
  282. pr = LPR("models")
  283. image = cv2.imread("tests/image")
  284. print(pr.plateRecognition(image))
  285. """
  286. if DB:
  287. image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  288. images = self.detect_traditional(image_gray)
  289. else:
  290. images = self.detect_ssd(image)
  291. res_set = []
  292. for j, plate in enumerate(images):
  293. plate, [left, top, right, bottom] = plate
  294. # print(left, top, right, bottom)
  295. if DB:
  296. w, h = right - left, bottom - top
  297. plate = image[top:bottom, left:right, :]
  298. crop_up = plate[int(h * 0.05):int(h * 0.4), int(w * 0.2):int(w * 0.75)]
  299. crop_down = plate[int(h * 0.4):int(h), int(w * 0.05):w]
  300. crop_up = cv2.resize(crop_up, (64, 40))
  301. crop_down = cv2.resize(crop_down, (96, 40))
  302. cropped_finetuned = np.concatenate([crop_up, crop_down], 1)
  303. # cv2.imshow("crop",plate)
  304. # cv2.waitKey(0)
  305. else:
  306. cropped = self.loose_crop(image, [left, top, right, bottom], 120 / 48)
  307. cropped_finetuned = self.finetune(cropped)
  308. res, confidence = self.segmentation_free_recognition(cropped_finetuned)
  309. res_set.append([res, confidence, [left, top, right, bottom]])
  310. return res_set