iou_rotated

原作者的代码只能对(B, 1, 5)维度计算。与原作者输入不同,修改后的demo文件支持(B, N, 5)计算,与计算方式,同时C++库的调用绝对路径也已修改

"""
Visualize the process of calculating rotated IoU step by step.
"""
import sys
sys.path.append("/usr/lib/python3.8/site-packages/sort_vertices-0.0.0-py3.8-linux-x86_64.egg")

import numpy as np
import torch
from utils.IoUs_2D_3D.cuda_op.cuda_ext import sort_v
EPSLION = 1e-8
# TODO: Convert the box (x, y, w, h, alpha) to 4corners (x1,y1,x2,y2,x3,y3,x4,y4).
def boxes2corners(boxes:torch.Tensor):
    """
        Args: 
            box: `torch.Tensor` shape: (B, N, 5), contains (x, y, w, h, alpha) of batch
        Returns:
            corners: `torch.Tensor` shape: (B, N, 4, 2)
    """
    x = boxes[..., 0:1] # (B, N, 1)
    y = boxes[..., 1:2]
    w = boxes[..., 2:3]
    h = boxes[..., 3:4]
    alpha = boxes[..., 4::5]
    tx = torch.Tensor([0.5, -0.5, -0.5, 0.5]).unsqueeze(0).unsqueeze(0).to(boxes.device)
    tx = tx * w
    ty = torch.Tensor([0.5, 0.5, -0.5, -0.5]).unsqueeze(0).unsqueeze(0).to(boxes.device)
    ty = ty * h
    # (B, N, 4, 2)
    txty = torch.stack([tx, ty], dim=-1)
    cos = torch.cos(alpha)
    sin = torch.sin(alpha)
    # print('sin',sin.shape)
    # (2, 2) counter clockwise
    # 初始化 rotate tensor
    rotate = torch.zeros([1, sin.shape[1], 2, 2]).to(boxes.device)  # 注意这里初始化的形状是 [1, N, 2, 2]

    # 填充 rotate tensor
    rotate[:, :, 0, 0] = cos[:, :, 0]
    rotate[:, :, 0, 1] = -sin[:, :, 0]
    rotate[:, :, 1, 0] = sin[:, :, 0]
    rotate[:, :, 1, 1] = cos[:, :, 0]
    # rotate
    # print('1',txty.shape, rotate.shape)
    txty = txty @ rotate
    # print('2',txty.shape)
    # (B, N, 4, 2)
    xy = torch.cat([x, y], dim=-1).unsqueeze(2).repeat([1,1,4,1])
    # translate
    corners = xy + txty
    return corners

# TODO: Find the intersection of boxes.
def boxes_intersection(corners1:torch.Tensor, corners2:torch.Tensor):
    """
        Args:
            corners1: `torch.Tensor` shape: (B, N, 4, 2), contains the l t r b corners of boxes
            corners2: `torch.Tensor` shape: (B, N, 4, 2), contains the l t r b corners of boxes
        Returns: 
            inters: `torch.Tensor` shape: (B, N, 16, 2) intersections of 2 boxes. There are 4 combinations for one line,
                    each box has 4 line. Thus, there are 16 (4 x 4) combinations finnally. 
            mask: `torch.Tensor` [!BOOL!] shape: (B, N, 16) mask of intersection. The mask marks the valid intersection.
        
        How to get the intersection of two lines: 
            Reference: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
    """
    # construct lines (B, N, 4, 4)
    lines1 = torch.cat([corners1, corners1[:, :, [1, 2, 3, 0], :]], dim=-1)
    lines2 = torch.cat([corners2, corners2[:, :, [1, 2, 3, 0], :]], dim=-1)
    # (B, N, 4, 4, 4)
    ext_lines1 = lines1.unsqueeze(3).repeat([1,1,1,4,1])
    ext_lines2 = lines2.unsqueeze(2).repeat([1,1,4,1,1])
    # (B, N, 4, 4)
    x1 = ext_lines1[..., 0]
    y1 = ext_lines1[..., 1]
    x2 = ext_lines1[..., 2]
    y2 = ext_lines1[..., 3]
    
    x3 = ext_lines2[..., 0]
    y3 = ext_lines2[..., 1]
    x4 = ext_lines2[..., 2]
    y4 = ext_lines2[..., 3]

    # Refer to intersection of lines: 
    # https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
    denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
    molecular_t = (x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)
    molecular_u = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)

    t = molecular_t / denominator
    u = molecular_u / denominator

    mask_t = (t > 0.) * (t < 1.)
    mask_u = (u > 0.) * (u < 1.)
    mask = mask_t * mask_u
    t = molecular_t / ( denominator + EPSLION)
    inters = torch.stack([ x1 + t * (x2 - x1), y1 + t * (y2 - y1)], dim=-1)
    inters = inters * mask.float().unsqueeze(-1)
    # (B, N, 4, 4, 2), (B, N, 4, 4) -> (B, N, 16, 2), (B, N, 16) 
    B = inters.size()[0]
    N = inters.size()[1]
    return inters.reshape((B, N, -1, 2)), mask.reshape((B, N, -1))

