rbox_utils.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. # Copyright (c) 2022 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. import math
  15. import paddle
  16. import numpy as np
  17. import cv2
  18. def norm_angle(angle, range=[-np.pi / 4, np.pi]):
  19. return (angle - range[0]) % range[1] + range[0]
  20. # rbox function implemented using numpy
  21. def poly2rbox_le135_np(poly):
  22. """convert poly to rbox [-pi / 4, 3 * pi / 4]
  23. Args:
  24. poly: [x1, y1, x2, y2, x3, y3, x4, y4]
  25. Returns:
  26. rbox: [cx, cy, w, h, angle]
  27. """
  28. poly = np.array(poly[:8], dtype=np.float32)
  29. pt1 = (poly[0], poly[1])
  30. pt2 = (poly[2], poly[3])
  31. pt3 = (poly[4], poly[5])
  32. pt4 = (poly[6], poly[7])
  33. edge1 = np.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) *
  34. (pt1[1] - pt2[1]))
  35. edge2 = np.sqrt((pt2[0] - pt3[0]) * (pt2[0] - pt3[0]) + (pt2[1] - pt3[1]) *
  36. (pt2[1] - pt3[1]))
  37. width = max(edge1, edge2)
  38. height = min(edge1, edge2)
  39. rbox_angle = 0
  40. if edge1 > edge2:
  41. rbox_angle = np.arctan2(float(pt2[1] - pt1[1]), float(pt2[0] - pt1[0]))
  42. elif edge2 >= edge1:
  43. rbox_angle = np.arctan2(float(pt4[1] - pt1[1]), float(pt4[0] - pt1[0]))
  44. rbox_angle = norm_angle(rbox_angle)
  45. x_ctr = float(pt1[0] + pt3[0]) / 2
  46. y_ctr = float(pt1[1] + pt3[1]) / 2
  47. return [x_ctr, y_ctr, width, height, rbox_angle]
  48. def poly2rbox_oc_np(poly):
  49. """convert poly to rbox (0, pi / 2]
  50. Args:
  51. poly: [x1, y1, x2, y2, x3, y3, x4, y4]
  52. Returns:
  53. rbox: [cx, cy, w, h, angle]
  54. """
  55. points = np.array(poly, dtype=np.float32).reshape((-1, 2))
  56. (cx, cy), (w, h), angle = cv2.minAreaRect(points)
  57. # using the new OpenCV Rotated BBox definition since 4.5.1
  58. # if angle < 0, opencv is older than 4.5.1, angle is in [-90, 0)
  59. if angle < 0:
  60. angle += 90
  61. w, h = h, w
  62. # convert angle to [0, 90)
  63. if angle == -0.0:
  64. angle = 0.0
  65. if angle == 90.0:
  66. angle = 0.0
  67. w, h = h, w
  68. angle = angle / 180 * np.pi
  69. return [cx, cy, w, h, angle]
  70. def poly2rbox_np(polys, rbox_type='oc'):
  71. """
  72. polys: [x0,y0,x1,y1,x2,y2,x3,y3]
  73. to
  74. rboxes: [x_ctr,y_ctr,w,h,angle]
  75. """
  76. assert rbox_type in ['oc', 'le135'], 'only oc or le135 is supported now'
  77. poly2rbox_fn = poly2rbox_oc_np if rbox_type == 'oc' else poly2rbox_le135_np
  78. rboxes = []
  79. for poly in polys:
  80. x, y, w, h, angle = poly2rbox_fn(poly)
  81. rbox = np.array([x, y, w, h, angle], dtype=np.float32)
  82. rboxes.append(rbox)
  83. return np.array(rboxes)
  84. def cal_line_length(point1, point2):
  85. return math.sqrt(
  86. math.pow(point1[0] - point2[0], 2) + math.pow(point1[1] - point2[1], 2))
  87. def get_best_begin_point_single(coordinate):
  88. x1, y1, x2, y2, x3, y3, x4, y4 = coordinate
  89. xmin = min(x1, x2, x3, x4)
  90. ymin = min(y1, y2, y3, y4)
  91. xmax = max(x1, x2, x3, x4)
  92. ymax = max(y1, y2, y3, y4)
  93. combinate = [[[x1, y1], [x2, y2], [x3, y3], [x4, y4]],
  94. [[x4, y4], [x1, y1], [x2, y2], [x3, y3]],
  95. [[x3, y3], [x4, y4], [x1, y1], [x2, y2]],
  96. [[x2, y2], [x3, y3], [x4, y4], [x1, y1]]]
  97. dst_coordinate = [[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]]
  98. force = 100000000.0
  99. force_flag = 0
  100. for i in range(4):
  101. temp_force = cal_line_length(combinate[i][0], dst_coordinate[0]) \
  102. + cal_line_length(combinate[i][1], dst_coordinate[1]) \
  103. + cal_line_length(combinate[i][2], dst_coordinate[2]) \
  104. + cal_line_length(combinate[i][3], dst_coordinate[3])
  105. if temp_force < force:
  106. force = temp_force
  107. force_flag = i
  108. if force_flag != 0:
  109. pass
  110. return np.array(combinate[force_flag]).reshape(8)
  111. def rbox2poly_np(rboxes):
  112. """
  113. rboxes:[x_ctr,y_ctr,w,h,angle]
  114. to
  115. poly:[x0,y0,x1,y1,x2,y2,x3,y3]
  116. """
  117. polys = []
  118. for i in range(len(rboxes)):
  119. x_ctr, y_ctr, width, height, angle = rboxes[i][:5]
  120. tl_x, tl_y, br_x, br_y = -width / 2, -height / 2, width / 2, height / 2
  121. rect = np.array([[tl_x, br_x, br_x, tl_x], [tl_y, tl_y, br_y, br_y]])
  122. R = np.array([[np.cos(angle), -np.sin(angle)],
  123. [np.sin(angle), np.cos(angle)]])
  124. poly = R.dot(rect)
  125. x0, x1, x2, x3 = poly[0, :4] + x_ctr
  126. y0, y1, y2, y3 = poly[1, :4] + y_ctr
  127. poly = np.array([x0, y0, x1, y1, x2, y2, x3, y3], dtype=np.float32)
  128. poly = get_best_begin_point_single(poly)
  129. polys.append(poly)
  130. polys = np.array(polys)
  131. return polys
  132. # rbox function implemented using paddle
  133. def box2corners(box):
  134. """convert box coordinate to corners
  135. Args:
  136. box (Tensor): (B, N, 5) with (x, y, w, h, alpha) angle is in [0, 90)
  137. Returns:
  138. corners (Tensor): (B, N, 4, 2) with (x1, y1, x2, y2, x3, y3, x4, y4)
  139. """
  140. B = box.shape[0]
  141. x, y, w, h, alpha = paddle.split(box, 5, axis=-1)
  142. x4 = paddle.to_tensor(
  143. [0.5, 0.5, -0.5, -0.5], dtype=paddle.float32).reshape(
  144. (1, 1, 4)) # (1,1,4)
  145. x4 = x4 * w # (B, N, 4)
  146. y4 = paddle.to_tensor(
  147. [-0.5, 0.5, 0.5, -0.5], dtype=paddle.float32).reshape((1, 1, 4))
  148. y4 = y4 * h # (B, N, 4)
  149. corners = paddle.stack([x4, y4], axis=-1) # (B, N, 4, 2)
  150. sin = paddle.sin(alpha)
  151. cos = paddle.cos(alpha)
  152. row1 = paddle.concat([cos, sin], axis=-1)
  153. row2 = paddle.concat([-sin, cos], axis=-1) # (B, N, 2)
  154. rot_T = paddle.stack([row1, row2], axis=-2) # (B, N, 2, 2)
  155. rotated = paddle.bmm(corners.reshape([-1, 4, 2]), rot_T.reshape([-1, 2, 2]))
  156. rotated = rotated.reshape([B, -1, 4, 2]) # (B*N, 4, 2) -> (B, N, 4, 2)
  157. rotated[..., 0] += x
  158. rotated[..., 1] += y
  159. return rotated
  160. def paddle_gather(x, dim, index):
  161. index_shape = index.shape
  162. index_flatten = index.flatten()
  163. if dim < 0:
  164. dim = len(x.shape) + dim
  165. nd_index = []
  166. for k in range(len(x.shape)):
  167. if k == dim:
  168. nd_index.append(index_flatten)
  169. else:
  170. reshape_shape = [1] * len(x.shape)
  171. reshape_shape[k] = x.shape[k]
  172. x_arange = paddle.arange(x.shape[k], dtype=index.dtype)
  173. x_arange = x_arange.reshape(reshape_shape)
  174. dim_index = paddle.expand(x_arange, index_shape).flatten()
  175. nd_index.append(dim_index)
  176. ind2 = paddle.transpose(paddle.stack(nd_index), [1, 0]).astype("int64")
  177. paddle_out = paddle.gather_nd(x, ind2).reshape(index_shape)
  178. return paddle_out
  179. def check_points_in_polys(points, polys):
  180. """Check whether point is in rotated boxes
  181. Args:
  182. points (tensor): (1, L, 2) anchor points
  183. polys (tensor): [B, N, 4, 2] gt_polys
  184. eps (float): default 1e-9
  185. Returns:
  186. is_in_polys (tensor): (B, N, L)
  187. """
  188. # [1, L, 2] -> [1, 1, L, 2]
  189. points = points.unsqueeze(0)
  190. # [B, N, 4, 2] -> [B, N, 1, 2]
  191. a, b, c, d = polys.split(4, axis=2)
  192. ab = b - a
  193. ad = d - a
  194. # [B, N, L, 2]
  195. ap = points - a
  196. # [B, N, 1]
  197. norm_ab = paddle.sum(ab * ab, axis=-1)
  198. # [B, N, 1]
  199. norm_ad = paddle.sum(ad * ad, axis=-1)
  200. # [B, N, L] dot product
  201. ap_dot_ab = paddle.sum(ap * ab, axis=-1)
  202. # [B, N, L] dot product
  203. ap_dot_ad = paddle.sum(ap * ad, axis=-1)
  204. # [B, N, L] <A, B> = |A|*|B|*cos(theta)
  205. is_in_polys = (ap_dot_ab >= 0) & (ap_dot_ab <= norm_ab) & (
  206. ap_dot_ad >= 0) & (ap_dot_ad <= norm_ad)
  207. return is_in_polys
  208. def check_points_in_rotated_boxes(points, boxes):
  209. """Check whether point is in rotated boxes
  210. Args:
  211. points (tensor): (1, L, 2) anchor points
  212. boxes (tensor): [B, N, 5] gt_bboxes
  213. eps (float): default 1e-9
  214. Returns:
  215. is_in_box (tensor): (B, N, L)
  216. """
  217. # [B, N, 5] -> [B, N, 4, 2]
  218. corners = box2corners(boxes)
  219. # [1, L, 2] -> [1, 1, L, 2]
  220. points = points.unsqueeze(0)
  221. # [B, N, 4, 2] -> [B, N, 1, 2]
  222. a, b, c, d = corners.split(4, axis=2)
  223. ab = b - a
  224. ad = d - a
  225. # [B, N, L, 2]
  226. ap = points - a
  227. # [B, N, L]
  228. norm_ab = paddle.sum(ab * ab, axis=-1)
  229. # [B, N, L]
  230. norm_ad = paddle.sum(ad * ad, axis=-1)
  231. # [B, N, L] dot product
  232. ap_dot_ab = paddle.sum(ap * ab, axis=-1)
  233. # [B, N, L] dot product
  234. ap_dot_ad = paddle.sum(ap * ad, axis=-1)
  235. # [B, N, L] <A, B> = |A|*|B|*cos(theta)
  236. is_in_box = (ap_dot_ab >= 0) & (ap_dot_ab <= norm_ab) & (ap_dot_ad >= 0) & (
  237. ap_dot_ad <= norm_ad)
  238. return is_in_box
  239. def rotated_iou_similarity(box1, box2, eps=1e-9, func=''):
  240. """Calculate iou of box1 and box2
  241. Args:
  242. box1 (Tensor): box with the shape [N, M1, 5]
  243. box2 (Tensor): box with the shape [N, M2, 5]
  244. Return:
  245. iou (Tensor): iou between box1 and box2 with the shape [N, M1, M2]
  246. """
  247. from ext_op import rbox_iou
  248. rotated_ious = []
  249. for b1, b2 in zip(box1, box2):
  250. rotated_ious.append(rbox_iou(b1, b2))
  251. return paddle.stack(rotated_ious, axis=0)