box_ops.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import torch
  2. import math
  3. import numpy as np
  4. from torchvision.ops.boxes import box_area
  5. # ------------------ Box ops ------------------
  6. def box_cxcywh_to_xyxy(x):
  7. x_c, y_c, w, h = x.unbind(-1)
  8. b = [(x_c - 0.5 * w), (y_c - 0.5 * h),
  9. (x_c + 0.5 * w), (y_c + 0.5 * h)]
  10. return torch.stack(b, dim=-1)
  11. def box_xyxy_to_cxcywh(x):
  12. x0, y0, x1, y1 = x.unbind(-1)
  13. b = [(x0 + x1) / 2, (y0 + y1) / 2,
  14. (x1 - x0), (y1 - y0)]
  15. return torch.stack(b, dim=-1)
  16. def rescale_bboxes(bboxes, origin_img_size, cur_img_size, deltas=None):
  17. origin_h, origin_w = origin_img_size
  18. cur_img_h, cur_img_w = cur_img_size
  19. if deltas is None:
  20. # rescale
  21. bboxes[..., [0, 2]] = bboxes[..., [0, 2]] / cur_img_w * origin_w
  22. bboxes[..., [1, 3]] = bboxes[..., [1, 3]] / cur_img_h * origin_h
  23. # clip bboxes
  24. bboxes[..., [0, 2]] = np.clip(bboxes[..., [0, 2]], a_min=0., a_max=origin_w)
  25. bboxes[..., [1, 3]] = np.clip(bboxes[..., [1, 3]], a_min=0., a_max=origin_h)
  26. else:
  27. # rescale
  28. bboxes[..., [0, 2]] = bboxes[..., [0, 2]] / (cur_img_w - deltas[0]) * origin_w
  29. bboxes[..., [1, 3]] = bboxes[..., [1, 3]] / (cur_img_h - deltas[1]) * origin_h
  30. # clip bboxes
  31. bboxes[..., [0, 2]] = np.clip(bboxes[..., [0, 2]], a_min=0., a_max=origin_w)
  32. bboxes[..., [1, 3]] = np.clip(bboxes[..., [1, 3]], a_min=0., a_max=origin_h)
  33. return bboxes
  34. def bbox2dist(anchor_points, bbox, reg_max):
  35. '''Transform bbox(xyxy) to dist(ltrb).'''
  36. x1y1, x2y2 = torch.split(bbox, 2, -1)
  37. lt = anchor_points - x1y1
  38. rb = x2y2 - anchor_points
  39. dist = torch.cat([lt, rb], -1).clamp(0, reg_max - 0.01)
  40. return dist
  41. # ------------------ IoU ops ------------------
  42. def box_iou(boxes1, boxes2):
  43. area1 = box_area(boxes1)
  44. area2 = box_area(boxes2)
  45. lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
  46. rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]
  47. wh = (rb - lt).clamp(min=0) # [N,M,2]
  48. inter = wh[:, :, 0] * wh[:, :, 1] # [N,M]
  49. union = area1[:, None] + area2 - inter
  50. iou = inter / union
  51. return iou, union
  52. def generalized_box_iou(boxes1, boxes2):
  53. """
  54. Generalized IoU from https://giou.stanford.edu/
  55. The boxes should be in [x0, y0, x1, y1] format
  56. Returns a [N, M] pairwise matrix, where N = len(boxes1)
  57. and M = len(boxes2)
  58. """
  59. # degenerate boxes gives inf / nan results
  60. # so do an early check
  61. assert (boxes1[:, 2:] >= boxes1[:, :2]).all()
  62. assert (boxes2[:, 2:] >= boxes2[:, :2]).all()
  63. iou, union = box_iou(boxes1, boxes2)
  64. lt = torch.min(boxes1[:, None, :2], boxes2[:, :2])
  65. rb = torch.max(boxes1[:, None, 2:], boxes2[:, 2:])
  66. wh = (rb - lt).clamp(min=0) # [N,M,2]
  67. area = wh[:, :, 0] * wh[:, :, 1]
  68. return iou - (area - union) / area
  69. def get_ious(bboxes1,
  70. bboxes2,
  71. box_mode="xyxy",
  72. iou_type="iou"):
  73. """
  74. Compute iou loss of type ['iou', 'giou', 'linear_iou']
  75. Args:
  76. inputs (tensor): pred values
  77. targets (tensor): target values
  78. weight (tensor): loss weight
  79. box_mode (str): 'xyxy' or 'ltrb', 'ltrb' is currently supported.
  80. loss_type (str): 'giou' or 'iou' or 'linear_iou'
  81. reduction (str): reduction manner
  82. Returns:
  83. loss (tensor): computed iou loss.
  84. """
  85. if box_mode == "ltrb":
  86. bboxes1 = torch.cat((-bboxes1[..., :2], bboxes1[..., 2:]), dim=-1)
  87. bboxes2 = torch.cat((-bboxes2[..., :2], bboxes2[..., 2:]), dim=-1)
  88. elif box_mode != "xyxy":
  89. raise NotImplementedError
  90. eps = torch.finfo(torch.float32).eps
  91. bboxes1_area = (bboxes1[..., 2] - bboxes1[..., 0]).clamp_(min=0) \
  92. * (bboxes1[..., 3] - bboxes1[..., 1]).clamp_(min=0)
  93. bboxes2_area = (bboxes2[..., 2] - bboxes2[..., 0]).clamp_(min=0) \
  94. * (bboxes2[..., 3] - bboxes2[..., 1]).clamp_(min=0)
  95. w_intersect = (torch.min(bboxes1[..., 2], bboxes2[..., 2])
  96. - torch.max(bboxes1[..., 0], bboxes2[..., 0])).clamp_(min=0)
  97. h_intersect = (torch.min(bboxes1[..., 3], bboxes2[..., 3])
  98. - torch.max(bboxes1[..., 1], bboxes2[..., 1])).clamp_(min=0)
  99. area_intersect = w_intersect * h_intersect
  100. area_union = bboxes2_area + bboxes1_area - area_intersect
  101. ious = area_intersect / area_union.clamp(min=eps)
  102. if iou_type == "iou":
  103. return ious
  104. elif iou_type == "giou":
  105. g_w_intersect = torch.max(bboxes1[..., 2], bboxes2[..., 2]) \
  106. - torch.min(bboxes1[..., 0], bboxes2[..., 0])
  107. g_h_intersect = torch.max(bboxes1[..., 3], bboxes2[..., 3]) \
  108. - torch.min(bboxes1[..., 1], bboxes2[..., 1])
  109. ac_uion = g_w_intersect * g_h_intersect
  110. gious = ious - (ac_uion - area_union) / ac_uion.clamp(min=eps)
  111. return gious
  112. else:
  113. raise NotImplementedError
  114. # copy from YOLOv5
  115. def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
  116. # Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)
  117. # Get the coordinates of bounding boxes
  118. if xywh: # transform from xywh to xyxy
  119. (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
  120. w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
  121. b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
  122. b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
  123. else: # x1, y1, x2, y2 = box1
  124. b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
  125. b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
  126. w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
  127. w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
  128. # Intersection area
  129. inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \
  130. (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)
  131. # Union Area
  132. union = w1 * h1 + w2 * h2 - inter + eps
  133. # IoU
  134. iou = inter / union
  135. if CIoU or DIoU or GIoU:
  136. cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width
  137. ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height
  138. if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
  139. c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared
  140. rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2
  141. if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
  142. v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
  143. with torch.no_grad():
  144. alpha = v / (v - iou + (1 + eps))
  145. return iou - (rho2 / c2 + v * alpha) # CIoU
  146. return iou - rho2 / c2 # DIoU
  147. c_area = cw * ch + eps # convex area
  148. return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
  149. return iou # IoU
  150. if __name__ == '__main__':
  151. box1 = torch.tensor([[10, 10, 20, 20]])
  152. box2 = torch.tensor([[15, 15, 20, 20]])
  153. iou = box_iou(box1, box2)
  154. print(iou)