#TODO: Judge if box is inside another box.
def box1_in_box2(corners1:torch.Tensor, corners2:torch.Tensor):
    """
        Args:
            corners1: `torch.Tensor` shape: (B, N, 4, 2), contains the l t r b corners of boxes
            corners2: `torch.Tensor` shape: (B, N, 4, 2), contains the l t r b corners of boxes
        Return:
            c: `torch.Tensor` shape: (B, N, 4), mark the corners that are inside another box
            
    """
    # (B, N, 1, 2)
    a = corners2[:, :, 0:1, :]
    b = corners2[:, :, 1:2, :]
    d = corners2[:, :, 3:4, :]
    ab = b - a
    ad = d - a
    # (B, N, 4, 2)
    am = corners1 - a
    # calculate the projection of am on ab through dot product 
    projected_ab = torch.sum(ab * am, dim=-1)
    projected_ad = torch.sum(ad * am, dim=-1)
    # calculate the length of ab and ad
    norm_ab = torch.sum( ab * ab, dim=-1)
    norm_ad = torch.sum( ad * ad, dim=-1)
    # line ab is parrel to  x axis, ad is parrel to y axis
    # judge if these corners' x/y coordinate are inside another box
    c_x = ( projected_ab / norm_ab > - 1e-6) * ( projected_ab / norm_ab < 1. + 1e-6)
    c_y = ( projected_ad / norm_ad > - 1e-6) * ( projected_ad / norm_ad < 1. + 1e-6)
    c = c_x * c_y
    return c

# TODO: Build vertices.
def build_vertices(corners1:torch.Tensor, corners2:torch.Tensor, inters:torch.Tensor, c1_in_2:torch.Tensor, c2_in_1:torch.Tensor, mask_inter:torch.Tensor):
    """
        Args:
            corners1: `torch.Tensor` shape: (B, N, 4, 2), contains the l t r b corners of boxes
            corners2: `torch.Tensor` shape: (B, N, 4, 2), contains the l t r b corners of boxes
            inters: `torch.Tensor` shape: (B, N, 16, 2) intersections of 2 boxes. 
            mask_inter: `torch.Tensor` shape: (B, N, 16) mask of intersection. The mask marks the valid intersection.
            c1_in_2, c2_in_1: `torch.Tensor` shape: (B, N, 4), mark the corners that are inside another box
        Returns:
            vertices: `torch.Tensor` shape: (B, N, 24, 2) [24 = 4+4+16] concanate corners1, corners2 and inters
            masks: `torch.Tensor` shape: (B, N, 24) concanate the c1_in_2, c2_in_1 and mask_inter. It marks the valid corner of the overlap (polygon)
    """
    # vertices: (B, N, 4+4+16, 2)
    vertices = torch.cat([corners1, corners2, inters], dim=2)
    # masks: (B, N, 4+4+16)
    masks = torch.cat([c1_in_2, c2_in_1, mask_inter], dim=2)
    return vertices, masks

