Commit 75d96044 authored by zhangwenwei's avatar zhangwenwei
Browse files

Add CAM box

parent 435fe45b
from abc import abstractmethod from abc import abstractmethod
import numpy as np
import torch import torch
from .utils import limit_period
class BaseInstance3DBoxes(object): class BaseInstance3DBoxes(object):
"""Base class for 3D Boxes """Base class for 3D Boxes
...@@ -36,6 +39,36 @@ class BaseInstance3DBoxes(object): ...@@ -36,6 +39,36 @@ class BaseInstance3DBoxes(object):
""" """
return self.tensor[:, 3] * self.tensor[:, 4] * self.tensor[:, 5] return self.tensor[:, 3] * self.tensor[:, 4] * self.tensor[:, 5]
@property
def dims(self):
"""Calculate the length in each dimension of all the boxes.
Convert the boxes to the form of (x_size, y_size, z_size)
Returns:
torch.Tensor: corners of each box with size (N, 8, 3)
"""
return self.tensor[:, 3:6]
@property
def center(self):
"""Calculate the center of all the boxes.
Note:
In the MMDetection.3D's convention, the bottom center is
usually taken as the default center.
The relative position of the centers in different kinds of
boxes are different, e.g., the relative center of a boxes is
[0.5, 1.0, 0.5] in camera and [0.5, 0.5, 0] in lidar.
It is recommended to use `bottom_center` or `gravity_center`
for more clear usage.
Returns:
torch.Tensor: a tensor with center of each box.
"""
return self.bottom_center
@property @property
def bottom_center(self): def bottom_center(self):
"""Calculate the bottom center of all the boxes. """Calculate the bottom center of all the boxes.
...@@ -43,7 +76,7 @@ class BaseInstance3DBoxes(object): ...@@ -43,7 +76,7 @@ class BaseInstance3DBoxes(object):
Returns: Returns:
torch.Tensor: a tensor with center of each box. torch.Tensor: a tensor with center of each box.
""" """
return self.tensor[..., :3] return self.tensor[:, :3]
@property @property
def gravity_center(self): def gravity_center(self):
...@@ -79,16 +112,17 @@ class BaseInstance3DBoxes(object): ...@@ -79,16 +112,17 @@ class BaseInstance3DBoxes(object):
""" """
pass pass
@abstractmethod
def translate(self, trans_vector): def translate(self, trans_vector):
"""Calculate whether the points is in any of the boxes """Calculate whether the points is in any of the boxes
Args: Args:
trans_vector (torch.Tensor): translation vector of size 1x3 trans_vector (torch.Tensor): translation vector of size 1x3
""" """
pass if not isinstance(trans_vector, torch.Tensor):
trans_vector = self.tensor.new_tensor(trans_vector)
self.tensor[:, :3] += trans_vector
@abstractmethod
def in_range_3d(self, box_range): def in_range_3d(self, box_range):
"""Check whether the boxes are in the given range """Check whether the boxes are in the given range
...@@ -96,11 +130,23 @@ class BaseInstance3DBoxes(object): ...@@ -96,11 +130,23 @@ class BaseInstance3DBoxes(object):
box_range (list | torch.Tensor): the range of box box_range (list | torch.Tensor): the range of box
(x_min, y_min, z_min, x_max, y_max, z_max) (x_min, y_min, z_min, x_max, y_max, z_max)
Note:
In the original implementation of SECOND, checking whether
a box in the range checks whether the points are in a convex
polygon, we try to reduce the burdun for simpler cases.
TODO: check whether this will effect the performance
Returns: Returns:
a binary vector, indicating whether each box is inside a binary vector, indicating whether each box is inside
the reference range. the reference range.
""" """
pass in_range_flags = ((self.tensor[:, 0] > box_range[0])
& (self.tensor[:, 1] > box_range[1])
& (self.tensor[:, 2] > box_range[2])
& (self.tensor[:, 0] < box_range[3])
& (self.tensor[:, 1] < box_range[4])
& (self.tensor[:, 2] < box_range[5]))
return in_range_flags
@abstractmethod @abstractmethod
def in_range_bev(self, box_range): def in_range_bev(self, box_range):
...@@ -116,15 +162,24 @@ class BaseInstance3DBoxes(object): ...@@ -116,15 +162,24 @@ class BaseInstance3DBoxes(object):
""" """
pass pass
@abstractmethod def scale(self, scale_factor):
def scale(self, scale_factors):
"""Scale the box with horizontal and vertical scaling factors """Scale the box with horizontal and vertical scaling factors
Args: Args:
scale_factors (float | torch.Tensor | list[float]): scale_factors (float):
scale factors to scale the boxes. scale factors to scale the boxes.
""" """
pass self.tensor[:, :6] *= scale_factor
self.tensor[:, 7:] *= scale_factor
def limit_yaw(self, offset=0.5, period=np.pi):
"""Limit the yaw to a given period and offset
Args:
offset (float): the offset of the yaw
period (float): the expected period
"""
self.tensor[:, 6] = limit_period(self.tensor[:, 6], offset, period)
def nonempty(self, threshold: float = 0.0): def nonempty(self, threshold: float = 0.0):
"""Find boxes that are non-empty. """Find boxes that are non-empty.
......
...@@ -8,13 +8,15 @@ import torch ...@@ -8,13 +8,15 @@ import torch
class Box3DMode(IntEnum): class Box3DMode(IntEnum):
r"""Enum of different ways to represent a box. r"""Enum of different ways to represent a box.
Coordinates in velodyne/LiDAR sensors: Coordinates in LiDAR:
.. code-block:: none .. code-block:: none
up z x front up z x front
^ ^ ^ ^
| / | /
| / | /
left y <------ 0 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.
Coordinates in camera: Coordinates in camera:
.. code-block:: none .. code-block:: none
...@@ -26,6 +28,8 @@ class Box3DMode(IntEnum): ...@@ -26,6 +28,8 @@ class Box3DMode(IntEnum):
| |
v v
down y down y
The relative coordinate of bottom center in a CAM box is [0.5, 1.0, 0.5],
and the yaw is around the y axis, thus the rotation axis=1.
Coordinates in Depth mode: Coordinates in Depth mode:
.. code-block:: none .. code-block:: none
...@@ -34,6 +38,8 @@ class Box3DMode(IntEnum): ...@@ -34,6 +38,8 @@ class Box3DMode(IntEnum):
| / | /
| / | /
front y <------ 0 front y <------ 0
The relative coordinate of bottom center in a DEPTH box is [0.5, 0.5, 0],
and the yaw is around the z axis, thus the rotation axis=2.
""" """
LIDAR = 0 LIDAR = 0
......
import numpy as np
import torch
from .base_box3d import BaseInstance3DBoxes
from .utils import limit_period, rotation_3d_in_axis
class CAMInstance3DBoxes(BaseInstance3DBoxes):
"""3D boxes of instances in CAM coordinates
Coordinates in camera:
.. code-block:: none
x right
/
/
front z <------ 0
|
|
v
down y
The relative coordinate of bottom center in a CAM box is [0.5, 1.0, 0.5],
and the yaw is around the y axis, thus the rotation axis=1.
Attributes:
tensor (torch.Tensor): float matrix of N x box_dim.
box_dim (int): integer indicates the dimension of a box
Each row is (x, y, z, x_size, y_size, z_size, yaw, ...).
"""
@property
def gravity_center(self):
"""Calculate the gravity center of all the boxes.
Returns:
torch.Tensor: a tensor with center of each box.
"""
bottom_center = self.bottom_center
gravity_center = torch.zeros_like(bottom_center)
gravity_center[:, [0, 2]] = bottom_center[:, [0, 2]]
gravity_center[:, 1] = bottom_center[:, 1] - self.tensor[:, 5] * 0.5
return gravity_center
@property
def corners(self):
"""Calculate the coordinates of corners of all the boxes.
Convert the boxes to in clockwise order, in the form of
(x0y0z0, x0y0z1, x0y1z1, x0y1z0, x1y0z0, x1y0z1, x1y1z0, x1y1z1)
.. code-block:: none
front z
/
/
(x0, y0, z1) + ----------- + (x1, y0, z1)
/| / |
/ | / |
(x0, y0, z0) + ----------- + + (x1, y1, z0)
| / . | /
| / oriign | /
(x0, y1, z0) + ----------- + -------> x right
| (x1, y1, z0)
|
v
down y
Returns:
torch.Tensor: corners of each box with size (N, 8, 3)
"""
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, 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])
# rotate around y axis
corners = rotation_3d_in_axis(corners, self.tensor[:, 6], axis=1)
corners += self.tensor[:, :3].view(-1, 1, 3)
return corners
@property
def nearset_bev(self):
"""Calculate the 2D bounding boxes in BEV without rotation
Returns:
torch.Tensor: a tensor of 2D BEV box of each box.
"""
# Obtain BEV boxes with rotation in XZWHR format
bev_rotated_boxes = self.tensor[:, [0, 2, 3, 5, 6]]
# convert the rotation to a valid range
rotations = bev_rotated_boxes[:, -1]
normed_rotations = torch.abs(limit_period(rotations, 0.5, np.pi))
# find the center of boxes
conditions = (normed_rotations > np.pi / 4)[..., None]
bboxes_xywh = torch.where(conditions, bev_rotated_boxes[:,
[0, 1, 3, 2]],
bev_rotated_boxes[:, :4])
centers = bboxes_xywh[:, :2]
dims = bboxes_xywh[:, 2:]
bev_boxes = torch.cat([centers - dims / 2, centers + dims / 2], dim=-1)
return bev_boxes
def rotate(self, angle):
"""Calculate whether the points is in any of the boxes
Args:
angles (float | torch.Tensor): rotation angle
Returns:
None if `return_rot_mat=False`,
torch.Tensor if `return_rot_mat=True`
"""
if not isinstance(angle, torch.Tensor):
angle = self.tensor.new_tensor(angle)
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[:, :3] = self.tensor[:, :3] @ rot_mat_T
self.tensor[:, 6] += angle
def flip(self):
"""Flip the boxes in horizontal direction
In CAM coordinates, it flips the x axis.
"""
self.tensor[:, 0::7] = -self.tensor[:, 0::7]
self.tensor[:, 6] = -self.tensor[:, 6] + np.pi
def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range
Args:
box_range (list | torch.Tensor): the range of box
(x_min, z_min, x_max, z_max)
Note:
In the original implementation of SECOND, checking whether
a box in the range checks whether the points are in a convex
polygon, we try to reduce the burdun for simpler cases.
TODO: check whether this will effect the performance
Returns:
a binary vector, indicating whether each box is inside
the reference range.
"""
in_range_flags = ((self.tensor[:, 0] > box_range[0])
& (self.tensor[:, 2] > box_range[1])
& (self.tensor[:, 0] < box_range[2])
& (self.tensor[:, 2] < box_range[3]))
return in_range_flags
...@@ -8,13 +8,15 @@ from .utils import limit_period, rotation_3d_in_axis ...@@ -8,13 +8,15 @@ from .utils import limit_period, rotation_3d_in_axis
class LiDARInstance3DBoxes(BaseInstance3DBoxes): class LiDARInstance3DBoxes(BaseInstance3DBoxes):
"""3D boxes of instances in LIDAR coordinates """3D boxes of instances in LIDAR coordinates
This structure stores a list of boxes as a NxM torch.Tensor, Coordinates in LiDAR:
where M >= 7. .. code-block:: none
It supports some common methods about boxes up z x front
(`area`, `clip`, `nonempty`, etc), ^ ^
and also behaves like a Tensor | /
(support indexing, `to(device)`, `.device`, and iteration over all boxes) | /
By default the (x, y, z) is the bottom center of a box 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.
Attributes: Attributes:
tensor (torch.Tensor): float matrix of N x box_dim. tensor (torch.Tensor): float matrix of N x box_dim.
...@@ -39,18 +41,22 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -39,18 +41,22 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
def corners(self): def corners(self):
"""Calculate the coordinates of corners of all the boxes. """Calculate the coordinates of corners of all the boxes.
Convert the boxes to the form of Convert the boxes to corners in clockwise order, in form of
(x0y0z0, x0y0z1, x0y1z1, x0y1z0, x1y0z0, x1y0z1, x1y1z0, x1y1z1) (x0y0z0, x0y0z1, x0y1z1, x0y1z0, x1y0z0, x1y0z1, x1y1z0, x1y1z1)
.. code-block:: none .. code-block:: none
(x0, y0, z1) + ----------- + (x1, y1, z1) up z
/| / | front x ^
/ | / | / |
(x0, y0, z1) + ----------- + + (x1, y1, z0) / |
| / . | / (x1, y0, z1) + ----------- + (x1, y1, z1)
| / oriign | / /| / |
(x0, y0, z0) + ----------- + (x1, y0, z0) / | / |
(x0, y0, z1) + ----------- + + (x1, y1, z0)
| / . | /
| / oriign | /
left y<-------- + ----------- + (x0, y1, z0)
(x0, y0, z0)
Returns: Returns:
torch.Tensor: corners of each box with size (N, 8, 3) torch.Tensor: corners of each box with size (N, 8, 3)
""" """
...@@ -60,24 +66,15 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -60,24 +66,15 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
device=dims.device, dtype=dims.dtype) device=dims.device, dtype=dims.dtype)
corners_norm = corners_norm[[0, 1, 3, 2, 4, 5, 7, 6]] 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_norm = corners_norm - dims.new_tensor([0.5, 0.5, 0])
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
corners = rotation_3d_in_axis(corners, self.tensor[:, 6], axis=2) corners = rotation_3d_in_axis(corners, self.tensor[:, 6], axis=2)
corners += self.tensor[:, :3].view(-1, 1, 3) corners += self.tensor[:, :3].view(-1, 1, 3)
return corners return corners
@property
def dims(self):
"""Calculate the length in each dimension of all the boxes.
Convert the boxes to the form of (x_size, y_size, z_size)
Returns:
torch.Tensor: corners of each box with size (N, 8, 3)
"""
return self.tensor[:, 3:6]
@property @property
def nearset_bev(self): def nearset_bev(self):
"""Calculate the 2D bounding boxes in BEV without rotation """Calculate the 2D bounding boxes in BEV without rotation
...@@ -123,44 +120,12 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -123,44 +120,12 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
self.tensor[:, 6] += angle self.tensor[:, 6] += angle
def flip(self): def flip(self):
self.tensor[:, 1::7] = -self.tensor[:, 1::7] """Flip the boxes in horizontal direction
self.tensor[:, 6] = -self.tensor[:, 6] + np.pi
def translate(self, trans_vector):
"""Calculate whether the points is in any of the boxes
Args:
trans_vector (torch.Tensor): translation vector of size 1x3
In LIDAR coordinates, it flips the y axis.
""" """
if not isinstance(trans_vector, torch.Tensor): self.tensor[:, 1::7] = -self.tensor[:, 1::7]
trans_vector = self.tensor.new_tensor(trans_vector) self.tensor[:, 6] = -self.tensor[:, 6] + np.pi
self.tensor[:, :3] += trans_vector
def in_range_3d(self, box_range):
"""Check whether the boxes are in the given range
Args:
box_range (list | torch.Tensor): the range of box
(x_min, y_min, z_min, x_max, y_max, z_max)
Note:
In the original implementation of SECOND, checking whether
a box in the range checks whether the points are in a convex
polygon, we try to reduce the burdun for simpler cases.
TODO: check whether this will effect the performance
Returns:
a binary vector, indicating whether each box is inside
the reference range.
"""
in_range_flags = ((self.tensor[:, 0] > box_range[0])
& (self.tensor[:, 1] > box_range[1])
& (self.tensor[:, 2] > box_range[2])
& (self.tensor[:, 0] < box_range[3])
& (self.tensor[:, 1] < box_range[4])
& (self.tensor[:, 2] < box_range[5]))
return in_range_flags
def in_range_bev(self, box_range): def in_range_bev(self, box_range):
"""Check whether the boxes are in the given range """Check whether the boxes are in the given range
...@@ -184,22 +149,3 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes): ...@@ -184,22 +149,3 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
& (self.tensor[:, 0] < box_range[2]) & (self.tensor[:, 0] < box_range[2])
& (self.tensor[:, 1] < box_range[3])) & (self.tensor[:, 1] < box_range[3]))
return in_range_flags return in_range_flags
def scale(self, scale_factor):
"""Scale the box with horizontal and vertical scaling factors
Args:
scale_factors (float):
scale factors to scale the boxes.
"""
self.tensor[:, :6] *= scale_factor
self.tensor[:, 7:] *= scale_factor
def limit_yaw(self, offset=0.5, period=np.pi):
"""Limit the yaw to a given period and offset
Args:
offset (float): the offset of the yaw
period (float): the expected period
"""
self.tensor[:, 6] = limit_period(self.tensor[:, 6], offset, period)
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