lilanxiao/Rotated_IoU

same rotated bbox while iou == 0

ucasqcz opened this issue · 6 comments

box = [[0, 0], [100, 0], [200, 100], [100, 100]]
cal_iou(box,box)
while result iou is 0?
is this a bug or?

Would you please provide the complete code to reproduce the result? My previous code indeed had problems with the same boxes but I've already fixed it.

Also, I provide test cases in test_corner_cases.py to validate my code. Would you please send me the result you get?

i modify the representation of rbbox from (x,y,w,h,theta) -> (x1,y1,...,x4,y4) format in function cal_iou of oriented_iou_loss.py

 def cal_iou(box1: torch.Tensor, box2: torch.Tensor):
    corners1 = box1
    corners2 = box2
    inter_area, _ = oriented_box_intersection_2d(corners1, corners2)  # (B, N)
    #area1 = box1[:, :, 2] * box1[:, :, 3]
    area1 = calc_area(corners1)
    area2 = calc_area(corners2)
    #area2 = box2[:, :, 2] * box2[:, :, 3]
    u = area1 + area2 - inter_area
    iou = inter_area / u
    return iou, corners1, corners2, u

where calc_area function is from Shoelace Theorem

def calculate_area(idx_sorted: torch.Tensor, vertices: torch.Tensor):
    """calculate area of intersection

    Args:
        idx_sorted (torch.Tensor): (B, N, 9)
        vertices (torch.Tensor): (B, N, 24, 2)

    return:
        area: (B, N), area of intersection
        selected: (B, N, 9, 2), vertices of polygon with zero padding 
    """
    idx_ext = idx_sorted.unsqueeze(-1).repeat([1, 1, 1, 2])
    selected = torch.gather(vertices, 2, idx_ext)
    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

then for the test_corner_cases.py

def test_iou():
    expect = 1.0
    box_1 = [[0, 0], [100, 0], [200, 100], [100, 100]]
    box_1 = torch.tensor(box_1, device=device).reshape(-1, 2)
    box_2 = [[0, 0], [100, 0], [100, 100], [0, 100]]
    box_2 = torch.tensor(box_2, device=device).reshape(-1, 2)
    box_1 = box_1[None, ...].repeat(3, 1, 1)
    box_2 = box_2[None, ...].repeat(3, 1, 1)

    result = cal_iou(box_1[None, ...], box_1[None,  ...])[0].cpu().numpy()
    print(f'expect: {expect}, get: {result[0,0]}')

if i am wrong, point out for me, thanks

I get your idea. You want to use corners directly as input instead of the five parameters. The idea is fine. The problem is, the box_1 in your test case is not a rectangle. In this case, my code doesn't work. (BTW, I got the correct result with box_2, which is a rectangle.)

Check the function box1_in_box2 in box_intersection_2d.py. This function assumes that the inputs are two rectangles. You should modify this function to make your code work.

The function is based on the projection of vectors and only works with rectangles. To support other types of polygons, you should use a more general method, such as point-in-polygon.

YES, i have found the problem, and i replace the follow function to replace the original box1_in_box2,

def box1_in_box2(corners1: torch.Tensor, corners2: torch.Tensor):
    """check if corners of box1 lie in box2
    Convention: if a corner is exactly on the edge of the other box, it's also a valid point

    Args:
        corners1 (torch.Tensor): (B, N, 4, 2)
        corners2 (torch.Tensor): (B, N, 4, 2)

    Returns:
        c1_in_2: (B, N, 4) Bool
    """
    # a->b->c->d  -->  b->c->d->a
    corners2_clockwise = torch.roll(corners2, -1, -2)
    # a->b->c->d  -->  d->a->b->c
    corners2_anti = torch.roll(corners2, 1, -2)
    edge_clockwise = corners2_clockwise - corners2
    edge_anti = corners2_anti - corners2
    edge_clockwise = edge_clockwise.unsqueeze(2).repeat([1, 1, 4, 1, 1])
    edge_anti = edge_anti.unsqueeze(2).repeat([1, 1, 4, 1, 1])

    # x dot for
    c1 = corners1.unsqueeze(3).repeat([1, 1, 1, 4, 1])
    c2 = corners2.unsqueeze(2).repeat([1, 1, 4, 1, 1])
    point_to_p = c1 - c2
    ABXAP = edge_clockwise[..., 0] * point_to_p[...,
                                                1] - edge_clockwise[..., 1] * point_to_p[..., 0]
    ADXAP = edge_anti[..., 0] * point_to_p[...,
                                           1] - edge_anti[..., 1] * point_to_p[..., 0]

    # (B, N, 4, 4)
    indicator = ABXAP * ADXAP
    indicator = indicator <= 0
    # (B, N , 4)
    indicator = indicator.all(dim=-1)

    return indicator

