utils.py 7.01 KB
Newer Older
dingchang's avatar
dingchang committed
1
# Copyright (c) OpenMMLab. All rights reserved.
2
3
import numpy as np
import torch
4
from logging import warning
5
6
7


def limit_period(val, offset=0.5, period=np.pi):
zhangwenwei's avatar
zhangwenwei committed
8
9
10
    """Limit the value into a period for periodic function.

    Args:
liyinhao's avatar
liyinhao committed
11
        val (torch.Tensor): The value to be converted.
wangtai's avatar
wangtai committed
12
        offset (float, optional): Offset to set the value range. \
zhangwenwei's avatar
zhangwenwei committed
13
14
15
16
            Defaults to 0.5.
        period ([type], optional): Period of the value. Defaults to np.pi.

    Returns:
wangtai's avatar
wangtai committed
17
        torch.Tensor: Value in the range of \
zhangwenwei's avatar
zhangwenwei committed
18
19
            [-offset * period, (1-offset) * period]
    """
20
21
22
23
    return val - torch.floor(val / period + offset) * period


def rotation_3d_in_axis(points, angles, axis=0):
zhangwenwei's avatar
zhangwenwei committed
24
    """Rotate points by angles according to axis.
zhangwenwei's avatar
zhangwenwei committed
25
26
27
28
29
30
31

    Args:
        points (torch.Tensor): Points of shape (N, M, 3).
        angles (torch.Tensor): Vector of angles in shape (N,)
        axis (int, optional): The axis to be rotated. Defaults to 0.

    Raises:
zhangwenwei's avatar
zhangwenwei committed
32
        ValueError: when the axis is not in range [0, 1, 2], it will \
zhangwenwei's avatar
zhangwenwei committed
33
34
35
            raise value error.

    Returns:
zhangwenwei's avatar
zhangwenwei committed
36
        torch.Tensor: Rotated points in shape (N, M, 3)
zhangwenwei's avatar
zhangwenwei committed
37
    """
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    rot_sin = torch.sin(angles)
    rot_cos = torch.cos(angles)
    ones = torch.ones_like(rot_cos)
    zeros = torch.zeros_like(rot_cos)
    if axis == 1:
        rot_mat_T = torch.stack([
            torch.stack([rot_cos, zeros, -rot_sin]),
            torch.stack([zeros, ones, zeros]),
            torch.stack([rot_sin, zeros, rot_cos])
        ])
    elif axis == 2 or axis == -1:
        rot_mat_T = torch.stack([
            torch.stack([rot_cos, -rot_sin, zeros]),
            torch.stack([rot_sin, rot_cos, zeros]),
            torch.stack([zeros, zeros, ones])
        ])
    elif axis == 0:
        rot_mat_T = torch.stack([
            torch.stack([zeros, rot_cos, -rot_sin]),
            torch.stack([zeros, rot_sin, rot_cos]),
            torch.stack([ones, zeros, zeros])
        ])
    else:
zhangwenwei's avatar
zhangwenwei committed
61
        raise ValueError(f'axis should in range [0, 1, 2], got {axis}')
62
63

    return torch.einsum('aij,jka->aik', (points, rot_mat_T))
64
65
66


def xywhr2xyxyr(boxes_xywhr):
zhangwenwei's avatar
zhangwenwei committed
67
68
69
70
71
72
73
74
    """Convert a rotated boxes in XYWHR format to XYXYR format.

    Args:
        boxes_xywhr (torch.Tensor): Rotated boxes in XYWHR format.

    Returns:
        torch.Tensor: Converted boxes in XYXYR format.
    """
75
76
77
78
79
80
81
82
83
84
    boxes = torch.zeros_like(boxes_xywhr)
    half_w = boxes_xywhr[:, 2] / 2
    half_h = boxes_xywhr[:, 3] / 2

    boxes[:, 0] = boxes_xywhr[:, 0] - half_w
    boxes[:, 1] = boxes_xywhr[:, 1] - half_h
    boxes[:, 2] = boxes_xywhr[:, 0] + half_w
    boxes[:, 3] = boxes_xywhr[:, 1] + half_h
    boxes[:, 4] = boxes_xywhr[:, 4]
    return boxes
zhangwenwei's avatar
zhangwenwei committed
85
86


wuyuefeng's avatar
Demo  
wuyuefeng committed
87
88
89
90
def get_box_type(box_type):
    """Get the type and mode of box structure.

    Args:
91
        box_type (str): The type of box structure.
wuyuefeng's avatar
Demo  
wuyuefeng committed
92
93
94
            The valid value are "LiDAR", "Camera", or "Depth".

    Returns:
wangtai's avatar
wangtai committed
95
        tuple: Box type and box mode.
wuyuefeng's avatar
Demo  
wuyuefeng committed
96
    """
zhangwenwei's avatar
zhangwenwei committed
97
98
    from .box_3d_mode import (Box3DMode, CameraInstance3DBoxes,
                              DepthInstance3DBoxes, LiDARInstance3DBoxes)
