| |
| |
| |
|
|
| import torch |
| import numpy as np |
| import trimesh |
| import math |
| from scipy.spatial.transform import Rotation |
| from PIL import ImageFont, ImageDraw, Image |
|
|
| OPENCV_TO_OPENGL_CAMERA_CONVENTION = np.array([[1, 0, 0, 0], |
| [0, -1, 0, 0], |
| [0, 0, -1, 0], |
| [0, 0, 0, 1]]) |
|
|
| def geotrf( Trf, pts, ncol=None, norm=False): |
| """ Apply a geometric transformation to a list of 3-D points. |
| H: 3x3 or 4x4 projection matrix (typically a Homography) |
| p: numpy/torch/tuple of coordinates. Shape must be (...,2) or (...,3) |
| |
| ncol: int. number of columns of the result (2 or 3) |
| norm: float. if != 0, the resut is projected on the z=norm plane. |
| |
| Returns an array of projected 2d points. |
| """ |
| assert Trf.ndim in (2,3) |
| if isinstance(Trf, np.ndarray): |
| pts = np.asarray(pts) |
| elif isinstance(Trf, torch.Tensor): |
| pts = torch.as_tensor(pts, dtype=Trf.dtype) |
|
|
| ncol = ncol or pts.shape[-1] |
|
|
| |
| output_reshape = pts.shape[:-1] |
| if Trf.ndim == 3: |
| assert len(Trf) == len(pts), 'batch size does not match' |
| if Trf.ndim == 3 and pts.ndim > 3: |
| |
| pts = pts.reshape(pts.shape[0], -1, pts.shape[-1]) |
| elif Trf.ndim == 3 and pts.ndim == 2: |
| |
| pts = pts[:, None, :] |
|
|
| if pts.shape[-1]+1 == Trf.shape[-1]: |
| Trf = Trf.swapaxes(-1,-2) |
| pts = pts @ Trf[...,:-1,:] + Trf[...,-1:,:] |
| elif pts.shape[-1] == Trf.shape[-1]: |
| Trf = Trf.swapaxes(-1,-2) |
| pts = pts @ Trf |
| else: |
| pts = Trf @ pts.T |
| if pts.ndim >= 2: pts = pts.swapaxes(-1,-2) |
| if norm: |
| pts = pts / pts[...,-1:] |
| if norm != 1: pts *= norm |
|
|
| return pts[...,:ncol].reshape(*output_reshape, ncol) |
|
|
| def create_scene(img_pil, l_mesh, l_face, color=None, metallicFactor=0., roughnessFactor=0.5, focal=600): |
| |
| scene = trimesh.Scene( |
| lights=trimesh.scene.lighting.Light(intensity=3.0) |
| ) |
|
|
| |
| for i, mesh in enumerate(l_mesh): |
| if color is None: |
| _color = (np.random.choice(range(1,225))/255, np.random.choice(range(1,225))/255, np.random.choice(range(1,225))/255) |
| else: |
| if isinstance(color,list): |
| _color = color[i] |
| elif isinstance(color,tuple): |
| _color = color |
| else: |
| raise NotImplementedError |
| mesh = trimesh.Trimesh(mesh, l_face[i]) |
| mesh.visual = trimesh.visual.TextureVisuals( |
| uv=None, |
| material=trimesh.visual.material.PBRMaterial( |
| metallicFactor=metallicFactor, |
| roughnessFactor=roughnessFactor, |
| alphaMode='OPAQUE', |
| baseColorFactor=(_color[0], _color[1], _color[2], 1.0) |
| ), |
| image=None, |
| face_materials=None |
| ) |
| scene.add_geometry(mesh) |
|
|
| |
| H, W = img_pil.size[0], img_pil.size[1] |
| screen_width = 0.3 |
| height = focal * screen_width / H |
| width = screen_width * 0.5**0.5 |
| rot45 = np.eye(4) |
| rot45[:3,:3] = Rotation.from_euler('z',np.deg2rad(45)).as_matrix() |
| rot45[2,3] = -height |
| aspect_ratio = np.eye(4) |
| aspect_ratio[0,0] = W/H |
| transform = OPENCV_TO_OPENGL_CAMERA_CONVENTION @ aspect_ratio @ rot45 |
| cam = trimesh.creation.cone(width, height, sections=4, transform=transform) |
| |
| |
| |
|
|
| |
| vertices = cam.vertices[[4,5,1,3]] |
| faces = np.array([[0, 1, 2], [0, 2, 3], [2, 1, 0], [3, 2, 0]]) |
| img = trimesh.Trimesh(vertices=vertices, faces=faces) |
| uv_coords = np.float32([[0, 0], [1, 0], [1, 1], [0, 1]]) |
| |
| material = trimesh.visual.texture.SimpleMaterial(image=img_pil, |
| diffuse=[255,255,255,0], |
| ambient=[255,255,255,0], |
| specular=[255,255,255,0], |
| glossiness=1.0) |
| img.visual = trimesh.visual.TextureVisuals(uv=uv_coords, image=img_pil) |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
| scene.add_geometry(img) |
|
|
| |
| rot2 = np.eye(4) |
| rot2[:3,:3] = Rotation.from_euler('z',np.deg2rad(2)).as_matrix() |
| |
| |
| |
| |
| vertices = np.r_[cam.vertices, 0.95*cam.vertices, geotrf(rot2, cam.vertices)] |
| |
| faces = [] |
| for face in cam.faces: |
| if 0 in face: continue |
| a,b,c = face |
| a2,b2,c2 = face + len(cam.vertices) |
| a3,b3,c3 = face + 2*len(cam.vertices) |
|
|
| |
| faces.append((a,b,b2)) |
| faces.append((a,a2,c)) |
| faces.append((c2,b,c)) |
|
|
| faces.append((a,b,b3)) |
| faces.append((a,a3,c)) |
| faces.append((c3,b,c)) |
|
|
| |
| faces += [(c,b,a) for a,b,c in faces] |
|
|
| cam = trimesh.Trimesh(vertices=vertices, faces=faces) |
| cam.visual.face_colors[:,:3] = (255, 0, 0) |
| scene.add_geometry(cam) |
| |
| |
| rot = np.eye(4) |
| cams2world = np.eye(4) |
| rot[:3,:3] = Rotation.from_euler('y',np.deg2rad(180)).as_matrix() |
| scene.apply_transform(np.linalg.inv(cams2world @ OPENCV_TO_OPENGL_CAMERA_CONVENTION @ rot)) |
|
|
| return scene |
|
|
|
|
| def length(v): |
| return math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) |
|
|
| def cross(v0, v1): |
| return [ |
| v0[1]*v1[2]-v1[1]*v0[2], |
| v0[2]*v1[0]-v1[2]*v0[0], |
| v0[0]*v1[1]-v1[0]*v0[1]] |
|
|
| def dot(v0, v1): |
| return v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2] |
|
|
| def normalize(v, eps=1e-13): |
| l = length(v) |
| return [v[0]/(l+eps), v[1]/(l+eps), v[2]/(l+eps)] |
|
|
| def lookAt(eye, target, *args, **kwargs): |
| """ |
| eye is the point of view, target is the point which is looked at and up is the upwards direction. |
| |
| Input should be in OpenCV format - we transform arguments to OpenGL |
| Do compute in OpenGL and then transform back to OpenCV |
| |
| """ |
| |
| |
| |
| up = [0,-1,0] |
|
|
| eye, at, up = eye, target, up |
| zaxis = normalize((at[0]-eye[0], at[1]-eye[1], at[2]-eye[2])) |
| xaxis = normalize(cross(zaxis, up)) |
| yaxis = cross(xaxis, zaxis) |
|
|
| zaxis = [-zaxis[0],-zaxis[1],-zaxis[2]] |
|
|
| viewMatrix = np.asarray([ |
| [xaxis[0], xaxis[1], xaxis[2], -dot(xaxis, eye)], |
| [yaxis[0], yaxis[1], yaxis[2], -dot(yaxis, eye)], |
| [zaxis[0], zaxis[1], zaxis[2], -dot(zaxis, eye)], |
| [0, 0, 0, 1]] |
| ).reshape(4,4) |
|
|
| |
| viewMatrix = OPENCV_TO_OPENGL_CAMERA_CONVENTION @ viewMatrix |
| |
| return viewMatrix |
|
|
| def print_distance_on_image(pred_rend_array, humans, _color): |
| |
| font = ImageFont.load_default() |
| rend_pil = Image.fromarray(pred_rend_array) |
| draw = ImageDraw.Draw(rend_pil) |
| for i_hum, hum in enumerate(humans): |
| |
| transl = hum['transl_pelvis'].cpu().numpy().reshape(3) |
| dist_cam = np.sqrt(((transl[[0,2]])**2).sum()) |
| |
| bbox = get_bbox(hum['j2d_smplx'].cpu().numpy(), factor=1.35, output_format='x1y1x2y2') |
| loc = [(bbox[0] + bbox[2]) / 2., bbox[1]] |
| txt = f"{dist_cam:.2f}m" |
| length = font.getlength(txt) |
| loc[0] = loc[0] - length // 2 |
| fill = tuple((np.asarray(_color[i_hum]) * 255).astype(np.int32).tolist()) |
| draw.text((loc[0], loc[1]), txt, fill=fill, font=font) |
| return np.asarray(rend_pil) |
|
|
| def get_bbox(points, factor=1., output_format='xywh'): |
| """ |
| Args: |
| - y: [k,2] |
| Return: |
| - bbox: [4] in a specific format |
| """ |
| assert len(points.shape) == 2, f"Wrong shape, expected two-dimensional array. Got shape {points.shape}" |
| assert points.shape[1] == 2 |
| x1, x2 = points[:,0].min(), points[:,0].max() |
| y1, y2 = points[:,1].min(), points[:,1].max() |
| cx, cy = (x2 + x1) / 2., (y2 + y1) / 2. |
| sx, sy = np.abs(x2 - x1), np.abs(y2 - y1) |
| sx, sy = int(factor * sx), int(factor * sy) |
| x1, y1 = int(cx - sx / 2.), int(cy - sy / 2.) |
| x2, y2 = int(cx + sx / 2.), int(cy + sy / 2.) |
| if output_format == 'xywh': |
| return [x1,y1,sx,sy] |
| elif output_format == 'x1y1x2y2': |
| return [x1,y1,x2,y2] |
| else: |
| raise NotImplementedError |
|
|
|
|