From ee58a4aac56b52bdd8bf3b79eff5cae86724d892 Mon Sep 17 00:00:00 2001 From: lanzeshun <962034936@qq.com> Date: Fri, 12 Jan 2024 14:13:20 +0800 Subject: [PATCH] [Add] Add rotate iou cpu evaluation on kitti dateset --- .../evaluation/functional/kitti_utils/eval.py | 20 +- .../functional/kitti_utils/rotate_iou_cpu.py | 254 ++++++++++++++++++ .../test_functional/test_kitti_eval.py | 6 - 3 files changed, 269 insertions(+), 11 deletions(-) create mode 100644 mmdet3d/evaluation/functional/kitti_utils/rotate_iou_cpu.py diff --git a/mmdet3d/evaluation/functional/kitti_utils/eval.py b/mmdet3d/evaluation/functional/kitti_utils/eval.py index 5e559529f..9ee23342b 100644 --- a/mmdet3d/evaluation/functional/kitti_utils/eval.py +++ b/mmdet3d/evaluation/functional/kitti_utils/eval.py @@ -4,6 +4,7 @@ import numba import numpy as np +import torch @numba.jit @@ -115,8 +116,12 @@ def image_box_overlap(boxes, query_boxes, criterion=-1): def bev_box_overlap(boxes, qboxes, criterion=-1): - from .rotate_iou import rotate_iou_gpu_eval - riou = rotate_iou_gpu_eval(boxes, qboxes, criterion) + if torch.cuda.is_available(): + from .rotate_iou import rotate_iou_gpu_eval + riou = rotate_iou_gpu_eval(boxes, qboxes, criterion) + else: + from .rotate_iou_cpu import rotate_iou_cpu_eval + riou = rotate_iou_cpu_eval(boxes, qboxes, criterion) return riou @@ -153,9 +158,14 @@ def d3_box_overlap_kernel(boxes, qboxes, rinc, criterion=-1): def d3_box_overlap(boxes, qboxes, criterion=-1): - from .rotate_iou import rotate_iou_gpu_eval - rinc = rotate_iou_gpu_eval(boxes[:, [0, 2, 3, 5, 6]], - qboxes[:, [0, 2, 3, 5, 6]], 2) + if torch.cuda.is_available(): + from .rotate_iou import rotate_iou_gpu_eval + rinc = rotate_iou_gpu_eval(boxes[:, [0, 2, 3, 5, 6]], + qboxes[:, [0, 2, 3, 5, 6]], 2) + else: + from .rotate_iou_cpu import rotate_iou_cpu_eval + rinc = rotate_iou_cpu_eval(boxes[:, [0, 2, 3, 5, 6]], + qboxes[:, [0, 2, 3, 5, 6]], 2) d3_box_overlap_kernel(boxes, qboxes, rinc, criterion) return rinc diff --git a/mmdet3d/evaluation/functional/kitti_utils/rotate_iou_cpu.py b/mmdet3d/evaluation/functional/kitti_utils/rotate_iou_cpu.py new file mode 100644 index 000000000..8f40ac40a --- /dev/null +++ b/mmdet3d/evaluation/functional/kitti_utils/rotate_iou_cpu.py @@ -0,0 +1,254 @@ +# Copyright (c) OpenMMLab. All rights reserved. +##################### +# Based on https://github.com/hongzhenwang/RRPN-revise +# Licensed under The MIT License +# Author: yanyan, scrin@foxmail.com +##################### +import math + +import numpy as np + + +def div_up(m, n): + return m // n + (m % n > 0) + + +def trangle_area(a, b, c): + return ((a[0] - c[0]) * (b[1] - c[1]) - (a[1] - c[1]) * + (b[0] - c[0])) / 2.0 + + +def area(int_pts, num_of_inter): + area_val = 0.0 + for i in range(num_of_inter - 2): + area_val += abs( + trangle_area(int_pts[:2], int_pts[2 * i + 2:2 * i + 4], + int_pts[2 * i + 4:2 * i + 6])) + return area_val + + +def sort_vertex_in_convex_polygon(int_pts, num_of_inter): + if num_of_inter > 0: + center = np.zeros((2, ), dtype=np.float32) + center[:] = 0.0 + for i in range(num_of_inter): + center[0] += int_pts[2 * i] + center[1] += int_pts[2 * i + 1] + center[0] /= num_of_inter + center[1] /= num_of_inter + v = np.zeros((2, ), dtype=np.float32) + vs = np.zeros((16, ), dtype=np.float32) + for i in range(num_of_inter): + v[0] = int_pts[2 * i] - center[0] + v[1] = int_pts[2 * i + 1] - center[1] + d = math.sqrt(v[0] * v[0] + v[1] * v[1]) + v[0] = v[0] / d + v[1] = v[1] / d + if v[1] < 0: + v[0] = -2 - v[0] + vs[i] = v[0] + j = 0 + temp = 0 + for i in range(1, num_of_inter): + if vs[i - 1] > vs[i]: + temp = vs[i] + tx = int_pts[2 * i] + ty = int_pts[2 * i + 1] + j = i + while j > 0 and vs[j - 1] > temp: + vs[j] = vs[j - 1] + int_pts[j * 2] = int_pts[j * 2 - 2] + int_pts[j * 2 + 1] = int_pts[j * 2 - 1] + j -= 1 + + vs[j] = temp + int_pts[j * 2] = tx + int_pts[j * 2 + 1] = ty + + +def line_segment_intersection(pts1, pts2, i, j, temp_pts): + A = np.zeros((2, ), dtype=np.float32) + B = np.zeros((2, ), dtype=np.float32) + C = np.zeros((2, ), dtype=np.float32) + D = np.zeros((2, ), dtype=np.float32) + + A[0] = pts1[2 * i] + A[1] = pts1[2 * i + 1] + + B[0] = pts1[2 * ((i + 1) % 4)] + B[1] = pts1[2 * ((i + 1) % 4) + 1] + + C[0] = pts2[2 * j] + C[1] = pts2[2 * j + 1] + + D[0] = pts2[2 * ((j + 1) % 4)] + D[1] = pts2[2 * ((j + 1) % 4) + 1] + BA0 = B[0] - A[0] + BA1 = B[1] - A[1] + DA0 = D[0] - A[0] + CA0 = C[0] - A[0] + DA1 = D[1] - A[1] + CA1 = C[1] - A[1] + acd = DA1 * CA0 > CA1 * DA0 + bcd = (D[1] - B[1]) * (C[0] - B[0]) > (C[1] - B[1]) * (D[0] - B[0]) + if acd != bcd: + abc = CA1 * BA0 > BA1 * CA0 + abd = DA1 * BA0 > BA1 * DA0 + if abc != abd: + DC0 = D[0] - C[0] + DC1 = D[1] - C[1] + ABBA = A[0] * B[1] - B[0] * A[1] + CDDC = C[0] * D[1] - D[0] * C[1] + DH = BA1 * DC0 - BA0 * DC1 + Dx = ABBA * DC0 - BA0 * CDDC + Dy = ABBA * DC1 - BA1 * CDDC + temp_pts[0] = Dx / DH + temp_pts[1] = Dy / DH + return True + return False + + +def line_segment_intersection_v1(pts1, pts2, i, j, temp_pts): + a = np.zeros((2, ), dtype=np.float32) + b = np.zeros((2, ), dtype=np.float32) + c = np.zeros((2, ), dtype=np.float32) + d = np.zeros((2, ), dtype=np.float32) + + a[0] = pts1[2 * i] + a[1] = pts1[2 * i + 1] + + b[0] = pts1[2 * ((i + 1) % 4)] + b[1] = pts1[2 * ((i + 1) % 4) + 1] + + c[0] = pts2[2 * j] + c[1] = pts2[2 * j + 1] + + d[0] = pts2[2 * ((j + 1) % 4)] + d[1] = pts2[2 * ((j + 1) % 4) + 1] + + area_abc = trangle_area(a, b, c) + area_abd = trangle_area(a, b, d) + + if area_abc * area_abd >= 0: + return False + + area_cda = trangle_area(c, d, a) + area_cdb = area_cda + area_abc - area_abd + + if area_cda * area_cdb >= 0: + return False + t = area_cda / (area_abd - area_abc) + + dx = t * (b[0] - a[0]) + dy = t * (b[1] - a[1]) + temp_pts[0] = a[0] + dx + temp_pts[1] = a[1] + dy + return True + + +def point_in_quadrilateral(pt_x, pt_y, corners): + ab0 = corners[2] - corners[0] + ab1 = corners[3] - corners[1] + + ad0 = corners[6] - corners[0] + ad1 = corners[7] - corners[1] + + ap0 = pt_x - corners[0] + ap1 = pt_y - corners[1] + + abab = ab0 * ab0 + ab1 * ab1 + abap = ab0 * ap0 + ab1 * ap1 + adad = ad0 * ad0 + ad1 * ad1 + adap = ad0 * ap0 + ad1 * ap1 + + return abab >= abap and abap >= 0 and adad >= adap and adap >= 0 + + +def quadrilateral_intersection(pts1, pts2, int_pts): + num_of_inter = 0 + for i in range(4): + if point_in_quadrilateral(pts1[2 * i], pts1[2 * i + 1], pts2): + int_pts[num_of_inter * 2] = pts1[2 * i] + int_pts[num_of_inter * 2 + 1] = pts1[2 * i + 1] + num_of_inter += 1 + if point_in_quadrilateral(pts2[2 * i], pts2[2 * i + 1], pts1): + int_pts[num_of_inter * 2] = pts2[2 * i] + int_pts[num_of_inter * 2 + 1] = pts2[2 * i + 1] + num_of_inter += 1 + temp_pts = np.zeros((2, ), dtype=np.float32) + for i in range(4): + for j in range(4): + has_pts = line_segment_intersection(pts1, pts2, i, j, temp_pts) + if has_pts: + int_pts[num_of_inter * 2] = temp_pts[0] + int_pts[num_of_inter * 2 + 1] = temp_pts[1] + num_of_inter += 1 + + return num_of_inter + + +def rbbox_to_corners(corners, rbbox): + # generate clockwise corners and rotate it clockwise + angle = rbbox[4] + a_cos = math.cos(angle) + a_sin = math.sin(angle) + center_x = rbbox[0] + center_y = rbbox[1] + x_d = rbbox[2] + y_d = rbbox[3] + corners_x = np.zeros((4, ), dtype=np.float32) + corners_y = np.zeros((4, ), dtype=np.float32) + corners_x[0] = -x_d / 2 + corners_x[1] = -x_d / 2 + corners_x[2] = x_d / 2 + corners_x[3] = x_d / 2 + corners_y[0] = -y_d / 2 + corners_y[1] = y_d / 2 + corners_y[2] = y_d / 2 + corners_y[3] = -y_d / 2 + for i in range(4): + corners[2 * i] = a_cos * corners_x[i] + a_sin * corners_y[i] + center_x + corners[2 * i + + 1] = -a_sin * corners_x[i] + a_cos * corners_y[i] + center_y + + +def inter(rbbox1, rbbox2): + corners1 = np.zeros((8, ), dtype=np.float32) + corners2 = np.zeros((8, ), dtype=np.float32) + intersection_corners = np.zeros((16, ), dtype=np.float32) + + rbbox_to_corners(corners1, rbbox1) + rbbox_to_corners(corners2, rbbox2) + + num_intersection = quadrilateral_intersection(corners1, corners2, + intersection_corners) + sort_vertex_in_convex_polygon(intersection_corners, num_intersection) + # print(intersection_corners.reshape([-1, 2])[:num_intersection]) + + return area(intersection_corners, num_intersection) + + +def devRotateIoUEval(rbox1, rbox2, criterion=-1): + area1 = rbox1[2] * rbox1[3] + area2 = rbox2[2] * rbox2[3] + area_inter = inter(rbox1, rbox2) + if criterion == -1: + return area_inter / (area1 + area2 - area_inter) + elif criterion == 0: + return area_inter / area1 + elif criterion == 1: + return area_inter / area2 + else: + return area_inter + + +def rotate_iou_cpu_eval(dev_boxes, dev_query_boxes, criterion=-1): + num_boxes = dev_boxes.shape[0] + num_qboxes = dev_query_boxes.shape[0] + dev_iou = np.zeros((num_boxes, num_qboxes)) + for box_i in range(num_boxes): + for qbox_i in range(num_qboxes): + dev_iou[box_i, + qbox_i] = devRotateIoUEval(dev_query_boxes[qbox_i], + dev_boxes[box_i], criterion) + return dev_iou diff --git a/tests/test_evaluation/test_functional/test_kitti_eval.py b/tests/test_evaluation/test_functional/test_kitti_eval.py index f8608af79..1d3432a8f 100644 --- a/tests/test_evaluation/test_functional/test_kitti_eval.py +++ b/tests/test_evaluation/test_functional/test_kitti_eval.py @@ -1,14 +1,10 @@ # Copyright (c) OpenMMLab. All rights reserved. import numpy as np -import pytest -import torch from mmdet3d.evaluation import do_eval, eval_class, kitti_eval def test_do_eval(): - if not torch.cuda.is_available(): - pytest.skip('test requires GPU and CUDA') gt_name = np.array( ['Pedestrian', 'Cyclist', 'Car', 'Car', 'Car', 'DontCare', 'DontCare']) gt_truncated = np.array([0., 0., 0., -1., -1., -1., -1.]) @@ -128,8 +124,6 @@ def test_do_eval(): def test_kitti_eval(): - if not torch.cuda.is_available(): - pytest.skip('test requires GPU and CUDA') gt_name = np.array( ['Pedestrian', 'Cyclist', 'Car', 'Car', 'Car', 'DontCare', 'DontCare']) gt_truncated = np.array([0., 0., 0., -1., -1., -1., -1.])