box_3d_mode.py 7 KB
Newer Older
dingchang's avatar
dingchang committed
1
# Copyright (c) OpenMMLab. All rights reserved.
2
3
from enum import IntEnum, unique

4
5
6
import numpy as np
import torch

zhangwenwei's avatar
zhangwenwei committed
7
8
from .base_box3d import BaseInstance3DBoxes
from .cam_box3d import CameraInstance3DBoxes
wuyuefeng's avatar
wuyuefeng committed
9
from .depth_box3d import DepthInstance3DBoxes
zhangwenwei's avatar
zhangwenwei committed
10
from .lidar_box3d import LiDARInstance3DBoxes
11
from .utils import limit_period
zhangwenwei's avatar
zhangwenwei committed
12

13
14
15

@unique
class Box3DMode(IntEnum):
zhangwenwei's avatar
zhangwenwei committed
16
17
    r"""Enum of different ways to represent a box.

zhangwenwei's avatar
zhangwenwei committed
18
    Coordinates in LiDAR:
19

zhangwenwei's avatar
zhangwenwei committed
20
    .. code-block:: none
zhangwenwei's avatar
zhangwenwei committed
21

zhangwenwei's avatar
zhangwenwei committed
22
23
                    up z
                       ^   x front
zhangwenwei's avatar
zhangwenwei committed
24
25
26
                       |  /
                       | /
        left y <------ 0
zhangwenwei's avatar
zhangwenwei committed
27

wuyuefeng's avatar
wuyuefeng committed
28
    The relative coordinate of bottom center in a LiDAR box is (0.5, 0.5, 0),
zhangwenwei's avatar
zhangwenwei committed
29
    and the yaw is around the z axis, thus the rotation axis=2.
zhangwenwei's avatar
zhangwenwei committed
30
31

    Coordinates in camera:
32

zhangwenwei's avatar
zhangwenwei committed
33
    .. code-block:: none
zhangwenwei's avatar
zhangwenwei committed
34

zhangwenwei's avatar
zhangwenwei committed
35
36
37
38
39
40
41
42
                z front
               /
              /
             0 ------> x right
             |
             |
             v
        down y
zhangwenwei's avatar
zhangwenwei committed
43

zhangwenwei's avatar
zhangwenwei committed
44
45
    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.
zhangwenwei's avatar
zhangwenwei committed
46
47

    Coordinates in Depth mode:
48

zhangwenwei's avatar
zhangwenwei committed
49
    .. code-block:: none
zhangwenwei's avatar
zhangwenwei committed
50

zhangwenwei's avatar
zhangwenwei committed
51
52
53
54
55
        up z
           ^   y front
           |  /
           | /
           0 ------> x right
zhangwenwei's avatar
zhangwenwei committed
56

wuyuefeng's avatar
wuyuefeng committed
57
    The relative coordinate of bottom center in a DEPTH box is (0.5, 0.5, 0),
zhangwenwei's avatar
zhangwenwei committed
58
    and the yaw is around the z axis, thus the rotation axis=2.
59
60
61
62
63
64
65
    """

    LIDAR = 0
    CAM = 1
    DEPTH = 2

    @staticmethod
66
    def convert(box, src, dst, rt_mat=None, with_yaw=True):
zhangwenwei's avatar
zhangwenwei committed
67
68
        """Convert boxes from `src` mode to `dst` mode.

69
        Args:
70
            box (tuple | list | np.ndarray |
71
                torch.Tensor | :obj:`BaseInstance3DBoxes`):
liyinhao's avatar
liyinhao committed
72
                Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
73
74
            src (:obj:`Box3DMode`): The src Box mode.
            dst (:obj:`Box3DMode`): The target Box mode.
75
76
77
            rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
                translation matrix between different coordinates.
                Defaults to None.
zhangwenwei's avatar
zhangwenwei committed
78
79
80
                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.
81
            with_yaw (bool, optional): If `box` is an instance of
82
83
                :obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
                Defaults to True.
84
85

        Returns:
86
87
            (tuple | list | np.ndarray | torch.Tensor |
                :obj:`BaseInstance3DBoxes`):
zhangwenwei's avatar
zhangwenwei committed
88
                The converted box of the same type.
89
        """
