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 |