"csrc/vscode:/vscode.git/clone" did not exist on "e0c913979ac928b6e5614203de1cc0efcb0be4aa"
Commit 2eebdc2d authored by Yezhen Cong's avatar Yezhen Cong Committed by Tai-Wang
Browse files

[Refactor] Main code modification for coordinate system refactor (#677)

parent 26ab7ff2
...@@ -17,7 +17,7 @@ model = dict( ...@@ -17,7 +17,7 @@ model = dict(
anchor_generator=dict( anchor_generator=dict(
type='AlignedAnchor3DRangeGenerator', type='AlignedAnchor3DRangeGenerator',
ranges=[[-74.88, -74.88, -0.0345, 74.88, 74.88, -0.0345]], ranges=[[-74.88, -74.88, -0.0345, 74.88, 74.88, -0.0345]],
sizes=[[2.08, 4.73, 1.77]], sizes=[[4.73, 2.08, 1.77]],
rotations=[0, 1.57], rotations=[0, 1.57],
reshape_out=True)), reshape_out=True)),
# model training and testing settings # model training and testing settings
......
...@@ -14,7 +14,7 @@ model = dict( ...@@ -14,7 +14,7 @@ model = dict(
anchor_generator=dict( anchor_generator=dict(
type='AlignedAnchor3DRangeGenerator', type='AlignedAnchor3DRangeGenerator',
ranges=[[-74.88, -74.88, -0.0345, 74.88, 74.88, -0.0345]], ranges=[[-74.88, -74.88, -0.0345, 74.88, 74.88, -0.0345]],
sizes=[[2.08, 4.73, 1.77]], sizes=[[4.73, 2.08, 1.77]],
rotations=[0, 1.57], rotations=[0, 1.57],
reshape_out=True)), reshape_out=True)),
# model training and testing settings # model training and testing settings
......
...@@ -25,15 +25,15 @@ model = dict( ...@@ -25,15 +25,15 @@ model = dict(
[-80, -80, -0.9122268, 80, 80, -0.9122268], [-80, -80, -0.9122268, 80, 80, -0.9122268],
[-80, -80, -1.8012227, 80, 80, -1.8012227]], [-80, -80, -1.8012227, 80, 80, -1.8012227]],
sizes=[ sizes=[
[1.92, 4.75, 1.71], # car [4.75, 1.92, 1.71], # car
[2.84, 10.24, 3.44], # truck [10.24, 2.84, 3.44], # truck
[2.92, 12.70, 3.42], # bus [12.70, 2.92, 3.42], # bus
[2.42, 6.52, 2.34], # emergency vehicle [6.52, 2.42, 2.34], # emergency vehicle
[2.75, 8.17, 3.20], # other vehicle [8.17, 2.75, 3.20], # other vehicle
[0.96, 2.35, 1.59], # motorcycle [2.35, 0.96, 1.59], # motorcycle
[0.63, 1.76, 1.44], # bicycle [1.76, 0.63, 1.44], # bicycle
[0.76, 0.80, 1.76], # pedestrian [0.80, 0.76, 1.76], # pedestrian
[0.35, 0.73, 0.50] # animal [0.73, 0.35, 0.50] # animal
], ],
rotations=[0, 1.57], rotations=[0, 1.57],
reshape_out=True))) reshape_out=True)))
...@@ -25,13 +25,13 @@ model = dict( ...@@ -25,13 +25,13 @@ model = dict(
[-49.6, -49.6, -1.763965, 49.6, 49.6, -1.763965], [-49.6, -49.6, -1.763965, 49.6, 49.6, -1.763965],
], ],
sizes=[ sizes=[
[1.95017717, 4.60718145, 1.72270761], # car [4.60718145, 1.95017717, 1.72270761], # car
[2.4560939, 6.73778078, 2.73004906], # truck [6.73778078, 2.4560939, 2.73004906], # truck
[2.87427237, 12.01320693, 3.81509561], # trailer [12.01320693, 2.87427237, 3.81509561], # trailer
[0.60058911, 1.68452161, 1.27192197], # bicycle [1.68452161, 0.60058911, 1.27192197], # bicycle
[0.66344886, 0.7256437, 1.75748069], # pedestrian [0.7256437, 0.66344886, 1.75748069], # pedestrian
[0.39694519, 0.40359262, 1.06232151], # traffic_cone [0.40359262, 0.39694519, 1.06232151], # traffic_cone
[2.49008838, 0.48578221, 0.98297065], # barrier [0.48578221, 2.49008838, 0.98297065], # barrier
], ],
custom_values=[0, 0], custom_values=[0, 0],
rotations=[0, 1.57], rotations=[0, 1.57],
......
...@@ -26,15 +26,15 @@ model = dict( ...@@ -26,15 +26,15 @@ model = dict(
[-100, -100, -0.9122268, 100, 100, -0.9122268], [-100, -100, -0.9122268, 100, 100, -0.9122268],
[-100, -100, -1.8012227, 100, 100, -1.8012227]], [-100, -100, -1.8012227, 100, 100, -1.8012227]],
sizes=[ sizes=[
[1.92, 4.75, 1.71], # car [4.75, 1.92, 1.71], # car
[2.84, 10.24, 3.44], # truck [10.24, 2.84, 3.44], # truck
[2.92, 12.70, 3.42], # bus [12.70, 2.92, 3.42], # bus
[2.42, 6.52, 2.34], # emergency vehicle [6.52, 2.42, 2.34], # emergency vehicle
[2.75, 8.17, 3.20], # other vehicle [8.17, 2.75, 3.20], # other vehicle
[0.96, 2.35, 1.59], # motorcycle [2.35, 0.96, 1.59], # motorcycle
[0.63, 1.76, 1.44], # bicycle [1.76, 0.63, 1.44], # bicycle
[0.76, 0.80, 1.76], # pedestrian [0.80, 0.76, 1.76], # pedestrian
[0.35, 0.73, 0.50] # animal [0.73, 0.35, 0.50] # animal
], ],
rotations=[0, 1.57], rotations=[0, 1.57],
reshape_out=True))) reshape_out=True)))
...@@ -12,7 +12,7 @@ model = dict( ...@@ -12,7 +12,7 @@ model = dict(
_delete_=True, _delete_=True,
type='Anchor3DRangeGenerator', type='Anchor3DRangeGenerator',
ranges=[[0, -40.0, -1.78, 70.4, 40.0, -1.78]], ranges=[[0, -40.0, -1.78, 70.4, 40.0, -1.78]],
sizes=[[1.6, 3.9, 1.56]], sizes=[[3.9, 1.6, 1.56]],
rotations=[0, 1.57], rotations=[0, 1.57],
reshape_out=True)), reshape_out=True)),
# model training and testing settings # model training and testing settings
......
...@@ -21,7 +21,10 @@ db_sampler = dict( ...@@ -21,7 +21,10 @@ db_sampler = dict(
classes=class_names, classes=class_names,
sample_groups=dict(Car=15, Pedestrian=10, Cyclist=10), sample_groups=dict(Car=15, Pedestrian=10, Cyclist=10),
points_loader=dict( points_loader=dict(
type='LoadPointsFromFile', load_dim=5, use_dim=[0, 1, 2, 3, 4])) type='LoadPointsFromFile',
coord_type='LIDAR',
load_dim=5,
use_dim=[0, 1, 2, 3, 4]))
train_pipeline = [ train_pipeline = [
dict(type='LoadPointsFromFile', coord_type='LIDAR', load_dim=6, use_dim=5), dict(type='LoadPointsFromFile', coord_type='LIDAR', load_dim=6, use_dim=5),
......
...@@ -96,15 +96,15 @@ model = dict( ...@@ -96,15 +96,15 @@ model = dict(
[-100, -100, -0.6276341, 100, 100, -0.6276341], [-100, -100, -0.6276341, 100, 100, -0.6276341],
[-100, -100, -0.3033737, 100, 100, -0.3033737]], [-100, -100, -0.3033737, 100, 100, -0.3033737]],
sizes=[ sizes=[
[0.63, 1.76, 1.44], # bicycle [1.76, 0.63, 1.44], # bicycle
[0.96, 2.35, 1.59], # motorcycle [2.35, 0.96, 1.59], # motorcycle
[0.76, 0.80, 1.76], # pedestrian [0.80, 0.76, 1.76], # pedestrian
[0.35, 0.73, 0.50], # animal [0.73, 0.35, 0.50], # animal
[1.92, 4.75, 1.71], # car [4.75, 1.92, 1.71], # car
[2.42, 6.52, 2.34], # emergency vehicle [6.52, 2.42, 2.34], # emergency vehicle
[2.92, 12.70, 3.42], # bus [12.70, 2.92, 3.42], # bus
[2.75, 8.17, 3.20], # other vehicle [8.17, 2.75, 3.20], # other vehicle
[2.84, 10.24, 3.44] # truck [10.24, 2.84, 3.44] # truck
], ],
custom_values=[], custom_values=[],
rotations=[0, 1.57], rotations=[0, 1.57],
...@@ -137,7 +137,7 @@ model = dict( ...@@ -137,7 +137,7 @@ model = dict(
], ],
assign_per_class=True, assign_per_class=True,
diff_rad_by_sin=True, diff_rad_by_sin=True,
dir_offset=0.7854, # pi/4 dir_offset=-0.7854, # -pi/4
dir_limit_offset=0, dir_limit_offset=0,
bbox_coder=dict(type='DeltaXYZWLHRBBoxCoder', code_size=7), bbox_coder=dict(type='DeltaXYZWLHRBBoxCoder', code_size=7),
loss_cls=dict( loss_cls=dict(
......
...@@ -94,16 +94,16 @@ model = dict( ...@@ -94,16 +94,16 @@ model = dict(
[-50, -50, -1.80673031, 50, 50, -1.80673031], [-50, -50, -1.80673031, 50, 50, -1.80673031],
[-50, -50, -1.64824291, 50, 50, -1.64824291]], [-50, -50, -1.64824291, 50, 50, -1.64824291]],
sizes=[ sizes=[
[0.60058911, 1.68452161, 1.27192197], # bicycle [1.68452161, 0.60058911, 1.27192197], # bicycle
[0.76279481, 2.09973778, 1.44403034], # motorcycle [2.09973778, 0.76279481, 1.44403034], # motorcycle
[0.66344886, 0.72564370, 1.75748069], # pedestrian [0.72564370, 0.66344886, 1.75748069], # pedestrian
[0.39694519, 0.40359262, 1.06232151], # traffic cone [0.40359262, 0.39694519, 1.06232151], # traffic cone
[2.49008838, 0.48578221, 0.98297065], # barrier [0.48578221, 2.49008838, 0.98297065], # barrier
[1.95017717, 4.60718145, 1.72270761], # car [4.60718145, 1.95017717, 1.72270761], # car
[2.45609390, 6.73778078, 2.73004906], # truck [6.73778078, 2.45609390, 2.73004906], # truck
[2.87427237, 12.01320693, 3.81509561], # trailer [12.01320693, 2.87427237, 3.81509561], # trailer
[2.94046906, 11.1885991, 3.47030982], # bus [11.1885991, 2.94046906, 3.47030982], # bus
[2.73050468, 6.38352896, 3.13312415] # construction vehicle [6.38352896, 2.73050468, 3.13312415] # construction vehicle
], ],
custom_values=[0, 0], custom_values=[0, 0],
rotations=[0, 1.57], rotations=[0, 1.57],
...@@ -144,7 +144,7 @@ model = dict( ...@@ -144,7 +144,7 @@ model = dict(
], ],
assign_per_class=True, assign_per_class=True,
diff_rad_by_sin=True, diff_rad_by_sin=True,
dir_offset=0.7854, # pi/4 dir_offset=-0.7854, # -pi/4
dir_limit_offset=0, dir_limit_offset=0,
bbox_coder=dict(type='DeltaXYZWLHRBBoxCoder', code_size=9), bbox_coder=dict(type='DeltaXYZWLHRBBoxCoder', code_size=9),
loss_cls=dict( loss_cls=dict(
......
...@@ -8,10 +8,9 @@ from mmcv.parallel import collate, scatter ...@@ -8,10 +8,9 @@ from mmcv.parallel import collate, scatter
from mmcv.runner import load_checkpoint from mmcv.runner import load_checkpoint
from os import path as osp from os import path as osp
from mmdet3d.core import (Box3DMode, CameraInstance3DBoxes, from mmdet3d.core import (Box3DMode, Coord3DMode, DepthInstance3DBoxes,
DepthInstance3DBoxes, LiDARInstance3DBoxes, LiDARInstance3DBoxes, show_multi_modality_result,
show_multi_modality_result, show_result, show_result, show_seg_result)
show_seg_result)
from mmdet3d.core.bbox import get_box_type from mmdet3d.core.bbox import get_box_type
from mmdet3d.datasets.pipelines import Compose from mmdet3d.datasets.pipelines import Compose
from mmdet3d.models import build_model from mmdet3d.models import build_model
...@@ -316,8 +315,7 @@ def show_det_result_meshlab(data, ...@@ -316,8 +315,7 @@ def show_det_result_meshlab(data,
# for now we convert points into depth mode # for now we convert points into depth mode
box_mode = data['img_metas'][0][0]['box_mode_3d'] box_mode = data['img_metas'][0][0]['box_mode_3d']
if box_mode != Box3DMode.DEPTH: if box_mode != Box3DMode.DEPTH:
points = points[..., [1, 0, 2]] points = Coord3DMode.convert(points, box_mode, Coord3DMode.DEPTH)
points[..., 0] *= -1
show_bboxes = Box3DMode.convert(pred_bboxes, box_mode, Box3DMode.DEPTH) show_bboxes = Box3DMode.convert(pred_bboxes, box_mode, Box3DMode.DEPTH)
else: else:
show_bboxes = deepcopy(pred_bboxes) show_bboxes = deepcopy(pred_bboxes)
......
...@@ -32,7 +32,7 @@ class Anchor3DRangeGenerator(object): ...@@ -32,7 +32,7 @@ class Anchor3DRangeGenerator(object):
def __init__(self, def __init__(self,
ranges, ranges,
sizes=[[1.6, 3.9, 1.56]], sizes=[[3.9, 1.6, 1.56]],
scales=[1], scales=[1],
rotations=[0, 1.5707963], rotations=[0, 1.5707963],
custom_values=(), custom_values=(),
...@@ -149,7 +149,7 @@ class Anchor3DRangeGenerator(object): ...@@ -149,7 +149,7 @@ class Anchor3DRangeGenerator(object):
feature_size, feature_size,
anchor_range, anchor_range,
scale=1, scale=1,
sizes=[[1.6, 3.9, 1.56]], sizes=[[3.9, 1.6, 1.56]],
rotations=[0, 1.5707963], rotations=[0, 1.5707963],
device='cuda'): device='cuda'):
"""Generate anchors in a single range. """Generate anchors in a single range.
...@@ -245,7 +245,7 @@ class AlignedAnchor3DRangeGenerator(Anchor3DRangeGenerator): ...@@ -245,7 +245,7 @@ class AlignedAnchor3DRangeGenerator(Anchor3DRangeGenerator):
feature_size, feature_size,
anchor_range, anchor_range,
scale, scale,
sizes=[[1.6, 3.9, 1.56]], sizes=[[3.9, 1.6, 1.56]],
rotations=[0, 1.5707963], rotations=[0, 1.5707963],
device='cuda'): device='cuda'):
"""Generate anchors in a single range. """Generate anchors in a single range.
......
# Copyright (c) OpenMMLab. All rights reserved. # Copyright (c) OpenMMLab. All rights reserved.
# TODO: clean the functions in this file and move the APIs into box structures # TODO: clean the functions in this file and move the APIs into box structures
# in the future # in the future
import numba import numba
import numpy as np import numpy as np
from .structures.utils import limit_period, points_cam2img, rotation_3d_in_axis
def camera_to_lidar(points, r_rect, velo2cam): def camera_to_lidar(points, r_rect, velo2cam):
"""Convert points in camera coordinate to lidar coordinate. """Convert points in camera coordinate to lidar coordinate.
Note:
This function is for KITTI only.
Args: Args:
points (np.ndarray, shape=[N, 3]): Points in camera coordinate. points (np.ndarray, shape=[N, 3]): Points in camera coordinate.
r_rect (np.ndarray, shape=[4, 4]): Matrix to project points in r_rect (np.ndarray, shape=[4, 4]): Matrix to project points in
...@@ -27,7 +31,10 @@ def camera_to_lidar(points, r_rect, velo2cam): ...@@ -27,7 +31,10 @@ def camera_to_lidar(points, r_rect, velo2cam):
def box_camera_to_lidar(data, r_rect, velo2cam): def box_camera_to_lidar(data, r_rect, velo2cam):
"""Covert boxes in camera coordinate to lidar coordinate. """Convert boxes in camera coordinate to lidar coordinate.
Note:
This function is for KITTI only.
Args: Args:
data (np.ndarray, shape=[N, 7]): Boxes in camera coordinate. data (np.ndarray, shape=[N, 7]): Boxes in camera coordinate.
...@@ -40,10 +47,13 @@ def box_camera_to_lidar(data, r_rect, velo2cam): ...@@ -40,10 +47,13 @@ def box_camera_to_lidar(data, r_rect, velo2cam):
np.ndarray, shape=[N, 3]: Boxes in lidar coordinate. np.ndarray, shape=[N, 3]: Boxes in lidar coordinate.
""" """
xyz = data[:, 0:3] xyz = data[:, 0:3]
l, h, w = data[:, 3:4], data[:, 4:5], data[:, 5:6] dx, dy, dz = data[:, 3:4], data[:, 4:5], data[:, 5:6]
r = data[:, 6:7] r = data[:, 6:7]
xyz_lidar = camera_to_lidar(xyz, r_rect, velo2cam) xyz_lidar = camera_to_lidar(xyz, r_rect, velo2cam)
return np.concatenate([xyz_lidar, w, l, h, r], axis=1) # yaw and dims also needs to be converted
r_new = -r - np.pi / 2
r_new = limit_period(r_new, period=np.pi * 2)
return np.concatenate([xyz_lidar, dx, dz, dy, r_new], axis=1)
def corners_nd(dims, origin=0.5): def corners_nd(dims, origin=0.5):
...@@ -80,23 +90,6 @@ def corners_nd(dims, origin=0.5): ...@@ -80,23 +90,6 @@ def corners_nd(dims, origin=0.5):
return corners return corners
def rotation_2d(points, angles):
"""Rotation 2d points based on origin point clockwise when angle positive.
Args:
points (np.ndarray): Points to be rotated with shape \
(N, point_size, 2).
angles (np.ndarray): Rotation angle with shape (N).
Returns:
np.ndarray: Same shape as points.
"""
rot_sin = np.sin(angles)
rot_cos = np.cos(angles)
rot_mat_T = np.stack([[rot_cos, -rot_sin], [rot_sin, rot_cos]])
return np.einsum('aij,jka->aik', points, rot_mat_T)
def center_to_corner_box2d(centers, dims, angles=None, origin=0.5): def center_to_corner_box2d(centers, dims, angles=None, origin=0.5):
"""Convert kitti locations, dimensions and angles to corners. """Convert kitti locations, dimensions and angles to corners.
format: center(xy), dims(xy), angles(clockwise when positive) format: center(xy), dims(xy), angles(clockwise when positive)
...@@ -118,7 +111,7 @@ def center_to_corner_box2d(centers, dims, angles=None, origin=0.5): ...@@ -118,7 +111,7 @@ def center_to_corner_box2d(centers, dims, angles=None, origin=0.5):
corners = corners_nd(dims, origin=origin) corners = corners_nd(dims, origin=origin)
# corners: [N, 4, 2] # corners: [N, 4, 2]
if angles is not None: if angles is not None:
corners = rotation_2d(corners, angles) corners = rotation_3d_in_axis(corners, angles)
corners += centers.reshape([-1, 1, 2]) corners += centers.reshape([-1, 1, 2])
return corners return corners
...@@ -172,37 +165,6 @@ def depth_to_lidar_points(depth, trunc_pixel, P2, r_rect, velo2cam): ...@@ -172,37 +165,6 @@ def depth_to_lidar_points(depth, trunc_pixel, P2, r_rect, velo2cam):
return lidar_points return lidar_points
def rotation_3d_in_axis(points, angles, axis=0):
"""Rotate points in specific axis.
Args:
points (np.ndarray, shape=[N, point_size, 3]]):
angles (np.ndarray, shape=[N]]):
axis (int, optional): Axis to rotate at. Defaults to 0.
Returns:
np.ndarray: Rotated points.
"""
# points: [N, point_size, 3]
rot_sin = np.sin(angles)
rot_cos = np.cos(angles)
ones = np.ones_like(rot_cos)
zeros = np.zeros_like(rot_cos)
if axis == 1:
rot_mat_T = np.stack([[rot_cos, zeros, -rot_sin], [zeros, ones, zeros],
[rot_sin, zeros, rot_cos]])
elif axis == 2 or axis == -1:
rot_mat_T = np.stack([[rot_cos, -rot_sin, zeros],
[rot_sin, rot_cos, zeros], [zeros, zeros, ones]])
elif axis == 0:
rot_mat_T = np.stack([[zeros, rot_cos, -rot_sin],
[zeros, rot_sin, rot_cos], [ones, zeros, zeros]])
else:
raise ValueError('axis should in range')
return np.einsum('aij,jka->aik', points, rot_mat_T)
def center_to_corner_box3d(centers, def center_to_corner_box3d(centers,
dims, dims,
angles=None, angles=None,
...@@ -259,8 +221,8 @@ def box2d_to_corner_jit(boxes): ...@@ -259,8 +221,8 @@ def box2d_to_corner_jit(boxes):
rot_sin = np.sin(boxes[i, -1]) rot_sin = np.sin(boxes[i, -1])
rot_cos = np.cos(boxes[i, -1]) rot_cos = np.cos(boxes[i, -1])
rot_mat_T[0, 0] = rot_cos rot_mat_T[0, 0] = rot_cos
rot_mat_T[0, 1] = -rot_sin rot_mat_T[0, 1] = rot_sin
rot_mat_T[1, 0] = rot_sin rot_mat_T[1, 0] = -rot_sin
rot_mat_T[1, 1] = rot_cos rot_mat_T[1, 1] = rot_cos
box_corners[i] = corners[i] @ rot_mat_T + boxes[i, :2] box_corners[i] = corners[i] @ rot_mat_T + boxes[i, :2]
return box_corners return box_corners
...@@ -327,15 +289,15 @@ def rotation_points_single_angle(points, angle, axis=0): ...@@ -327,15 +289,15 @@ def rotation_points_single_angle(points, angle, axis=0):
rot_cos = np.cos(angle) rot_cos = np.cos(angle)
if axis == 1: if axis == 1:
rot_mat_T = np.array( rot_mat_T = np.array(
[[rot_cos, 0, -rot_sin], [0, 1, 0], [rot_sin, 0, rot_cos]], [[rot_cos, 0, rot_sin], [0, 1, 0], [-rot_sin, 0, rot_cos]],
dtype=points.dtype) dtype=points.dtype)
elif axis == 2 or axis == -1: elif axis == 2 or axis == -1:
rot_mat_T = np.array( rot_mat_T = np.array(
[[rot_cos, -rot_sin, 0], [rot_sin, rot_cos, 0], [0, 0, 1]], [[rot_cos, rot_sin, 0], [-rot_sin, rot_cos, 0], [0, 0, 1]],
dtype=points.dtype) dtype=points.dtype)
elif axis == 0: elif axis == 0:
rot_mat_T = np.array( rot_mat_T = np.array(
[[1, 0, 0], [0, rot_cos, -rot_sin], [0, rot_sin, rot_cos]], [[1, 0, 0], [0, rot_cos, rot_sin], [0, -rot_sin, rot_cos]],
dtype=points.dtype) dtype=points.dtype)
else: else:
raise ValueError('axis should in range') raise ValueError('axis should in range')
...@@ -343,44 +305,6 @@ def rotation_points_single_angle(points, angle, axis=0): ...@@ -343,44 +305,6 @@ def rotation_points_single_angle(points, angle, axis=0):
return points @ rot_mat_T, rot_mat_T return points @ rot_mat_T, rot_mat_T
def points_cam2img(points_3d, proj_mat, with_depth=False):
"""Project points in camera coordinates to image coordinates.
Args:
points_3d (np.ndarray): Points in shape (N, 3)
proj_mat (np.ndarray): Transformation matrix between coordinates.
with_depth (bool, optional): Whether to keep depth in the output.
Defaults to False.
Returns:
np.ndarray: Points in image coordinates with shape [N, 2].
"""
points_shape = list(points_3d.shape)
points_shape[-1] = 1
assert len(proj_mat.shape) == 2, 'The dimension of the projection'\
f' matrix should be 2 instead of {len(proj_mat.shape)}.'
d1, d2 = proj_mat.shape[:2]
assert (d1 == 3 and d2 == 3) or (d1 == 3 and d2 == 4) or (
d1 == 4 and d2 == 4), 'The shape of the projection matrix'\
f' ({d1}*{d2}) is not supported.'
if d1 == 3:
proj_mat_expanded = np.eye(4, dtype=proj_mat.dtype)
proj_mat_expanded[:d1, :d2] = proj_mat
proj_mat = proj_mat_expanded
points_4 = np.concatenate([points_3d, np.ones(points_shape)], axis=-1)
point_2d = points_4 @ proj_mat.T
point_2d_res = point_2d[..., :2] / point_2d[..., 2:3]
if with_depth:
points_2d_depth = np.concatenate([point_2d_res, point_2d[..., 2:3]],
axis=-1)
return points_2d_depth
return point_2d_res
def box3d_to_bbox(box3d, P2): def box3d_to_bbox(box3d, P2):
"""Convert box3d in camera coordinates to bbox in image coordinates. """Convert box3d in camera coordinates to bbox in image coordinates.
...@@ -461,25 +385,9 @@ def minmax_to_corner_2d(minmax_box): ...@@ -461,25 +385,9 @@ def minmax_to_corner_2d(minmax_box):
return center_to_corner_box2d(center, dims, origin=0.0) return center_to_corner_box2d(center, dims, origin=0.0)
def limit_period(val, offset=0.5, period=np.pi):
"""Limit the value into a period for periodic function.
Args:
val (np.ndarray): The value to be converted.
offset (float, optional): Offset to set the value range. \
Defaults to 0.5.
period (float, optional): Period of the value. Defaults to np.pi.
Returns:
torch.Tensor: Value in the range of \
[-offset * period, (1-offset) * period]
"""
return val - np.floor(val / period + offset) * period
def create_anchors_3d_range(feature_size, def create_anchors_3d_range(feature_size,
anchor_range, anchor_range,
sizes=((1.6, 3.9, 1.56), ), sizes=((3.9, 1.6, 1.56), ),
rotations=(0, np.pi / 2), rotations=(0, np.pi / 2),
dtype=np.float32): dtype=np.float32):
"""Create anchors 3d by range. """Create anchors 3d by range.
...@@ -492,14 +400,14 @@ def create_anchors_3d_range(feature_size, ...@@ -492,14 +400,14 @@ def create_anchors_3d_range(feature_size,
(x_min, y_min, z_min, x_max, y_max, z_max). (x_min, y_min, z_min, x_max, y_max, z_max).
sizes (list[list] | np.ndarray | torch.Tensor, optional): sizes (list[list] | np.ndarray | torch.Tensor, optional):
Anchor size with shape [N, 3], in order of x, y, z. Anchor size with shape [N, 3], in order of x, y, z.
Defaults to ((1.6, 3.9, 1.56), ). Defaults to ((3.9, 1.6, 1.56), ).
rotations (list[float] | np.ndarray | torch.Tensor, optional): rotations (list[float] | np.ndarray | torch.Tensor, optional):
Rotations of anchors in a single feature grid. Rotations of anchors in a single feature grid.
Defaults to (0, np.pi / 2). Defaults to (0, np.pi / 2).
dtype (type, optional): Data type. Default to np.float32. dtype (type, optional): Data type. Default to np.float32.
Returns: Returns:
np.ndarray: Range based anchors with shape of \ np.ndarray: Range based anchors with shape of
(*feature_size, num_sizes, num_rots, 7). (*feature_size, num_sizes, num_rots, 7).
""" """
anchor_range = np.array(anchor_range, dtype) anchor_range = np.array(anchor_range, dtype)
...@@ -550,7 +458,7 @@ def rbbox2d_to_near_bbox(rbboxes): ...@@ -550,7 +458,7 @@ def rbbox2d_to_near_bbox(rbboxes):
"""convert rotated bbox to nearest 'standing' or 'lying' bbox. """convert rotated bbox to nearest 'standing' or 'lying' bbox.
Args: Args:
rbboxes (np.ndarray): Rotated bboxes with shape of \ rbboxes (np.ndarray): Rotated bboxes with shape of
(N, 5(x, y, xdim, ydim, rad)). (N, 5(x, y, xdim, ydim, rad)).
Returns: Returns:
...@@ -841,8 +749,8 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True): ...@@ -841,8 +749,8 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True):
Args: Args:
boxes3d (np.ndarray): Boxes with shape of (N, 7) boxes3d (np.ndarray): Boxes with shape of (N, 7)
[x, y, z, w, l, h, ry] in LiDAR coords, see the definition of ry [x, y, z, dx, dy, dz, ry] in LiDAR coords, see the definition of
in KITTI dataset. ry in KITTI dataset.
bottom_center (bool, optional): Whether z is on the bottom center bottom_center (bool, optional): Whether z is on the bottom center
of object. Defaults to True. of object. Defaults to True.
...@@ -850,19 +758,25 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True): ...@@ -850,19 +758,25 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True):
np.ndarray: Box corners with the shape of [N, 8, 3]. np.ndarray: Box corners with the shape of [N, 8, 3].
""" """
boxes_num = boxes3d.shape[0] boxes_num = boxes3d.shape[0]
w, l, h = boxes3d[:, 3], boxes3d[:, 4], boxes3d[:, 5] dx, dy, dz = boxes3d[:, 3], boxes3d[:, 4], boxes3d[:, 5]
x_corners = np.array( x_corners = np.array([
[w / 2., -w / 2., -w / 2., w / 2., w / 2., -w / 2., -w / 2., w / 2.], dx / 2., -dx / 2., -dx / 2., dx / 2., dx / 2., -dx / 2., -dx / 2.,
dtype=np.float32).T dx / 2.
y_corners = np.array( ],
[-l / 2., -l / 2., l / 2., l / 2., -l / 2., -l / 2., l / 2., l / 2.], dtype=np.float32).T
dtype=np.float32).T y_corners = np.array([
-dy / 2., -dy / 2., dy / 2., dy / 2., -dy / 2., -dy / 2., dy / 2.,
dy / 2.
],
dtype=np.float32).T
if bottom_center: if bottom_center:
z_corners = np.zeros((boxes_num, 8), dtype=np.float32) z_corners = np.zeros((boxes_num, 8), dtype=np.float32)
z_corners[:, 4:8] = h.reshape(boxes_num, 1).repeat(4, axis=1) # (N, 8) z_corners[:, 4:8] = dz.reshape(boxes_num, 1).repeat(
4, axis=1) # (N, 8)
else: else:
z_corners = np.array([ z_corners = np.array([
-h / 2., -h / 2., -h / 2., -h / 2., h / 2., h / 2., h / 2., h / 2. -dz / 2., -dz / 2., -dz / 2., -dz / 2., dz / 2., dz / 2., dz / 2.,
dz / 2.
], ],
dtype=np.float32).T dtype=np.float32).T
...@@ -870,9 +784,9 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True): ...@@ -870,9 +784,9 @@ def boxes3d_to_corners3d_lidar(boxes3d, bottom_center=True):
zeros, ones = np.zeros( zeros, ones = np.zeros(
ry.size, dtype=np.float32), np.ones( ry.size, dtype=np.float32), np.ones(
ry.size, dtype=np.float32) ry.size, dtype=np.float32)
rot_list = np.array([[np.cos(ry), -np.sin(ry), zeros], rot_list = np.array([[np.cos(ry), np.sin(ry), zeros],
[np.sin(ry), np.cos(ry), zeros], [zeros, zeros, [-np.sin(ry), np.cos(ry), zeros],
ones]]) # (3, 3, N) [zeros, zeros, ones]]) # (3, 3, N)
R_list = np.transpose(rot_list, (2, 0, 1)) # (N, 3, 3) R_list = np.transpose(rot_list, (2, 0, 1)) # (N, 3, 3)
temp_corners = np.concatenate((x_corners.reshape( temp_corners = np.concatenate((x_corners.reshape(
......
...@@ -31,8 +31,10 @@ class BboxOverlapsNearest3D(object): ...@@ -31,8 +31,10 @@ class BboxOverlapsNearest3D(object):
between each aligned pair of bboxes1 and bboxes2. between each aligned pair of bboxes1 and bboxes2.
Args: Args:
bboxes1 (torch.Tensor): shape (N, 7+N) [x, y, z, h, w, l, ry, v]. bboxes1 (torch.Tensor): shape (N, 7+N)
bboxes2 (torch.Tensor): shape (M, 7+N) [x, y, z, h, w, l, ry, v]. [x, y, z, dx, dy, dz, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+N)
[x, y, z, dx, dy, dz, ry, v].
mode (str): "iou" (intersection over union) or iof mode (str): "iou" (intersection over union) or iof
(intersection over foreground). (intersection over foreground).
is_aligned (bool): Whether the calculation is aligned. is_aligned (bool): Whether the calculation is aligned.
...@@ -74,8 +76,8 @@ class BboxOverlaps3D(object): ...@@ -74,8 +76,8 @@ class BboxOverlaps3D(object):
calculate the actual 3D IoUs of boxes. calculate the actual 3D IoUs of boxes.
Args: Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, h, w, l, ry]. bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, dx, dy, dz, ry].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, h, w, l, ry]. bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, dx, dy, dz, ry].
mode (str): "iou" (intersection over union) or mode (str): "iou" (intersection over union) or
iof (intersection over foreground). iof (intersection over foreground).
...@@ -110,8 +112,8 @@ def bbox_overlaps_nearest_3d(bboxes1, ...@@ -110,8 +112,8 @@ def bbox_overlaps_nearest_3d(bboxes1,
aligned pair of bboxes1 and bboxes2. aligned pair of bboxes1 and bboxes2.
Args: Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, h, w, l, ry, v]. bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, dx, dy, dz, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, h, w, l, ry, v]. bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, dx, dy, dz, ry, v].
mode (str): "iou" (intersection over union) or iof mode (str): "iou" (intersection over union) or iof
(intersection over foreground). (intersection over foreground).
is_aligned (bool): Whether the calculation is aligned is_aligned (bool): Whether the calculation is aligned
...@@ -148,8 +150,8 @@ def bbox_overlaps_3d(bboxes1, bboxes2, mode='iou', coordinate='camera'): ...@@ -148,8 +150,8 @@ def bbox_overlaps_3d(bboxes1, bboxes2, mode='iou', coordinate='camera'):
calculate the actual IoUs of boxes. calculate the actual IoUs of boxes.
Args: Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, h, w, l, ry]. bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, dx, dy, dz, ry].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, h, w, l, ry]. bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, dx, dy, dz, ry].
mode (str): "iou" (intersection over union) or mode (str): "iou" (intersection over union) or
iof (intersection over foreground). iof (intersection over foreground).
coordinate (str): 'camera' or 'lidar' coordinate system. coordinate (str): 'camera' or 'lidar' coordinate system.
......
...@@ -9,8 +9,8 @@ from . import RandomSampler, SamplingResult ...@@ -9,8 +9,8 @@ from . import RandomSampler, SamplingResult
class IoUNegPiecewiseSampler(RandomSampler): class IoUNegPiecewiseSampler(RandomSampler):
"""IoU Piece-wise Sampling. """IoU Piece-wise Sampling.
Sampling negtive proposals according to a list of IoU thresholds. Sampling negative proposals according to a list of IoU thresholds.
The negtive proposals are divided into several pieces according The negative proposals are divided into several pieces according
to `neg_iou_piece_thrs`. And the ratio of each piece is indicated to `neg_iou_piece_thrs`. And the ratio of each piece is indicated
by `neg_piece_fractions`. by `neg_piece_fractions`.
...@@ -18,11 +18,11 @@ class IoUNegPiecewiseSampler(RandomSampler): ...@@ -18,11 +18,11 @@ class IoUNegPiecewiseSampler(RandomSampler):
num (int): Number of proposals. num (int): Number of proposals.
pos_fraction (float): The fraction of positive proposals. pos_fraction (float): The fraction of positive proposals.
neg_piece_fractions (list): A list contains fractions that indicates neg_piece_fractions (list): A list contains fractions that indicates
the ratio of each piece of total negtive samplers. the ratio of each piece of total negative samplers.
neg_iou_piece_thrs (list): A list contains IoU thresholds that neg_iou_piece_thrs (list): A list contains IoU thresholds that
indicate the upper bound of this piece. indicate the upper bound of this piece.
neg_pos_ub (float): The total ratio to limit the upper bound neg_pos_ub (float): The total ratio to limit the upper bound
number of negtive samples. number of negative samples.
add_gt_as_proposals (bool): Whether to add gt as proposals. add_gt_as_proposals (bool): Whether to add gt as proposals.
""" """
......
...@@ -3,6 +3,7 @@ import numpy as np ...@@ -3,6 +3,7 @@ import numpy as np
import torch import torch
from abc import abstractmethod from abc import abstractmethod
from mmdet3d.ops import points_in_boxes_batch, points_in_boxes_gpu
from mmdet3d.ops.iou3d import iou3d_cuda from mmdet3d.ops.iou3d import iou3d_cuda
from .utils import limit_period, xywhr2xyxyr from .utils import limit_period, xywhr2xyxyr
...@@ -131,8 +132,8 @@ class BaseInstance3DBoxes(object): ...@@ -131,8 +132,8 @@ class BaseInstance3DBoxes(object):
@abstractmethod @abstractmethod
def rotate(self, angle, points=None): def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or \ """Rotate boxes with points (optional) with the given angle or rotation
rotation matrix. matrix.
Args: Args:
angle (float | torch.Tensor | np.ndarray): angle (float | torch.Tensor | np.ndarray):
...@@ -170,7 +171,7 @@ class BaseInstance3DBoxes(object): ...@@ -170,7 +171,7 @@ class BaseInstance3DBoxes(object):
polygon, we try to reduce the burden for simpler cases. polygon, we try to reduce the burden for simpler cases.
Returns: Returns:
torch.Tensor: A binary vector indicating whether each box is \ torch.Tensor: A binary vector indicating whether each box is
inside the reference range. inside the reference range.
""" """
in_range_flags = ((self.tensor[:, 0] > box_range[0]) in_range_flags = ((self.tensor[:, 0] > box_range[0])
...@@ -190,7 +191,7 @@ class BaseInstance3DBoxes(object): ...@@ -190,7 +191,7 @@ class BaseInstance3DBoxes(object):
in order of (x_min, y_min, x_max, y_max). in order of (x_min, y_min, x_max, y_max).
Returns: Returns:
torch.Tensor: Indicating whether each box is inside \ torch.Tensor: Indicating whether each box is inside
the reference range. the reference range.
""" """
pass pass
...@@ -208,7 +209,7 @@ class BaseInstance3DBoxes(object): ...@@ -208,7 +209,7 @@ class BaseInstance3DBoxes(object):
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: The converted box of the same type \ :obj:`BaseInstance3DBoxes`: The converted box of the same type
in the `dst` mode. in the `dst` mode.
""" """
pass pass
...@@ -241,7 +242,7 @@ class BaseInstance3DBoxes(object): ...@@ -241,7 +242,7 @@ class BaseInstance3DBoxes(object):
threshold (float): The threshold of minimal sizes. threshold (float): The threshold of minimal sizes.
Returns: Returns:
torch.Tensor: A binary vector which represents whether each \ torch.Tensor: A binary vector which represents whether each
box is empty (False) or non-empty (True). box is empty (False) or non-empty (True).
""" """
box = self.tensor box = self.tensor
...@@ -267,8 +268,8 @@ class BaseInstance3DBoxes(object): ...@@ -267,8 +268,8 @@ class BaseInstance3DBoxes(object):
subject to Pytorch's indexing semantics. subject to Pytorch's indexing semantics.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: A new object of \ :obj:`BaseInstance3DBoxes`: A new object of
:class:`BaseInstances3DBoxes` after indexing. :class:`BaseInstance3DBoxes` after indexing.
""" """
original_type = type(self) original_type = type(self)
if isinstance(item, int): if isinstance(item, int):
...@@ -319,7 +320,7 @@ class BaseInstance3DBoxes(object): ...@@ -319,7 +320,7 @@ class BaseInstance3DBoxes(object):
device (str | :obj:`torch.device`): The name of the device. device (str | :obj:`torch.device`): The name of the device.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: A new boxes object on the \ :obj:`BaseInstance3DBoxes`: A new boxes object on the
specific device. specific device.
""" """
original_type = type(self) original_type = type(self)
...@@ -332,7 +333,7 @@ class BaseInstance3DBoxes(object): ...@@ -332,7 +333,7 @@ class BaseInstance3DBoxes(object):
"""Clone the Boxes. """Clone the Boxes.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: Box object with the same properties \ :obj:`BaseInstance3DBoxes`: Box object with the same properties
as self. as self.
""" """
original_type = type(self) original_type = type(self)
...@@ -444,14 +445,14 @@ class BaseInstance3DBoxes(object): ...@@ -444,14 +445,14 @@ class BaseInstance3DBoxes(object):
def new_box(self, data): def new_box(self, data):
"""Create a new box object with data. """Create a new box object with data.
The new box and its tensor has the similar properties \ The new box and its tensor has the similar properties
as self and self.tensor, respectively. as self and self.tensor, respectively.
Args: Args:
data (torch.Tensor | numpy.array | list): Data to be copied. data (torch.Tensor | numpy.array | list): Data to be copied.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: A new bbox object with ``data``, \ :obj:`BaseInstance3DBoxes`: A new bbox object with ``data``,
the object's other properties are similar to ``self``. the object's other properties are similar to ``self``.
""" """
new_tensor = self.tensor.new_tensor(data) \ new_tensor = self.tensor.new_tensor(data) \
...@@ -459,3 +460,48 @@ class BaseInstance3DBoxes(object): ...@@ -459,3 +460,48 @@ class BaseInstance3DBoxes(object):
original_type = type(self) original_type = type(self)
return original_type( return original_type(
new_tensor, box_dim=self.box_dim, with_yaw=self.with_yaw) new_tensor, box_dim=self.box_dim, with_yaw=self.with_yaw)
def points_in_boxes(self, points, boxes_override=None):
"""Find the box which the points are in.
Args:
points (torch.Tensor): Points in shape (N, 3).
Returns:
torch.Tensor: The index of box where each point are in.
"""
if boxes_override is not None:
boxes = boxes_override
else:
boxes = self.tensor
box_idx = points_in_boxes_gpu(
points.unsqueeze(0),
boxes.unsqueeze(0).to(points.device)).squeeze(0)
return box_idx
def points_in_boxes_batch(self, points, boxes_override=None):
"""Find points that are in boxes (CUDA).
Args:
points (torch.Tensor): Points in shape [1, M, 3] or [M, 3],
3 dimensions are [x, y, z] in LiDAR coordinate.
Returns:
torch.Tensor: The index of boxes each point lies in with shape
of (B, M, T).
"""
if boxes_override is not None:
boxes = boxes_override
else:
boxes = self.tensor
points_clone = points.clone()[..., :3]
if points_clone.dim() == 2:
points_clone = points_clone.unsqueeze(0)
else:
assert points_clone.dim() == 3 and points_clone.shape[0] == 1
boxes = boxes.to(points_clone.device).unsqueeze(0)
box_idxs_of_pts = points_in_boxes_batch(points_clone, boxes)
return box_idxs_of_pts.squeeze(0)
...@@ -7,6 +7,7 @@ from .base_box3d import BaseInstance3DBoxes ...@@ -7,6 +7,7 @@ from .base_box3d import BaseInstance3DBoxes
from .cam_box3d import CameraInstance3DBoxes from .cam_box3d import CameraInstance3DBoxes
from .depth_box3d import DepthInstance3DBoxes from .depth_box3d import DepthInstance3DBoxes
from .lidar_box3d import LiDARInstance3DBoxes from .lidar_box3d import LiDARInstance3DBoxes
from .utils import limit_period
@unique @unique
...@@ -61,12 +62,12 @@ class Box3DMode(IntEnum): ...@@ -61,12 +62,12 @@ class Box3DMode(IntEnum):
DEPTH = 2 DEPTH = 2
@staticmethod @staticmethod
def convert(box, src, dst, rt_mat=None): def convert(box, src, dst, rt_mat=None, with_yaw=True):
"""Convert boxes from `src` mode to `dst` mode. """Convert boxes from `src` mode to `dst` mode.
Args: Args:
box (tuple | list | np.ndarray | box (tuple | list | np.ndarray |
torch.Tensor | BaseInstance3DBoxes): torch.Tensor | :obj:`BaseInstance3DBoxes`):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7. Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode`): The src Box mode. src (:obj:`Box3DMode`): The src Box mode.
dst (:obj:`Box3DMode`): The target Box mode. dst (:obj:`Box3DMode`): The target Box mode.
...@@ -75,9 +76,13 @@ class Box3DMode(IntEnum): ...@@ -75,9 +76,13 @@ class Box3DMode(IntEnum):
The conversion from `src` coordinates to `dst` coordinates The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
with_yaw (bool): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
Returns: Returns:
(tuple | list | np.ndarray | torch.Tensor | BaseInstance3DBoxes): \ (tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes`):
The converted box of the same type. The converted box of the same type.
""" """
if src == dst: if src == dst:
...@@ -100,32 +105,53 @@ class Box3DMode(IntEnum): ...@@ -100,32 +105,53 @@ class Box3DMode(IntEnum):
else: else:
arr = box.clone() arr = box.clone()
if is_Instance3DBoxes:
with_yaw = box.with_yaw
# convert box from `src` mode to `dst` mode. # convert box from `src` mode to `dst` mode.
x_size, y_size, z_size = arr[..., 3:4], arr[..., 4:5], arr[..., 5:6] x_size, y_size, z_size = arr[..., 3:4], arr[..., 4:5], arr[..., 5:6]
if with_yaw:
yaw = arr[..., 6:7]
if src == Box3DMode.LIDAR and dst == Box3DMode.CAM: if src == Box3DMode.LIDAR and dst == Box3DMode.CAM:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[0, -1, 0], [0, 0, -1], [1, 0, 0]]) rt_mat = arr.new_tensor([[0, -1, 0], [0, 0, -1], [1, 0, 0]])
xyz_size = torch.cat([y_size, z_size, x_size], dim=-1) xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
if with_yaw:
yaw = -yaw - np.pi / 2
yaw = limit_period(yaw, period=np.pi * 2)
elif src == Box3DMode.CAM and dst == Box3DMode.LIDAR: elif src == Box3DMode.CAM and dst == Box3DMode.LIDAR:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[0, 0, 1], [-1, 0, 0], [0, -1, 0]]) rt_mat = arr.new_tensor([[0, 0, 1], [-1, 0, 0], [0, -1, 0]])
xyz_size = torch.cat([z_size, x_size, y_size], dim=-1) xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
if with_yaw:
yaw = -yaw - np.pi / 2
yaw = limit_period(yaw, period=np.pi * 2)
elif src == Box3DMode.DEPTH and dst == Box3DMode.CAM: elif src == Box3DMode.DEPTH and dst == Box3DMode.CAM:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, 1], [0, -1, 0]]) rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, 1], [0, -1, 0]])
xyz_size = torch.cat([x_size, z_size, y_size], dim=-1) xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
if with_yaw:
yaw = -yaw
elif src == Box3DMode.CAM and dst == Box3DMode.DEPTH: elif src == Box3DMode.CAM and dst == Box3DMode.DEPTH:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, -1], [0, 1, 0]]) rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
xyz_size = torch.cat([x_size, z_size, y_size], dim=-1) xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
if with_yaw:
yaw = -yaw
elif src == Box3DMode.LIDAR and dst == Box3DMode.DEPTH: elif src == Box3DMode.LIDAR and dst == Box3DMode.DEPTH:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[0, -1, 0], [1, 0, 0], [0, 0, 1]]) rt_mat = arr.new_tensor([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
xyz_size = torch.cat([y_size, x_size, z_size], dim=-1) xyz_size = torch.cat([x_size, y_size, z_size], dim=-1)
if with_yaw:
yaw = yaw + np.pi / 2
yaw = limit_period(yaw, period=np.pi * 2)
elif src == Box3DMode.DEPTH and dst == Box3DMode.LIDAR: elif src == Box3DMode.DEPTH and dst == Box3DMode.LIDAR:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[0, 1, 0], [-1, 0, 0], [0, 0, 1]]) rt_mat = arr.new_tensor([[0, 1, 0], [-1, 0, 0], [0, 0, 1]])
xyz_size = torch.cat([y_size, x_size, z_size], dim=-1) xyz_size = torch.cat([x_size, y_size, z_size], dim=-1)
if with_yaw:
yaw = yaw - np.pi / 2
yaw = limit_period(yaw, period=np.pi * 2)
else: else:
raise NotImplementedError( raise NotImplementedError(
f'Conversion from Box3DMode {src} to {dst} ' f'Conversion from Box3DMode {src} to {dst} '
...@@ -135,13 +161,17 @@ class Box3DMode(IntEnum): ...@@ -135,13 +161,17 @@ class Box3DMode(IntEnum):
rt_mat = arr.new_tensor(rt_mat) rt_mat = arr.new_tensor(rt_mat)
if rt_mat.size(1) == 4: if rt_mat.size(1) == 4:
extended_xyz = torch.cat( extended_xyz = torch.cat(
[arr[:, :3], arr.new_ones(arr.size(0), 1)], dim=-1) [arr[..., :3], arr.new_ones(arr.size(0), 1)], dim=-1)
xyz = extended_xyz @ rt_mat.t() xyz = extended_xyz @ rt_mat.t()
else: else:
xyz = arr[:, :3] @ rt_mat.t() xyz = arr[..., :3] @ rt_mat.t()
remains = arr[..., 6:] if with_yaw:
arr = torch.cat([xyz[:, :3], xyz_size, remains], dim=-1) remains = arr[..., 7:]
arr = torch.cat([xyz[..., :3], xyz_size, yaw, remains], dim=-1)
else:
remains = arr[..., 6:]
arr = torch.cat([xyz[..., :3], xyz_size, remains], dim=-1)
# convert arr to the original type # convert arr to the original type
original_type = type(box) original_type = type(box)
...@@ -160,7 +190,6 @@ class Box3DMode(IntEnum): ...@@ -160,7 +190,6 @@ class Box3DMode(IntEnum):
raise NotImplementedError( raise NotImplementedError(
f'Conversion to {dst} through {original_type}' f'Conversion to {dst} through {original_type}'
' is not supported yet') ' is not supported yet')
return target_type( return target_type(arr, box_dim=arr.size(-1), with_yaw=with_yaw)
arr, box_dim=arr.size(-1), with_yaw=box.with_yaw)
else: else:
return arr return arr
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import numpy as np import numpy as np
import torch import torch
from mmdet3d.core.points import BasePoints from ...points import BasePoints
from .base_box3d import BaseInstance3DBoxes from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis from .utils import limit_period, rotation_3d_in_axis
...@@ -38,6 +38,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -38,6 +38,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes. boxes.
""" """
YAW_AXIS = 1
def __init__(self, def __init__(self,
tensor, tensor,
...@@ -117,16 +118,16 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -117,16 +118,16 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
/ | / | / | / |
(x0, y0, z0) + ----------- + + (x1, y1, z1) (x0, y0, z0) + ----------- + + (x1, y1, z1)
| / . | / | / . | /
| / oriign | / | / origin | /
(x0, y1, z0) + ----------- + -------> x right (x0, y1, z0) + ----------- + -------> x right
| (x1, y1, z0) | (x1, y1, z0)
| |
v v
down y down y
""" """
# TODO: rotation_3d_in_axis function do not support if self.tensor.numel() == 0:
# empty tensor currently. return torch.empty([0, 8, 3], device=self.tensor.device)
assert len(self.tensor) != 0
dims = self.dims dims = self.dims
corners_norm = torch.from_numpy( corners_norm = torch.from_numpy(
np.stack(np.unravel_index(np.arange(8), [2] * 3), axis=1)).to( np.stack(np.unravel_index(np.arange(8), [2] * 3), axis=1)).to(
...@@ -137,8 +138,11 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -137,8 +138,11 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
corners_norm = corners_norm - dims.new_tensor([0.5, 1, 0.5]) corners_norm = corners_norm - dims.new_tensor([0.5, 1, 0.5])
corners = dims.view([-1, 1, 3]) * corners_norm.reshape([1, 8, 3]) corners = dims.view([-1, 1, 3]) * corners_norm.reshape([1, 8, 3])
# rotate around y axis # positive direction of the gravity axis
corners = rotation_3d_in_axis(corners, self.tensor[:, 6], axis=1) # in cam coord system points to the earth
# so the rotation is clockwise if viewed from above
corners = rotation_3d_in_axis(
corners, self.tensor[:, 6], axis=self.YAW_AXIS, clockwise=True)
corners += self.tensor[:, :3].view(-1, 1, 3) corners += self.tensor[:, :3].view(-1, 1, 3)
return corners return corners
...@@ -146,7 +150,12 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -146,7 +150,12 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
def bev(self): def bev(self):
"""torch.Tensor: A n x 5 tensor of 2D BEV box of each box """torch.Tensor: A n x 5 tensor of 2D BEV box of each box
with rotation in XYWHR format.""" with rotation in XYWHR format."""
return self.tensor[:, [0, 2, 3, 5, 6]] bev = self.tensor[:, [0, 2, 3, 5, 6]].clone()
# positive direction of the gravity axis
# in cam coord system points to the earth
# so the bev yaw angle needs to be reversed
bev[:, -1] = -bev[:, -1]
return bev
@property @property
def nearest_bev(self): def nearest_bev(self):
...@@ -170,8 +179,8 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -170,8 +179,8 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
return bev_boxes return bev_boxes
def rotate(self, angle, points=None): def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or \ """Rotate boxes with points (optional) with the given angle or rotation
rotation matrix. matrix.
Args: Args:
angle (float | torch.Tensor | np.ndarray): angle (float | torch.Tensor | np.ndarray):
...@@ -180,39 +189,43 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -180,39 +189,43 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
Points to rotate. Defaults to None. Points to rotate. Defaults to None.
Returns: Returns:
tuple or None: When ``points`` is None, the function returns \ tuple or None: When ``points`` is None, the function returns
None, otherwise it returns the rotated points and the \ None, otherwise it returns the rotated points and the
rotation matrix ``rot_mat_T``. rotation matrix ``rot_mat_T``.
""" """
if not isinstance(angle, torch.Tensor): if not isinstance(angle, torch.Tensor):
angle = self.tensor.new_tensor(angle) angle = self.tensor.new_tensor(angle)
assert angle.shape == torch.Size([3, 3]) or angle.numel() == 1, \ assert angle.shape == torch.Size([3, 3]) or angle.numel() == 1, \
f'invalid rotation angle shape {angle.shape}' f'invalid rotation angle shape {angle.shape}'
if angle.numel() == 1: if angle.numel() == 1:
rot_sin = torch.sin(angle) self.tensor[:, 0:3], rot_mat_T = rotation_3d_in_axis(
rot_cos = torch.cos(angle) self.tensor[:, 0:3],
rot_mat_T = self.tensor.new_tensor([[rot_cos, 0, -rot_sin], angle,
[0, 1, 0], axis=self.YAW_AXIS,
[rot_sin, 0, rot_cos]]) return_mat=True,
# positive direction of the gravity axis
# in cam coord system points to the earth
# so the rotation is clockwise if viewed from above
clockwise=True)
else: else:
rot_mat_T = angle rot_mat_T = angle
rot_sin = rot_mat_T[2, 0] rot_sin = rot_mat_T[2, 0]
rot_cos = rot_mat_T[0, 0] rot_cos = rot_mat_T[0, 0]
angle = np.arctan2(rot_sin, rot_cos) angle = np.arctan2(rot_sin, rot_cos)
self.tensor[:, 0:3] = self.tensor[:, 0:3] @ rot_mat_T
self.tensor[:, :3] = self.tensor[:, :3] @ rot_mat_T
self.tensor[:, 6] += angle self.tensor[:, 6] += angle
if points is not None: if points is not None:
if isinstance(points, torch.Tensor): if isinstance(points, torch.Tensor):
points[:, :3] = points[:, :3] @ rot_mat_T points[:, :3] = points[:, :3] @ rot_mat_T
elif isinstance(points, np.ndarray): elif isinstance(points, np.ndarray):
rot_mat_T = rot_mat_T.numpy() rot_mat_T = rot_mat_T.cpu().numpy()
points[:, :3] = np.dot(points[:, :3], rot_mat_T) points[:, :3] = np.dot(points[:, :3], rot_mat_T)
elif isinstance(points, BasePoints): elif isinstance(points, BasePoints):
# clockwise points.rotate(rot_mat_T)
points.rotate(-angle)
else: else:
raise ValueError raise ValueError
return points, rot_mat_T return points, rot_mat_T
...@@ -264,7 +277,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -264,7 +277,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
polygon, we reduce the burden for simpler cases. polygon, we reduce the burden for simpler cases.
Returns: Returns:
torch.Tensor: Indicating whether each box is inside \ torch.Tensor: Indicating whether each box is inside
the reference range. the reference range.
""" """
in_range_flags = ((self.tensor[:, 0] > box_range[0]) in_range_flags = ((self.tensor[:, 0] > box_range[0])
...@@ -296,8 +309,8 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -296,8 +309,8 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
boxes2_top_height = boxes2.top_height.view(1, -1) boxes2_top_height = boxes2.top_height.view(1, -1)
boxes2_bottom_height = boxes2.bottom_height.view(1, -1) boxes2_bottom_height = boxes2.bottom_height.view(1, -1)
# In camera coordinate system # positive direction of the gravity axis
# from up to down is the positive direction # in cam coord system points to the earth
heighest_of_bottom = torch.min(boxes1_bottom_height, heighest_of_bottom = torch.min(boxes1_bottom_height,
boxes2_bottom_height) boxes2_bottom_height)
lowest_of_top = torch.max(boxes1_top_height, boxes2_top_height) lowest_of_top = torch.max(boxes1_top_height, boxes2_top_height)
...@@ -316,9 +329,50 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes): ...@@ -316,9 +329,50 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: \ :obj:`BaseInstance3DBoxes`:
The converted box of the same type in the ``dst`` mode. The converted box of the same type in the ``dst`` mode.
""" """
from .box_3d_mode import Box3DMode from .box_3d_mode import Box3DMode
return Box3DMode.convert( return Box3DMode.convert(
box=self, src=Box3DMode.CAM, dst=dst, rt_mat=rt_mat) box=self, src=Box3DMode.CAM, dst=dst, rt_mat=rt_mat)
def points_in_boxes(self, points):
"""Find the box which the points are in.
Args:
points (torch.Tensor): Points in shape (N, 3).
Returns:
torch.Tensor: The index of box where each point are in.
"""
from .coord_3d_mode import Coord3DMode
points_lidar = Coord3DMode.convert(points, Coord3DMode.CAM,
Coord3DMode.LIDAR)
boxes_lidar = Coord3DMode.convert(self.tensor, Coord3DMode.CAM,
Coord3DMode.LIDAR)
box_idx = super().points_in_boxes(self, points_lidar, boxes_lidar)
return box_idx
def points_in_boxes_batch(self, points):
"""Find points that are in boxes (CUDA).
Args:
points (torch.Tensor): Points in shape [1, M, 3] or [M, 3],
3 dimensions are [x, y, z] in LiDAR coordinate.
Returns:
torch.Tensor: The index of boxes each point lies in with shape
of (B, M, T).
"""
from .coord_3d_mode import Coord3DMode
points_lidar = Coord3DMode.convert(points, Coord3DMode.CAM,
Coord3DMode.LIDAR)
boxes_lidar = Coord3DMode.convert(self.tensor, Coord3DMode.CAM,
Coord3DMode.LIDAR)
box_idx = super().points_in_boxes_batch(self, points_lidar,
boxes_lidar)
return box_idx
...@@ -3,12 +3,9 @@ import numpy as np ...@@ -3,12 +3,9 @@ import numpy as np
import torch import torch
from enum import IntEnum, unique from enum import IntEnum, unique
from mmdet3d.core.points import (BasePoints, CameraPoints, DepthPoints, from ...points import BasePoints, CameraPoints, DepthPoints, LiDARPoints
LiDARPoints)
from .base_box3d import BaseInstance3DBoxes from .base_box3d import BaseInstance3DBoxes
from .cam_box3d import CameraInstance3DBoxes from .box_3d_mode import Box3DMode
from .depth_box3d import DepthInstance3DBoxes
from .lidar_box3d import LiDARInstance3DBoxes
@unique @unique
...@@ -64,119 +61,73 @@ class Coord3DMode(IntEnum): ...@@ -64,119 +61,73 @@ class Coord3DMode(IntEnum):
DEPTH = 2 DEPTH = 2
@staticmethod @staticmethod
def convert(input, src, dst, rt_mat=None): def convert(input, src, dst, rt_mat=None, with_yaw=True, is_point=True):
"""Convert boxes or points from `src` mode to `dst` mode.""" """Convert boxes or points from `src` mode to `dst` mode.
Args:
input (tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes` | :obj:`BasePoints`):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode` | :obj:`Coord3DMode`): The source mode.
dst (:obj:`Box3DMode` | :obj:`Coord3DMode`): The target mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
with_yaw (bool): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
is_point (bool): If `input` is neither an instance of
:obj:`BaseInstance3DBoxes` nor an instance of
:obj:`BasePoints`, whether or not it is point data.
Defaults to True.
Returns:
(tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes` | :obj:`BasePoints`):
The converted box of the same type.
"""
if isinstance(input, BaseInstance3DBoxes): if isinstance(input, BaseInstance3DBoxes):
return Coord3DMode.convert_box(input, src, dst, rt_mat=rt_mat) return Coord3DMode.convert_box(
input, src, dst, rt_mat=rt_mat, with_yaw=with_yaw)
elif isinstance(input, BasePoints): elif isinstance(input, BasePoints):
return Coord3DMode.convert_point(input, src, dst, rt_mat=rt_mat) return Coord3DMode.convert_point(input, src, dst, rt_mat=rt_mat)
elif isinstance(input, (tuple, list, np.ndarray, torch.Tensor)):
if is_point:
return Coord3DMode.convert_point(
input, src, dst, rt_mat=rt_mat)
else:
return Coord3DMode.convert_box(
input, src, dst, rt_mat=rt_mat, with_yaw=with_yaw)
else: else:
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def convert_box(box, src, dst, rt_mat=None): def convert_box(box, src, dst, rt_mat=None, with_yaw=True):
"""Convert boxes from `src` mode to `dst` mode. """Convert boxes from `src` mode to `dst` mode.
Args: Args:
box (tuple | list | np.ndarray | box (tuple | list | np.ndarray |
torch.Tensor | BaseInstance3DBoxes): torch.Tensor | :obj:`BaseInstance3DBoxes`):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7. Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`CoordMode`): The src Box mode. src (:obj:`Box3DMode`): The src Box mode.
dst (:obj:`CoordMode`): The target Box mode. dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None. matrix between different coordinates. Defaults to None.
The conversion from `src` coordinates to `dst` coordinates The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
with_yaw (bool): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
Returns: Returns:
(tuple | list | np.ndarray | torch.Tensor | BaseInstance3DBoxes): \ (tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes`):
The converted box of the same type. The converted box of the same type.
""" """
if src == dst: return Box3DMode.convert(box, src, dst, rt_mat=rt_mat)
return box
is_numpy = isinstance(box, np.ndarray)
is_Instance3DBoxes = isinstance(box, BaseInstance3DBoxes)
single_box = isinstance(box, (list, tuple))
if single_box:
assert len(box) >= 7, (
'CoordMode.convert takes either a k-tuple/list or '
'an Nxk array/tensor, where k >= 7')
arr = torch.tensor(box)[None, :]
else:
# avoid modifying the input box
if is_numpy:
arr = torch.from_numpy(np.asarray(box)).clone()
elif is_Instance3DBoxes:
arr = box.tensor.clone()
else:
arr = box.clone()
# convert box from `src` mode to `dst` mode.
x_size, y_size, z_size = arr[..., 3:4], arr[..., 4:5], arr[..., 5:6]
if src == Coord3DMode.LIDAR and dst == Coord3DMode.CAM:
if rt_mat is None:
rt_mat = arr.new_tensor([[0, -1, 0], [0, 0, -1], [1, 0, 0]])
xyz_size = torch.cat([y_size, z_size, x_size], dim=-1)
elif src == Coord3DMode.CAM and dst == Coord3DMode.LIDAR:
if rt_mat is None:
rt_mat = arr.new_tensor([[0, 0, 1], [-1, 0, 0], [0, -1, 0]])
xyz_size = torch.cat([z_size, x_size, y_size], dim=-1)
elif src == Coord3DMode.DEPTH and dst == Coord3DMode.CAM:
if rt_mat is None:
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, 1], [0, -1, 0]])
xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
elif src == Coord3DMode.CAM and dst == Coord3DMode.DEPTH:
if rt_mat is None:
rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
elif src == Coord3DMode.LIDAR and dst == Coord3DMode.DEPTH:
if rt_mat is None:
rt_mat = arr.new_tensor([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
xyz_size = torch.cat([y_size, x_size, z_size], dim=-1)
elif src == Coord3DMode.DEPTH and dst == Coord3DMode.LIDAR:
if rt_mat is None:
rt_mat = arr.new_tensor([[0, 1, 0], [-1, 0, 0], [0, 0, 1]])
xyz_size = torch.cat([y_size, x_size, z_size], dim=-1)
else:
raise NotImplementedError(
f'Conversion from Coord3DMode {src} to {dst} '
'is not supported yet')
if not isinstance(rt_mat, torch.Tensor):
rt_mat = arr.new_tensor(rt_mat)
if rt_mat.size(1) == 4:
extended_xyz = torch.cat(
[arr[:, :3], arr.new_ones(arr.size(0), 1)], dim=-1)
xyz = extended_xyz @ rt_mat.t()
else:
xyz = arr[:, :3] @ rt_mat.t()
remains = arr[..., 6:]
arr = torch.cat([xyz[:, :3], xyz_size, remains], dim=-1)
# convert arr to the original type
original_type = type(box)
if single_box:
return original_type(arr.flatten().tolist())
if is_numpy:
return arr.numpy()
elif is_Instance3DBoxes:
if dst == Coord3DMode.CAM:
target_type = CameraInstance3DBoxes
elif dst == Coord3DMode.LIDAR:
target_type = LiDARInstance3DBoxes
elif dst == Coord3DMode.DEPTH:
target_type = DepthInstance3DBoxes
else:
raise NotImplementedError(
f'Conversion to {dst} through {original_type}'
' is not supported yet')
return target_type(
arr, box_dim=arr.size(-1), with_yaw=box.with_yaw)
else:
return arr
@staticmethod @staticmethod
def convert_point(point, src, dst, rt_mat=None): def convert_point(point, src, dst, rt_mat=None):
...@@ -184,7 +135,7 @@ class Coord3DMode(IntEnum): ...@@ -184,7 +135,7 @@ class Coord3DMode(IntEnum):
Args: Args:
point (tuple | list | np.ndarray | point (tuple | list | np.ndarray |
torch.Tensor | BasePoints): torch.Tensor | :obj:`BasePoints`):
Can be a k-tuple, k-list or an Nxk array/tensor. Can be a k-tuple, k-list or an Nxk array/tensor.
src (:obj:`CoordMode`): The src Point mode. src (:obj:`CoordMode`): The src Point mode.
dst (:obj:`CoordMode`): The target Point mode. dst (:obj:`CoordMode`): The target Point mode.
...@@ -195,7 +146,7 @@ class Coord3DMode(IntEnum): ...@@ -195,7 +146,7 @@ class Coord3DMode(IntEnum):
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
Returns: Returns:
(tuple | list | np.ndarray | torch.Tensor | BasePoints): \ (tuple | list | np.ndarray | torch.Tensor | :obj:`BasePoints`):
The converted point of the same type. The converted point of the same type.
""" """
if src == dst: if src == dst:
...@@ -219,8 +170,6 @@ class Coord3DMode(IntEnum): ...@@ -219,8 +170,6 @@ class Coord3DMode(IntEnum):
arr = point.clone() arr = point.clone()
# convert point from `src` mode to `dst` mode. # convert point from `src` mode to `dst` mode.
# TODO: LIDAR
# only implemented provided Rt matrix in cam-depth conversion
if src == Coord3DMode.LIDAR and dst == Coord3DMode.CAM: if src == Coord3DMode.LIDAR and dst == Coord3DMode.CAM:
if rt_mat is None: if rt_mat is None:
rt_mat = arr.new_tensor([[0, -1, 0], [0, 0, -1], [1, 0, 0]]) rt_mat = arr.new_tensor([[0, -1, 0], [0, 0, -1], [1, 0, 0]])
...@@ -248,13 +197,13 @@ class Coord3DMode(IntEnum): ...@@ -248,13 +197,13 @@ class Coord3DMode(IntEnum):
rt_mat = arr.new_tensor(rt_mat) rt_mat = arr.new_tensor(rt_mat)
if rt_mat.size(1) == 4: if rt_mat.size(1) == 4:
extended_xyz = torch.cat( extended_xyz = torch.cat(
[arr[:, :3], arr.new_ones(arr.size(0), 1)], dim=-1) [arr[..., :3], arr.new_ones(arr.size(0), 1)], dim=-1)
xyz = extended_xyz @ rt_mat.t() xyz = extended_xyz @ rt_mat.t()
else: else:
xyz = arr[:, :3] @ rt_mat.t() xyz = arr[..., :3] @ rt_mat.t()
remains = arr[:, 3:] remains = arr[..., 3:]
arr = torch.cat([xyz[:, :3], remains], dim=-1) arr = torch.cat([xyz[..., :3], remains], dim=-1)
# convert arr to the original type # convert arr to the original type
original_type = type(point) original_type = type(point)
......
...@@ -3,7 +3,6 @@ import numpy as np ...@@ -3,7 +3,6 @@ import numpy as np
import torch import torch
from mmdet3d.core.points import BasePoints from mmdet3d.core.points import BasePoints
from mmdet3d.ops import points_in_boxes_batch
from .base_box3d import BaseInstance3DBoxes from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis from .utils import limit_period, rotation_3d_in_axis
...@@ -38,6 +37,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -38,6 +37,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes. boxes.
""" """
YAW_AXIS = 2
@property @property
def gravity_center(self): def gravity_center(self):
...@@ -67,7 +67,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -67,7 +67,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
/ | / | / | / |
(x0, y0, z1) + ----------- + + (x1, y1, z0) (x0, y0, z1) + ----------- + + (x1, y1, z0)
| / . | / | / . | /
| / oriign | / | / origin | /
(x0, y0, z0) + ----------- + --------> right x (x0, y0, z0) + ----------- + --------> right x
(x1, y0, z0) (x1, y0, z0)
""" """
...@@ -85,7 +85,8 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -85,7 +85,8 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
corners = dims.view([-1, 1, 3]) * corners_norm.reshape([1, 8, 3]) corners = dims.view([-1, 1, 3]) * corners_norm.reshape([1, 8, 3])
# rotate around z axis # rotate around z axis
corners = rotation_3d_in_axis(corners, self.tensor[:, 6], axis=2) corners = rotation_3d_in_axis(
corners, self.tensor[:, 6], axis=self.YAW_AXIS)
corners += self.tensor[:, :3].view(-1, 1, 3) corners += self.tensor[:, :3].view(-1, 1, 3)
return corners return corners
...@@ -117,8 +118,8 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -117,8 +118,8 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
return bev_boxes return bev_boxes
def rotate(self, angle, points=None): def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or \ """Rotate boxes with points (optional) with the given angle or rotation
rotation matrix. matrix.
Args: Args:
angle (float | torch.Tensor | np.ndarray): angle (float | torch.Tensor | np.ndarray):
...@@ -127,30 +128,31 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -127,30 +128,31 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
Points to rotate. Defaults to None. Points to rotate. Defaults to None.
Returns: Returns:
tuple or None: When ``points`` is None, the function returns \ tuple or None: When ``points`` is None, the function returns
None, otherwise it returns the rotated points and the \ None, otherwise it returns the rotated points and the
rotation matrix ``rot_mat_T``. rotation matrix ``rot_mat_T``.
""" """
if not isinstance(angle, torch.Tensor): if not isinstance(angle, torch.Tensor):
angle = self.tensor.new_tensor(angle) angle = self.tensor.new_tensor(angle)
assert angle.shape == torch.Size([3, 3]) or angle.numel() == 1, \ assert angle.shape == torch.Size([3, 3]) or angle.numel() == 1, \
f'invalid rotation angle shape {angle.shape}' f'invalid rotation angle shape {angle.shape}'
if angle.numel() == 1: if angle.numel() == 1:
rot_sin = torch.sin(angle) self.tensor[:, 0:3], rot_mat_T = rotation_3d_in_axis(
rot_cos = torch.cos(angle) self.tensor[:, 0:3],
rot_mat_T = self.tensor.new_tensor([[rot_cos, -rot_sin, 0], angle,
[rot_sin, rot_cos, 0], axis=self.YAW_AXIS,
[0, 0, 1]]).T return_mat=True)
else: else:
rot_mat_T = angle.T rot_mat_T = angle
rot_sin = rot_mat_T[0, 1] rot_sin = rot_mat_T[0, 1]
rot_cos = rot_mat_T[0, 0] rot_cos = rot_mat_T[0, 0]
angle = np.arctan2(rot_sin, rot_cos) angle = np.arctan2(rot_sin, rot_cos)
self.tensor[:, 0:3] = self.tensor[:, 0:3] @ rot_mat_T
self.tensor[:, 0:3] = self.tensor[:, 0:3] @ rot_mat_T
if self.with_yaw: if self.with_yaw:
self.tensor[:, 6] -= angle self.tensor[:, 6] += angle
else: else:
corners_rot = self.corners @ rot_mat_T corners_rot = self.corners @ rot_mat_T
new_x_size = corners_rot[..., 0].max( new_x_size = corners_rot[..., 0].max(
...@@ -165,11 +167,10 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -165,11 +167,10 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
if isinstance(points, torch.Tensor): if isinstance(points, torch.Tensor):
points[:, :3] = points[:, :3] @ rot_mat_T points[:, :3] = points[:, :3] @ rot_mat_T
elif isinstance(points, np.ndarray): elif isinstance(points, np.ndarray):
rot_mat_T = rot_mat_T.numpy() rot_mat_T = rot_mat_T.cpu().numpy()
points[:, :3] = np.dot(points[:, :3], rot_mat_T) points[:, :3] = np.dot(points[:, :3], rot_mat_T)
elif isinstance(points, BasePoints): elif isinstance(points, BasePoints):
# anti-clockwise points.rotate(rot_mat_T)
points.rotate(angle)
else: else:
raise ValueError raise ValueError
return points, rot_mat_T return points, rot_mat_T
...@@ -221,7 +222,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -221,7 +222,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
polygon, we try to reduce the burdun for simpler cases. polygon, we try to reduce the burdun for simpler cases.
Returns: Returns:
torch.Tensor: Indicating whether each box is inside \ torch.Tensor: Indicating whether each box is inside
the reference range. the reference range.
""" """
in_range_flags = ((self.tensor[:, 0] > box_range[0]) in_range_flags = ((self.tensor[:, 0] > box_range[0])
...@@ -242,41 +243,13 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -242,41 +243,13 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
Returns: Returns:
:obj:`DepthInstance3DBoxes`: \ :obj:`DepthInstance3DBoxes`:
The converted box of the same type in the ``dst`` mode. The converted box of the same type in the ``dst`` mode.
""" """
from .box_3d_mode import Box3DMode from .box_3d_mode import Box3DMode
return Box3DMode.convert( return Box3DMode.convert(
box=self, src=Box3DMode.DEPTH, dst=dst, rt_mat=rt_mat) box=self, src=Box3DMode.DEPTH, dst=dst, rt_mat=rt_mat)
def points_in_boxes(self, points):
"""Find points that are in boxes (CUDA).
Args:
points (torch.Tensor): Points in shape [1, M, 3] or [M, 3], \
3 dimensions are [x, y, z] in LiDAR coordinate.
Returns:
torch.Tensor: The index of boxes each point lies in with shape \
of (B, M, T).
"""
from .box_3d_mode import Box3DMode
# to lidar
points_lidar = points.clone()
points_lidar = points_lidar[..., [1, 0, 2]]
points_lidar[..., 1] *= -1
if points.dim() == 2:
points_lidar = points_lidar.unsqueeze(0)
else:
assert points.dim() == 3 and points_lidar.shape[0] == 1
boxes_lidar = self.convert_to(Box3DMode.LIDAR).tensor
boxes_lidar = boxes_lidar.to(points.device).unsqueeze(0)
box_idxs_of_pts = points_in_boxes_batch(points_lidar, boxes_lidar)
return box_idxs_of_pts.squeeze(0)
def enlarged_box(self, extra_width): def enlarged_box(self, extra_width):
"""Enlarge the length, width and height boxes. """Enlarge the length, width and height boxes.
...@@ -331,13 +304,12 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes): ...@@ -331,13 +304,12 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
-1, 3) -1, 3)
surface_rot = rot_mat_T.repeat(6, 1, 1) surface_rot = rot_mat_T.repeat(6, 1, 1)
surface_3d = torch.matmul( surface_3d = torch.matmul(surface_3d.unsqueeze(-2),
surface_3d.unsqueeze(-2), surface_rot.transpose(2, 1)).squeeze(-2) surface_rot).squeeze(-2)
surface_center = center.repeat(1, 6, 1).reshape(-1, 3) + surface_3d surface_center = center.repeat(1, 6, 1).reshape(-1, 3) + surface_3d
line_rot = rot_mat_T.repeat(12, 1, 1) line_rot = rot_mat_T.repeat(12, 1, 1)
line_3d = torch.matmul( line_3d = torch.matmul(line_3d.unsqueeze(-2), line_rot).squeeze(-2)
line_3d.unsqueeze(-2), line_rot.transpose(2, 1)).squeeze(-2)
line_center = center.repeat(1, 12, 1).reshape(-1, 3) + line_3d line_center = center.repeat(1, 12, 1).reshape(-1, 3) + line_3d
return surface_center, line_center return surface_center, line_center
...@@ -3,7 +3,6 @@ import numpy as np ...@@ -3,7 +3,6 @@ import numpy as np
import torch import torch
from mmdet3d.core.points import BasePoints from mmdet3d.core.points import BasePoints
from mmdet3d.ops.roiaware_pool3d import points_in_boxes_gpu
from .base_box3d import BaseInstance3DBoxes from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis from .utils import limit_period, rotation_3d_in_axis
...@@ -15,16 +14,16 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -15,16 +14,16 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
.. code-block:: none .. code-block:: none
up z x front (yaw=-0.5*pi) up z x front (yaw=0)
^ ^ ^ ^
| / | /
| / | /
(yaw=-pi) left y <------ 0 -------- (yaw=0) (yaw=0.5*pi) left y <------ 0
The relative coordinate of bottom center in a LiDAR box is (0.5, 0.5, 0), The relative coordinate of bottom center in a LiDAR box is (0.5, 0.5, 0),
and the yaw is around the z axis, thus the rotation axis=2. and the yaw is around the z axis, thus the rotation axis=2.
The yaw is 0 at the negative direction of y axis, and decreases from The yaw is 0 at the positive direction of x axis, and increases from
the negative direction of y to the positive direction of x. the positive direction of x to the positive direction of y.
A refactor is ongoing to make the three coordinate systems A refactor is ongoing to make the three coordinate systems
easier to understand and convert between each other. easier to understand and convert between each other.
...@@ -36,6 +35,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -36,6 +35,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes. boxes.
""" """
YAW_AXIS = 2
@property @property
def gravity_center(self): def gravity_center(self):
...@@ -65,7 +65,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -65,7 +65,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
/ | / | / | / |
(x0, y0, z1) + ----------- + + (x1, y1, z0) (x0, y0, z1) + ----------- + + (x1, y1, z0)
| / . | / | / . | /
| / oriign | / | / origin | /
left y<-------- + ----------- + (x0, y1, z0) left y<-------- + ----------- + (x0, y1, z0)
(x0, y0, z0) (x0, y0, z0)
""" """
...@@ -83,7 +83,8 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -83,7 +83,8 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
corners = dims.view([-1, 1, 3]) * corners_norm.reshape([1, 8, 3]) corners = dims.view([-1, 1, 3]) * corners_norm.reshape([1, 8, 3])
# rotate around z axis # rotate around z axis
corners = rotation_3d_in_axis(corners, self.tensor[:, 6], axis=2) corners = rotation_3d_in_axis(
corners, self.tensor[:, 6], axis=self.YAW_AXIS)
corners += self.tensor[:, :3].view(-1, 1, 3) corners += self.tensor[:, :3].view(-1, 1, 3)
return corners return corners
...@@ -115,8 +116,8 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -115,8 +116,8 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
return bev_boxes return bev_boxes
def rotate(self, angle, points=None): def rotate(self, angle, points=None):
"""Rotate boxes with points (optional) with the given angle or \ """Rotate boxes with points (optional) with the given angle or rotation
rotation matrix. matrix.
Args: Args:
angles (float | torch.Tensor | np.ndarray): angles (float | torch.Tensor | np.ndarray):
...@@ -125,28 +126,29 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -125,28 +126,29 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
Points to rotate. Defaults to None. Points to rotate. Defaults to None.
Returns: Returns:
tuple or None: When ``points`` is None, the function returns \ tuple or None: When ``points`` is None, the function returns
None, otherwise it returns the rotated points and the \ None, otherwise it returns the rotated points and the
rotation matrix ``rot_mat_T``. rotation matrix ``rot_mat_T``.
""" """
if not isinstance(angle, torch.Tensor): if not isinstance(angle, torch.Tensor):
angle = self.tensor.new_tensor(angle) angle = self.tensor.new_tensor(angle)
assert angle.shape == torch.Size([3, 3]) or angle.numel() == 1, \ assert angle.shape == torch.Size([3, 3]) or angle.numel() == 1, \
f'invalid rotation angle shape {angle.shape}' f'invalid rotation angle shape {angle.shape}'
if angle.numel() == 1: if angle.numel() == 1:
rot_sin = torch.sin(angle) self.tensor[:, 0:3], rot_mat_T = rotation_3d_in_axis(
rot_cos = torch.cos(angle) self.tensor[:, 0:3],
rot_mat_T = self.tensor.new_tensor([[rot_cos, -rot_sin, 0], angle,
[rot_sin, rot_cos, 0], axis=self.YAW_AXIS,
[0, 0, 1]]) return_mat=True)
else: else:
rot_mat_T = angle rot_mat_T = angle
rot_sin = rot_mat_T[1, 0] rot_sin = rot_mat_T[0, 1]
rot_cos = rot_mat_T[0, 0] rot_cos = rot_mat_T[0, 0]
angle = np.arctan2(rot_sin, rot_cos) angle = np.arctan2(rot_sin, rot_cos)
self.tensor[:, 0:3] = self.tensor[:, 0:3] @ rot_mat_T
self.tensor[:, :3] = self.tensor[:, :3] @ rot_mat_T
self.tensor[:, 6] += angle self.tensor[:, 6] += angle
if self.tensor.shape[1] == 9: if self.tensor.shape[1] == 9:
...@@ -157,11 +159,10 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -157,11 +159,10 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
if isinstance(points, torch.Tensor): if isinstance(points, torch.Tensor):
points[:, :3] = points[:, :3] @ rot_mat_T points[:, :3] = points[:, :3] @ rot_mat_T
elif isinstance(points, np.ndarray): elif isinstance(points, np.ndarray):
rot_mat_T = rot_mat_T.numpy() rot_mat_T = rot_mat_T.cpu().numpy()
points[:, :3] = np.dot(points[:, :3], rot_mat_T) points[:, :3] = np.dot(points[:, :3], rot_mat_T)
elif isinstance(points, BasePoints): elif isinstance(points, BasePoints):
# clockwise points.rotate(rot_mat_T)
points.rotate(-angle)
else: else:
raise ValueError raise ValueError
return points, rot_mat_T return points, rot_mat_T
...@@ -183,11 +184,11 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -183,11 +184,11 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
if bev_direction == 'horizontal': if bev_direction == 'horizontal':
self.tensor[:, 1::7] = -self.tensor[:, 1::7] self.tensor[:, 1::7] = -self.tensor[:, 1::7]
if self.with_yaw: if self.with_yaw:
self.tensor[:, 6] = -self.tensor[:, 6] + np.pi self.tensor[:, 6] = -self.tensor[:, 6]
elif bev_direction == 'vertical': elif bev_direction == 'vertical':
self.tensor[:, 0::7] = -self.tensor[:, 0::7] self.tensor[:, 0::7] = -self.tensor[:, 0::7]
if self.with_yaw: if self.with_yaw:
self.tensor[:, 6] = -self.tensor[:, 6] self.tensor[:, 6] = -self.tensor[:, 6] + np.pi
if points is not None: if points is not None:
assert isinstance(points, (torch.Tensor, np.ndarray, BasePoints)) assert isinstance(points, (torch.Tensor, np.ndarray, BasePoints))
...@@ -233,7 +234,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -233,7 +234,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
to LiDAR. This requires a transformation matrix. to LiDAR. This requires a transformation matrix.
Returns: Returns:
:obj:`BaseInstance3DBoxes`: \ :obj:`BaseInstance3DBoxes`:
The converted box of the same type in the ``dst`` mode. The converted box of the same type in the ``dst`` mode.
""" """
from .box_3d_mode import Box3DMode from .box_3d_mode import Box3DMode
...@@ -254,17 +255,3 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -254,17 +255,3 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
# bottom center z minus extra_width # bottom center z minus extra_width
enlarged_boxes[:, 2] -= extra_width enlarged_boxes[:, 2] -= extra_width
return self.new_box(enlarged_boxes) return self.new_box(enlarged_boxes)
def points_in_boxes(self, points):
"""Find the box which the points are in.
Args:
points (torch.Tensor): Points in shape (N, 3).
Returns:
torch.Tensor: The index of box where each point are in.
"""
box_idx = points_in_boxes_gpu(
points.unsqueeze(0),
self.tensor.unsqueeze(0).to(points.device)).squeeze(0)
return box_idx
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment