| import imp |
| import os |
| from pickle import NONE |
| |
| import torch |
| import trimesh |
| import numpy as np |
| |
| from skimage.transform import resize |
| from torchvision.utils import make_grid |
| import torch.nn.functional as F |
|
|
| from models.smpl import get_smpl_faces, get_model_faces, get_model_tpose |
| from utils.densepose_methods import DensePoseMethods |
| from core import constants, path_config |
| import json |
| from .geometry import convert_to_full_img_cam |
| from utils.imutils import crop |
|
|
| try: |
| import math |
| import pyrender |
| from pyrender.constants import RenderFlags |
| except: |
| pass |
| try: |
| from opendr.renderer import ColoredRenderer |
| from opendr.lighting import LambertianPointLight, SphericalHarmonics |
| from opendr.camera import ProjectPoints |
| except: |
| pass |
|
|
| from pytorch3d.structures.meshes import Meshes |
| |
|
|
| from pytorch3d.renderer import ( |
| look_at_view_transform, FoVPerspectiveCameras, PerspectiveCameras, AmbientLights, PointLights, |
| RasterizationSettings, BlendParams, MeshRenderer, MeshRasterizer, SoftPhongShader, |
| SoftSilhouetteShader, HardPhongShader, HardGouraudShader, HardFlatShader, TexturesVertex |
| ) |
|
|
| import logging |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class WeakPerspectiveCamera(pyrender.Camera): |
| def __init__( |
| self, scale, translation, znear=pyrender.camera.DEFAULT_Z_NEAR, zfar=None, name=None |
| ): |
| super(WeakPerspectiveCamera, self).__init__( |
| znear=znear, |
| zfar=zfar, |
| name=name, |
| ) |
| self.scale = scale |
| self.translation = translation |
|
|
| def get_projection_matrix(self, width=None, height=None): |
| P = np.eye(4) |
| P[0, 0] = self.scale[0] |
| P[1, 1] = self.scale[1] |
| P[0, 3] = self.translation[0] * self.scale[0] |
| P[1, 3] = -self.translation[1] * self.scale[1] |
| P[2, 2] = -1 |
| return P |
|
|
|
|
| class PyRenderer: |
| def __init__( |
| self, resolution=(224, 224), orig_img=False, wireframe=False, scale_ratio=1., vis_ratio=1. |
| ): |
| self.resolution = (resolution[0] * scale_ratio, resolution[1] * scale_ratio) |
| |
|
|
| self.faces = { |
| 'smplx': get_model_faces('smplx'), |
| 'smpl': get_model_faces('smpl'), |
| |
| |
| } |
| self.orig_img = orig_img |
| self.wireframe = wireframe |
| self.renderer = pyrender.OffscreenRenderer( |
| viewport_width=self.resolution[0], viewport_height=self.resolution[1], point_size=1.0 |
| ) |
|
|
| self.vis_ratio = vis_ratio |
|
|
| |
| self.scene = pyrender.Scene(bg_color=[0.0, 0.0, 0.0, 0.0], ambient_light=(0.3, 0.3, 0.3)) |
|
|
| light = pyrender.PointLight(color=np.array([1.0, 1.0, 1.0]) * 0.2, intensity=1) |
|
|
| yrot = np.radians(120) |
|
|
| light_pose = np.eye(4) |
| light_pose[:3, 3] = [0, -1, 1] |
| self.scene.add(light, pose=light_pose) |
|
|
| light_pose[:3, 3] = [0, 1, 1] |
| self.scene.add(light, pose=light_pose) |
|
|
| light_pose[:3, 3] = [1, 1, 2] |
| self.scene.add(light, pose=light_pose) |
|
|
| spot_l = pyrender.SpotLight( |
| color=np.ones(3), intensity=15.0, innerConeAngle=np.pi / 3, outerConeAngle=np.pi / 2 |
| ) |
|
|
| light_pose[:3, 3] = [1, 2, 2] |
| self.scene.add(spot_l, pose=light_pose) |
|
|
| light_pose[:3, 3] = [-1, 2, 2] |
| self.scene.add(spot_l, pose=light_pose) |
|
|
| |
| |
|
|
| |
| |
|
|
| self.colors_dict = { |
| 'red': np.array([0.5, 0.2, 0.2]), |
| 'pink': np.array([0.7, 0.5, 0.5]), |
| 'neutral': np.array([0.7, 0.7, 0.6]), |
| |
| 'purple': np.array([0.55, 0.4, 0.9]), |
| 'green': np.array([0.5, 0.55, 0.3]), |
| 'sky': np.array([0.3, 0.5, 0.55]), |
| 'white': np.array([1.0, 0.98, 0.94]), |
| } |
|
|
| def __call__( |
| self, |
| verts, |
| faces=None, |
| img=np.zeros((224, 224, 3)), |
| cam=np.array([1, 0, 0]), |
| focal_length=[5000, 5000], |
| camera_rotation=np.eye(3), |
| crop_info=None, |
| angle=None, |
| axis=None, |
| mesh_filename=None, |
| color_type=None, |
| color=[1.0, 1.0, 0.9], |
| iwp_mode=True, |
| crop_img=True, |
| mesh_type='smpl', |
| scale_ratio=1., |
| rgba_mode=False |
| ): |
|
|
| if faces is None: |
| faces = self.faces[mesh_type] |
| mesh = trimesh.Trimesh(vertices=verts, faces=faces, process=False) |
|
|
| Rx = trimesh.transformations.rotation_matrix(math.radians(180), [1, 0, 0]) |
| mesh.apply_transform(Rx) |
|
|
| if mesh_filename is not None: |
| mesh.export(mesh_filename) |
|
|
| if angle and axis: |
| R = trimesh.transformations.rotation_matrix(math.radians(angle), axis) |
| mesh.apply_transform(R) |
|
|
| cam = cam.copy() |
| if iwp_mode: |
| resolution = np.array(img.shape[:2]) * scale_ratio |
| if len(cam) == 4: |
| sx, sy, tx, ty = cam |
| |
| camera_translation = np.array( |
| [tx, ty, 2 * focal_length[0] / (resolution[0] * sy + 1e-9)] |
| ) |
| elif len(cam) == 3: |
| sx, tx, ty = cam |
| sy = sx |
| camera_translation = np.array( |
| [-tx, ty, 2 * focal_length[0] / (resolution[0] * sy + 1e-9)] |
| ) |
| render_res = resolution |
| self.renderer.viewport_width = render_res[1] |
| self.renderer.viewport_height = render_res[0] |
| else: |
| if crop_info['opt_cam_t'] is None: |
| camera_translation = convert_to_full_img_cam( |
| pare_cam=cam[None], |
| bbox_height=crop_info['bbox_scale'] * 200., |
| bbox_center=crop_info['bbox_center'], |
| img_w=crop_info['img_w'], |
| img_h=crop_info['img_h'], |
| focal_length=focal_length[0], |
| ) |
| else: |
| camera_translation = crop_info['opt_cam_t'] |
| if torch.is_tensor(camera_translation): |
| camera_translation = camera_translation[0].cpu().numpy() |
| camera_translation = camera_translation.copy() |
| camera_translation[0] *= -1 |
| if 'img_h' in crop_info and 'img_w' in crop_info: |
| render_res = (int(crop_info['img_h'][0]), int(crop_info['img_w'][0])) |
| else: |
| render_res = img.shape[:2] if type(img) is not list else img[0].shape[:2] |
| self.renderer.viewport_width = render_res[1] |
| self.renderer.viewport_height = render_res[0] |
| camera_rotation = camera_rotation.T |
| camera = pyrender.IntrinsicsCamera( |
| fx=focal_length[0], fy=focal_length[1], cx=render_res[1] / 2., cy=render_res[0] / 2. |
| ) |
|
|
| if color_type != None: |
| color = self.colors_dict[color_type] |
|
|
| material = pyrender.MetallicRoughnessMaterial( |
| metallicFactor=0.2, |
| roughnessFactor=0.6, |
| alphaMode='OPAQUE', |
| baseColorFactor=(color[0], color[1], color[2], 1.0) |
| ) |
|
|
| mesh = pyrender.Mesh.from_trimesh(mesh, material=material) |
|
|
| mesh_node = self.scene.add(mesh, 'mesh') |
|
|
| camera_pose = np.eye(4) |
| camera_pose[:3, :3] = camera_rotation |
| camera_pose[:3, 3] = camera_rotation @ camera_translation |
| cam_node = self.scene.add(camera, pose=camera_pose) |
|
|
| if self.wireframe: |
| render_flags = RenderFlags.RGBA | RenderFlags.ALL_WIREFRAME | RenderFlags.SHADOWS_SPOT |
| else: |
| render_flags = RenderFlags.RGBA | RenderFlags.SHADOWS_SPOT |
|
|
| rgb, _ = self.renderer.render(self.scene, flags=render_flags) |
| if crop_info is not None and crop_img: |
| crop_res = img.shape[:2] |
| rgb, _, _ = crop(rgb, crop_info['bbox_center'][0], crop_info['bbox_scale'][0], crop_res) |
|
|
| valid_mask = (rgb[:, :, -1] > 0)[:, :, np.newaxis] |
|
|
| image_list = [img] if type(img) is not list else img |
|
|
| return_img = [] |
| for item in image_list: |
| if scale_ratio != 1: |
| orig_size = item.shape[:2] |
| item = resize( |
| item, (orig_size[0] * scale_ratio, orig_size[1] * scale_ratio), |
| anti_aliasing=True |
| ) |
| item = (item * 255).astype(np.uint8) |
| output_img = rgb[:, :, :-1] * valid_mask * self.vis_ratio + ( |
| 1 - valid_mask * self.vis_ratio |
| ) * item |
| |
| |
| |
| if rgba_mode: |
| output_img_rgba = np.zeros((output_img.shape[0], output_img.shape[1], 4)) |
| output_img_rgba[:, :, :3] = output_img |
| output_img_rgba[:, :, 3][valid_mask[:, :, 0]] = 255 |
| output_img = output_img_rgba.astype(np.uint8) |
| image = output_img.astype(np.uint8) |
| return_img.append(image) |
| return_img.append(item) |
|
|
| if type(img) is not list: |
| |
| return_img = return_img[0] |
|
|
| self.scene.remove_node(mesh_node) |
| self.scene.remove_node(cam_node) |
|
|
| return return_img |
|
|
|
|
| class OpenDRenderer: |
| def __init__(self, resolution=(224, 224), ratio=1): |
| self.resolution = (resolution[0] * ratio, resolution[1] * ratio) |
| self.ratio = ratio |
| self.focal_length = 5000. |
| self.K = np.array( |
| [ |
| [self.focal_length, 0., self.resolution[1] / 2.], |
| [0., self.focal_length, self.resolution[0] / 2.], [0., 0., 1.] |
| ] |
| ) |
| self.colors_dict = { |
| 'red': np.array([0.5, 0.2, 0.2]), |
| 'pink': np.array([0.7, 0.5, 0.5]), |
| 'neutral': np.array([0.7, 0.7, 0.6]), |
| 'purple': np.array([0.5, 0.5, 0.7]), |
| 'green': np.array([0.5, 0.55, 0.3]), |
| 'sky': np.array([0.3, 0.5, 0.55]), |
| 'white': np.array([1.0, 0.98, 0.94]), |
| } |
| self.renderer = ColoredRenderer() |
| self.faces = get_smpl_faces() |
|
|
| def reset_res(self, resolution): |
| self.resolution = (resolution[0] * self.ratio, resolution[1] * self.ratio) |
| self.K = np.array( |
| [ |
| [self.focal_length, 0., self.resolution[1] / 2.], |
| [0., self.focal_length, self.resolution[0] / 2.], [0., 0., 1.] |
| ] |
| ) |
|
|
| def __call__( |
| self, |
| verts, |
| faces=None, |
| color=None, |
| color_type='white', |
| R=None, |
| mesh_filename=None, |
| img=np.zeros((224, 224, 3)), |
| cam=np.array([1, 0, 0]), |
| rgba=False, |
| addlight=True |
| ): |
| '''Render mesh using OpenDR |
| verts: shape - (V, 3) |
| faces: shape - (F, 3) |
| img: shape - (224, 224, 3), range - [0, 255] (np.uint8) |
| axis: rotate along with X/Y/Z axis (by angle) |
| R: rotation matrix (used to manipulate verts) shape - [3, 3] |
| Return: |
| rendered img: shape - (224, 224, 3), range - [0, 255] (np.uint8) |
| ''' |
| |
| rn = self.renderer |
| h, w = self.resolution |
| K = self.K |
|
|
| f = np.array([K[0, 0], K[1, 1]]) |
| c = np.array([K[0, 2], K[1, 2]]) |
|
|
| if faces is None: |
| faces = self.faces |
| if len(cam) == 4: |
| t = np.array([cam[2], cam[3], 2 * K[0, 0] / (w * cam[0] + 1e-9)]) |
| elif len(cam) == 3: |
| t = np.array([cam[1], cam[2], 2 * K[0, 0] / (w * cam[0] + 1e-9)]) |
|
|
| rn.camera = ProjectPoints(rt=np.array([0, 0, 0]), t=t, f=f, c=c, k=np.zeros(5)) |
| rn.frustum = {'near': 1., 'far': 1000., 'width': w, 'height': h} |
|
|
| albedo = np.ones_like(verts) * .9 |
|
|
| if color is not None: |
| color0 = np.array(color) |
| color1 = np.array(color) |
| color2 = np.array(color) |
| elif color_type == 'white': |
| color0 = np.array([1., 1., 1.]) |
| color1 = np.array([1., 1., 1.]) |
| color2 = np.array([0.7, 0.7, 0.7]) |
| color = np.ones_like(verts) * self.colors_dict[color_type][None, :] |
| else: |
| color0 = self.colors_dict[color_type] * 1.2 |
| color1 = self.colors_dict[color_type] * 1.2 |
| color2 = self.colors_dict[color_type] * 1.2 |
| color = np.ones_like(verts) * self.colors_dict[color_type][None, :] |
|
|
| |
| if R is not None: |
| assert R.shape == (3, 3), "Shape of rotation matrix should be (3, 3)" |
| verts = np.dot(verts, R) |
|
|
| rn.set(v=verts, f=faces, vc=color, bgcolor=np.zeros(3)) |
|
|
| if addlight: |
| yrot = np.radians(120) |
| |
| rn.vc = LambertianPointLight( |
| f=rn.f, |
| v=rn.v, |
| num_verts=len(rn.v), |
| light_pos=rotateY(np.array([-200, -100, -100]), yrot), |
| vc=albedo, |
| light_color=color0 |
| ) |
|
|
| |
| rn.vc += LambertianPointLight( |
| f=rn.f, |
| v=rn.v, |
| num_verts=len(rn.v), |
| light_pos=rotateY(np.array([800, 10, 300]), yrot), |
| vc=albedo, |
| light_color=color1 |
| ) |
|
|
| |
| rn.vc += LambertianPointLight( |
| f=rn.f, |
| v=rn.v, |
| num_verts=len(rn.v), |
| light_pos=rotateY(np.array([-500, 500, 1000]), yrot), |
| vc=albedo, |
| light_color=color2 |
| ) |
|
|
| rendered_image = rn.r |
| visibility_image = rn.visibility_image |
|
|
| image_list = [img] if type(img) is not list else img |
|
|
| return_img = [] |
| for item in image_list: |
| if self.ratio != 1: |
| img_resized = resize( |
| item, (item.shape[0] * self.ratio, item.shape[1] * self.ratio), |
| anti_aliasing=True |
| ) |
| else: |
| img_resized = item / 255. |
|
|
| try: |
| img_resized[visibility_image != (2**32 - 1) |
| ] = rendered_image[visibility_image != (2**32 - 1)] |
| except: |
| logger.warning('Can not render mesh.') |
|
|
| img_resized = (img_resized * 255).astype(np.uint8) |
| res = img_resized |
|
|
| if rgba: |
| img_resized_rgba = np.zeros((img_resized.shape[0], img_resized.shape[1], 4)) |
| img_resized_rgba[:, :, :3] = img_resized |
| img_resized_rgba[:, :, 3][visibility_image != (2**32 - 1)] = 255 |
| res = img_resized_rgba.astype(np.uint8) |
| return_img.append(res) |
|
|
| if type(img) is not list: |
| return_img = return_img[0] |
|
|
| return return_img |
|
|
|
|
| |
| def rotateY(points, angle): |
| """Rotate all points in a 2D array around the y axis.""" |
| ry = np.array( |
| [[np.cos(angle), 0., np.sin(angle)], [0., 1., 0.], [-np.sin(angle), 0., |
| np.cos(angle)]] |
| ) |
| return np.dot(points, ry) |
|
|
|
|
| def rotateX(points, angle): |
| """Rotate all points in a 2D array around the x axis.""" |
| rx = np.array( |
| [[1., 0., 0.], [0., np.cos(angle), -np.sin(angle)], [0., np.sin(angle), |
| np.cos(angle)]] |
| ) |
| return np.dot(points, rx) |
|
|
|
|
| def rotateZ(points, angle): |
| """Rotate all points in a 2D array around the z axis.""" |
| rz = np.array( |
| [[np.cos(angle), -np.sin(angle), 0.], [np.sin(angle), np.cos(angle), 0.], [0., 0., 1.]] |
| ) |
| return np.dot(points, rz) |
|
|
|
|
| class IUV_Renderer(object): |
| def __init__( |
| self, |
| focal_length=5000., |
| orig_size=224, |
| output_size=56, |
| mode='iuv', |
| device=torch.device('cuda'), |
| mesh_type='smpl' |
| ): |
|
|
| self.focal_length = focal_length |
| self.orig_size = orig_size |
| self.output_size = output_size |
|
|
| if mode in ['iuv']: |
| if mesh_type == 'smpl': |
| DP = DensePoseMethods() |
|
|
| vert_mapping = DP.All_vertices.astype('int64') - 1 |
| self.vert_mapping = torch.from_numpy(vert_mapping) |
|
|
| faces = DP.FacesDensePose |
| faces = faces[None, :, :] |
| self.faces = torch.from_numpy( |
| faces.astype(np.int32) |
| ) |
|
|
| num_part = float(np.max(DP.FaceIndices)) |
| self.num_part = num_part |
|
|
| dp_vert_pid_fname = 'data/dp_vert_pid.npy' |
| if os.path.exists(dp_vert_pid_fname): |
| dp_vert_pid = list(np.load(dp_vert_pid_fname)) |
| else: |
| print('creating data/dp_vert_pid.npy') |
| dp_vert_pid = [] |
| for v in range(len(vert_mapping)): |
| for i, f in enumerate(DP.FacesDensePose): |
| if v in f: |
| dp_vert_pid.append(DP.FaceIndices[i]) |
| break |
| np.save(dp_vert_pid_fname, np.array(dp_vert_pid)) |
|
|
| textures_vts = np.array( |
| [ |
| (dp_vert_pid[i] / num_part, DP.U_norm[i], DP.V_norm[i]) |
| for i in range(len(vert_mapping)) |
| ] |
| ) |
| self.textures_vts = torch.from_numpy( |
| textures_vts[None].astype(np.float32) |
| ) |
| elif mode == 'pncc': |
| self.vert_mapping = None |
| self.faces = torch.from_numpy( |
| get_model_faces(mesh_type)[None].astype(np.int32) |
| ) |
| textures_vts = get_model_tpose(mesh_type).unsqueeze( |
| 0 |
| ) |
|
|
| texture_min = torch.min(textures_vts) - 0.001 |
| texture_range = torch.max(textures_vts) - texture_min + 0.001 |
| self.textures_vts = (textures_vts - texture_min) / texture_range |
| elif mode in ['seg']: |
| self.vert_mapping = None |
| body_model = 'smpl' |
|
|
| self.faces = torch.from_numpy(get_smpl_faces().astype(np.int32)[None]) |
|
|
| with open( |
| os.path.join( |
| path_config.SMPL_MODEL_DIR, '{}_vert_segmentation.json'.format(body_model) |
| ), 'rb' |
| ) as json_file: |
| smpl_part_id = json.load(json_file) |
|
|
| v_id = [] |
| for k in smpl_part_id.keys(): |
| v_id.extend(smpl_part_id[k]) |
|
|
| v_id = torch.tensor(v_id) |
| n_verts = len(torch.unique(v_id)) |
| num_part = len(constants.SMPL_PART_ID.keys()) |
| self.num_part = num_part |
|
|
| seg_vert_pid = np.zeros(n_verts) |
| for k in smpl_part_id.keys(): |
| seg_vert_pid[smpl_part_id[k]] = constants.SMPL_PART_ID[k] |
|
|
| print('seg_vert_pid', seg_vert_pid.shape) |
| textures_vts = seg_vert_pid[:, None].repeat(3, axis=1) / num_part |
| print('textures_vts', textures_vts.shape) |
| |
| |
| |
| self.textures_vts = torch.from_numpy(textures_vts[None].astype(np.float32)) |
|
|
| K = np.array( |
| [ |
| [self.focal_length, 0., self.orig_size / 2.], |
| [0., self.focal_length, self.orig_size / 2.], [0., 0., 1.] |
| ] |
| ) |
|
|
| R = np.array([[-1., 0., 0.], [0., -1., 0.], [0., 0., 1.]]) |
|
|
| t = np.array([0, 0, 5]) |
|
|
| if self.orig_size != 224: |
| rander_scale = self.orig_size / float(224) |
| K[0, 0] *= rander_scale |
| K[1, 1] *= rander_scale |
| K[0, 2] *= rander_scale |
| K[1, 2] *= rander_scale |
|
|
| self.K = torch.FloatTensor(K[None, :, :]) |
| self.R = torch.FloatTensor(R[None, :, :]) |
| self.t = torch.FloatTensor(t[None, None, :]) |
|
|
| camK = F.pad(self.K, (0, 1, 0, 1), "constant", 0) |
| camK[:, 2, 2] = 0 |
| camK[:, 3, 2] = 1 |
| camK[:, 2, 3] = 1 |
|
|
| self.K = camK |
|
|
| self.device = device |
| lights = AmbientLights(device=self.device) |
|
|
| raster_settings = RasterizationSettings( |
| image_size=output_size, |
| blur_radius=0, |
| faces_per_pixel=1, |
| ) |
| self.renderer = MeshRenderer( |
| rasterizer=MeshRasterizer(raster_settings=raster_settings), |
| shader=HardFlatShader( |
| device=self.device, |
| lights=lights, |
| blend_params=BlendParams(background_color=[0, 0, 0], sigma=0.0, gamma=0.0) |
| ) |
| ) |
|
|
| def camera_matrix(self, cam): |
| batch_size = cam.size(0) |
|
|
| K = self.K.repeat(batch_size, 1, 1) |
| R = self.R.repeat(batch_size, 1, 1) |
| t = torch.stack( |
| [-cam[:, 1], -cam[:, 2], 2 * self.focal_length / (self.orig_size * cam[:, 0] + 1e-9)], |
| dim=-1 |
| ) |
|
|
| if cam.is_cuda: |
| |
| K = K.to(cam.device) |
| R = R.to(cam.device) |
| t = t.to(cam.device) |
|
|
| return K, R, t |
|
|
| def verts2iuvimg(self, verts, cam, iwp_mode=True): |
| batch_size = verts.size(0) |
|
|
| K, R, t = self.camera_matrix(cam) |
|
|
| if self.vert_mapping is None: |
| vertices = verts |
| else: |
| vertices = verts[:, self.vert_mapping, :] |
|
|
| mesh = Meshes(vertices, self.faces.to(verts.device).expand(batch_size, -1, -1)) |
| mesh.textures = TexturesVertex( |
| verts_features=self.textures_vts.to(verts.device).expand(batch_size, -1, -1) |
| ) |
|
|
| cameras = PerspectiveCameras( |
| device=verts.device, |
| R=R, |
| T=t, |
| K=K, |
| in_ndc=False, |
| image_size=[(self.orig_size, self.orig_size)] |
| ) |
|
|
| iuv_image = self.renderer(mesh, cameras=cameras) |
| iuv_image = iuv_image[..., :3].permute(0, 3, 1, 2) |
|
|
| return iuv_image |
|
|