_metrics.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. """Module including useful metrics for Structure from Motion."""
  18. from torch import Tensor
  19. from kornia.core.check import KORNIA_CHECK_IS_TENSOR
  20. from kornia.geometry.conversions import convert_points_to_homogeneous
  21. from kornia.geometry.linalg import point_line_distance
  22. def sampson_epipolar_distance(
  23. pts1: Tensor, pts2: Tensor, Fm: Tensor, squared: bool = True, eps: float = 1e-8
  24. ) -> Tensor:
  25. """Return Sampson distance for correspondences given the fundamental matrix.
  26. Args:
  27. pts1: correspondences from the left images with shape :math:`(*, N, (2|3))`. If they are not homogeneous,
  28. converted automatically.
  29. pts2: correspondences from the right images with shape :math:`(*, N, (2|3))`. If they are not homogeneous,
  30. converted automatically.
  31. Fm: Fundamental matrices with shape :math:`(*, 3, 3)`. Called Fm to avoid ambiguity with torch.nn.functional.
  32. squared: if True (default), the squared distance is returned.
  33. eps: Small constant for safe sqrt.
  34. Returns:
  35. the computed Sampson distance with shape :math:`(*, N)`.
  36. """
  37. if not isinstance(Fm, Tensor):
  38. raise TypeError(f"Fm type is not a torch.Tensor. Got {type(Fm)}")
  39. if (len(Fm.shape) < 3) or not Fm.shape[-2:] == (3, 3):
  40. raise ValueError(f"Fm must be a (*, 3, 3) tensor. Got {Fm.shape}")
  41. if pts1.shape[-1] == 2:
  42. pts1 = convert_points_to_homogeneous(pts1)
  43. if pts2.shape[-1] == 2:
  44. pts2 = convert_points_to_homogeneous(pts2)
  45. # From Hartley and Zisserman, Sampson error (11.9)
  46. # sam = (x'^T F x) ** 2 / ( (((Fx)_1**2) + (Fx)_2**2)) + (((F^Tx')_1**2) + (F^Tx')_2**2)) )
  47. # line1_in_2 = (F @ pts1.transpose(dim0=-2, dim1=-1)).transpose(dim0=-2, dim1=-1)
  48. # line2_in_1 = (F.transpose(dim0=-2, dim1=-1) @ pts2.transpose(dim0=-2, dim1=-1)).transpose(dim0=-2, dim1=-1)
  49. # Instead we can just transpose F once and switch the order of multiplication
  50. F_t: Tensor = Fm.transpose(dim0=-2, dim1=-1)
  51. line1_in_2: Tensor = pts1 @ F_t
  52. line2_in_1: Tensor = pts2 @ Fm
  53. # numerator = (x'^T F x) ** 2
  54. numerator: Tensor = (pts2 * line1_in_2).sum(dim=-1).pow(2)
  55. # denominator = (((Fx)_1**2) + (Fx)_2**2)) + (((F^Tx')_1**2) + (F^Tx')_2**2))
  56. denominator: Tensor = line1_in_2[..., :2].norm(2, dim=-1).pow(2) + line2_in_1[..., :2].norm(2, dim=-1).pow(2)
  57. out: Tensor = numerator / denominator
  58. if squared:
  59. return out
  60. return (out + eps).sqrt()
  61. def symmetrical_epipolar_distance(
  62. pts1: Tensor, pts2: Tensor, Fm: Tensor, squared: bool = True, eps: float = 1e-8
  63. ) -> Tensor:
  64. """Return symmetrical epipolar distance for correspondences given the fundamental matrix.
  65. Args:
  66. pts1: correspondences from the left images with shape :math:`(*, N, (2|3))`. If they are not homogeneous,
  67. converted automatically.
  68. pts2: correspondences from the right images with shape :math:`(*, N, (2|3))`. If they are not homogeneous,
  69. converted automatically.
  70. Fm: Fundamental matrices with shape :math:`(*, 3, 3)`. Called Fm to avoid ambiguity with torch.nn.functional.
  71. squared: if True (default), the squared distance is returned.
  72. eps: Small constant for safe sqrt.
  73. Returns:
  74. the computed Symmetrical distance with shape :math:`(*, N)`.
  75. """
  76. if not isinstance(Fm, Tensor):
  77. raise TypeError(f"Fm type is not a torch.Tensor. Got {type(Fm)}")
  78. if (len(Fm.shape) < 3) or not Fm.shape[-2:] == (3, 3):
  79. raise ValueError(f"Fm must be a (*, 3, 3) tensor. Got {Fm.shape}")
  80. if pts1.shape[-1] == 2:
  81. pts1 = convert_points_to_homogeneous(pts1)
  82. if pts2.shape[-1] == 2:
  83. pts2 = convert_points_to_homogeneous(pts2)
  84. # From Hartley and Zisserman, symmetric epipolar distance (11.10)
  85. # sed = (x'^T F x) ** 2 / (((Fx)_1**2) + (Fx)_2**2)) + 1/ (((F^Tx')_1**2) + (F^Tx')_2**2))
  86. # line1_in_2 = (F @ pts1.transpose(dim0=-2, dim1=-1)).transpose(dim0=-2, dim1=-1)
  87. # line2_in_1 = (F.transpose(dim0=-2, dim1=-1) @ pts2.transpose(dim0=-2, dim1=-1)).transpose(dim0=-2, dim1=-1)
  88. # Instead we can just transpose F once and switch the order of multiplication
  89. F_t: Tensor = Fm.transpose(dim0=-2, dim1=-1)
  90. line1_in_2: Tensor = pts1 @ F_t
  91. line2_in_1: Tensor = pts2 @ Fm
  92. # numerator = (x'^T F x) ** 2
  93. numerator: Tensor = (pts2 * line1_in_2).sum(dim=-1).pow(2)
  94. # denominator_inv = 1/ (((Fx)_1**2) + (Fx)_2**2)) + 1/ (((F^Tx')_1**2) + (F^Tx')_2**2))
  95. denominator_inv: Tensor = 1.0 / (line1_in_2[..., :2].norm(2, dim=-1).pow(2)) + 1.0 / (
  96. line2_in_1[..., :2].norm(2, dim=-1).pow(2)
  97. )
  98. out: Tensor = numerator * denominator_inv
  99. if squared:
  100. return out
  101. return (out + eps).sqrt()
  102. def left_to_right_epipolar_distance(pts1: Tensor, pts2: Tensor, Fm: Tensor) -> Tensor:
  103. r"""Return one-sided epipolar distance for correspondences given the fundamental matrix.
  104. This method measures the distance from points in the right images to the epilines
  105. of the corresponding points in the left images as they reflect in the right images.
  106. Args:
  107. pts1: correspondences from the left images with shape
  108. :math:`(*, N, 2 or 3)`. If they are not homogeneous, converted automatically.
  109. pts2: correspondences from the right images with shape
  110. :math:`(*, N, 2 or 3)`. If they are not homogeneous, converted automatically.
  111. Fm: Fundamental matrices with shape :math:`(*, 3, 3)`. Called Fm to
  112. avoid ambiguity with torch.nn.functional.
  113. Returns:
  114. the computed Symmetrical distance with shape :math:`(*, N)`.
  115. """
  116. KORNIA_CHECK_IS_TENSOR(pts1)
  117. KORNIA_CHECK_IS_TENSOR(pts2)
  118. KORNIA_CHECK_IS_TENSOR(Fm)
  119. if (len(Fm.shape) < 3) or not Fm.shape[-2:] == (3, 3):
  120. raise ValueError(f"Fm must be a (*, 3, 3) tensor. Got {Fm.shape}")
  121. if pts1.shape[-1] == 2:
  122. pts1 = convert_points_to_homogeneous(pts1)
  123. F_t: Tensor = Fm.transpose(dim0=-2, dim1=-1)
  124. line1_in_2: Tensor = pts1 @ F_t
  125. return point_line_distance(pts2, line1_in_2)
  126. def right_to_left_epipolar_distance(pts1: Tensor, pts2: Tensor, Fm: Tensor) -> Tensor:
  127. r"""Return one-sided epipolar distance for correspondences given the fundamental matrix.
  128. This method measures the distance from points in the left images to the epilines
  129. of the corresponding points in the right images as they reflect in the left images.
  130. Args:
  131. pts1: correspondences from the left images with shape
  132. :math:`(*, N, 2 or 3)`. If they are not homogeneous, converted automatically.
  133. pts2: correspondences from the right images with shape
  134. :math:`(*, N, 2 or 3)`. If they are not homogeneous, converted automatically.
  135. Fm: Fundamental matrices with shape :math:`(*, 3, 3)`. Called Fm to
  136. avoid ambiguity with torch.nn.functional.
  137. Returns:
  138. the computed Symmetrical distance with shape :math:`(*, N)`.
  139. """
  140. KORNIA_CHECK_IS_TENSOR(pts1)
  141. KORNIA_CHECK_IS_TENSOR(pts2)
  142. KORNIA_CHECK_IS_TENSOR(Fm)
  143. if (len(Fm.shape) < 3) or not Fm.shape[-2:] == (3, 3):
  144. raise ValueError(f"Fm must be a (*, 3, 3) tensor. Got {Fm.shape}")
  145. if pts2.shape[-1] == 2:
  146. pts2 = convert_points_to_homogeneous(pts2)
  147. line2_in_1: Tensor = pts2 @ Fm
  148. return point_line_distance(pts1, line2_in_1)