import sys sys.path.append('./extern/dust3r') from dust3r.inference import inference, load_model from dust3r.utils.image import load_images from dust3r.image_pairs import make_pairs from dust3r.cloud_opt import global_aligner, GlobalAlignerMode from dust3r.utils.device import to_numpy import trimesh import torch import numpy as np import torchvision import os import copy import cv2 from PIL import Image import pytorch3d print(pytorch3d.__version__) from pytorch3d.structures import Pointclouds from torchvision.utils import save_image import torch.nn.functional as F import torchvision.transforms as transforms from PIL import Image from utils.pvd_utils import * from omegaconf import OmegaConf from pytorch_lightning import seed_everything from utils.diffusion_utils import instantiate_from_config,load_model_checkpoint,image_guided_synthesis from pathlib import Path from torchvision.utils import save_image class ViewCrafter: def __init__(self, opts, gradio = False): self.opts = opts self.device = opts.device self.setup_dust3r() self.setup_diffusion() #fixme # initialize ref images, pcd if not gradio: self.images, self.img_ori = self.load_initial_images(image_dir=self.opts.image_dir) self.run_dust3r(input_images=self.images) def run_dust3r(self, input_images,clean_pc = False): pairs = make_pairs(input_images, scene_graph='complete', prefilter=None, symmetrize=True) output = inference(pairs, self.dust3r, self.device, batch_size=self.opts.batch_size) mode = GlobalAlignerMode.PointCloudOptimizer #if len(self.images) > 2 else GlobalAlignerMode.PairViewer scene = global_aligner(output, device=self.device, mode=mode) if mode == GlobalAlignerMode.PointCloudOptimizer: loss = scene.compute_global_alignment(init='mst', niter=self.opts.niter, schedule=self.opts.schedule, lr=self.opts.lr) if clean_pc: self.scene = scene.clean_pointcloud() else: self.scene = scene def render_pcd(self,pts3d,imgs,masks,views,renderer,device): imgs = to_numpy(imgs) pts3d = to_numpy(pts3d) if masks == None: pts = torch.from_numpy(np.concatenate([p for p in pts3d])).view(-1, 3).to(device) col = torch.from_numpy(np.concatenate([p for p in imgs])).view(-1, 3).to(device) else: # masks = to_numpy(masks) pts = torch.from_numpy(np.concatenate([p[m] for p, m in zip(pts3d, masks)])).to(device) col = torch.from_numpy(np.concatenate([p[m] for p, m in zip(imgs, masks)])).to(device) color_mask = torch.ones(col.shape).to(device) # point_cloud_mask = Pointclouds(points=[pts],features=[color_mask]).extend(views) point_cloud = Pointclouds(points=[pts], features=[col]).extend(views) images = renderer(point_cloud) # view_masks = renderer(point_cloud_mask) return images, None def run_render(self, pcd, imgs,masks, H, W, camera_traj,num_views,use_cpu=False): if use_cpu: device = torch.device("cpu") else: device = self.device render_setup = setup_renderer(camera_traj, image_size=(H,W)) renderer = render_setup['renderer'] render_results, viewmask = self.render_pcd(pcd, imgs, masks, num_views,renderer,device) return render_results, viewmask def run_diffusion(self, renderings): prompts = [self.opts.prompt] videos = (renderings * 2. - 1.).permute(3,0,1,2).unsqueeze(0).to(self.device) condition_index = [0] with torch.no_grad(), torch.cuda.amp.autocast(): # [1,1,c,t,h,w] batch_samples = image_guided_synthesis(self.diffusion, prompts, videos, self.noise_shape, self.opts.n_samples, self.opts.ddim_steps, self.opts.ddim_eta, \ self.opts.unconditional_guidance_scale, self.opts.cfg_img, self.opts.frame_stride, self.opts.text_input, self.opts.multiple_cond_cfg, self.opts.timestep_spacing, self.opts.guidance_rescale, condition_index) # save_results_seperate(batch_samples[0], self.opts.save_dir, fps=8) # torch.Size([1, 3, 25, 576, 1024]) [-1,1] return torch.clamp(batch_samples[0][0].permute(1,2,3,0), -1., 1.) def nvs_single_view(self, gradio=False): # 最后一个view为 0 pose c2ws = self.scene.get_im_poses().detach()[1:] principal_points = self.scene.get_principal_points().detach()[1:] #cx cy focals = self.scene.get_focals().detach()[1:] shape = self.images[0]['true_shape'] H, W = int(shape[0][0]), int(shape[0][1]) pcd = [i.detach() for i in self.scene.get_pts3d(clip_thred=self.opts.dpt_trd)] # a list of points of size whc depth = [i.detach() for i in self.scene.get_depthmaps()] depth_avg = depth[-1][H//2,W//2] #以图像中心处的depth(z)为球心旋转 radius = depth_avg*self.opts.center_scale #缩放调整 ## change coordinate c2ws,pcd = world_point_to_obj(poses=c2ws, points=torch.stack(pcd), k=-1, r=radius, elevation=self.opts.elevation, device=self.device) imgs = np.array(self.scene.imgs) masks = None if self.opts.mode == 'single_view_nbv': ## 输入candidate->渲染mask->最大mask对应的pose作为nbv ## nbv模式下self.opts.d_theta[0], self.opts.d_phi[0]代表search space中的网格theta, phi之间的间距; self.opts.d_phi[0]的符号代表方向,分为左右两个方向 ## FIXME hard coded candidate view数量, 以left为例,第一次迭代从[左,左上]中选取, 从第二次开始可以从[左,左上,左下]中选取 num_candidates = 2 candidate_poses,thetas,phis = generate_candidate_poses(c2ws, H, W, focals, principal_points, self.opts.d_theta[0], self.opts.d_phi[0],num_candidates, self.device) _, viewmask = self.run_render([pcd[-1]], [imgs[-1]],masks, H, W, candidate_poses,num_candidates,use_cpu=False) nbv_id = torch.argmin(viewmask.sum(dim=[1,2,3])).item() save_image( viewmask.permute(0,3,1,2), os.path.join(self.opts.save_dir,f"candidate_mask0_nbv{nbv_id}.png"), normalize=True, value_range=(0, 1)) theta_nbv = thetas[nbv_id] phi_nbv = phis[nbv_id] # generate camera trajectory from T_curr to T_nbv camera_traj,num_views = generate_traj_specified(c2ws, H, W, focals, principal_points, theta_nbv, phi_nbv, self.opts.d_r[0],self.opts.video_length, self.device) # 重置elevation self.opts.elevation -= theta_nbv elif self.opts.mode == 'single_view_target': camera_traj,num_views = generate_traj_specified(c2ws, H, W, focals, principal_points, self.opts.d_theta[0], self.opts.d_phi[0], self.opts.d_r[0],self.opts.video_length, self.device) elif self.opts.mode == 'single_view_txt': if not gradio: with open(self.opts.traj_txt, 'r') as file: lines = file.readlines() phi = [float(i) for i in lines[0].split()] theta = [float(i) for i in lines[1].split()] r = [float(i) for i in lines[2].split()] else: phi, theta, r = self.gradio_traj # device = torch.device("cpu") device = self.device camera_traj,num_views = generate_traj_txt(c2ws, H, W, focals, principal_points, phi, theta, r,self.opts.video_length, device,viz_traj=True, save_dir = self.opts.save_dir) # camera_traj,num_views = generate_traj_txt(c2ws, H, W, focals, principal_points, phi, theta, r,self.opts.video_length, self.device,viz_traj=True, save_dir = self.opts.save_dir) else: raise KeyError(f"Invalid Mode: {self.opts.mode}") render_results, viewmask = self.run_render([pcd[-1]], [imgs[-1]],masks, H, W, camera_traj,num_views,use_cpu=False) render_results = render_results.to(self.device) render_results = F.interpolate(render_results.permute(0,3,1,2), size=(576, 1024), mode='bilinear', align_corners=False).permute(0,2,3,1) render_results[0] = self.img_ori if self.opts.mode == 'single_view_txt': if phi[-1]==0. and theta[-1]==0. and r[-1]==0.: render_results[-1] = self.img_ori # torch.Size([25, 576, 1024, 3]), [0,1] # save_pointcloud_with_normals([imgs[-1]], [pcd[-1]], msk=None, save_path=os.path.join(self.opts.save_dir,'pcd0.ply') , mask_pc=False, reduce_pc=False) diffusion_results = self.run_diffusion(render_results) save_video((diffusion_results + 1.0) / 2.0, os.path.join(self.opts.save_dir, 'diffusion0.mp4')) return render_results def nvs_sparse_view(self,iter): c2ws = self.scene.get_im_poses().detach() principal_points = self.scene.get_principal_points().detach() focals = self.scene.get_focals().detach() shape = self.images[0]['true_shape'] H, W = int(shape[0][0]), int(shape[0][1]) pcd = [i.detach() for i in self.scene.get_pts3d(clip_thred=self.opts.dpt_trd)] # a list of points of size whc depth = [i.detach() for i in self.scene.get_depthmaps()] depth_avg = depth[0][H//2,W//2] #以ref图像中心处的depth(z)为球心旋转 radius = depth_avg*self.opts.center_scale #缩放调整 ## masks for cleaner point cloud self.scene.min_conf_thr = float(self.scene.conf_trf(torch.tensor(self.opts.min_conf_thr))) masks = self.scene.get_masks() depth = self.scene.get_depthmaps() bgs_mask = [dpt > self.opts.bg_trd*(torch.max(dpt[40:-40,:])+torch.min(dpt[40:-40,:])) for dpt in depth] masks_new = [m+mb for m, mb in zip(masks,bgs_mask)] masks = to_numpy(masks_new) ## render, 从c2ws[0]即ref image对应的相机开始 imgs = np.array(self.scene.imgs) if self.opts.mode == 'single_view_ref_iterative': c2ws,pcd = world_point_to_obj(poses=c2ws, points=torch.stack(pcd), k=0, r=radius, elevation=self.opts.elevation, device=self.device) camera_traj,num_views = generate_traj_specified(c2ws[0:1], H, W, focals[0:1], principal_points[0:1], self.opts.d_theta[iter], self.opts.d_phi[iter], self.opts.d_r[iter],self.opts.video_length, self.device) render_results, viewmask = self.run_render(pcd, imgs,masks, H, W, camera_traj,num_views) render_results = F.interpolate(render_results.permute(0,3,1,2), size=(576, 1024), mode='bilinear', align_corners=False).permute(0,2,3,1) render_results[0] = self.img_ori elif self.opts.mode == 'single_view_1drc_iterative': self.opts.elevation -= self.opts.d_theta[iter-1] c2ws,pcd = world_point_to_obj(poses=c2ws, points=torch.stack(pcd), k=-1, r=radius, elevation=self.opts.elevation, device=self.device) camera_traj,num_views = generate_traj_specified(c2ws[-1:], H, W, focals[-1:], principal_points[-1:], self.opts.d_theta[iter], self.opts.d_phi[iter], self.opts.d_r[iter],self.opts.video_length, self.device) render_results, viewmask = self.run_render(pcd, imgs,masks, H, W, camera_traj,num_views) render_results = F.interpolate(render_results.permute(0,3,1,2), size=(576, 1024), mode='bilinear', align_corners=False).permute(0,2,3,1) render_results[0] = (self.images[-1]['img_ori'].squeeze(0).permute(1,2,0)+1.)/2. elif self.opts.mode == 'single_view_nbv': c2ws,pcd = world_point_to_obj(poses=c2ws, points=torch.stack(pcd), k=-1, r=radius, elevation=self.opts.elevation, device=self.device) ## 输入candidate->渲染mask->最大mask对应的pose作为nbv ## nbv模式下self.opts.d_theta[0], self.opts.d_phi[0]代表search space中的网格theta, phi之间的间距; self.opts.d_phi[0]的符号代表方向,分为左右两个方向 ## FIXME hard coded candidate view数量, 以left为例,第一次迭代从[左,左上]中选取, 从第二次开始可以从[左,左上,左下]中选取 num_candidates = 3 candidate_poses,thetas,phis = generate_candidate_poses(c2ws[-1:], H, W, focals[-1:], principal_points[-1:], self.opts.d_theta[0], self.opts.d_phi[0], num_candidates, self.device) _, viewmask = self.run_render(pcd, imgs,masks, H, W, candidate_poses,num_candidates) nbv_id = torch.argmin(viewmask.sum(dim=[1,2,3])).item() save_image(viewmask.permute(0,3,1,2), os.path.join(self.opts.save_dir,f"candidate_mask{iter}_nbv{nbv_id}.png"), normalize=True, value_range=(0, 1)) theta_nbv = thetas[nbv_id] phi_nbv = phis[nbv_id] # generate camera trajectory from T_curr to T_nbv camera_traj,num_views = generate_traj_specified(c2ws[-1:], H, W, focals[-1:], principal_points[-1:], theta_nbv, phi_nbv, self.opts.d_r[0],self.opts.video_length, self.device) # 重置elevation self.opts.elevation -= theta_nbv render_results, viewmask = self.run_render(pcd, imgs,masks, H, W, camera_traj,num_views) render_results = F.interpolate(render_results.permute(0,3,1,2), size=(576, 1024), mode='bilinear', align_corners=False).permute(0,2,3,1) render_results[0] = (self.images[-1]['img_ori'].squeeze(0).permute(1,2,0)+1.)/2. else: raise KeyError(f"Invalid Mode: {self.opts.mode}") save_video(render_results, os.path.join(self.opts.save_dir, f'render{iter}.mp4')) save_pointcloud_with_normals(imgs, pcd, msk=masks, save_path=os.path.join(self.opts.save_dir, f'pcd{iter}.ply') , mask_pc=True, reduce_pc=False) diffusion_results = self.run_diffusion(render_results) save_video((diffusion_results + 1.0) / 2.0, os.path.join(self.opts.save_dir, f'diffusion{iter}.mp4')) # torch.Size([25, 576, 1024, 3]) return diffusion_results def nvs_single_view_ref_iterative(self): all_results = [] sample_rate = 6 idx = 1 #初始包含1张ref image for itr in range(0, len(self.opts.d_phi)): if itr == 0: self.images = [self.images[0]] #去掉后一份copy diffusion_results_itr = self.nvs_single_view() # diffusion_results_itr = torch.randn([25, 576, 1024, 3]).to(self.device) diffusion_results_itr = diffusion_results_itr.permute(0,3,1,2) all_results.append(diffusion_results_itr) else: for i in range(0+sample_rate, diffusion_results_itr.shape[0], sample_rate): self.images.append(get_input_dict(diffusion_results_itr[i:i+1,...],idx,dtype = torch.float32)) idx += 1 self.run_dust3r(input_images=self.images, clean_pc=True) diffusion_results_itr = self.nvs_sparse_view(itr) # diffusion_results_itr = torch.randn([25, 576, 1024, 3]).to(self.device) diffusion_results_itr = diffusion_results_itr.permute(0,3,1,2) all_results.append(diffusion_results_itr) return all_results def nvs_single_view_1drc_iterative(self): all_results = [] sample_rate = 6 idx = 1 #初始包含1张ref image for itr in range(0, len(self.opts.d_phi)): if itr == 0: self.images = [self.images[0]] #去掉后一份copy diffusion_results_itr = self.nvs_single_view() # diffusion_results_itr = torch.randn([25, 576, 1024, 3]).to(self.device) diffusion_results_itr = diffusion_results_itr.permute(0,3,1,2) all_results.append(diffusion_results_itr) else: for i in range(0+sample_rate, diffusion_results_itr.shape[0], sample_rate): self.images.append(get_input_dict(diffusion_results_itr[i:i+1,...],idx,dtype = torch.float32)) idx += 1 self.run_dust3r(input_images=self.images, clean_pc=True) diffusion_results_itr = self.nvs_sparse_view(itr) # diffusion_results_itr = torch.randn([25, 576, 1024, 3]).to(self.device) diffusion_results_itr = diffusion_results_itr.permute(0,3,1,2) all_results.append(diffusion_results_itr) return all_results def nvs_single_view_nbv(self): # lef and right # d_theta and a_phi 是搜索空间的顶点间隔 all_results = [] ## FIXME: hard coded sample_rate = 6 max_itr = 3 idx = 1 #初始包含1张ref image for itr in range(0, max_itr): if itr == 0: self.images = [self.images[0]] #去掉后一份copy diffusion_results_itr = self.nvs_single_view() # diffusion_results_itr = torch.randn([25, 576, 1024, 3]).to(self.device) diffusion_results_itr = diffusion_results_itr.permute(0,3,1,2) all_results.append(diffusion_results_itr) else: for i in range(0+sample_rate, diffusion_results_itr.shape[0], sample_rate): self.images.append(get_input_dict(diffusion_results_itr[i:i+1,...],idx,dtype = torch.float32)) idx += 1 self.run_dust3r(input_images=self.images, clean_pc=True) diffusion_results_itr = self.nvs_sparse_view(itr) # diffusion_results_itr = torch.randn([25, 576, 1024, 3]).to(self.device) diffusion_results_itr = diffusion_results_itr.permute(0,3,1,2) all_results.append(diffusion_results_itr) return all_results def setup_diffusion(self): seed_everything(self.opts.seed) config = OmegaConf.load(self.opts.config) model_config = config.pop("model", OmegaConf.create()) ## set use_checkpoint as False as when using deepspeed, it encounters an error "deepspeed backend not set" model_config['params']['unet_config']['params']['use_checkpoint'] = False model = instantiate_from_config(model_config) model = model.to(self.device) model.cond_stage_model.device = self.device model.perframe_ae = self.opts.perframe_ae assert os.path.exists(self.opts.ckpt_path), "Error: checkpoint Not Found!" model = load_model_checkpoint(model, self.opts.ckpt_path) model.eval() self.diffusion = model h, w = self.opts.height // 8, self.opts.width // 8 channels = model.model.diffusion_model.out_channels n_frames = self.opts.video_length self.noise_shape = [self.opts.bs, channels, n_frames, h, w] def setup_dust3r(self): self.dust3r = load_model(self.opts.model_path, self.device) def load_initial_images(self, image_dir): ## load images ## dict_keys(['img', 'true_shape', 'idx', 'instance', 'img_ori']),张量形式 images = load_images([image_dir], size=512,force_1024 = True) img_ori = (images[0]['img_ori'].squeeze(0).permute(1,2,0)+1.)/2. # [576,1024,3] [0,1] # img_ori = Image.open(image_dir).convert('RGB') # transform = transforms.Compose([ # transforms.Resize((576, 1024)), # transforms.ToTensor(), # transforms.Normalize((0., 0., 0.), (1., 1., 1.)) # 归一化到[-1,1],如果要归一化到[0,1],请使用transforms.Normalize((0., 0., 0.), (1., 1., 1.)) # ]) # img_ori = transform(img_ori).permute(1,2,0).to(self.device) if len(images) == 1: images = [images[0], copy.deepcopy(images[0])] images[1]['idx'] = 1 return images, img_ori def run_traj(self,i2v_input_image, i2v_elevation, i2v_center_scale, i2v_pose): self.opts.elevation = float(i2v_elevation) self.opts.center_scale = float(i2v_center_scale) i2v_d_phi,i2v_d_theta,i2v_d_r = [i for i in i2v_pose.split(';')] self.gradio_traj = [float(i) for i in i2v_d_phi.split()],[float(i) for i in i2v_d_theta.split()],[float(i) for i in i2v_d_r.split()] transform = transforms.Compose([ transforms.Resize((576,1024)), # transforms.CenterCrop((576,1024)), ]) torch.cuda.empty_cache() img_tensor = torch.from_numpy(i2v_input_image).permute(2, 0, 1).unsqueeze(0).float().to(self.device) img_tensor = (img_tensor / 255. - 0.5) * 2 image_tensor_resized = transform(img_tensor) #1,3,h,w images = get_input_dict(image_tensor_resized,idx = 0,dtype = torch.float32) images = [images, copy.deepcopy(images)] images[1]['idx'] = 1 self.images = images self.img_ori = (image_tensor_resized.squeeze(0).permute(1,2,0) + 1.)/2. # self.images: torch.Size([1, 3, 288, 512]), [-1,1] # self.img_ori: torch.Size([576, 1024, 3]), [0,1] # self.images, self.img_ori = self.load_initial_images(image_dir=i2v_input_image) self.run_dust3r(input_images=self.images) render_results = self.nvs_single_view(gradio=True) save_video(render_results, os.path.join(self.opts.save_dir, 'render0.mp4')) traj_dir = os.path.join(self.opts.save_dir, "viz_traj.mp4") return traj_dir def run_gen(self,i2v_steps, i2v_seed): self.opts.ddim_steps = i2v_steps seed_everything(i2v_seed) render_dir = os.path.join(self.opts.save_dir, 'render0.mp4') video = imageio.get_reader(render_dir, 'ffmpeg') frames = [] for frame in video: frame = frame / 255.0 frames.append(frame) frames = np.array(frames) ##torch.Size([25, 576, 1024, 3]) render_results = torch.from_numpy(frames).to(self.device).half() gen_dir = os.path.join(self.opts.save_dir, "diffusion0.mp4") diffusion_results = self.run_diffusion(render_results) save_video((diffusion_results + 1.0) / 2.0, os.path.join(self.opts.save_dir, 'diffusion0.mp4')) return gen_dir def run_both(self,i2v_input_image, i2v_elevation, i2v_center_scale, i2v_pose,i2v_steps, i2v_seed): self.opts.ddim_steps = i2v_steps seed_everything(i2v_seed) self.opts.elevation = float(i2v_elevation) self.opts.center_scale = float(i2v_center_scale) i2v_d_phi,i2v_d_theta,i2v_d_r = [i for i in i2v_pose.split(';')] self.gradio_traj = [float(i) for i in i2v_d_phi.split()],[float(i) for i in i2v_d_theta.split()],[float(i) for i in i2v_d_r.split()] torch.cuda.empty_cache() img_tensor = torch.from_numpy(i2v_input_image).permute(2, 0, 1).unsqueeze(0).float().to(self.device) img_tensor = (img_tensor / 255. - 0.5) * 2 image_tensor_resized = center_crop_image(img_tensor) #1,3,h,w images = get_input_dict(image_tensor_resized,idx = 0,dtype = torch.float32) images = [images, copy.deepcopy(images)] images[1]['idx'] = 1 self.images = images self.img_ori = (image_tensor_resized.squeeze(0).permute(1,2,0) + 1.)/2. # self.images: torch.Size([1, 3, 288, 512]), [-1,1] # self.img_ori: torch.Size([576, 1024, 3]), [0,1] # self.images, self.img_ori = self.load_initial_images(image_dir=i2v_input_image) self.run_dust3r(input_images=self.images) self.nvs_single_view(gradio=True) traj_dir = os.path.join(self.opts.save_dir, "viz_traj.mp4") gen_dir = os.path.join(self.opts.save_dir, "diffusion0.mp4") return gen_dir,traj_dir,