import taichi_three as t3 import numpy as np from taichi_three.transform import * import math from pathlib import Path from tqdm import tqdm import os import cv2 import pickle import json os.environ["KMP_DUPLICATE_LIB_OK"] = "True" def extr2tf_mat(extr): """ Return C2W matrix in the shape of (4, 4). """ # flip y and z axis in the camera coordinate space, according to # [ns-data conventions](https://docs.nerf.studio/quickstart/data_conventions.html#camera-view-space) trans_mat = np.eye(4) trans_mat[1, 1] = -1 trans_mat[2, 2] = -1 tf_mat = np.stack([extr[0], extr[1], extr[2], [0, 0, 0, 1]], axis=0) tf_mat = trans_mat @ tf_mat tf_mat = np.linalg.inv(tf_mat) return tf_mat def intr2dict(intr: np.ndarray): intr_dict = { "fl_x": intr[0, 0], "fl_y": intr[1, 1], "cx": intr[0, 2], "cy": intr[1, 2], } return intr_dict def save(data_id, save_path, extrs, intrs, depths, imgs, masks): instance_path = os.path.join(save_path, data_id) img_save_path = os.path.join(save_path, data_id, 'images') depth_save_path = os.path.join(save_path, data_id, 'depth') mask_save_path = os.path.join(save_path, data_id, 'mask') Path(img_save_path).mkdir(exist_ok=True, parents=True) Path(mask_save_path).mkdir(exist_ok=True, parents=True) Path(depth_save_path).mkdir(exist_ok=True, parents=True) h, w = imgs[0].shape[:2] transforms_dict = { "w": w, "h": h, "k1": 0, "k2": 0, "p1": 0, "p2": 0, "camera_model": "OPENCV", } frames = [] num_views = len(extrs) for pid in range(num_views): # nyw note: depth is normalized to [0, 1] in taichi_three,so we need to scale it back to [0, 2^15] depth = depths[pid] * 2.0 ** 15 cv2.imwrite(os.path.join(depth_save_path, f'{pid:03d}.png'), depth.astype(np.uint16)) img = (np.clip(imgs[pid], 0, 1) * 255.0 + 0.5).astype(np.uint8)[:, :, ::-1] cv2.imwrite(os.path.join(img_save_path, f'{pid:03d}.jpg'), img) mask = (np.clip(masks[pid], 0, 1) * 255.0 + 0.5).astype(np.uint8) cv2.imwrite(os.path.join(mask_save_path, f'{pid:03d}.png'), mask[:, :, 0]) frame_dict = { "file_path": f"images/{pid:03d}.jpg", "mask_path": f"mask/{pid:03d}.png", "depth_file_path": f"depth/{pid:03d}.png", "transform_matrix": extr2tf_mat(extrs[pid]).tolist(), } intr_dict = intr2dict(intrs[pid]) frame_dict.update(intr_dict) frames.append(frame_dict) transforms_dict["frames"] = frames with open(os.path.join(instance_path, "transforms.json"), "w") as f: json.dump(transforms_dict, f, indent=4) # with open(os.path.join(instance_path, "_transforms.json"), "w") as f: # json.dump(transforms_dict, f, indent=4) def save_normal(data_id, save_path, depths, normals): normal_save_path = os.path.join(save_path, data_id, 'normals') depth_save_path = os.path.join(save_path, data_id, 'depth_smpls') Path(normal_save_path).mkdir(exist_ok=True, parents=True) Path(depth_save_path).mkdir(exist_ok=True, parents=True) num_views = len(depths) for pid in range(num_views): # nyw note: depth is normalized to [0, 1] in taichi_three,so we need to scale it back to [0, 2^15] depth = depths[pid] * 2.0 ** 15 cv2.imwrite(os.path.join(depth_save_path, f'{pid:03d}.png'), depth.astype(np.uint16)) normal = (np.clip(normals[pid], 0, 1) * 255.0 + 0.5).astype(np.uint8)[:, :, ::-1] cv2.imwrite(os.path.join(normal_save_path, f'{pid:03d}.jpg'), normal) class StaticRenderer: def __init__(self): ti.init(arch=ti.cuda, device_memory_fraction=0.8) self.scene = t3.Scene() self.N = 10 def change_all(self): save_obj = [] save_tex = [] for model in self.scene.models: save_obj.append(model.init_obj) save_tex.append(model.init_tex) ti.init(arch=ti.cuda, device_memory_fraction=0.8) print('init') self.scene = t3.Scene() for i in range(len(save_obj)): model = t3.StaticModel(self.N, obj=save_obj[i], tex=save_tex[i]) self.scene.add_model(model) def check_update(self, obj): temp_n = self.N self.N = max(obj['vi'].shape[0], self.N) self.N = max(obj['f'].shape[0], self.N) if not (obj['vt'] is None): self.N = max(obj['vt'].shape[0], self.N) if self.N > temp_n: self.N *= 2 self.change_all() self.camera_light() def add_model(self, obj, tex=None): self.check_update(obj) model = t3.StaticModel(self.N, obj=obj, tex=tex) self.scene.add_model(model) def modify_model(self, index, obj, tex=None): self.check_update(obj) self.scene.models[index].init_obj = obj self.scene.models[index].init_tex = tex self.scene.models[index]._init() def camera_light(self): camera = t3.Camera(res=(1024, 1024)) self.scene.add_camera(camera) camera_hr = t3.Camera(res=(2048, 2048)) self.scene.add_camera(camera_hr) light_dir = np.array([0, 0, 1]) light_list = [] for l in range(6): rotate = np.matmul(rotationX(math.radians(np.random.uniform(-30, 30))), rotationY(math.radians(360 // 6 * l))) dir = [*np.matmul(rotate, light_dir)] light = t3.Light(dir, color=[1.0, 1.0, 1.0]) light_list.append(light) lights = t3.Lights(light_list) self.scene.add_lights(lights) def render_data(renderer, data_path, phase, data_id, save_path, cam_nums, res, dis=1.0, is_thuman=False, is_smpl_model=False, seed_value=0): np.random.seed(seed_value) if not is_smpl_model: obj_path = os.path.join(data_path, phase, data_id, '%s.obj' % data_id) texture_path = data_path img_path = os.path.join(texture_path, phase, data_id, 'material0.jpeg') texture = cv2.imread(img_path)[:, :, ::-1] # ################ nyw add equalizeHist for texture ################ # # comment out the following lines to disable equalizeHist for texture # texture = cv2.cvtColor(texture, cv2.COLOR_RGB2HSV) # texture[:, :, 2] = cv2.equalizeHist(texture[:, :, 2]) * 0.85 # scale down the brightness by 0.85 # texture = cv2.cvtColor(texture, cv2.COLOR_HSV2RGB) # ################ nyw add equalizeHist for texture ################ texture = np.ascontiguousarray(texture) texture = texture.swapaxes(0, 1)[:, ::-1, :] else: # obj_path = '/data1/hezijian/Thuman2.1_GPS/0000.obj' obj_path = '/data1/hezijian/Thuman2.1/THuman2.0_Smpl_X_Paras/%s/mesh_smplx.obj' % data_id obj = t3.readobj(obj_path, scale=1) # height normalization vy_max = np.max(obj['vi'][:, 1]) vy_min = np.min(obj['vi'][:, 1]) human_height = 1.80 + np.random.uniform(-0.05, 0.05, 1) obj['vi'][:, :3] = obj['vi'][:, :3] / (vy_max - vy_min) * human_height obj['vi'][:, 1] -= np.min(obj['vi'][:, 1]) look_at_center = np.array([0, 0.85, 0]) base_cam_pitch = -8 ################ nyw: add multi-pitchs for better reconstruction ################ # base_cam_pitch = -8 cam_pitchs = [-8, 45, -45, 90, -90] cam_nums_for_each_pitch = [cam_nums, cam_nums//2, cam_nums//2, 1, 1] ################ nyw: add multi-pitchs for better reconstruction ################ # randomly move the scan move_range = 0.1 if human_height < 1.80 else 0.05 delta_x = np.max(obj['vi'][:, 0]) - np.min(obj['vi'][:, 0]) delta_z = np.max(obj['vi'][:, 2]) - np.min(obj['vi'][:, 2]) if delta_x > 1.0 or delta_z > 1.0: move_range = 0.01 obj['vi'][:, 0] += np.random.uniform(-move_range, move_range, 1) obj['vi'][:, 2] += np.random.uniform(-move_range, move_range, 1) if len(renderer.scene.models) >= 1: if not is_smpl_model: renderer.modify_model(0, obj, texture) else: renderer.modify_model(0, obj) else: if not is_smpl_model: renderer.add_model(obj, texture) else: renderer.add_model(obj) if is_thuman: # thuman needs a normalization of orientation smpl_path = os.path.join(data_path, 'THuman2.0_Smpl_X_Paras', data_id, 'smplx_param.pkl') with open(smpl_path, 'rb') as f: smpl_para = pickle.load(f) y_orient = smpl_para['global_orient'][0][1] angle_base = (y_orient*180.0/np.pi) # nyw note: generate one instance of thuman in this loop extrs, intrs, depths, imgs, masks, normals = [], [], [], [], [], [] for ci, cam_pitch in enumerate(cam_pitchs): for pid in range(cam_nums := cam_nums_for_each_pitch[ci]): degree_interval = 360 / cam_nums angle = angle_base + pid * degree_interval def render(dis, angle, look_at_center, p, renderer, render_2k=False, render_normal=False): ori_vec = np.array([0, 0, dis]) rotate = np.matmul(rotationY(math.radians(angle)), rotationX(math.radians(p))) fwd = np.matmul(rotate, ori_vec) cam_pos = look_at_center + fwd x_min = 0 y_min = -25 cx = res[0] * 0.5 cy = res[1] * 0.5 fx = res[0] * 0.8 fy = res[1] * 0.8 _cx = cx - x_min _cy = cy - y_min renderer.scene.cameras[0].set_intrinsic(fx, fy, _cx, _cy) renderer.scene.cameras[0].set(pos=cam_pos, target=look_at_center) renderer.scene.cameras[0]._init() if render_2k: fx = res[0] * 0.8 * 2 fy = res[1] * 0.8 * 2 _cx = (res[0] * 0.5 - x_min) * 2 _cy = (res[1] * 0.5 - y_min) * 2 renderer.scene.cameras[1].set_intrinsic(fx, fy, _cx, _cy) renderer.scene.cameras[1].set(pos=cam_pos, target=look_at_center) renderer.scene.cameras[1]._init() renderer.scene.render() camera = renderer.scene.cameras[0] camera_hr = renderer.scene.cameras[1] extrinsic = camera.export_extrinsic() intrinsic = camera.export_intrinsic() depth_map = camera.zbuf.to_numpy().swapaxes(0, 1) img = camera.img.to_numpy().swapaxes(0, 1) img_hr = camera_hr.img.to_numpy().swapaxes(0, 1) mask = camera.mask.to_numpy().swapaxes(0, 1) return extrinsic, intrinsic, depth_map, img, mask, img_hr renderer.scene.render() camera = renderer.scene.cameras[0] extrinsic = camera.export_extrinsic() intrinsic = camera.export_intrinsic() depth_map = camera.zbuf.to_numpy().swapaxes(0, 1) if not render_normal: img = camera.img.to_numpy().swapaxes(0, 1) else: img = camera.normal_map.to_numpy().swapaxes(0, 1) mask = camera.mask.to_numpy().swapaxes(0, 1) return extrinsic, intrinsic, depth_map, img, mask if not is_smpl_model: extr, intr, depth, img, mask = render(dis, angle, look_at_center, cam_pitch, renderer) extrs.append(extr) intrs.append(intr) depths.append(depth) imgs.append(img) masks.append(mask) else: extr, intr, depth, img, mask = render(dis, angle, look_at_center, cam_pitch, renderer, render_normal=True) depths.append(depth) normals.append(img) if not is_smpl_model: save(data_id, save_path, extrs, intrs, depths, imgs, masks) else: save_normal(data_id, save_path, depths, normals) if __name__ == '__main__': cam_nums = 80 scene_radius = 2.0 res = (1024, 1024) thuman_root = '/PATH/TO/Thuman2.1/' save_root = '/PATH/TO/OUTPUT' renderer = StaticRenderer() # for phase in ['train', 'val']: phase = 'all' thuman_list = sorted(os.listdir(os.path.join(thuman_root, phase))) thuman_list = thuman_list[512:513] save_path = os.path.join(save_root, phase) seed_value = np.random.randint(1,1000) for data_id in tqdm(thuman_list): render_data(renderer, thuman_root, phase, data_id, save_path, cam_nums, res, dis=scene_radius, is_thuman=True, seed_value=seed_value) render_data(renderer, thuman_root, phase, data_id, save_path, cam_nums, res, dis=scene_radius, is_thuman=True, is_smpl_model=True, seed_value=seed_value)