wuyuefeng's avatar
Demo  
wuyuefeng committed
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
    box_type_lower = box_type.lower()
    if box_type_lower == 'lidar':
        box_type_3d = LiDARInstance3DBoxes
        box_mode_3d = Box3DMode.LIDAR
    elif box_type_lower == 'camera':
        box_type_3d = CameraInstance3DBoxes
        box_mode_3d = Box3DMode.CAM
    elif box_type_lower == 'depth':
        box_type_3d = DepthInstance3DBoxes
        box_mode_3d = Box3DMode.DEPTH
    else:
        raise ValueError('Only "box_type" of "camera", "lidar", "depth"'
                         f' are supported, got {box_type}')

    return box_type_3d, box_mode_3d


116
def points_cam2img(points_3d, proj_mat, with_depth=False):
zhangwenwei's avatar
zhangwenwei committed
117
    """Project points from camera coordicates to image coordinates.
zhangwenwei's avatar
zhangwenwei committed
118
119

    Args:
120
        points_3d (torch.Tensor): Points in shape (N, 3).
zhangwenwei's avatar
zhangwenwei committed
121
        proj_mat (torch.Tensor): Transformation matrix between coordinates.
122
123
        with_depth (bool, optional): Whether to keep depth in the output.
            Defaults to False.
zhangwenwei's avatar
zhangwenwei committed
124
125
126
127

    Returns:
        torch.Tensor: Points in image coordinates with shape [N, 2].
    """
zhangwenwei's avatar
zhangwenwei committed
128
    points_num = list(points_3d.shape)[:-1]
129

zhangwenwei's avatar
zhangwenwei committed
130
    points_shape = np.concatenate([points_num, [1]], axis=0).tolist()
131
132
    assert len(proj_mat.shape) == 2, 'The dimension of the projection'\
        f' matrix should be 2 instead of {len(proj_mat.shape)}.'
133
134
    d1, d2 = proj_mat.shape[:2]
    assert (d1 == 3 and d2 == 3) or (d1 == 3 and d2 == 4) or (
135
        d1 == 4 and d2 == 4), 'The shape of the projection matrix'\
136
137
138
139
140
141
142
        f' ({d1}*{d2}) is not supported.'
    if d1 == 3:
        proj_mat_expanded = torch.eye(
            4, device=proj_mat.device, dtype=proj_mat.dtype)
        proj_mat_expanded[:d1, :d2] = proj_mat
        proj_mat = proj_mat_expanded

zhangwenwei's avatar
zhangwenwei committed
143
144
145
146
147
    # previous implementation use new_zeros, new_one yeilds better results
    points_4 = torch.cat(
        [points_3d, points_3d.new_ones(*points_shape)], dim=-1)
    point_2d = torch.matmul(points_4, proj_mat.t())
    point_2d_res = point_2d[..., :2] / point_2d[..., 2:3]
148
149
150

    if with_depth:
        return torch.cat([point_2d_res, point_2d[..., 2:3]], dim=-1)
zhangwenwei's avatar
zhangwenwei committed
151
    return point_2d_res
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171


def mono_cam_box2vis(cam_box):
    """This is a post-processing function on the bboxes from Mono-3D task. If
    we want to perform projection visualization, we need to:

        1. rotate the box along x-axis for np.pi / 2 (roll)
        2. change orientation from local yaw to global yaw
        3. convert yaw by (np.pi / 2 - yaw)

    After applying this function, we can project and draw it on 2D images.

    Args:
        cam_box (:obj:`CameraInstance3DBoxes`): 3D bbox in camera coordinate \
            system before conversion. Could be gt bbox loaded from dataset or \
                network prediction output.

    Returns:
        :obj:`CameraInstance3DBoxes`: Box after conversion.
    """
172
173
174
    warning.warn('DeprecationWarning: The hack of yaw and dimension in the '
                 'monocular 3D detection on nuScenes has been removed. The '
                 'function mono_cam_box2vis will be deprecated.')
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    from . import CameraInstance3DBoxes
    assert isinstance(cam_box, CameraInstance3DBoxes), \
        'input bbox should be CameraInstance3DBoxes!'

    loc = cam_box.gravity_center
    dim = cam_box.dims
    yaw = cam_box.yaw
    feats = cam_box.tensor[:, 7:]
    # rotate along x-axis for np.pi / 2
    # see also here: https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/datasets/nuscenes_mono_dataset.py#L557  # noqa
    dim[:, [1, 2]] = dim[:, [2, 1]]
    # change local yaw to global yaw for visualization
    # refer to https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/datasets/nuscenes_mono_dataset.py#L164-L166  # noqa
    yaw += torch.atan2(loc[:, 0], loc[:, 2])
Ziyi Wu's avatar
Ziyi Wu committed
189
190
191
192
    # convert yaw by (-yaw - np.pi / 2)
    # this is because mono 3D box class such as `NuScenesBox` has different
    # definition of rotation with our `CameraInstance3DBoxes`
    yaw = -yaw - np.pi / 2
193
194
195
196
197
    cam_box = torch.cat([loc, dim, yaw[:, None], feats], dim=1)
    cam_box = CameraInstance3DBoxes(
        cam_box, box_dim=cam_box.shape[-1], origin=(0.5, 0.5, 0.5))

    return cam_box