zhangwenwei's avatar
zhangwenwei committed
90
        if src == dst:
91
92
93
            return box

        is_numpy = isinstance(box, np.ndarray)
zhangwenwei's avatar
zhangwenwei committed
94
        is_Instance3DBoxes = isinstance(box, BaseInstance3DBoxes)
95
96
97
        single_box = isinstance(box, (list, tuple))
        if single_box:
            assert len(box) >= 7, (
98
                'Box3DMode.convert takes either a k-tuple/list or '
99
100
101
102
103
104
                '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()
zhangwenwei's avatar
zhangwenwei committed
105
106
            elif is_Instance3DBoxes:
                arr = box.tensor.clone()
107
108
109
            else:
                arr = box.clone()

110
111
112
        if is_Instance3DBoxes:
            with_yaw = box.with_yaw

zhangwenwei's avatar
zhangwenwei committed
113
114
        # convert box from `src` mode to `dst` mode.
        x_size, y_size, z_size = arr[..., 3:4], arr[..., 4:5], arr[..., 5:6]
115
116
        if with_yaw:
            yaw = arr[..., 6:7]
zhangwenwei's avatar
zhangwenwei committed
117
118
119
        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]])
120
121
122
123
            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)
zhangwenwei's avatar
zhangwenwei committed
124
125
126
        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]])
127
128
129
130
            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)
zhangwenwei's avatar
zhangwenwei committed
131
132
        elif src == Box3DMode.DEPTH and dst == Box3DMode.CAM:
            if rt_mat is None:
133
                rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
zhangwenwei's avatar
zhangwenwei committed
134
            xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
135
136
            if with_yaw:
                yaw = -yaw
zhangwenwei's avatar
zhangwenwei committed
137
138
        elif src == Box3DMode.CAM and dst == Box3DMode.DEPTH:
            if rt_mat is None:
139
                rt_mat = arr.new_tensor([[1, 0, 0], [0, 0, 1], [0, -1, 0]])
zhangwenwei's avatar
zhangwenwei committed
140
            xyz_size = torch.cat([x_size, z_size, y_size], dim=-1)
141
142
            if with_yaw:
                yaw = -yaw
wuyuefeng's avatar
wuyuefeng committed
143
144
145
        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]])
146
147
148
149
            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)
wuyuefeng's avatar
wuyuefeng committed
150
151
152
        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]])
153
154
155
156
            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)
zhangwenwei's avatar
zhangwenwei committed
157
158
159
160
161
162
163
164
165
        else:
            raise NotImplementedError(
                f'Conversion from Box3DMode {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(
166
                [arr[..., :3], arr.new_ones(arr.size(0), 1)], dim=-1)
zhangwenwei's avatar
zhangwenwei committed
167
168
            xyz = extended_xyz @ rt_mat.t()
        else:
169
            xyz = arr[..., :3] @ rt_mat.t()
zhangwenwei's avatar
zhangwenwei committed
170

171
172
173
174
175
176
        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)
zhangwenwei's avatar
zhangwenwei committed
177
178

        # convert arr to the original type
zhangwenwei's avatar
zhangwenwei committed
179
        original_type = type(box)
180
181
182
183
        if single_box:
            return original_type(arr.flatten().tolist())
        if is_numpy:
            return arr.numpy()
zhangwenwei's avatar
zhangwenwei committed
184
185
186
187
188
        elif is_Instance3DBoxes:
            if dst == Box3DMode.CAM:
                target_type = CameraInstance3DBoxes
            elif dst == Box3DMode.LIDAR:
                target_type = LiDARInstance3DBoxes
wuyuefeng's avatar
wuyuefeng committed
189
190
            elif dst == Box3DMode.DEPTH:
                target_type = DepthInstance3DBoxes
zhangwenwei's avatar
zhangwenwei committed
191
192
193
194
            else:
                raise NotImplementedError(
                    f'Conversion to {dst} through {original_type}'
                    ' is not supported yet')
195
            return target_type(arr, box_dim=arr.size(-1), with_yaw=with_yaw)
196
197
        else:
            return arr