|
|
from typing import Dict, List, Optional, Tuple, Union |
|
|
|
|
|
import cv2 |
|
|
import utils.transform as box_transform |
|
|
import utils.calibration as calib_utils |
|
|
import utils.bev as bev_vis |
|
|
import utils.canvas_3d as vis_3d |
|
|
import utils.color as color_vis |
|
|
import matplotlib.pyplot as plt |
|
|
plt.switch_backend('agg') |
|
|
import numpy as np |
|
|
import scipy |
|
|
from PIL import Image |
|
|
from pathlib import Path |
|
|
import os |
|
|
|
|
|
|
|
|
def get_color_object(obj_id: int, color_list: List) -> np.array: |
|
|
"""Get color for each object for vis, w.r.t. the ID""" |
|
|
|
|
|
color_float: Tuple[float, float, float] = color_list[obj_id % len(color_list)] |
|
|
color = np.array( |
|
|
[ |
|
|
int(color_float[0] * 255), |
|
|
int(color_float[1] * 255), |
|
|
int(color_float[2] * 255), |
|
|
] |
|
|
) |
|
|
|
|
|
return color |
|
|
|
|
|
|
|
|
def vis_box_on_lidar( |
|
|
lidar: Union[str, np.ndarray], |
|
|
save_path: str, |
|
|
bbox_det_in_ego_list: Optional[List[np.ndarray]] = None, |
|
|
bbox_gt_in_ego_list: Optional[List[np.ndarray]] = None, |
|
|
id_list: Optional[List[int]] = None, |
|
|
state_list: Optional[List[np.ndarray]] = None, |
|
|
fut_traj: Optional[List[np.ndarray]] = None, |
|
|
fut_traj_mask: Optional[List[np.ndarray]] = None, |
|
|
pre_traj: Optional[List[np.ndarray]] = None, |
|
|
pre_traj_mask: Optional[List[np.ndarray]] = None, |
|
|
ego2lidar: Optional[np.ndarray] = None, |
|
|
bev: bool = True, |
|
|
box_order_nuscenes: bool = False, |
|
|
left_hand: bool = True, |
|
|
vis_text: bool = True, |
|
|
command: Optional[int] = 2, |
|
|
command_dict: Optional[Dict[int, str]] = {0: 'TURN RIGHT', 1: 'TURN LEFT', 2: 'KEEP FORWARD'}, |
|
|
bev_map: Optional[np.ndarray] = None, |
|
|
) -> None: |
|
|
"""Visualize detection/GT boxes on the LiDAR point cloud |
|
|
|
|
|
Args: |
|
|
lidar: str -> path to the data, np array -> N x 3 data already loaded |
|
|
state_list: a list of array including velocity/acceleration/angular_velocity |
|
|
fut_traj: N x 12 x 4 or N, 12 x 4 |
|
|
past_traj: N x 8 x 2 or N, 8 x 2 |
|
|
|
|
|
Note that carla lidar coordinate is |
|
|
+x -> left |
|
|
+y -> front |
|
|
+z -> up |
|
|
""" |
|
|
|
|
|
|
|
|
if isinstance(lidar, str): |
|
|
|
|
|
|
|
|
|
|
|
lidar = np.fromfile(lidar, dtype=np.float32) |
|
|
lidar = lidar.reshape(-1, 4) |
|
|
lidar = lidar[:, :3] |
|
|
else: |
|
|
assert isinstance(lidar, np.ndarray), "the detection is either str or dict" |
|
|
|
|
|
|
|
|
|
|
|
if id_list is not None and ( |
|
|
bbox_det_in_ego_list is None or bbox_gt_in_ego_list is None |
|
|
): |
|
|
|
|
|
color_list: List[Tuple[float, float, float]] = color_vis.random_colors() |
|
|
|
|
|
if bbox_gt_in_ego_list is None: |
|
|
num_obj = len(bbox_det_in_ego_list) |
|
|
else: |
|
|
num_obj = len(bbox_gt_in_ego_list) |
|
|
|
|
|
colors = np.zeros((num_obj, 3), dtype=np.uint8) |
|
|
assert len(id_list) == num_obj, "number of objects is wrong" |
|
|
|
|
|
for obj_index, obj_id in enumerate(id_list): |
|
|
color: np.ndarray = get_color_object(obj_id, color_list) |
|
|
colors[obj_index, :] = color |
|
|
else: |
|
|
colors = None |
|
|
|
|
|
|
|
|
|
|
|
text_corner = 1 |
|
|
if id_list is not None and vis_text: |
|
|
texts = [str(obj_id) for obj_id in id_list] |
|
|
|
|
|
|
|
|
if state_list is not None: |
|
|
for index in range(len(state_list)): |
|
|
states: Union[np.ndarray, List] = state_list[index] |
|
|
state_str: str = " ".join(["%.2f" % state for state in states]) |
|
|
texts[index] = texts[index] + " " + state_str |
|
|
else: |
|
|
texts = None |
|
|
|
|
|
|
|
|
if bev: |
|
|
canvas = bev_vis.Canvas_BEV() |
|
|
canvas_xy, valid_mask = canvas.get_canvas_coords(lidar) |
|
|
canvas.draw_canvas_points(canvas_xy[valid_mask]) |
|
|
else: |
|
|
canvas = vis_3d.Canvas_3D() |
|
|
canvas_xyz, valid_mask = canvas.get_canvas_coords(lidar) |
|
|
color_based_on_intensity = ( |
|
|
(lidar[valid_mask, -1] * 255).astype("uint8").reshape((-1, 1)) |
|
|
) |
|
|
color_based_on_intensity = np.repeat( |
|
|
color_based_on_intensity, 3, axis=1 |
|
|
) |
|
|
canvas.draw_canvas_points( |
|
|
canvas_xyz[valid_mask], colors=color_based_on_intensity |
|
|
) |
|
|
|
|
|
|
|
|
if (bbox_det_in_ego_list is not None) or (bbox_gt_in_ego_list is not None): |
|
|
if bbox_det_in_ego_list is not None: |
|
|
num_obj = len(bbox_det_in_ego_list) |
|
|
else: |
|
|
num_obj = len(bbox_gt_in_ego_list) |
|
|
height_all_lidar = np.zeros((num_obj), dtype='float32') |
|
|
|
|
|
|
|
|
if bbox_det_in_ego_list is not None: |
|
|
bbox_det_in_lidar_list: List[np.ndarray] = list() |
|
|
count = 0 |
|
|
box_in_ego: np.ndarray |
|
|
for box_in_ego in bbox_det_in_ego_list: |
|
|
|
|
|
|
|
|
box_in_lidar: np.ndarray = box_transform.box_in_ego_to_lidar( |
|
|
box_in_ego, |
|
|
ego2lidar=ego2lidar, |
|
|
) |
|
|
height_all_lidar[count] = np.average(box_in_lidar, axis=0)[2] |
|
|
|
|
|
if bev: |
|
|
if box_order_nuscenes: |
|
|
box_in_lidar = box_in_lidar[ |
|
|
[1, 2, 6, 5], :2 |
|
|
] |
|
|
else: |
|
|
box_in_lidar = box_in_lidar[:4, :2] |
|
|
else: |
|
|
box_in_lidar = box_in_lidar[:, :3] |
|
|
bbox_det_in_lidar_list.append(np.array(box_in_lidar)) |
|
|
count += 1 |
|
|
|
|
|
if len(bbox_det_in_lidar_list) > 0: |
|
|
bbox_det_in_lidar = np.stack(bbox_det_in_lidar_list, axis=0) |
|
|
if colors is None: |
|
|
colors_det = np.full( |
|
|
(len(bbox_det_in_lidar), 3), fill_value=0, dtype=np.uint8 |
|
|
) |
|
|
colors_det[:, :2] = 255 |
|
|
else: |
|
|
colors_det = colors |
|
|
canvas.draw_boxes( |
|
|
corners=bbox_det_in_lidar, |
|
|
colors=colors_det, |
|
|
texts=texts, |
|
|
text_corner=text_corner, |
|
|
) |
|
|
|
|
|
|
|
|
if bbox_gt_in_ego_list is not None: |
|
|
bbox_gt_in_lidar_list: List[np.ndarray] = list() |
|
|
box_in_ego: np.ndarray |
|
|
count = 0 |
|
|
for box_in_ego in bbox_gt_in_ego_list: |
|
|
|
|
|
|
|
|
box_in_lidar: np.ndarray = box_transform.box_in_ego_to_lidar( |
|
|
box_in_ego, |
|
|
ego2lidar=ego2lidar, |
|
|
) |
|
|
height_all_lidar[count] = np.average(box_in_lidar, axis=0)[2] |
|
|
|
|
|
|
|
|
if bev: |
|
|
if box_order_nuscenes: |
|
|
box_in_lidar = box_in_lidar[ |
|
|
[1, 2, 6, 5], :2 |
|
|
] |
|
|
else: |
|
|
box_in_lidar = box_in_lidar[:4, :2] |
|
|
|
|
|
|
|
|
else: |
|
|
box_in_lidar = box_in_lidar[:, :3] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bbox_gt_in_lidar_list.append(np.array(box_in_lidar)) |
|
|
count += 1 |
|
|
|
|
|
if len(bbox_gt_in_lidar_list) > 0: |
|
|
bbox_gt_in_lidar = np.stack(bbox_gt_in_lidar_list, axis=0) |
|
|
if colors is None: |
|
|
colors_gt = np.full( |
|
|
(len(bbox_gt_in_lidar), 3), fill_value=0, dtype=np.uint8 |
|
|
) |
|
|
colors_gt[:, 2] = 255 |
|
|
else: |
|
|
colors_gt = colors |
|
|
|
|
|
canvas.draw_boxes( |
|
|
corners=bbox_gt_in_lidar, |
|
|
colors=colors_gt, |
|
|
texts=texts, |
|
|
text_corner=text_corner, |
|
|
) |
|
|
|
|
|
|
|
|
if fut_traj is not None: |
|
|
fut_traj_obj: np.ndarray |
|
|
obj_index = 0 |
|
|
for fut_traj_obj in fut_traj: |
|
|
|
|
|
|
|
|
if fut_traj_obj.shape[1] == 2 and not bev: |
|
|
fut_traj_obj_addh = np.concatenate([fut_traj_obj, np.zeros((fut_traj_obj.shape[0], 1))], axis=1) |
|
|
fut_traj_obj_addh[:, -1] = height_all_lidar[obj_index] |
|
|
fut_traj_obj = fut_traj_obj_addh |
|
|
|
|
|
|
|
|
canvas_xy, valid_mask = canvas.get_canvas_coords( |
|
|
fut_traj_obj |
|
|
) |
|
|
valid_mask = valid_mask.astype("bool") |
|
|
|
|
|
|
|
|
fut_traj_mask_obj: np.ndarray = fut_traj_mask[obj_index, :, 0].astype( |
|
|
"bool" |
|
|
) |
|
|
mask_combined = fut_traj_mask_obj & valid_mask |
|
|
|
|
|
|
|
|
canvas.draw_canvas_points( |
|
|
canvas_xy[mask_combined], colors=tuple(colors[obj_index]), radius=2 |
|
|
) |
|
|
obj_index += 1 |
|
|
|
|
|
if pre_traj is not None: |
|
|
pre_traj_obj: np.ndarray |
|
|
obj_index = 0 |
|
|
for pre_traj_obj in pre_traj: |
|
|
|
|
|
|
|
|
if pre_traj_obj.shape[1] == 2 and not bev: |
|
|
pre_traj_obj_addh = np.concatenate([pre_traj_obj, np.zeros((pre_traj_obj.shape[0], 1))], axis=1) |
|
|
pre_traj_obj_addh[:, -1] = height_all_lidar[obj_index] |
|
|
pre_traj_obj = pre_traj_obj_addh |
|
|
|
|
|
canvas_xy, valid_mask = canvas.get_canvas_coords( |
|
|
pre_traj_obj |
|
|
) |
|
|
valid_mask = valid_mask.astype("bool") |
|
|
|
|
|
|
|
|
pre_traj_mask_obj: np.ndarray = pre_traj_mask[obj_index, :, 0].astype( |
|
|
"bool" |
|
|
) |
|
|
mask_combined = pre_traj_mask_obj & valid_mask |
|
|
canvas.draw_canvas_points( |
|
|
canvas_xy[mask_combined], colors=tuple(colors[obj_index]), radius=2 |
|
|
) |
|
|
obj_index += 1 |
|
|
|
|
|
|
|
|
|
|
|
if bev: |
|
|
canvas.canvas = np.flip(canvas.canvas, axis=(0)) |
|
|
|
|
|
if left_hand: |
|
|
canvas.canvas = np.flip(canvas.canvas, axis=(1)) |
|
|
|
|
|
|
|
|
|
|
|
plt.figure(figsize=(20, 20)) |
|
|
plt.axis("off") |
|
|
plt.imshow(canvas.canvas) |
|
|
plt.tight_layout() |
|
|
|
|
|
|
|
|
plt.text(30, 50, command_dict[int(command)], fontsize=45, color=(1, 1, 1)) |
|
|
|
|
|
|
|
|
save_dir = Path(save_path).resolve().parent |
|
|
if not os.path.exists(save_dir): |
|
|
os.makedirs(save_dir) |
|
|
plt.savefig(save_path, transparent=False) |
|
|
plt.close() |
|
|
|
|
|
|
|
|
if bev_map is not None and bev: |
|
|
traj_vis = cv2.imread(save_path) |
|
|
|
|
|
|
|
|
bev_map = cv2.resize(bev_map, traj_vis.shape[1::-1]) |
|
|
bev_map = (bev_map * 255).astype('uint8') |
|
|
|
|
|
|
|
|
if len(bev_map.shape) == 2: |
|
|
bev_map = np.expand_dims(bev_map, axis=-1) |
|
|
bev_map = np.repeat(bev_map, 3, axis=-1) |
|
|
|
|
|
|
|
|
if bev: |
|
|
bev_map = np.flip(bev_map, axis=0) |
|
|
if left_hand: |
|
|
bev_map = np.flip(bev_map, axis=1) |
|
|
|
|
|
|
|
|
dst = cv2.addWeighted(traj_vis, 0.7, bev_map, 0.3, 0) |
|
|
cv2.imwrite(save_path, dst) |
|
|
|
|
|
def vis_box_on_camera( |
|
|
cameras: Dict[str, np.ndarray], |
|
|
save_path: str, |
|
|
cam_param_all: Dict[str, Dict], |
|
|
bbox_det_in_ego_list: Optional[List[np.ndarray]] = None, |
|
|
bbox_gt_in_ego_list: Optional[List[np.ndarray]] = None, |
|
|
left_hand_system: bool = True, |
|
|
) -> None: |
|
|
"""Visualize detection/GT boxes on the multiple cameras |
|
|
|
|
|
Args: |
|
|
camera: keys can be ["left", "front", "right", "back"] |
|
|
values are H x W x 3 |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
cam_name: str |
|
|
cam_param: Dict[str, float] |
|
|
for cam_name, cam_param in cam_param_all.items(): |
|
|
|
|
|
|
|
|
if "intrinsics" not in cam_param: |
|
|
intrinsics: np.ndarray = calib_utils.get_camera_intrinsics(cam_param, dim=4) |
|
|
else: |
|
|
intrinsics: np.ndarray = cam_param["intrinsics"] |
|
|
|
|
|
|
|
|
if bbox_det_in_ego_list is not None: |
|
|
box_in_ego: np.ndarray |
|
|
for box_in_ego in bbox_det_in_ego_list: |
|
|
|
|
|
|
|
|
|
|
|
box_in_camera: np.ndarray = box_transform.box_in_ego_to_camera( |
|
|
box_in_ego, cam_param |
|
|
) |
|
|
|
|
|
|
|
|
box_2d_in_image: np.ndarray = box_transform.box_projection_to_image( |
|
|
box_in_camera, |
|
|
intrinsics, |
|
|
left_hand_system=left_hand_system, |
|
|
) |
|
|
|
|
|
|
|
|
if box_2d_in_image is not None: |
|
|
cameras[cam_name], _ = vis_box_on_single_image( |
|
|
cameras[cam_name], |
|
|
box_2d_in_image, |
|
|
img_size=(cam_param["height"], cam_param["width"]), |
|
|
color=(255, 255, 0), |
|
|
thickness=2, |
|
|
) |
|
|
|
|
|
|
|
|
if bbox_gt_in_ego_list is not None: |
|
|
box_in_ego: np.ndarray |
|
|
for box_in_ego in bbox_gt_in_ego_list: |
|
|
|
|
|
|
|
|
|
|
|
box_in_camera: np.ndarray = box_transform.box_in_ego_to_camera( |
|
|
box_in_ego, cam_param |
|
|
) |
|
|
|
|
|
|
|
|
box_2d_in_image: np.ndarray = box_transform.box_projection_to_image( |
|
|
box_in_camera, |
|
|
intrinsics, |
|
|
left_hand_system=left_hand_system, |
|
|
) |
|
|
|
|
|
|
|
|
if box_2d_in_image is not None: |
|
|
cameras[cam_name], _ = vis_box_on_single_image( |
|
|
cameras[cam_name], |
|
|
box_2d_in_image, |
|
|
img_size=(cam_param["height"], cam_param["width"]), |
|
|
color=(0, 0, 255), |
|
|
thickness=2, |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
image = np.concatenate(list(cameras.values()), axis=1) |
|
|
image = image[:, :, ::-1] |
|
|
image = Image.fromarray(image) |
|
|
image = image.resize( |
|
|
(cam_param["width"] * len(cameras.keys()), cam_param["height"]) |
|
|
) |
|
|
save_dir = Path(save_path).resolve().parent |
|
|
if not os.path.exists(save_dir): |
|
|
os.makedirs(save_dir) |
|
|
image.save(save_path) |
|
|
|
|
|
|
|
|
def check_outside_image(x, y, height, width): |
|
|
if x < 0 or x >= width: |
|
|
return True |
|
|
if y < 0 or y >= height: |
|
|
return True |
|
|
|
|
|
|
|
|
def vis_box_on_single_image( |
|
|
image, qs, img_size=(900, 1600), color=(255, 255, 255), thickness=4 |
|
|
): |
|
|
"""Draw 3d bounding box in image |
|
|
qs: (8,2) array of vertices for the 3d box in following order: |
|
|
1 -------- 0 |
|
|
/| /| |
|
|
2 -------- 3 . |
|
|
| | | | |
|
|
. 5 -------- 4 |
|
|
|/ |/ |
|
|
6 -------- 7 |
|
|
""" |
|
|
|
|
|
|
|
|
pts_outside = 0 |
|
|
for index in range(8): |
|
|
check = check_outside_image( |
|
|
qs[index, 0], qs[index, 1], img_size[0], img_size[1] |
|
|
) |
|
|
if check: |
|
|
pts_outside += 1 |
|
|
if pts_outside >= 6: |
|
|
return image, False |
|
|
|
|
|
|
|
|
if qs is not None: |
|
|
qs = qs.astype(np.int32) |
|
|
for k in range(0, 4): |
|
|
i, j = k, (k + 1) % 4 |
|
|
cv2.line( |
|
|
image, |
|
|
(qs[i, 0], qs[i, 1]), |
|
|
(qs[j, 0], qs[j, 1]), |
|
|
color, |
|
|
thickness, |
|
|
cv2.LINE_AA, |
|
|
) |
|
|
|
|
|
i, j = k + 4, (k + 1) % 4 + 4 |
|
|
cv2.line( |
|
|
image, |
|
|
(qs[i, 0], qs[i, 1]), |
|
|
(qs[j, 0], qs[j, 1]), |
|
|
color, |
|
|
thickness, |
|
|
cv2.LINE_AA, |
|
|
) |
|
|
|
|
|
i, j = k, k + 4 |
|
|
cv2.line( |
|
|
image, |
|
|
(qs[i, 0], qs[i, 1]), |
|
|
(qs[j, 0], qs[j, 1]), |
|
|
color, |
|
|
thickness, |
|
|
cv2.LINE_AA, |
|
|
) |
|
|
|
|
|
return image, True |
|
|
|