Source code for mmdet3d.structures.bbox_3d.lidar_box3d
# Copyright (c) OpenMMLab. All rights reserved.
from typing import Optional, Tuple, Union
import numpy as np
import torch
from torch import Tensor
from mmdet3d.structures.points import BasePoints
from .base_box3d import BaseInstance3DBoxes
from .utils import rotation_3d_in_axis
[docs]class LiDARInstance3DBoxes(BaseInstance3DBoxes):
"""3D boxes of instances in LIDAR coordinates.
Coordinates in LiDAR:
.. code-block:: none
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 positive direction of x axis, and increases from the positive direction
of x to the positive direction of y.
Attributes:
tensor (Tensor): Float matrix with shape (N, box_dim).
box_dim (int): Integer indicating the dimension of a box. Each row is
(x, y, z, x_size, y_size, z_size, yaw, ...).
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes.
"""
YAW_AXIS = 2
@property
def corners(self) -> Tensor:
"""Convert boxes to corners in clockwise order, in the form of (x0y0z0,
x0y0z1, x0y1z1, x0y1z0, x1y0z0, x1y0z1, x1y1z1, x1y1z0).
.. code-block:: none
up z
front x ^
/ |
/ |
(x1, y0, z1) + ----------- + (x1, y1, z1)
/| / |
/ | / |
(x0, y0, z1) + ----------- + + (x1, y1, z0)
| / . | /
| / origin | /
left y <------- + ----------- + (x0, y1, z0)
(x0, y0, z0)
Returns:
Tensor: A tensor with 8 corners of each box in shape (N, 8, 3).
"""
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(
device=dims.device, dtype=dims.dtype)
corners_norm = corners_norm[[0, 1, 3, 2, 4, 5, 7, 6]]
# use relative origin (0.5, 0.5, 0)
corners_norm = corners_norm - dims.new_tensor([0.5, 0.5, 0])
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=self.YAW_AXIS)
corners += self.tensor[:, :3].view(-1, 1, 3)
return corners
[docs] def rotate(
self,
angle: Union[Tensor, np.ndarray, float],
points: Optional[Union[Tensor, np.ndarray, BasePoints]] = None
) -> Union[Tuple[Tensor, Tensor], Tuple[np.ndarray, np.ndarray], Tuple[
BasePoints, Tensor], None]:
"""Rotate boxes with points (optional) with the given angle or rotation
matrix.
Args:
angle (Tensor or np.ndarray or float): Rotation angle or rotation
matrix.
points (Tensor or np.ndarray or :obj:`BasePoints`, optional):
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 rotation matrix
``rot_mat_T``.
"""
if not isinstance(angle, 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:
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[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[:, 6] += angle
if self.tensor.shape[1] == 9:
# rotate velo vector
self.tensor[:, 7:9] = self.tensor[:, 7:9] @ rot_mat_T[:2, :2]
if points is not None:
if isinstance(points, Tensor):
points[:, :3] = points[:, :3] @ rot_mat_T
elif isinstance(points, np.ndarray):
rot_mat_T = rot_mat_T.cpu().numpy()
points[:, :3] = np.dot(points[:, :3], rot_mat_T)
elif isinstance(points, BasePoints):
points.rotate(rot_mat_T)
else:
raise ValueError
return points, rot_mat_T
[docs] def flip(
self,
bev_direction: str = 'horizontal',
points: Optional[Union[Tensor, np.ndarray, BasePoints]] = None
) -> Union[Tensor, np.ndarray, BasePoints, None]:
"""Flip the boxes in BEV along given BEV direction.
In LIDAR coordinates, it flips the y (horizontal) or x (vertical) axis.
Args:
bev_direction (str): Direction by which to flip. Can be chosen from
'horizontal' and 'vertical'. Defaults to 'horizontal'.
points (Tensor or np.ndarray or :obj:`BasePoints`, optional):
Points to flip. Defaults to None.
Returns:
Tensor or np.ndarray or :obj:`BasePoints` or None: When ``points``
is None, the function returns None, otherwise it returns the
flipped points.
"""
assert bev_direction in ('horizontal', 'vertical')
if bev_direction == 'horizontal':
self.tensor[:, 1::7] = -self.tensor[:, 1::7]
if self.with_yaw:
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] + np.pi
if points is not None:
assert isinstance(points, (Tensor, np.ndarray, BasePoints))
if isinstance(points, (Tensor, np.ndarray)):
if bev_direction == 'horizontal':
points[:, 1] = -points[:, 1]
elif bev_direction == 'vertical':
points[:, 0] = -points[:, 0]
elif isinstance(points, BasePoints):
points.flip(bev_direction)
return points
[docs] def convert_to(self,
dst: int,
rt_mat: Optional[Union[Tensor, np.ndarray]] = None,
correct_yaw: bool = False) -> 'BaseInstance3DBoxes':
"""Convert self to ``dst`` mode.
Args:
dst (int): The target Box mode.
rt_mat (Tensor or np.ndarray, optional): 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.
correct_yaw (bool): Whether to convert the yaw angle to the target
coordinate. Defaults to False.
Returns:
: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.LIDAR,
dst=dst,
rt_mat=rt_mat,
correct_yaw=correct_yaw)
[docs] def enlarged_box(
self, extra_width: Union[float, Tensor]) -> 'LiDARInstance3DBoxes':
"""Enlarge the length, width and height of boxes.
Args:
extra_width (float or Tensor): Extra width to enlarge the box.
Returns:
:obj:`LiDARInstance3DBoxes`: Enlarged boxes.
"""
enlarged_boxes = self.tensor.clone()
enlarged_boxes[:, 3:6] += extra_width * 2
# bottom center z minus extra_width
enlarged_boxes[:, 2] -= extra_width
return self.new_box(enlarged_boxes)