perspective.py 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. # LICENSE HEADER MANAGED BY add-license-header
  2. #
  3. # Copyright 2018 Kornia Team
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. import torch
  18. import torch.nn.functional as F
  19. from kornia.geometry.conversions import (
  20. convert_points_from_homogeneous,
  21. convert_points_to_homogeneous,
  22. denormalize_points_with_intrinsics,
  23. normalize_points_with_intrinsics,
  24. )
  25. def project_points(point_3d: torch.Tensor, camera_matrix: torch.Tensor) -> torch.Tensor:
  26. r"""Project a 3d point onto the 2d camera plane.
  27. Args:
  28. point_3d: tensor containing the 3d points to be projected
  29. to the camera plane. The shape of the tensor can be :math:`(*, 3)`.
  30. camera_matrix: tensor containing the intrinsics camera
  31. matrix. The tensor shape must be :math:`(*, 3, 3)`.
  32. Returns:
  33. tensor of (u, v) cam coordinates with shape :math:`(*, 2)`.
  34. Example:
  35. >>> _ = torch.manual_seed(0)
  36. >>> X = torch.rand(1, 3)
  37. >>> K = torch.eye(3)[None]
  38. >>> project_points(X, K)
  39. tensor([[5.6088, 8.6827]])
  40. """
  41. # projection eq. [u, v, w]' = K * [x y z 1]'
  42. # u = fx * X / Z + cx
  43. # v = fy * Y / Z + cy
  44. # project back using depth dividing in a safe way
  45. xy_coords: torch.Tensor = convert_points_from_homogeneous(point_3d)
  46. return denormalize_points_with_intrinsics(xy_coords, camera_matrix)
  47. def unproject_points(
  48. point_2d: torch.Tensor, depth: torch.Tensor, camera_matrix: torch.Tensor, normalize: bool = False
  49. ) -> torch.Tensor:
  50. r"""Unproject a 2d point in 3d.
  51. Transform coordinates in the pixel frame to the camera frame.
  52. Args:
  53. point_2d: tensor containing the 2d to be projected to
  54. world coordinates. The shape of the tensor can be :math:`(*, 2)`.
  55. depth: tensor containing the depth value of each 2d
  56. points. The tensor shape must be equal to point2d :math:`(*, 1)`.
  57. camera_matrix: tensor containing the intrinsics camera
  58. matrix. The tensor shape must be :math:`(*, 3, 3)`.
  59. normalize: whether to normalize the pointcloud. This
  60. must be set to `True` when the depth is represented as the Euclidean
  61. ray length from the camera position.
  62. Returns:
  63. tensor of (x, y, z) world coordinates with shape :math:`(*, 3)`.
  64. Example:
  65. >>> _ = torch.manual_seed(0)
  66. >>> x = torch.rand(1, 2)
  67. >>> depth = torch.ones(1, 1)
  68. >>> K = torch.eye(3)[None]
  69. >>> unproject_points(x, depth, K)
  70. tensor([[0.4963, 0.7682, 1.0000]])
  71. """
  72. if not isinstance(depth, torch.Tensor):
  73. raise TypeError(f"Input depth type is not a torch.Tensor. Got {type(depth)}")
  74. if not depth.shape[-1] == 1:
  75. raise ValueError(f"Input depth must be in the shape of (*, 1). Got {depth.shape}")
  76. xy: torch.Tensor = normalize_points_with_intrinsics(point_2d, camera_matrix)
  77. xyz: torch.Tensor = convert_points_to_homogeneous(xy)
  78. if normalize:
  79. xyz = F.normalize(xyz, dim=-1, p=2.0)
  80. return xyz * depth