from typing import List import math import numpy as np import torch from torchvision.ops.boxes import box_area # ------------------ Box ops ------------------ def box_cxcywh_to_xyxy(x): x_c, y_c, w, h = x.unbind(-1) b = [(x_c - 0.5 * w), (y_c - 0.5 * h), (x_c + 0.5 * w), (y_c + 0.5 * h)] return torch.stack(b, dim=-1) def box_xyxy_to_cxcywh(x): x0, y0, x1, y1 = x.unbind(-1) b = [(x0 + x1) / 2, (y0 + y1) / 2, (x1 - x0), (y1 - y0)] return torch.stack(b, dim=-1) def rescale_bboxes(bboxes, origin_size, ratio): # rescale bboxes if isinstance(ratio, float): bboxes /= ratio elif isinstance(ratio, List) and len(ratio) == 2: bboxes[..., [0, 2]] /= ratio[0] bboxes[..., [1, 3]] /= ratio[1] else: raise NotImplementedError("ratio should be a int or List[int, int] type.") # clip bboxes bboxes[..., [0, 2]] = np.clip(bboxes[..., [0, 2]], a_min=0., a_max=origin_size[0]) bboxes[..., [1, 3]] = np.clip(bboxes[..., [1, 3]], a_min=0., a_max=origin_size[1]) return bboxes def bbox2dist(anchor_points, bbox, reg_max): '''Transform bbox(xyxy) to dist(ltrb).''' x1y1, x2y2 = torch.split(bbox, 2, -1) lt = anchor_points - x1y1 rb = x2y2 - anchor_points dist = torch.cat([lt, rb], -1).clamp(0, reg_max - 0.01) return dist def bbox2delta(proposals, gt, means=(0., 0., 0., 0.), stds=(1., 1., 1., 1.)): # hack for matcher if proposals.size() != gt.size(): proposals = proposals[:, None] gt = gt[None] proposals = proposals.float() gt = gt.float() px, py, pw, ph = proposals.unbind(-1) gx, gy, gw, gh = gt.unbind(-1) dx = (gx - px) / (pw + 0.1) dy = (gy - py) / (ph + 0.1) dw = torch.log(gw / (pw + 0.1)) dh = torch.log(gh / (ph + 0.1)) deltas = torch.stack([dx, dy, dw, dh], dim=-1) means = deltas.new_tensor(means).unsqueeze(0) stds = deltas.new_tensor(stds).unsqueeze(0) deltas = deltas.sub_(means).div_(stds) return deltas # ------------------ IoU ops ------------------ def box_iou(boxes1, boxes2): area1 = box_area(boxes1) area2 = box_area(boxes2) lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2] rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2] wh = (rb - lt).clamp(min=0) # [N,M,2] inter = wh[:, :, 0] * wh[:, :, 1] # [N,M] union = area1[:, None] + area2 - inter iou = inter / union return iou, union def generalized_box_iou(boxes1, boxes2): """ Generalized IoU from https://giou.stanford.edu/ The boxes should be in [x0, y0, x1, y1] format Returns a [N, M] pairwise matrix, where N = len(boxes1) and M = len(boxes2) """ # degenerate boxes gives inf / nan results # so do an early check assert (boxes1[:, 2:] >= boxes1[:, :2]).all() assert (boxes2[:, 2:] >= boxes2[:, :2]).all() iou, union = box_iou(boxes1, boxes2) lt = torch.min(boxes1[:, None, :2], boxes2[:, :2]) rb = torch.max(boxes1[:, None, 2:], boxes2[:, 2:]) wh = (rb - lt).clamp(min=0) # [N,M,2] area = wh[:, :, 0] * wh[:, :, 1] return iou - (area - union) / area def get_ious(bboxes1, bboxes2, box_mode="xyxy", iou_type="iou"): """ Compute iou loss of type ['iou', 'giou', 'linear_iou'] Args: inputs (tensor): pred values targets (tensor): target values weight (tensor): loss weight box_mode (str): 'xyxy' or 'ltrb', 'ltrb' is currently supported. loss_type (str): 'giou' or 'iou' or 'linear_iou' reduction (str): reduction manner Returns: loss (tensor): computed iou loss. """ if box_mode == "ltrb": bboxes1 = torch.cat((-bboxes1[..., :2], bboxes1[..., 2:]), dim=-1) bboxes2 = torch.cat((-bboxes2[..., :2], bboxes2[..., 2:]), dim=-1) elif box_mode != "xyxy": raise NotImplementedError eps = torch.finfo(torch.float32).eps bboxes1_area = (bboxes1[..., 2] - bboxes1[..., 0]).clamp_(min=0) \ * (bboxes1[..., 3] - bboxes1[..., 1]).clamp_(min=0) bboxes2_area = (bboxes2[..., 2] - bboxes2[..., 0]).clamp_(min=0) \ * (bboxes2[..., 3] - bboxes2[..., 1]).clamp_(min=0) w_intersect = (torch.min(bboxes1[..., 2], bboxes2[..., 2]) - torch.max(bboxes1[..., 0], bboxes2[..., 0])).clamp_(min=0) h_intersect = (torch.min(bboxes1[..., 3], bboxes2[..., 3]) - torch.max(bboxes1[..., 1], bboxes2[..., 1])).clamp_(min=0) area_intersect = w_intersect * h_intersect area_union = bboxes2_area + bboxes1_area - area_intersect ious = area_intersect / area_union.clamp(min=eps) if iou_type == "iou": return ious elif iou_type == "giou": g_w_intersect = torch.max(bboxes1[..., 2], bboxes2[..., 2]) \ - torch.min(bboxes1[..., 0], bboxes2[..., 0]) g_h_intersect = torch.max(bboxes1[..., 3], bboxes2[..., 3]) \ - torch.min(bboxes1[..., 1], bboxes2[..., 1]) ac_uion = g_w_intersect * g_h_intersect gious = ious - (ac_uion - area_union) / ac_uion.clamp(min=eps) return gious else: raise NotImplementedError # copy from YOLOv5 def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7): # Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4) # Get the coordinates of bounding boxes if xywh: # transform from xywh to xyxy (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1) w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2 b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_ b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_ else: # x1, y1, x2, y2 = box1 b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1) b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1) w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps # Intersection area inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \ (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0) # Union Area union = w1 * h1 + w2 * h2 - inter + eps # IoU iou = inter / union if CIoU or DIoU or GIoU: cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1 c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2 if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47 v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2) with torch.no_grad(): alpha = v / (v - iou + (1 + eps)) return iou - (rho2 / c2 + v * alpha) # CIoU return iou - rho2 / c2 # DIoU c_area = cw * ch + eps # convex area return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf return iou # IoU if __name__ == '__main__': box1 = torch.tensor([[10, 10, 20, 20]]) box2 = torch.tensor([[15, 15, 20, 20]]) iou = box_iou(box1, box2) print(iou)