#TODO: Sort vertices.
def sort_vertices(vertices, masks):
    """
        Args:
            vertices: `torch.Tensor` shape: (B, N, 24, 2) [24 = 4+4+16] concanate corners1, corners2 and inters
            masks: `torch.Tensor` shape: (B, N, 24) concanate the c1_in_2, c2_in_1 and mask_inter. It marks the valid corner of the overlap (polygon)
        Return:
            sorted_index: `torch.Tensor` shape: (B, N, 24)
        
        Steps:(1)normalize (- mean) (2) sort
    """
    # (B, N)
    num_valid = torch.sum(masks.int(), dim=-1).int()
    # (B, N, 1, 2)
    mean = torch.sum( vertices * masks.unsqueeze(-1), dim=2, keepdim=True) / num_valid.unsqueeze(dim=-1).unsqueeze(dim=-1)
    vertices = vertices - mean
    # NOTICE: masks must be bool instead of int or float
    return sort_v(vertices, masks, num_valid).long()

#TODO: Calculate the are of overlap.
def calculate_area(vertices, sorted_index):
    """
        Args:
            vertices: `torch.Tensor` shape: (B, N, 24, 2) [24 = 4+4+16] concanate corners1, corners2 and inters
            sorted_index: `torch.Tensor` shape: (B, N, k) sorted index of vertices. `k` is the number of valid corners of overlap
        Returns:
            area: `torch.Tensor` (B, N) area of overlap
    """
    sorted_index = sorted_index.unsqueeze(-1).repeat([1,1,1,2])
    # (B, N, k, 2)
    selected = torch.gather(vertices, 2, sorted_index)
    # How to calculate the area of polygon? 
    # Refer to shoelace formula: https://en.wikipedia.org/wiki/Shoelace_formula
    # (B, N, k-1)
    total = selected[:, :, 0:-1, 0] * selected[:, :, 1:, 1] - selected[:, :, 0:-1, 1] * selected[:, :, 1:, 0]
    total = torch.sum(total, dim=2)
    area = torch.abs(total) / 2
    return area, selected

#TODO: Calculate Ious
def IoUs(bbox1, bbox2, overlap):
    """
        Args:
            bbox1, bbox2: `torch.Tensor` shape: (B, N, 5), contains (x, y, w, h, alpha) of batch
            overlap: `torch.Tensor` shape(B, N) the area of overlap
        Returns:
            ious: `torch.Tensor` shape(B, N)
    """
    area1 = bbox1[..., 2] * bbox1[..., 3]
    area2 = bbox2[..., 2] * bbox2[..., 3]
    return overlap / (area1 + area2 - overlap)

def rotate_iou_demo(box1, box2):
    box1 = box1.unsqueeze(0)
    box2 = box2.unsqueeze(0)
    # print(box1, box2, box1.shape,box2.shape)

    corners1 = boxes2corners(box1)
    corners2 = boxes2corners(box2)
    # plt.show()
    inters, mask_inters = boxes_intersection(corners1, corners2)

    c1_in_2 = box1_in_box2(corners1, corners2)
    c2_in_1 = box1_in_box2(corners2, corners1)

    vertices, masks = build_vertices(corners1, corners2, inters, c1_in_2, c2_in_1, mask_inters)

    sorted_index = sort_vertices(vertices, masks)

    overlap, _ = calculate_area(vertices, sorted_index)
    iouss = IoUs(box1, box2, overlap)
    return 1-iouss.clamp(min=1e-6)

