db_postprocess.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import absolute_import
  15. from __future__ import division
  16. from __future__ import print_function
  17. import numpy as np
  18. import cv2
  19. import paddle
  20. from shapely.geometry import Polygon
  21. import pyclipper
  22. class DBPostProcess(object):
  23. """
  24. The post process for Differentiable Binarization (DB).
  25. """
  26. def __init__(self,
  27. thresh=0.3,
  28. box_thresh=0.7,
  29. max_candidates=1000,
  30. unclip_ratio=2.0,
  31. use_dilation=False,
  32. **kwargs):
  33. self.thresh = thresh
  34. self.box_thresh = box_thresh
  35. self.max_candidates = max_candidates
  36. self.unclip_ratio = unclip_ratio
  37. self.min_size = 3
  38. self.dilation_kernel = None if not use_dilation else np.array(
  39. [[1, 1], [1, 1]])
  40. def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height):
  41. '''
  42. _bitmap: single map with shape (1, H, W),
  43. whose values are binarized as {0, 1}
  44. '''
  45. bitmap = _bitmap
  46. height, width = bitmap.shape
  47. outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST,
  48. cv2.CHAIN_APPROX_SIMPLE)
  49. if len(outs) == 3:
  50. img, contours, _ = outs[0], outs[1], outs[2]
  51. elif len(outs) == 2:
  52. contours, _ = outs[0], outs[1]
  53. num_contours = min(len(contours), self.max_candidates)
  54. boxes = []
  55. scores = []
  56. for index in range(num_contours):
  57. contour = contours[index]
  58. points, sside = self.get_mini_boxes(contour)
  59. if sside < self.min_size:
  60. continue
  61. points = np.array(points)
  62. # score = self.box_score_fast(pred, points.reshape(-1, 2)) # fast 近似计算得分
  63. score = self.box_score_slow(pred, contour) # slow
  64. if self.box_thresh > score:
  65. continue
  66. box = self.unclip(points).reshape(-1, 1, 2)
  67. box, sside = self.get_mini_boxes(box)
  68. if sside < self.min_size + 2:
  69. continue
  70. box = np.array(box)
  71. box[:, 0] = np.clip(
  72. np.round(box[:, 0] / width * dest_width), 0, dest_width)
  73. box[:, 1] = np.clip(
  74. np.round(box[:, 1] / height * dest_height), 0, dest_height)
  75. boxes.append(box.astype(np.int16))
  76. scores.append(score)
  77. return np.array(boxes, dtype=np.int16), scores
  78. def unclip(self, box):
  79. unclip_ratio = self.unclip_ratio
  80. poly = Polygon(box)
  81. distance = poly.area * unclip_ratio / poly.length
  82. offset = pyclipper.PyclipperOffset()
  83. offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
  84. expanded = np.array(offset.Execute(distance))
  85. return expanded
  86. def get_mini_boxes(self, contour):
  87. bounding_box = cv2.minAreaRect(contour)
  88. points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
  89. index_1, index_2, index_3, index_4 = 0, 1, 2, 3
  90. if points[1][1] > points[0][1]:
  91. index_1 = 0
  92. index_4 = 1
  93. else:
  94. index_1 = 1
  95. index_4 = 0
  96. if points[3][1] > points[2][1]:
  97. index_2 = 2
  98. index_3 = 3
  99. else:
  100. index_2 = 3
  101. index_3 = 2
  102. box = [
  103. points[index_1], points[index_2], points[index_3], points[index_4]
  104. ]
  105. return box, min(bounding_box[1])
  106. def box_score_fast(self, bitmap, _box):
  107. h, w = bitmap.shape[:2]
  108. box = _box.copy()
  109. xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int), 0, w - 1)
  110. xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int), 0, w - 1)
  111. ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int), 0, h - 1)
  112. ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int), 0, h - 1)
  113. mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
  114. box[:, 0] = box[:, 0] - xmin
  115. box[:, 1] = box[:, 1] - ymin
  116. cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1)
  117. return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]
  118. def box_score_slow(self, bitmap, contour):
  119. '''
  120. box_score_slow: use polyon mean score as the mean score
  121. '''
  122. h, w = bitmap.shape[:2]
  123. contour = contour.copy()
  124. contour = np.reshape(contour, (-1, 2))
  125. xmin = np.clip(np.min(contour[:, 0]), 0, w - 1)
  126. xmax = np.clip(np.max(contour[:, 0]), 0, w - 1)
  127. ymin = np.clip(np.min(contour[:, 1]), 0, h - 1)
  128. ymax = np.clip(np.max(contour[:, 1]), 0, h - 1)
  129. mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
  130. contour[:, 0] = contour[:, 0] - xmin
  131. contour[:, 1] = contour[:, 1] - ymin
  132. cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype(np.int32), 1)
  133. return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]
  134. def __call__(self, outs_dict, shape_list):
  135. pred = outs_dict['maps']
  136. if isinstance(pred, paddle.Tensor):
  137. pred = pred.numpy()
  138. pred = pred[:, 0, :, :]
  139. segmentation = pred > self.thresh
  140. boxes_batch = []
  141. for batch_index in range(pred.shape[0]):
  142. src_h, src_w, ratio_h, ratio_w = shape_list[batch_index]
  143. if self.dilation_kernel is not None:
  144. mask = cv2.dilate(
  145. np.array(segmentation[batch_index]).astype(np.uint8),
  146. self.dilation_kernel)
  147. else:
  148. mask = segmentation[batch_index]
  149. boxes, scores = self.boxes_from_bitmap(pred[batch_index], mask,
  150. src_w, src_h)
  151. boxes_batch.append({'points': boxes})
  152. return boxes_batch