now, the iou of box_1 and box_2 is 0 while the correct iou is 0.3,
if the two polygon share several points, the build_vertices function in

def oriented_box_intersection_2d(corners1: torch.Tensor, corners2: torch.Tensor):
    """calculate intersection area of 2d rectangles 

    Args:
        corners1 (torch.Tensor): (B, N, 4, 2)
        corners2 (torch.Tensor): (B, N, 4, 2)

    Returns:
        area: (B, N), area of intersection
        selected: (B, N, 9, 2), vertices of polygon with zero padding 
    """
    import pdb
    pdb.set_trace()
    inters, mask_inter = box_intersection_th(corners1, corners2)
    c12, c21 = box_in_box_th(corners1, corners2)
    vertices, mask = build_vertices(
        corners1, corners2, c12, c21, inters, mask_inter)

    sorted_indices = sort_indices(vertices, mask)
    return calculate_area(sorted_indices, vertices)

will add duplicate points, at the same time, the sorted_indices does not take mask into consideration,
the final iou is 0, do you have any idea to fix that case?

I don't have enough time to test your new code, since I have my own work. I can only give some basic ideas.

Yes, things would become tricky if the two polygons share points. In my code, I have to loose some conditions so that the IoU can be correctly calculated and the code is numerically stable. In my box1_in_box2 function, I use:

    cond1 = (p_ab / norm_ab > - 1e-6) * (p_ab / norm_ab < 1 + 1e-6)   # (B, N, 4)
    cond2 = (p_ad / norm_ad > - 1e-6) * (p_ad / norm_ad < 1 + 1e-6)   # (B, N, 4)

instead of the theoretical conditions:

    cond1 = (p_ab / norm_ab >= 0) * (p_ab / norm_ab <= 1)   # (B, N, 4)
    cond2 = (p_ad / norm_ad >= 0) * (p_ad / norm_ad <= 1)   # (B, N, 4)

I think you could probably modify the following line to loose the condition.

indicator = indicator <= 0

Yes, I also saw the duplicated points previously. I've added some code in the CUDA-Kernel to remove them. Since the added part is like some sort of dirty hack, it might not work with your code. If the duplicated points are still a problem, you could consider modifying the CUDA-Kernel.

  // for corner case: the two boxes are exactly the same.
  // in this case, idx would have duplicate elements, which makes the shoelace formula broken
  // because of the definition, the duplicate elements only appear in the first 8 positions
  // (they are "corners in box", not "intersection of edges")
  if (num_valid[i] == 8){
      int counter = 0;
      for (int j=0; j<4; ++j){
          int check = idx[i*MAX_NUM_VERT_IDX + j];
          for (int k=4; k<INTERSECTION_OFFSET; ++k){
              if (idx[i*MAX_NUM_VERT_IDX + k] == check)
                  counter++;
          }
      }
      if (counter == 4){
          idx[i*MAX_NUM_VERT_IDX + 4] = idx[i*MAX_NUM_VERT_IDX + 0];
          for (int j = 5; j<MAX_NUM_VERT_IDX; ++j){
              idx[i*MAX_NUM_VERT_IDX + j] = pad;
          }
      }                            
  }

thanks for you time and advices, I will try to fix it.