def _compute_variance_and_detMk(self, pointclouds, **kwargs): """ Compute the projected kernel variance Vk'+I Eq.(35) in [2], J V_k^r J^T + I Eq.(7) in [1] Args: pointclouds (PointClouds3D): point clouds in object coordinates Returns: variance (tensor): (N, 2, 2) detMk (tensor): (N, 1) determinant of Mk """ raster_settings = kwargs.get("raster_settings", self.raster_settings) WJk = self._compute_WJk(pointclouds, **kwargs) totalP = WJk.shape[0] if raster_settings.Vrk_invariant: Vrk, Sk = self._compute_global_Vrk(pointclouds, **kwargs) elif raster_settings.Vrk_isotropic: # (N, 3, 3) Vrk, Sk = self._compute_isotropic_Vrk(pointclouds, **kwargs) else: Vrk, Sk = self._compute_anisotropic_Vrk(pointclouds) Mk = Sk @ WJk Vk = WJk.transpose(1, 2) @ Vrk @ WJk # low-pass filter +sigma*I # NOTE: [2] is in pixel space, but we are in NDC space, so the variance should be # scaled by pixel_size pixel_size = 2.0 / raster_settings.image_size variance = Vk + raster_settings.antialiasing_sigma * \ ops3d.eyes(2, totalP, device=Vk.device, dtype=Vk.dtype) * (pixel_size**2) detMk = torch.det(Mk) return variance, detMk
mask = fragments.pix_to_face[..., :1] >= 0 mask_imgs = mask.to(dtype=torch.uint8) * 255 # use hard alpha values images = torch.cat([images[..., :3], mask.float()], dim=-1) dense_depths = cams.zfar.view(-1, 1, 1, 1).clone().expand_as(mask_imgs) dense_depths = torch.where(mask, fragments.zbuf[..., :1], dense_depths) # cameras camera_mat = cams.get_projection_transform().get_matrix().cpu() world_mat = cams.get_world_to_view_transform().get_matrix().cpu() id_mat = np.eye(4) # DVR scales x,y and does the projection step manually (/z) dvr_camera_mat = eyes(4, camera_mat.shape[0]).to(camera_mat.device) dvr_camera_mat[:, :2, :2] = camera_mat[:, :2, :2] # dense depth read from rasterizer for b in range(images.shape[0]): # save camera data data_dict['camera_mat'][idx, ...] = world_mat[b] data_dict['lights_%d' % idx] = convert_tensor_property_to_value_dict(lights) # DVR camera data cameras_dict['world_mat_%d' % idx] = world_mat[b].transpose( 0, 1) cameras_dict['scale_mat_%d' % idx] = id_mat cameras_dict['camera_mat_%d' % idx] = dvr_camera_mat[b].transpose(0, 1) # save dense depth
def test_opencv_conversion(self): """ Tests that the cameras converted from opencv to pytorch3d convention return correct projections of random 3D points. The check is done against a set of results precomuted using `cv2.projectPoints` function. """ device = torch.device("cuda:0") image_size = [[480, 640]] * 4 R = [ [ [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], ], [ [1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0], ], [ [0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], ], [ [0.0, 0.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], ], ] tvec = [ [0.0, 0.0, 3.0], [0.3, -0.3, 3.0], [-0.15, 0.1, 4.0], [0.0, 0.0, 4.0], ] focal_length = [ [100.0, 100.0], [115.0, 115.0], [105.0, 105.0], [120.0, 120.0], ] # These values are in y, x format, but they should be in x, y format. # The tests work like this because they only test for consistency, # but this format is misleading. principal_point = [ [240, 320], [240.5, 320.3], [241, 318], [242, 322], ] principal_point, focal_length, R, tvec, image_size = [ torch.tensor(x, device=device) for x in (principal_point, focal_length, R, tvec, image_size) ] camera_matrix = eyes(dim=3, N=4, device=device) camera_matrix[:, 0, 0], camera_matrix[:, 1, 1] = ( focal_length[:, 0], focal_length[:, 1], ) camera_matrix[:, :2, 2] = principal_point pts = torch.nn.functional.normalize(torch.randn(4, 1000, 3, device=device), dim=-1) # project the 3D points with the opencv projection function rvec = so3_log_map(R) pts_proj_opencv = cv2_project_points(pts, rvec, tvec, camera_matrix) # make the pytorch3d cameras cameras_opencv_to_pytorch3d = cameras_from_opencv_projection( R, tvec, camera_matrix, image_size) self.assertEqual(cameras_opencv_to_pytorch3d.device, device) # project the 3D points with converted cameras to screen space. pts_proj_pytorch3d_screen = cameras_opencv_to_pytorch3d.transform_points_screen( pts)[..., :2] # compare to the cached projected points self.assertClose(pts_proj_opencv, pts_proj_pytorch3d_screen, atol=1e-5) # Check the inverse. R_i, tvec_i, camera_matrix_i = opencv_from_cameras_projection( cameras_opencv_to_pytorch3d, image_size) self.assertClose(R, R_i) self.assertClose(tvec, tvec_i) self.assertClose(camera_matrix, camera_matrix_i)
def test_pulsar_conversion(self): """ Tests that the cameras converted from opencv to pulsar convention return correct projections of random 3D points. The check is done against a set of results precomputed using `cv2.projectPoints` function. """ image_size = [[480, 640]] R = [ [ [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], ], [ [0.1968, -0.6663, -0.7192], [0.7138, -0.4055, 0.5710], [-0.6721, -0.6258, 0.3959], ], ] tvec = [ [10.0, 10.0, 3.0], [-0.0, -0.0, 20.0], ] focal_length = [ [100.0, 100.0], [10.0, 10.0], ] principal_point = [ [320, 240], [320, 240], ] principal_point, focal_length, R, tvec, image_size = [ torch.FloatTensor(x) for x in (principal_point, focal_length, R, tvec, image_size) ] camera_matrix = eyes(dim=3, N=2) camera_matrix[:, 0, 0] = focal_length[:, 0] camera_matrix[:, 1, 1] = focal_length[:, 1] camera_matrix[:, :2, 2] = principal_point rvec = so3_log_map(R) pts = torch.tensor([[[0.0, 0.0, 120.0]], [[0.0, 0.0, 120.0]]], dtype=torch.float32) radii = torch.tensor([[1e-5], [1e-5]], dtype=torch.float32) col = torch.zeros((2, 1, 1), dtype=torch.float32) # project the 3D points with the opencv projection function pts_proj_opencv = cv2_project_points(pts, rvec, tvec, camera_matrix) pulsar_cam = pulsar_from_opencv_projection(R, tvec, camera_matrix, image_size, znear=100.0) pulsar_rend = PulsarRenderer(640, 480, 1, right_handed_system=False, n_channels=1) rendered = torch.flip( pulsar_rend( pts, col, radii, pulsar_cam, 1e-5, max_depth=150.0, min_depth=100.0, ), dims=(1, ), ) for batch_id in range(2): point_pos = torch.where( rendered[batch_id] == rendered[batch_id].min()) point_pos = point_pos[1][0], point_pos[0][0] self.assertLess( torch.abs(point_pos[0] - pts_proj_opencv[batch_id, 0, 0]), 2) self.assertLess( torch.abs(point_pos[1] - pts_proj_opencv[batch_id, 0, 1]), 2)
def _check_raysampler_ray_directions(self, cameras, raysampler, ray_bundle): """ Check the rays_directions_world output of raysamplers. """ batch_size = cameras.R.shape[0] n_pts_per_ray = ray_bundle.lengths.shape[-1] spatial_size = ray_bundle.xys.shape[1:-1] n_rays_per_image = spatial_size.numel() # obtain the ray points in world coords rays_points_world = cameras.unproject_points( torch.cat( ( ray_bundle.xys.view(batch_size, n_rays_per_image, 1, 2).expand( batch_size, n_rays_per_image, n_pts_per_ray, 2 ), ray_bundle.lengths.view( batch_size, n_rays_per_image, n_pts_per_ray, 1 ), ), dim=-1, ).view(batch_size, -1, 3) ).view(batch_size, -1, n_pts_per_ray, 3) # reshape to common testing size rays_directions_world_normed = torch.nn.functional.normalize( ray_bundle.directions.view(batch_size, -1, 3), dim=-1 ) # check that the l2-normed difference of all consecutive planes # of points in world coords matches ray_directions_world rays_directions_world_ = torch.nn.functional.normalize( rays_points_world[:, :, -1:] - rays_points_world[:, :, :-1], dim=-1 ) self.assertClose( rays_directions_world_normed[:, :, None].expand_as(rays_directions_world_), rays_directions_world_, atol=1e-4, ) # check the ray directions rotated using camera rotation matrix # match the ray directions of a camera with trivial extrinsics cameras_trivial_extrinsic = cameras.clone() cameras_trivial_extrinsic.R = eyes( N=batch_size, dim=3, dtype=cameras.R.dtype, device=cameras.device ) cameras_trivial_extrinsic.T = torch.zeros_like(cameras.T) # make sure we get the same random rays in case we call the # MonteCarloRaysampler twice below with torch.random.fork_rng(devices=range(torch.cuda.device_count())): torch.random.manual_seed(42) ray_bundle_world_fix_seed = raysampler(cameras=cameras) torch.random.manual_seed(42) ray_bundle_camera_fix_seed = raysampler(cameras=cameras_trivial_extrinsic) rays_directions_camera_fix_seed_ = Rotate( cameras.R, device=cameras.R.device ).transform_points(ray_bundle_world_fix_seed.directions.view(batch_size, -1, 3)) self.assertClose( rays_directions_camera_fix_seed_, ray_bundle_camera_fix_seed.directions.view(batch_size, -1, 3), atol=1e-5, )
def test_ndc_grid_sample_rendering(self): """ Use PyTorch3D point renderer to render a colored point cloud, then sample the image at the locations of the point projections with `ndc_grid_sample`. Finally, assert that the sampled colors are equal to the original point cloud colors. Note that, in order to ensure correctness, we use a nearest-neighbor assignment point renderer (i.e. no soft splatting). """ # generate a bunch of 3D points on a regular grid lying in the z-plane n_grid_pts = 10 grid_scale = 0.9 z_plane = 2.0 image_size = [128, 128] point_radius = 0.015 n_pts = n_grid_pts * n_grid_pts pts = torch.stack( meshgrid_ij([torch.linspace(-grid_scale, grid_scale, n_grid_pts)] * 2, ), dim=-1, ) pts = torch.cat([pts, z_plane * torch.ones_like(pts[..., :1])], dim=-1) pts = pts.reshape(1, n_pts, 3) # color the points randomly pts_colors = torch.rand(1, n_pts, 3) # make trivial rendering cameras cameras = PerspectiveCameras( R=eyes(dim=3, N=1), device=pts.device, T=torch.zeros(1, 3, dtype=torch.float32, device=pts.device), ) # render the point cloud pcl = Pointclouds(points=pts, features=pts_colors) renderer = NearestNeighborPointsRenderer( rasterizer=PointsRasterizer( cameras=cameras, raster_settings=PointsRasterizationSettings( image_size=image_size, radius=point_radius, points_per_pixel=1, ), ), compositor=AlphaCompositor(), ) im_render = renderer(pcl) # sample the render at projected pts pts_proj = cameras.transform_points(pcl.points_padded())[..., :2] pts_colors_sampled = ndc_grid_sample( im_render, pts_proj, mode="nearest", align_corners=False, ).permute(0, 2, 1) # assert that the samples are the same as original points self.assertClose(pts_colors, pts_colors_sampled, atol=1e-4)