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