Spaces:
Sleeping
Sleeping
| # Copyright (c) Meta Platforms, Inc. and affiliates | |
| import cv2 | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import os | |
| import math | |
| import torch | |
| from copy import deepcopy | |
| from pytorch3d.structures.meshes import join_meshes_as_scene | |
| from pytorch3d.transforms.so3 import ( | |
| so3_relative_angle, | |
| ) | |
| from matplotlib.path import Path | |
| from cubercnn import util | |
| def interp_color(dist, bounds=[0, 1], color_lo=(0,0, 250), color_hi=(0, 250, 250)): | |
| percent = (dist - bounds[0]) / (bounds[1] - bounds[0]) | |
| b = color_lo[0] * (1 - percent) + color_hi[0] * percent | |
| g = color_lo[1] * (1 - percent) + color_hi[1] * percent | |
| r = color_lo[2] * (1 - percent) + color_hi[2] * percent | |
| return (b, g, r) | |
| def draw_bev(canvas_bev, z3d, l3d, w3d, x3d, ry3d, color=(0, 200, 200), scale=1, thickness=2): | |
| w = l3d * scale | |
| l = w3d * scale | |
| x = x3d * scale | |
| z = z3d * scale | |
| r = ry3d*-1 | |
| corners1 = np.array([ | |
| [-w / 2, -l / 2, 1], | |
| [+w / 2, -l / 2, 1], | |
| [+w / 2, +l / 2, 1], | |
| [-w / 2, +l / 2, 1] | |
| ]) | |
| ry = np.array([ | |
| [+math.cos(r), -math.sin(r), 0], | |
| [+math.sin(r), math.cos(r), 0], | |
| [0, 0, 1], | |
| ]) | |
| corners2 = ry.dot(corners1.T).T | |
| corners2[:, 0] += w/2 + x + canvas_bev.shape[1] / 2 | |
| corners2[:, 1] += l/2 + z | |
| draw_line(canvas_bev, corners2[0], corners2[1], color=color, thickness=thickness) | |
| draw_line(canvas_bev, corners2[1], corners2[2], color=color, thickness=thickness) | |
| draw_line(canvas_bev, corners2[2], corners2[3], color=color, thickness=thickness) | |
| draw_line(canvas_bev, corners2[3], corners2[0], color=color, thickness=thickness) | |
| def draw_line(im, v0, v1, color=(0, 200, 200), thickness=1): | |
| cv2.line(im, (int(v0[0]), int(v0[1])), (int(v1[0]), int(v1[1])), color, thickness) | |
| def create_colorbar(height, width, color_lo=(0,0, 250), color_hi=(0, 250, 250)): | |
| im = np.zeros([height, width, 3]) | |
| for h in range(0, height): | |
| color = interp_color(h + 0.5, [0, height], color_hi, color_lo) | |
| im[h, :, 0] = (color[0]) | |
| im[h, :, 1] = (color[1]) | |
| im[h, :, 2] = (color[2]) | |
| return im.astype(np.uint8) | |
| def visualize_from_instances(detections, dataset, dataset_name, min_size_test, output_folder, category_names_official, iteration='',visualize_every=50): | |
| vis_folder = os.path.join(output_folder, 'vis') | |
| util.mkdir_if_missing(vis_folder) | |
| log_str = '' | |
| xy_errors = [] | |
| z_errors = [] | |
| w3d_errors = [] | |
| h3d_errors = [] | |
| l3d_errors = [] | |
| dim_errors = [] | |
| ry_errors = [] | |
| n_cats = len(category_names_official) | |
| thres = np.sqrt(1/n_cats) | |
| for imind, im_obj in enumerate(detections): | |
| write_sample = ((imind % visualize_every) == 0) | |
| annos = dataset._dataset[imind]['annotations'] | |
| gt_boxes_2d = np.array([anno['bbox'] for anno in annos]) | |
| if len(gt_boxes_2d)==0: | |
| continue | |
| gt_boxes_2d[:, 2] += gt_boxes_2d[:, 0] | |
| gt_boxes_2d[:, 3] += gt_boxes_2d[:, 1] | |
| gt_boxes_cat = np.array([anno['category_id'] for anno in annos]) | |
| if write_sample: | |
| data_obj = dataset[imind] | |
| assert(data_obj['image_id'] == im_obj['image_id']) | |
| im = util.imread(data_obj['file_name']) | |
| K = np.array(im_obj['K']) | |
| K_inv = np.linalg.inv(K) | |
| sf = im_obj['height'] / min_size_test | |
| for instance in im_obj['instances']: | |
| cat = category_names_official[instance['category_id']] | |
| score = instance['score'] | |
| x1, y1, w, h = instance['bbox'] | |
| x2 = x1 + w | |
| y2 = y1 + h | |
| alpha, h3d, w3d, l3d, x3d, y3d, z3d, ry3d = (-1,)*8 | |
| w3d, h3d, l3d = instance['dimensions'] | |
| # unproject | |
| cen_2d = np.array(instance['center_2D'] + [1]) | |
| z3d = instance['center_cam'][2] | |
| # get rotation (y-axis only) | |
| ry3d = np.array(instance['pose']) | |
| valid_gt_inds = np.flatnonzero(instance['category_id'] == gt_boxes_cat) | |
| if len(valid_gt_inds) > 0: | |
| quality_matrix = util.iou(np.array([[x1, y1, x2, y2]]), gt_boxes_2d[valid_gt_inds]) | |
| nearest_gt = quality_matrix.argmax(axis=1)[0] | |
| nearest_gt_iou = quality_matrix.max(axis=1)[0] | |
| valid_match = nearest_gt_iou >= 0.5 | |
| else: | |
| valid_match = False | |
| if valid_match: | |
| gt_x1, gt_y1, gt_w, gt_h = annos[valid_gt_inds[nearest_gt]]['bbox'] | |
| gt_x3d, gt_y3d, gt_z3d = annos[valid_gt_inds[nearest_gt]]['center_cam'] | |
| gt_w3d, gt_h3d, gt_l3d = annos[valid_gt_inds[nearest_gt]]['dimensions'] | |
| gt_cen_2d = K @ np.array([gt_x3d, gt_y3d, gt_z3d]) | |
| gt_cen_2d /= gt_cen_2d[2] | |
| gt_pose = annos[valid_gt_inds[nearest_gt]]['pose'] | |
| gt_ry3d = np.array(gt_pose) | |
| if valid_match: | |
| # compute errors | |
| xy_errors.append(np.sqrt(((cen_2d[:2] - gt_cen_2d[:2])**2).sum())) | |
| z_errors.append(np.abs(z3d - gt_z3d)) | |
| w3d_errors.append(np.abs(w3d - gt_w3d)) | |
| h3d_errors.append(np.abs(h3d - gt_h3d)) | |
| l3d_errors.append(np.abs(l3d - gt_l3d)) | |
| dim_errors.append(np.sqrt((w3d - gt_w3d)**2 + (h3d - gt_h3d)**2 + (l3d - gt_l3d)**2)) | |
| try: | |
| ry_errors.append(so3_relative_angle(torch.from_numpy(ry3d).unsqueeze(0), torch.from_numpy(gt_ry3d).unsqueeze(0), cos_bound=1).item()) | |
| except: | |
| pass | |
| # unproject point to 3D | |
| x3d, y3d, z3d = (K_inv @ (z3d*cen_2d)) | |
| # let us visualize the detections now | |
| if write_sample and score > thres: | |
| color = util.get_color(instance['category_id']) | |
| draw_3d_box(im, K, [x3d, y3d, z3d, w3d, h3d, l3d], ry3d, color=color, thickness=int(np.round(3*im.shape[0]/500)), draw_back=False) | |
| draw_text(im, '{}, z={:.1f}, s={:.2f}'.format(cat, z3d, score), [x1, y1, w, h], scale=0.50*im.shape[0]/500, bg_color=color) | |
| if write_sample: | |
| util.imwrite(im, os.path.join(vis_folder, '{:06d}.jpg'.format(imind))) | |
| # safety in case all rotation matrices failed. | |
| if len(ry_errors) == 0: | |
| ry_errors = [1000, 1000] | |
| log_str += dataset_name + 'iter={}, xy({:.2f}), z({:.2f}), whl({:.2f}, {:.2f}, {:.2f}), ry({:.2f})\n'.format( | |
| iteration, | |
| np.mean(xy_errors), np.mean(z_errors), | |
| np.mean(w3d_errors), np.mean(h3d_errors), np.mean(l3d_errors), | |
| np.mean(ry_errors), | |
| ) | |
| return log_str | |
| def imshow(im, fig_num=None): | |
| if fig_num is not None: plt.figure(fig_num) | |
| if len(im.shape) == 2: | |
| im = np.tile(im, [3, 1, 1]).transpose([1, 2, 0]) | |
| plt.imshow(cv2.cvtColor(im.astype(np.uint8), cv2.COLOR_RGB2BGR)) | |
| plt.show() | |
| def draw_scene_view(im, K, meshes, text=None, scale=1000, R=None, T=None, zoom_factor=1.0, mode='front_and_novel', blend_weight=0.80, blend_weight_overlay=1.0, ground_bounds=None, canvas=None, zplane=0.05, colors=None): | |
| """ | |
| Draws a scene from multiple different modes. | |
| Args: | |
| im (array): the image to draw onto | |
| K (array): the 3x3 matrix for projection to camera to screen | |
| meshes ([Mesh]): a list of meshes to draw into the scene | |
| text ([str]): optional strings to draw per mesh | |
| scale (int): the size of the square novel view canvas (pixels) | |
| R (array): a single 3x3 matrix defining the novel view | |
| T (array): a 3x vector defining the position of the novel view | |
| zoom_factor (float): an optional amount to zoom out (>1) or in (<1) | |
| mode (str): supports ['2D_only', 'front', 'novel', 'front_and_novel'] where | |
| front implies the front-facing camera view and novel is based on R,T | |
| blend_weight (float): blend factor for box edges over the RGB | |
| blend_weight_overlay (float): blends the RGB image with the rendered meshes | |
| ground_bounds (tuple): max_y3d, x3d_start, x3d_end, z3d_start, z3d_end for the Ground floor or | |
| None to let the renderer to estimate the ground bounds in the novel view itself. | |
| canvas (array): if the canvas doesn't change it can be faster to re-use it. Optional. | |
| zplane (float): a plane of depth to solve intersection when | |
| vertex points project behind the camera plane. | |
| """ | |
| if R is None: | |
| R = util.euler2mat([np.pi/3, 0, 0]) | |
| if mode == '2D_only': | |
| im_drawn_rgb = deepcopy(im) | |
| # go in order of reverse depth | |
| for mesh_idx in reversed(np.argsort([mesh.verts_padded().cpu().mean(1)[0, 1] for mesh in meshes])): | |
| mesh = meshes[mesh_idx] | |
| verts3D = mesh.verts_padded()[0].numpy() | |
| verts2D = (K @ verts3D.T) / verts3D[:, -1] | |
| color = [min(255, c*255*1.25) for c in mesh.textures.verts_features_padded()[0,0].tolist()] | |
| x1 = verts2D[0, :].min() | |
| y1 = verts2D[1, :].min() | |
| x2 = verts2D[0, :].max() | |
| y2 = verts2D[1, :].max() | |
| draw_2d_box(im_drawn_rgb, [x1, y1, x2-x1, y2-y1], color=color, thickness=max(2, int(np.round(3*im_drawn_rgb.shape[0]/1250)))) | |
| if text is not None: | |
| draw_text(im_drawn_rgb, '{}'.format(text[mesh_idx]), [x1, y1], scale=0.50*im_drawn_rgb.shape[0]/500, bg_color=color) | |
| return im_drawn_rgb | |
| else: | |
| meshes_scene = join_meshes_as_scene(meshes) | |
| if torch.cuda.is_available(): | |
| meshes_scene = meshes_scene.cuda() | |
| device = meshes_scene.device | |
| meshes_scene.textures = meshes_scene.textures.to(device) | |
| cameras = util.get_camera(K, im.shape[1], im.shape[0]).to(device) | |
| renderer = util.get_basic_renderer(cameras, im.shape[1], im.shape[0], use_color=True).to(device) | |
| if mode in ['front_and_novel', 'front']: | |
| ''' | |
| Render full scene from image view | |
| ''' | |
| im_drawn_rgb = deepcopy(im) | |
| # save memory if not blending the render | |
| if blend_weight > 0: | |
| rendered_img, _ = renderer(meshes_scene) | |
| sil_mask = rendered_img[0, :, :, 3].cpu().numpy() > 0.1 | |
| rendered_img = (rendered_img[0, :, :, :3].cpu().numpy() * 255).astype(np.uint8) | |
| im_drawn_rgb[sil_mask] = rendered_img[sil_mask] * blend_weight + im_drawn_rgb[sil_mask] * (1 - blend_weight) | |
| ''' | |
| Draw edges for image view | |
| ''' | |
| # go in order of reverse depth | |
| for mesh_idx in reversed(np.argsort([mesh.verts_padded().cpu().mean(1)[0, 1] for mesh in meshes])): | |
| mesh = meshes[mesh_idx] | |
| verts3D = mesh.verts_padded()[0].cpu().numpy() | |
| verts2D = (K @ verts3D.T) / verts3D[:, -1] | |
| if colors is not None: | |
| color = np.minimum(colors[mesh_idx][:-1] * 255 * 1.25, np.ones_like(colors[mesh_idx][:-1])*255).tolist() | |
| else: | |
| color = [min(255, c*255*1.25) for c in mesh.textures.verts_features_padded()[0,0].tolist()] | |
| draw_3d_box_from_verts( | |
| im_drawn_rgb, K, verts3D, color=color, | |
| thickness=max(2, int(np.round(3*im_drawn_rgb.shape[0]/1250))), | |
| draw_back=False, draw_top=False, zplane=zplane | |
| ) | |
| x1 = verts2D[0, :].min() #min(verts2D[0, (verts2D[0, :] > 0) & (verts2D[0, :] < im_drawn_rgb.shape[1])]) | |
| y1 = verts2D[1, :].min() #min(verts2D[1, (verts2D[1, :] > 0) & (verts2D[1, :] < im_drawn_rgb.shape[0])]) | |
| if text is not None: | |
| draw_text(im_drawn_rgb, '{}'.format(text[mesh_idx]), [x1, y1], scale=0.50*im_drawn_rgb.shape[0]/500, bg_color=color) | |
| if blend_weight_overlay < 1.0 and blend_weight_overlay > 0.0: | |
| im_drawn_rgb = im_drawn_rgb * blend_weight_overlay + deepcopy(im) * (1 - blend_weight_overlay) | |
| if mode == 'front': | |
| return im_drawn_rgb | |
| elif mode in ['front_and_novel', 'novel']: | |
| ''' | |
| Render from a new view | |
| ''' | |
| has_canvas_already = canvas is not None | |
| if not has_canvas_already: | |
| canvas = np.ones((scale, scale, 3)) | |
| view_R = torch.from_numpy(R).float().to(device) | |
| if T is None: | |
| center = (meshes_scene.verts_padded().min(1).values + meshes_scene.verts_padded().max(1).values).unsqueeze(0)/2 | |
| else: | |
| center = torch.from_numpy(T).float().to(device).view(1, 1, 3) | |
| verts_rotated = meshes_scene.verts_padded().clone() | |
| verts_rotated -= center | |
| verts_rotated = (view_R @ verts_rotated[0].T).T.unsqueeze(0) | |
| K_novelview = deepcopy(K) | |
| K_novelview[0, -1] *= scale / im.shape[1] | |
| K_novelview[1, -1] *= scale / im.shape[0] | |
| cameras = util.get_camera(K_novelview, scale, scale).to(device) | |
| renderer = util.get_basic_renderer(cameras, scale, scale, use_color=True).to(device) | |
| margin = 0.01 | |
| if T is None: | |
| max_trials = 10000 | |
| zoom_factor = 100.0 | |
| zoom_factor_in = zoom_factor | |
| while max_trials: | |
| zoom_factor_in = zoom_factor_in*0.95 | |
| verts = verts_rotated.clone() | |
| verts[:, :, -1] += center[:, :, -1]*zoom_factor_in | |
| verts_np = verts.cpu().numpy() | |
| proj = ((K_novelview @ verts_np[0].T) / verts_np[:, :, -1]) | |
| # some vertices are extremely close or negative... | |
| # this implies we have zoomed in too much | |
| if (verts[0, :, -1] < 0.25).any(): | |
| break | |
| # left or above image | |
| elif (proj[:2, :] < scale*margin).any(): | |
| break | |
| # right or below borders | |
| elif (proj[:2, :] > scale*(1 - margin)).any(): | |
| break | |
| # everything is in view. | |
| zoom_factor = zoom_factor_in | |
| max_trials -= 1 | |
| zoom_out_bias = center[:, :, -1].item() | |
| else: | |
| zoom_out_bias = 1.0 | |
| verts_rotated[:, :, -1] += zoom_out_bias*zoom_factor | |
| meshes_novel_view = meshes_scene.clone().update_padded(verts_rotated) | |
| rendered_img, _ = renderer(meshes_novel_view) | |
| im_novel_view = (rendered_img[0, :, :, :3].cpu().numpy() * 255).astype(np.uint8) | |
| sil_mask = rendered_img[0, :, :, 3].cpu().numpy() > 0.1 | |
| center_np = center.cpu().numpy() | |
| view_R_np = view_R.cpu().numpy() | |
| if not has_canvas_already: | |
| if ground_bounds is None: | |
| min_x3d, _, min_z3d = meshes_scene.verts_padded().min(1).values[0, :].tolist() | |
| max_x3d, max_y3d, max_z3d = meshes_scene.verts_padded().max(1).values[0, :].tolist() | |
| # go for grid projection, but with extremely bad guess at bounds | |
| x3d_start = np.round(min_x3d - (max_x3d - min_x3d)*50) | |
| x3d_end = np.round(max_x3d + (max_x3d - min_x3d)*50) | |
| z3d_start = np.round(min_z3d - (max_z3d - min_z3d)*50) | |
| z3d_end = np.round(max_z3d + (max_z3d - min_z3d)*50) | |
| grid_xs = np.arange(x3d_start, x3d_end) | |
| grid_zs = np.arange(z3d_start, z3d_end) | |
| xs_mesh, zs_mesh = np.meshgrid(grid_xs, grid_zs) | |
| ys_mesh = np.ones_like(xs_mesh)*max_y3d | |
| point_mesh = np.concatenate((xs_mesh[:, :, np.newaxis], ys_mesh[:, :, np.newaxis], zs_mesh[:, :, np.newaxis]), axis=2) | |
| point_mesh_orig = deepcopy(point_mesh) | |
| mesh_shape = point_mesh.shape | |
| point_mesh = view_R_np @ (point_mesh - center_np).transpose(2, 0, 1).reshape(3, -1) | |
| point_mesh[-1] += zoom_out_bias*zoom_factor | |
| point_mesh[-1, :] = point_mesh[-1, :].clip(0.25) | |
| point_mesh_2D = (K_novelview @ point_mesh) / point_mesh[-1] | |
| point_mesh_2D[-1] = point_mesh[-1] | |
| point_mesh = point_mesh.reshape(3, mesh_shape[0], mesh_shape[1]).transpose(1, 2, 0) | |
| point_mesh_2D = point_mesh_2D.reshape(3, mesh_shape[0], mesh_shape[1]).transpose(1, 2, 0) | |
| maskx = (point_mesh_2D[:, :, 0].T >= -50) & (point_mesh_2D[:, :, 0].T < scale+50) & (point_mesh_2D[:, :, 2].T > 0) | |
| maskz = (point_mesh_2D[:, :, 1].T >= -50) & (point_mesh_2D[:, :, 1].T < scale+50) & (point_mesh_2D[:, :, 2].T > 0) | |
| # invalid scene? | |
| if (not maskz.any()) or (not maskx.any()): | |
| return im, im, canvas | |
| # go for grid projection again!! but with sensible bounds | |
| x3d_start = np.round(point_mesh[:, :, 0].T[maskx].min() - 10) | |
| x3d_end = np.round(point_mesh[:, :, 0].T[maskx].max() + 10) | |
| z3d_start = np.round(point_mesh_orig[:, :, 2].T[maskz].min() - 10) | |
| z3d_end = np.round(point_mesh_orig[:, :, 2].T[maskz].max() + 10) | |
| else: | |
| max_y3d, x3d_start, x3d_end, z3d_start, z3d_end = ground_bounds | |
| grid_xs = np.arange(x3d_start, x3d_end) | |
| grid_zs = np.arange(z3d_start, z3d_end) | |
| xs_mesh, zs_mesh = np.meshgrid(grid_xs, grid_zs) | |
| ys_mesh = np.ones_like(xs_mesh)*max_y3d | |
| point_mesh = np.concatenate((xs_mesh[:, :, np.newaxis], ys_mesh[:, :, np.newaxis], zs_mesh[:, :, np.newaxis]), axis=2) | |
| mesh_shape = point_mesh.shape | |
| point_mesh = view_R_np @ (point_mesh - center_np).transpose(2, 0, 1).reshape(3, -1) | |
| point_mesh[-1] += zoom_out_bias*zoom_factor | |
| point_mesh[-1, :] = point_mesh[-1, :].clip(0.25) | |
| point_mesh_2D = (K_novelview @ point_mesh) / point_mesh[-1] | |
| point_mesh_2D[-1] = point_mesh[-1] | |
| point_mesh = point_mesh.reshape(3, mesh_shape[0], mesh_shape[1]).transpose(1, 2, 0) | |
| point_mesh_2D = point_mesh_2D.reshape(3, mesh_shape[0], mesh_shape[1]).transpose(1, 2, 0) | |
| bg_color = (225,)*3 | |
| line_color = (175,)*3 | |
| canvas[:, :, 0] = bg_color[0] | |
| canvas[:, :, 1] = bg_color[1] | |
| canvas[:, :, 2] = bg_color[2] | |
| lines_to_draw = set() | |
| for grid_row_idx in range(1, len(grid_zs)): | |
| pre_z = grid_zs[grid_row_idx-1] | |
| cur_z = grid_zs[grid_row_idx] | |
| for grid_col_idx in range(1, len(grid_xs)): | |
| pre_x = grid_xs[grid_col_idx-1] | |
| cur_x = grid_xs[grid_col_idx] | |
| p1 = point_mesh_2D[grid_row_idx-1, grid_col_idx-1] | |
| valid1 = p1[-1] > 0 | |
| p2 = point_mesh_2D[grid_row_idx-1, grid_col_idx] | |
| valid2 = p2[-1] > 0 | |
| if valid1 and valid2: | |
| line = (tuple(p1[:2].astype(int).tolist()), tuple(p2[:2].astype(int).tolist())) | |
| lines_to_draw.add(line) | |
| # draw vertical line from the previous row | |
| p1 = point_mesh_2D[grid_row_idx-1, grid_col_idx-1] | |
| valid1 = p1[-1] > 0 | |
| p2 = point_mesh_2D[grid_row_idx, grid_col_idx-1] | |
| valid2 = p2[-1] > 0 | |
| if valid1 and valid2: | |
| line = (tuple(p1[:2].astype(int).tolist()), tuple(p2[:2].astype(int).tolist())) | |
| lines_to_draw.add(line) | |
| for line in lines_to_draw: | |
| draw_line(canvas, line[0], line[1], color=line_color, thickness=max(1, int(np.round(3*scale/1250)))) | |
| im_novel_view[~sil_mask] = canvas[~sil_mask] | |
| ''' | |
| Draw edges for novel view | |
| ''' | |
| # apply novel view to meshes | |
| meshes_novel = [] | |
| for mesh in meshes: | |
| mesh_novel = mesh.clone().to(device) | |
| verts_rotated = mesh_novel.verts_padded() | |
| verts_rotated -= center | |
| verts_rotated = (view_R @ verts_rotated[0].T).T.unsqueeze(0) | |
| verts_rotated[:, :, -1] += zoom_out_bias*zoom_factor | |
| mesh_novel = mesh_novel.update_padded(verts_rotated) | |
| meshes_novel.append(mesh_novel) | |
| # go in order of reverse depth | |
| for mesh_idx in reversed(np.argsort([mesh.verts_padded().cpu().mean(1)[0, 1] for mesh in meshes_novel])): | |
| mesh = meshes_novel[mesh_idx] | |
| verts3D = mesh.verts_padded()[0].cpu().numpy() | |
| verts2D = (K_novelview @ verts3D.T) / verts3D[:, -1] | |
| if colors is not None: | |
| color = np.minimum(colors[mesh_idx][:-1] * 255 * 1.25, np.ones_like(colors[mesh_idx][:-1])*255).tolist() # colors[mesh_idx][:-1] * 255 * 1.25 | |
| else: | |
| color = [min(255, c*255*1.25) for c in mesh.textures.verts_features_padded()[0,0].tolist()] | |
| draw_3d_box_from_verts( | |
| im_novel_view, K_novelview, verts3D, color=color, | |
| thickness=max(2, int(np.round(3*im_novel_view.shape[0]/1250))), | |
| draw_back=False, draw_top=False, zplane=zplane | |
| ) | |
| x1 = verts2D[0, :].min() | |
| y1 = verts2D[1, :].min() | |
| if text is not None: | |
| draw_text(im_novel_view, '{}'.format(text[mesh_idx]), [x1, y1], scale=0.50*im_novel_view.shape[0]/500, bg_color=color) | |
| if mode == 'front_and_novel': | |
| return im_drawn_rgb, im_novel_view, canvas | |
| else: | |
| return im_novel_view, canvas | |
| else: | |
| raise ValueError('No visualization written for {}'.format(mode)) | |
| def get_polygon_grid(im, poly_verts): | |
| nx = im.shape[1] | |
| ny = im.shape[0] | |
| x, y = np.meshgrid(np.arange(nx), np.arange(ny)) | |
| x, y = x.flatten(), y.flatten() | |
| points = np.vstack((x, y)).T | |
| path = Path(poly_verts) | |
| grid = path.contains_points(points) | |
| grid = grid.reshape((ny, nx)) | |
| return grid | |
| def draw_circle(im, pos, radius=5, thickness=1, color=(250, 100, 100), fill=True): | |
| if fill: thickness = -1 | |
| cv2.circle(im, (int(pos[0]), int(pos[1])), radius, color=color, thickness=thickness) | |
| def draw_transparent_polygon(im, verts, blend=0.5, color=(0, 255, 255)): | |
| mask = get_polygon_grid(im, verts[:4, :]) | |
| im[mask, 0] = im[mask, 0] * blend + (1 - blend) * color[0] | |
| im[mask, 1] = im[mask, 1] * blend + (1 - blend) * color[1] | |
| im[mask, 2] = im[mask, 2] * blend + (1 - blend) * color[2] | |
| def draw_3d_box_from_verts(im, K, verts3d, color=(0, 200, 200), thickness=1, draw_back=False, draw_top=False, zplane=0.05, eps=1e-4): | |
| """ | |
| Draws a scene from multiple different modes. | |
| Args: | |
| im (array): the image to draw onto | |
| K (array): the 3x3 matrix for projection to camera to screen | |
| verts3d (array): the 8x3 matrix of vertices in camera space | |
| color (tuple): color in RGB scaled [0, 255) | |
| thickness (float): the line thickness for opencv lines | |
| draw_back (bool): whether a backface should be highlighted | |
| draw_top (bool): whether the top face should be highlighted | |
| zplane (float): a plane of depth to solve intersection when | |
| vertex points project behind the camera plane. | |
| """ | |
| if isinstance(K, torch.Tensor): | |
| K = K.detach().cpu().numpy() | |
| if isinstance(verts3d, torch.Tensor): | |
| verts3d = verts3d.detach().cpu().numpy() | |
| # reorder | |
| bb3d_lines_verts = [[0, 1], [1, 2], [2, 3], [3, 0], [1, 5], [5, 6], [6, 2], [4, 5], [4, 7], [6, 7], [0, 4], [3, 7]] | |
| # define back and top vetice planes | |
| back_idxs = [4, 0, 3, 7] | |
| top_idxs = [4, 0, 1, 5] | |
| for (i, j) in bb3d_lines_verts: | |
| v0 = verts3d[i] | |
| v1 = verts3d[j] | |
| z0, z1 = v0[-1], v1[-1] | |
| if (z0 >= zplane or z1 >= zplane): | |
| # computer intersection of v0, v1 and zplane | |
| s = (zplane - z0) / max((z1 - z0), eps) | |
| new_v = v0 + s * (v1 - v0) | |
| if (z0 < zplane) and (z1 >= zplane): | |
| # i0 vertex is behind the plane | |
| v0 = new_v | |
| elif (z0 >= zplane) and (z1 < zplane): | |
| # i1 vertex is behind the plane | |
| v1 = new_v | |
| v0_proj = (K @ v0)/max(v0[-1], eps) | |
| v1_proj = (K @ v1)/max(v1[-1], eps) | |
| # project vertices | |
| cv2.line(im, | |
| (int(v0_proj[0]), int(v0_proj[1])), | |
| (int(v1_proj[0]), int(v1_proj[1])), | |
| color, thickness | |
| ) | |
| # dont draw the planes if a vertex is out of bounds | |
| draw_back &= np.all(verts3d[back_idxs, -1] >= zplane) | |
| draw_top &= np.all(verts3d[top_idxs, -1] >= zplane) | |
| if draw_back or draw_top: | |
| # project to image | |
| verts2d = (K @ verts3d.T).T | |
| verts2d /= verts2d[:, -1][:, np.newaxis] | |
| if type(verts2d) == torch.Tensor: | |
| verts2d = verts2d.detach().cpu().numpy() | |
| if draw_back: | |
| draw_transparent_polygon(im, verts2d[back_idxs, :2], blend=0.5, color=color) | |
| if draw_top: | |
| draw_transparent_polygon(im, verts2d[top_idxs, :2], blend=0.5, color=color) | |
| def draw_3d_box(im, K, box3d, R, color=(0, 200, 200), thickness=1, draw_back=False, draw_top=False, view_R=None, view_T=None): | |
| verts2d, verts3d = util.get_cuboid_verts(K, box3d, R, view_R=view_R, view_T=view_T) | |
| draw_3d_box_from_verts(im, K, verts3d, color=color, thickness=thickness, draw_back=draw_back, draw_top=draw_top) | |
| def draw_text(im, text, pos, scale=0.4, color='auto', font=cv2.FONT_HERSHEY_SIMPLEX, bg_color=(0, 255, 255), | |
| blend=0.33, lineType=1): | |
| text = str(text) | |
| pos = [int(pos[0]), int(pos[1])] | |
| if color == 'auto': | |
| if bg_color is not None: | |
| color = (0, 0, 0) if ((bg_color[0] + bg_color[1] + bg_color[2])/3) > 127.5 else (255, 255, 255) | |
| else: | |
| color = (0, 0, 0) | |
| if bg_color is not None: | |
| text_size, _ = cv2.getTextSize(text, font, scale, lineType) | |
| x_s = int(np.clip(pos[0], a_min=0, a_max=im.shape[1])) | |
| x_e = int(np.clip(x_s + text_size[0] - 1 + 4, a_min=0, a_max=im.shape[1])) | |
| y_s = int(np.clip(pos[1] - text_size[1] - 2, a_min=0, a_max=im.shape[0])) | |
| y_e = int(np.clip(pos[1] + 1 - 2, a_min=0, a_max=im.shape[0])) | |
| im[y_s:y_e + 1, x_s:x_e + 1, 0] = im[y_s:y_e + 1, x_s:x_e + 1, 0]*blend + bg_color[0] * (1 - blend) | |
| im[y_s:y_e + 1, x_s:x_e + 1, 1] = im[y_s:y_e + 1, x_s:x_e + 1, 1]*blend + bg_color[1] * (1 - blend) | |
| im[y_s:y_e + 1, x_s:x_e + 1, 2] = im[y_s:y_e + 1, x_s:x_e + 1, 2]*blend + bg_color[2] * (1 - blend) | |
| pos[0] = int(np.clip(pos[0] + 2, a_min=0, a_max=im.shape[1])) | |
| pos[1] = int(np.clip(pos[1] - 2, a_min=0, a_max=im.shape[0])) | |
| cv2.putText(im, text, tuple(pos), font, scale, color, lineType) | |
| def draw_transparent_square(im, pos, alpha=1, radius=5, color=(250, 100, 100)): | |
| l = pos[1] - radius | |
| r = pos[1] + radius | |
| t = pos[0] - radius | |
| b = pos[0] + radius | |
| if (np.array([l, r, t, b]) >= 0).any(): | |
| l = np.clip(np.floor(l), 0, im.shape[0]).astype(int) | |
| r = np.clip(np.floor(r), 0, im.shape[0]).astype(int) | |
| t = np.clip(np.floor(t), 0, im.shape[1]).astype(int) | |
| b = np.clip(np.floor(b), 0, im.shape[1]).astype(int) | |
| # blend | |
| im[l:r + 1, t:b + 1, 0] = im[l:r + 1, t:b + 1, 0] * alpha + color[0] * (1 - alpha) | |
| im[l:r + 1, t:b + 1, 1] = im[l:r + 1, t:b + 1, 1] * alpha + color[1] * (1 - alpha) | |
| im[l:r + 1, t:b + 1, 2] = im[l:r + 1, t:b + 1, 2] * alpha + color[2] * (1 - alpha) | |
| def draw_2d_box(im, box, color=(0, 200, 200), thickness=1): | |
| x = box[0] | |
| y = box[1] | |
| w = box[2] | |
| h = box[3] | |
| x2 = (x + w) - 1 | |
| y2 = (y + h) - 1 | |
| cv2.rectangle(im, (int(x), int(y)), (int(x2), int(y2)), color, thickness) | |
| def imhstack(im1, im2): | |
| sf = im1.shape[0] / im2.shape[0] | |
| if sf > 1: | |
| im2 = cv2.resize(im2, (int(im2.shape[1] / sf), im1.shape[0])) | |
| elif sf < 1: | |
| im1 = cv2.resize(im1, (int(im1.shape[1] / sf), im2.shape[0])) | |
| im_concat = np.hstack((im1, im2)) | |
| return im_concat | |
| def imvstack(im1, im2): | |
| sf = im1.shape[1] / im2.shape[1] | |
| if sf > 1: | |
| im2 = cv2.resize(im2, (int(im2.shape[0] / sf), im1.shape[1])) | |
| elif sf < 1: | |
| im1 = cv2.resize(im1, (int(im1.shape[0] / sf), im2.shape[1])) | |
| im_concat = np.vstack((im1, im2)) | |
| return im_concat |