|
|
import copy |
|
|
from typing import Dict, Optional |
|
|
|
|
|
import numpy as np |
|
|
|
|
|
import utils.calibration as calib_utils |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_bb_points(size: np.ndarray) -> np.ndarray: |
|
|
"""Create bounding box's 8 corners in the object coordinate |
|
|
|
|
|
Note: we assume that the location of the object is at the center |
|
|
of the object, not the bottom center |
|
|
|
|
|
Args: |
|
|
size: (3, ), length, width and height, obtained from |
|
|
carla_utils.vec2np(obj.bounding_box.extent) |
|
|
|
|
|
Returns: |
|
|
corners: 8 x 4, in the object coordinate |
|
|
|
|
|
Carla use left-handed coordinate, world coordinate: |
|
|
+x -> front |
|
|
+y -> right |
|
|
+z -> up |
|
|
4 -------- 5 |
|
|
/| /| |
|
|
7 -------- 6 . |
|
|
| | | | |
|
|
. 0 -------- 1 |
|
|
|/ |/ |
|
|
3 -------- 2 |
|
|
e.g., if take points of 0, 1, 2, 3, it will return the box in bottom |
|
|
if take points of 0, 3, 4, 7, it will return the box on the left |
|
|
""" |
|
|
|
|
|
|
|
|
l, w, h = size |
|
|
|
|
|
|
|
|
corners = np.zeros((8, 4)) |
|
|
corners[0, :] = np.array([l / 2, -w / 2, -h / 2, 1]) |
|
|
corners[1, :] = np.array([l / 2, w / 2, -h / 2, 1]) |
|
|
corners[2, :] = np.array([-l / 2, w / 2, -h / 2, 1]) |
|
|
corners[3, :] = np.array([-l / 2, -w / 2, -h / 2, 1]) |
|
|
corners[4, :] = np.array([l / 2, -w / 2, h / 2, 1]) |
|
|
corners[5, :] = np.array([l / 2, w / 2, h / 2, 1]) |
|
|
corners[6, :] = np.array([-l / 2, w / 2, h / 2, 1]) |
|
|
corners[7, :] = np.array([-l / 2, -w / 2, h / 2, 1]) |
|
|
|
|
|
return corners |
|
|
|
|
|
|
|
|
def box_in_obj_to_world(box_in_object: np.ndarray, transform: np.ndarray) -> np.ndarray: |
|
|
"""Convert object boxes in its own coordinate to world coordinate |
|
|
|
|
|
Args: |
|
|
corners: 8 x 4 in the object coordinate |
|
|
transform: 4 x 4, object to world transformation |
|
|
""" |
|
|
|
|
|
box_in_world = np.dot(transform, np.transpose(box_in_object)).transpose() |
|
|
return box_in_world |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_box_in_world( |
|
|
location: np.ndarray, rotation: np.ndarray, size: np.ndarray |
|
|
) -> np.ndarray: |
|
|
"""Return 3D bounding box in world coordinate""" |
|
|
|
|
|
|
|
|
box_in_object: np.ndarray = create_bb_points(size) |
|
|
|
|
|
|
|
|
transform: np.ndarray = calib_utils.create_transform(location, rotation) |
|
|
box_in_world: np.ndarray = box_in_obj_to_world(box_in_object, transform) |
|
|
|
|
|
return box_in_world |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def box_in_world_to_ego( |
|
|
box_in_world: np.ndarray, ego_transform: np.ndarray |
|
|
) -> np.ndarray: |
|
|
"""Convert bbox in the world coordinate to the ego coordinate |
|
|
|
|
|
Args: |
|
|
box_in_world: (8, 4), 8 corners in the world coordinate |
|
|
ego_transform: (4, 4), transformation from ego to the world coordinate |
|
|
|
|
|
Returns: |
|
|
box_in_ego: (8, 4), 8 corners in the ego coordinate |
|
|
""" |
|
|
|
|
|
|
|
|
box_in_world = copy.copy(box_in_world) |
|
|
|
|
|
|
|
|
world2ego_transform = np.linalg.inv(ego_transform) |
|
|
box_in_ego = np.dot( |
|
|
world2ego_transform, np.transpose(box_in_world) |
|
|
).transpose() |
|
|
|
|
|
return box_in_ego |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def box_in_ego_to_lidar(box_in_ego: np.ndarray, ego2lidar: Optional[np.ndarray] = None) -> np.ndarray: |
|
|
"""Convert bbox in the ego coordinate to the lidar coordinate |
|
|
|
|
|
Arguments |
|
|
box_in_ego: 8 x 4 |
|
|
ego2lidar: 4 x 4 |
|
|
""" |
|
|
|
|
|
|
|
|
box_in_ego = copy.copy(box_in_ego) |
|
|
|
|
|
|
|
|
if ego2lidar is None: |
|
|
ego2lidar = np.linalg.inv(calib_utils.lidar_to_ego_transform()) |
|
|
|
|
|
|
|
|
box_in_lidar = np.dot(ego2lidar, np.transpose(box_in_ego)).transpose() |
|
|
|
|
|
return box_in_lidar |
|
|
|
|
|
|
|
|
def box_in_lidar_to_ego(box_in_lidar: np.ndarray, lidar2ego: Optional[np.ndarray] = None) -> np.ndarray: |
|
|
"""Convert bbox in the lidar coordinate to the ego coordinate |
|
|
|
|
|
Arguments |
|
|
box_in_lidar: 8 x 4 |
|
|
lidar2ego: 4 x 4 |
|
|
""" |
|
|
|
|
|
|
|
|
box_in_lidar = copy.copy(box_in_lidar) |
|
|
|
|
|
|
|
|
if lidar2ego is None: |
|
|
lidar2ego = calib_utils.lidar_to_ego_transform() |
|
|
|
|
|
assert False, 'No lidar2ego is provided!!!' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
box_in_ego = np.dot(lidar2ego, np.transpose(box_in_lidar)).transpose() |
|
|
|
|
|
return box_in_ego |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def box_in_ego_to_camera(box_in_ego: np.ndarray, cam_param: Dict) -> np.ndarray: |
|
|
"""Convert bbox in the ego coordinate to the camera coordinate""" |
|
|
|
|
|
|
|
|
box_in_ego = copy.copy(box_in_ego) |
|
|
|
|
|
|
|
|
if 'ego2cam' in cam_param: |
|
|
ego2cam: np.narray = cam_param['ego2cam'] |
|
|
else: |
|
|
ego2cam = np.linalg.inv( |
|
|
calib_utils.camera_to_ego_transform(cam_param) |
|
|
) |
|
|
|
|
|
|
|
|
box_in_camera = np.dot(ego2cam, np.transpose(box_in_ego)).transpose() |
|
|
|
|
|
return box_in_camera |
|
|
|
|
|
|
|
|
def box_projection_to_image( |
|
|
box_in_camera: np.ndarray, |
|
|
intrinsics: np.ndarray, |
|
|
front_dist_threshold: float = 1.5, |
|
|
left_hand_system: bool = True, |
|
|
) -> np.ndarray: |
|
|
"""Project 3D box in camera coordinate to 2D box in image coordinate |
|
|
|
|
|
Args: |
|
|
box_in_camera: (8, 4), 3D box in the camera coordinate |
|
|
intrinsics: (4, 4) camera intrinsic matrix |
|
|
front_dist_threshold: used to filter out bad project due to too close |
|
|
|
|
|
ReturnsL |
|
|
box_2d_in_image: (8, 2), 2D box in the image coordinate |
|
|
""" |
|
|
|
|
|
box_in_camera = copy.copy(box_in_camera).transpose() |
|
|
|
|
|
|
|
|
|
|
|
if left_hand_system: |
|
|
box_in_camera = np.vstack( |
|
|
( |
|
|
box_in_camera[1, :], |
|
|
-box_in_camera[2, :], |
|
|
box_in_camera[0, :], |
|
|
box_in_camera[3, :], |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
box_2d_in_image = (intrinsics @ box_in_camera).T |
|
|
box_2d_in_image[:, 0] = box_2d_in_image[:, 0] / box_2d_in_image[:, 2] |
|
|
box_2d_in_image[:, 1] = box_2d_in_image[:, 1] / box_2d_in_image[:, 2] |
|
|
|
|
|
|
|
|
if np.any(box_2d_in_image[:, 2] > front_dist_threshold): |
|
|
box_2d_in_image = np.array(box_2d_in_image)[:, :2] |
|
|
return box_2d_in_image |
|
|
else: |
|
|
return None |
|
|
|