if __name__ == '__main__':
    import matplotlib.pyplot as plt
    def viz(box:np.ndarray, ax=None, color='green', linewidth=3, xrange=(-3,3), yrange=(-3,3)):
        colors = ['red', 'green', 'blue', 'black']
        # box: (4, 2)
        if ax == None:
            fig = plt.figure(figsize=(8,8))
            ax = fig.add_subplot(111)
            # plt.xlim(xrange)
            # plt.ylim(yrange)
        print(box)
        for i in range(len(box)):
            if i != len(box) - 1:
                plt.plot([box[i][0], box[i+1][0]], [box[i][1], box[i+1][1]], color=colors[i], linewidth=linewidth)
            else:
                plt.plot([box[i][0], box[0][0]], [box[i][1], box[0][1]], color=colors[i], linewidth=linewidth)
        return ax
    
    def viz_scatter(inters, mask, ax, color='red'):
        # inters: (B, N, 16, 2) mask: (B, N, 16)  default B, N = 1, 1
        assert ax != None, 'ax must not be empty'
        inters = inters * mask.unsqueeze(dim=-1) # repeated operation. Removing it is ok.
        inters = inters.reshape(-1, 2)
        mask = mask.reshape(-1)
        for idx, m in enumerate(mask):
            if int(m) == 0:
                continue
            inter = inters.cpu().numpy()[idx]
            ax.scatter(inter[0], inter[1], color=color)
        return ax
    
    def evaluate():
        from IoU import IoUs2D
        device = torch.device('cuda:0')
        box_0 = torch.tensor([.0, .0, 2., 2., .0], device=device) # x y w h alpha

        def test_same_box():
            expect = 1.
            box_1 = torch.tensor([.0, .0, 2., 2., .0], device=device)
            result = IoUs2D(box_0[None, None, ...], box_1[None, None, ...])[0].cpu().numpy()
            print("expect:", expect, "get:", result[0,0])

        def test_same_edge():
            expect = 0.
            box_1 = torch.tensor([.0, 2, 2., 2., .0], device=device)
            result = IoUs2D(box_0[None, None, ...], box_1[None, None, ...])[0].cpu().numpy()
            print("expect:", expect, "get:", result[0,0])

        def test_same_edge_offset():
            expect = 0.3333
            box_1 = torch.tensor([.0, 1., 2., 2., .0], device=device)
            result = IoUs2D(box_0[None, None, ...], box_1[None, None, ...])[0].cpu().numpy()
            print("expect:", expect, "get:", result[0,0])

        def test_same_box2():
            expect = 1
            box_1 = torch.tensor([38, 120, 1.3, 20, (50 * np.pi) / 180], device=device)
            result = IoUs2D(box_1[None, None, ...], box_1[None, None, ...])[0].cpu().numpy()
            print("expect:", expect, "get:", result[0,0])
        
        test_same_box()
        test_same_edge()
        test_same_edge_offset()
        test_same_box2()

    def demo():
        device = torch.device('cuda')
        alpha = 120
        # box1 = torch.tensor([[1,2,2,3,0]], device=device).unsqueeze(0)
        # box2 = torch.tensor([[1,2,2,3,-np.pi/2]], device=device).unsqueeze(0)
        
        box1 = torch.tensor([[1,2,2,3,0],[1,2,2,3,-np.pi/2],[1,2,2,3,-np.pi/2]], device=device).unsqueeze(0)
        box2 = torch.tensor([[1,2,2,3,-np.pi/2],[1,2,2,3,0],[1,2,2,3,-np.pi/2]], device=device).unsqueeze(0)
        print(box1.shape,box2.shape)
        
        corners1 = boxes2corners(box1)
        corners2 = boxes2corners(box2)
        
        print('corners1',corners1,corners1.shape)
        # ax = viz(corners1.reshape(-1, 2).cpu())
        # ax = viz(corners2.reshape(-1, 2).cpu(), ax, color='red')
        # plt.show()
        inters, mask_inters = boxes_intersection(corners1, corners2)
        # ax = viz_scatter(inters, mask_inters, ax)

        c1_in_2 = box1_in_box2(corners1, corners2)
        c2_in_1 = box1_in_box2(corners2, corners1)

        vertices, masks = build_vertices(corners1, corners2, inters, c1_in_2, c2_in_1, mask_inters)

        sorted_index = sort_vertices(vertices, masks)
        
        overlap, _ = calculate_area(vertices, sorted_index)
        iouss = IoUs(box1, box2, overlap)
        print('IoUs:', iouss, iouss.shape)
        
        # visualize 
        plt.show()

    #-------------#
    #     Demo    #
    #-------------#
    
    # visualize the process of calculating rotated IoU step by step.
    demo()

    # evaluate different pairs of boxes
    # evaluate()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值