本文主要是介绍【视点合成】代码解读:生成demo视频,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
变换工具
def render_3dphoto(src_imgs, # 输入的源图像,维度为 [batch_size, 3, height, width]mpi_all_src, # 输入的所有源图像的MPI,维度为 [batch_size, num_planes, 4, height, width]disparity_all_src, # 所有源图像的视差信息,维度为 [batch_size, num_planes]k_src, # 源相机的内参矩阵,维度为 [batch_size, 3, 3]k_tgt, # 目标相机的内参矩阵,维度为 [batch_size, 3, 3]save_path, # 渲染视频的保存路径
):
disparity 也就是深度。
单应性变换
使用HomographySample变换。utils.mpi.homography_sampler.HomographySample
class HomographySample:"""处理与单应性变换相关的操作,它提供了生成二维网格坐标的功能,计算平面法线,以及将欧拉角转换为旋转矩阵"""def __init__(self, H_tgt, W_tgt, device=None):if device is None:self.device = torch.device("cpu")else:self.device = deviceself.Height_tgt = H_tgtself.Width_tgt = W_tgtself.meshgrid = self.grid_generation(self.Height_tgt, self.Width_tgt, self.device) # 生成目标图像的二维网格坐标self.meshgrid = self.meshgrid.permute(2, 0, 1).contiguous() # 3xHxW 对生成的网格进行维度重排,以匹配 3xHxW 的形状self.n = self.plane_normal_generation(self.device) # 生成平面法线
其中方法:
def euler_to_rotation_matrix(x_angle, y_angle, z_angle, seq='xyz', degrees=False):"""将欧拉角转换为旋转矩阵Note that here we want to return a rotation matrix rot_mtx, which transform the tgt points into src frame,i.e, rot_mtx * p_tgt = p_srcTherefore we need to add negative to x/y/z_angle 由于在欧拉角中,旋转是绕坐标轴的正方向进行的,而我们希望进行的是相反方向的旋转,因此需要对 x/y/z 分量的欧拉角添加负号,以实现从目标点到源坐标系的变换。注意 **这里的欧拉角需要取反,因为我们希望得到的旋转矩阵是将目标点变换到源坐标系的旋转** https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.html:param roll::param pitch::param yaw::return:"""r = Rotation.from_euler(seq,[-x_angle, -y_angle, -z_angle],degrees=degrees)rot_mtx = r.as_matrix().astype(np.float32)# 用于图像的单应性变换,其中 meshgrid 用于定义原始图像中每个像素的坐标,plane_normal_generation 用于定义平面法线(对于单应性变换而言通常是不必要的,但可能用于其他3D变换),# euler_to_rotation_matrix 则用于根据给定的欧拉角生成对应的旋转矩阵。这些操作都是在准备实施单应性变换或其他相关图像几何变换的数据和参数。return rot_mtx
映射前定义网格
@staticmethoddef grid_generation(H, W, device):""":生成网格坐标,用于后续的映射计算。"""# 使用 numpy 生成x和y方向的线性等分点,并通过 np.meshgrid 生成一个二维网格x = np.linspace(0, W - 1, W)y = np.linspace(0, H - 1, H)xv, yv = np.meshgrid(x, y) # HxWxv = torch.from_numpy(xv.astype(np.float32)).to(dtype=torch.float32, device=device)yv = torch.from_numpy(yv.astype(np.float32)).to(dtype=torch.float32, device=device)# 生成一个全为1的张量,与x和y坐标一起堆叠(通道维度)成3通道的网格坐标张量ones = torch.ones_like(xv)meshgrid = torch.stack((xv, yv, ones), dim=2) # HxWx3return meshgrid@staticmethoddef plane_normal_generation(device):"""创建一个表示垂直于xy平面(即z轴方向)的法线向量的张量"""n = torch.tensor([0, 0, 1], dtype=torch.float32, device=device)return n
使用
在utils.utils.render_novel_view
中:
- 计算深度
- 计算视点变换
- 执行变换
- 帧序列到视频
def render_novel_view(mpi_all_rgb_src,mpi_all_sigma_src,disparity_all_src,G_tgt_src,K_src_inv,K_tgt,homography_sampler,
):"""mpi_all_rgb_src, # 来源视角的RGB图像集合mpi_all_sigma_src, # 来源视角的不透明度(sigma)集合disparity_all_src, # 来源视角的视差图集合G_tgt_src, # 目标视角到来源视角的变换矩阵K_src_inv, # 来源视角的相机内参矩阵的逆矩阵K_tgt, # 目标视角的相机内参矩阵homography_sampler, # 用于生成和采样单应性(Homographies)的工具"""# 从源视角的视差图计算出每个平面上点的3D坐标xyz_src_BS3HW = mpi_rendering.get_src_xyz_from_plane_disparity(homography_sampler.meshgrid,disparity_all_src,K_src_inv)# 使用源视角的3D坐标和从目标视角到源视角的变换矩阵 G_tgt_src 来计算目标视角的3D坐标xyz_tgt_BS3HW = mpi_rendering.get_tgt_xyz_from_plane_disparity(xyz_src_BS3HW,G_tgt_src)# 调用 render_tgt_rgb_depth 函数,将源视角的RGB图像、不透明度、视差图和3D坐标映射到目标视角,生成合成的目标视角图像 tgt_imgs_syntgt_imgs_syn, _, _ = mpi_rendering.render_tgt_rgb_depth(homography_sampler,mpi_all_rgb_src,mpi_all_sigma_src,disparity_all_src,xyz_tgt_BS3HW,G_tgt_src,K_src_inv,K_tgt,use_alpha=False,is_bg_depth_inf=False,)return tgt_imgs_syn
生成
函数入口
def render_3dphoto(src_imgs, # [b,3,h,w]mpi_all_src, # [b,s,4,h,w]disparity_all_src, # [b,s]k_src, # [b,3,3]k_tgt, # [b,3,3]save_path,
):"""src_imgs, # 输入的源图像,维度为 [batch_size, 3, height, width]mpi_all_src, # 输入的所有源图像的MPI,维度为 [batch_size, num_planes, 4, height, width]disparity_all_src, # 所有源图像的视差信息,维度为 [batch_size, num_planes]k_src, # 源相机的内参矩阵,维度为 [batch_size, 3, 3]k_tgt, # 目标相机的内参矩阵,维度为 [batch_size, 3, 3]save_path, # 渲染视频的保存路径"""h, w = mpi_all_src.shape[-2:]device = mpi_all_src.devicehomography_sampler = HomographySample(h, w, device) # 生成单应性变换k_src_inv = torch.inverse(k_src) # 计算源相机内参矩阵的逆矩阵,用于从像素坐标到相机坐标系的转换。# preprocess the predict MPI# 将视差信息转换为3D空间坐标xyz_src_BS3HW = mpi_rendering.get_src_xyz_from_plane_disparity( homography_sampler.meshgrid,disparity_all_src,k_src_inv,)# 分离通道mpi_all_rgb_src = mpi_all_src[:, :, 0:3, :, :] # BxSx3xHxWmpi_all_sigma_src = mpi_all_src[:, :, 3:, :, :] # BxSx1xHxW# 调用一个渲染函数,用于计算从MPI合成图像时使用的混合权重_, _, blend_weights, _ = mpi_rendering.render(mpi_all_rgb_src,mpi_all_sigma_src,xyz_src_BS3HW,use_alpha=False,is_bg_depth_inf=False,)# 混合权重来更新源图像的MPI RGB部分,这里使用的是alpha blending算法mpi_all_rgb_src = blend_weights * src_imgs.unsqueeze(1) + (1 - blend_weights) * mpi_all_rgb_src# render novel views# 定义通过改变相机外参来模拟相机移动的参数。同时初始化一个空列表用于存储渲染出的帧。swing_path_list = gen_swing_path()frames = []# for cam_ext in tqdm(swing_path_list):# 利用render_novel_view函数来渲染从新视角看到的图像。这个函数需要当前的MPI RGB数据、透明度数据、视差数据、目标相机的外参(这里假设cam_ext是外参# ,cam_ext.cuda()将外参移动到GPU上)、源相机的内参矩阵的逆以及目标相机的内参矩阵。homography_sampler可能用于计算在新视角下图像的单应性变换。for cam_ext in swing_path_list:frame = render_novel_view(mpi_all_rgb_src,mpi_all_sigma_src,disparity_all_src,cam_ext.cuda(), # 目标视角到来源视角的变换矩阵k_src_inv,k_tgt, # 目标视角的相机内参矩阵homography_sampler,)# 张量转换为NumPy数组,并进行必要的排列以匹配图像的形状要求。然后它将像素值缩放到0-255范围,并转换为uint8类型frame_np = frame[0].permute(1, 2, 0).contiguous().cpu().numpy() # [b,h,w,3]frame_np = np.clip(np.round(frame_np * 255), a_min=0, a_max=255).astype(np.uint8)frames.append(frame_np)rgb_clip = ImageSequenceClip(frames, fps=30)# moviepy库的ImageSequenceClip类将帧列表转换成视频剪辑,设置每秒帧数为30。然后调用write_videofile方法将视频保存到指定的save_path路径# 使用mpeg4编解码器,不显示详细信息,设置比特率为3000kbps。rgb_clip.write_videofile(save_path, verbose=False, codec='mpeg4', logger=None, bitrate='3000k')
获取移动路径
def gen_swing_path(num_frames=90, r_x=0.14, r_y=0.0, r_z=0.10):"""生成一个摆动路径.生成一系列的4x4变换矩阵,每个矩阵代表了不同时间点的位置num_frames 表示要生成的帧数,默认为90帧;r_x、r_y 在x、y方向上的摆动半径r_z 表示z轴方向上的摆动半径"""# 创建了一个从0到1等间距的一维张量 t。这个张量表示从摆动开始到结束的时间线索引t = torch.arange(num_frames) / (num_frames - 1)# 创建了一个4x4的单位矩阵(identity matrix),然后将其重复 num_frames 次,用来存储最终生成的每一帧的变换矩阵。poses = torch.eye(4).repeat(num_frames, 1, 1)# 计算了在x轴上的摆动,使用正弦函数来生成x轴的平移部分,摆动的幅度由 r_x 确定# 正余弦: 需要一个物体的运动从最大幅度开始并且回到最大幅度poses[:, 0, 3] = r_x * torch.sin(2. * math.pi * t)poses[:, 1, 3] = r_y * (torch.cos(2. * math.pi * t) )poses[:, 2, 3] = r_z * (torch.cos(2. * math.pi * t) - 1.0) # 减去了1,这样摆动的起始点会更低return poses.unbind() # 张量 poses 拆分成一个元组,元组中的每个元素是一个4x4矩阵,代表了每一帧的变换矩阵
这篇关于【视点合成】代码解读:生成demo视频的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!