|
| 1 | +import torch |
| 2 | + |
| 3 | +from ..renderer import PerspectiveCameras |
| 4 | +from ..transforms import so3_exponential_map |
| 5 | + |
| 6 | + |
| 7 | +def cameras_from_opencv_projection( |
| 8 | + rvec: torch.Tensor, |
| 9 | + tvec: torch.Tensor, |
| 10 | + camera_matrix: torch.Tensor, |
| 11 | + image_size: torch.Tensor, |
| 12 | +) -> PerspectiveCameras: |
| 13 | + """ |
| 14 | + Converts a batch of OpenCV-conventioned cameras parametrized with the |
| 15 | + axis-angle rotation vectors `rvec`, translation vectors `tvec`, and the camera |
| 16 | + calibration matrices `camera_matrix` to `PerspectiveCameras` in PyTorch3D |
| 17 | + convention. |
| 18 | +
|
| 19 | + More specifically, the conversion is carried out such that a projection |
| 20 | + of a 3D shape to the OpenCV-conventioned screen of size `image_size` results |
| 21 | + in the same image as a projection with the corresponding PyTorch3D camera |
| 22 | + to the NDC screen convention of PyTorch3D. |
| 23 | +
|
| 24 | + More specifically, the OpenCV convention projects points to the OpenCV screen |
| 25 | + space as follows: |
| 26 | + ``` |
| 27 | + x_screen_opencv = camera_matrix @ (exp(rvec) @ x_world + tvec) |
| 28 | + ``` |
| 29 | + followed by the homogenization of `x_screen_opencv`. |
| 30 | +
|
| 31 | + Note: |
| 32 | + The parameters `rvec, tvec, camera_matrix` correspond e.g. to the inputs |
| 33 | + of `cv2.projectPoints`, or to the ouputs of `cv2.calibrateCamera`. |
| 34 | +
|
| 35 | + Args: |
| 36 | + rvec: A batch of axis-angle rotation vectors of shape `(N, 3)`. |
| 37 | + tvec: A batch of translation vectors of shape `(N, 3)`. |
| 38 | + camera_matrix: A batch of camera calibration matrices of shape `(N, 3, 3)`. |
| 39 | + image_size: A tensor of shape `(N, 2)` containing the sizes of the images |
| 40 | + (height, width) attached to each camera. |
| 41 | +
|
| 42 | + Returns: |
| 43 | + cameras_pytorch3d: A batch of `N` cameras in the PyTorch3D convention. |
| 44 | + """ |
| 45 | + |
| 46 | + R = so3_exponential_map(rvec) |
| 47 | + focal_length = torch.stack([camera_matrix[:, 0, 0], camera_matrix[:, 1, 1]], dim=-1) |
| 48 | + principal_point = camera_matrix[:, :2, 2] |
| 49 | + |
| 50 | + # Retype the image_size correctly and flip to width, height. |
| 51 | + image_size_wh = image_size.to(R).flip(dims=(1,)) |
| 52 | + |
| 53 | + # Get the PyTorch3D focal length and principal point. |
| 54 | + focal_pytorch3d = focal_length / (0.5 * image_size_wh) |
| 55 | + p0_pytorch3d = -(principal_point / (0.5 * image_size_wh) - 1) |
| 56 | + |
| 57 | + # For R, T we flip x, y axes (opencv screen space has an opposite |
| 58 | + # orientation of screen axes). |
| 59 | + # We also transpose R (opencv multiplies points from the opposite=left side). |
| 60 | + R_pytorch3d = R.permute(0, 2, 1) |
| 61 | + T_pytorch3d = tvec.clone() |
| 62 | + R_pytorch3d[:, :, :2] *= -1 |
| 63 | + T_pytorch3d[:, :2] *= -1 |
| 64 | + |
| 65 | + return PerspectiveCameras( |
| 66 | + R=R_pytorch3d, |
| 67 | + T=T_pytorch3d, |
| 68 | + focal_length=focal_pytorch3d, |
| 69 | + principal_point=p0_pytorch3d, |
| 70 | + ) |
0 commit comments