pinhole.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  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. from typing import Iterable, List, Union
  18. import torch
  19. from kornia.core import Device, Tensor, eye, stack, zeros
  20. from kornia.core.check import KORNIA_CHECK_SAME_DEVICE
  21. from kornia.geometry.conversions import convert_points_from_homogeneous, convert_points_to_homogeneous
  22. from kornia.geometry.linalg import inverse_transformation, transform_points
  23. from kornia.utils.helpers import _torch_inverse_cast
  24. class PinholeCamera:
  25. r"""Class that represents a Pinhole Camera model.
  26. Args:
  27. intrinsics: tensor with shape :math:`(B, 4, 4)`
  28. containing the full 4x4 camera calibration matrix.
  29. extrinsics: tensor with shape :math:`(B, 4, 4)`
  30. containing the full 4x4 rotation-translation matrix.
  31. height: tensor with shape :math:`(B)` containing the image height.
  32. width: tensor with shape :math:`(B)` containing the image width.
  33. .. note::
  34. We assume that the class attributes are in batch form in order to take
  35. advantage of PyTorch parallelism to boost computing performance.
  36. """
  37. def __init__(self, intrinsics: Tensor, extrinsics: Tensor, height: Tensor, width: Tensor) -> None:
  38. # verify batch size and shapes
  39. self._check_valid([intrinsics, extrinsics, height, width])
  40. self._check_valid_params(intrinsics, "intrinsics")
  41. self._check_valid_params(extrinsics, "extrinsics")
  42. self._check_valid_shape(height, "height")
  43. self._check_valid_shape(width, "width")
  44. self._check_consistent_device([intrinsics, extrinsics, height, width])
  45. # set class attributes
  46. self.height: Tensor = height
  47. self.width: Tensor = width
  48. self._intrinsics: Tensor = intrinsics
  49. self._extrinsics: Tensor = extrinsics
  50. @staticmethod
  51. def _check_valid(data_iter: Iterable[Tensor]) -> bool:
  52. if not all(data.shape[0] for data in data_iter):
  53. raise ValueError("Arguments shapes must match")
  54. return True
  55. @staticmethod
  56. def _check_valid_params(data: Tensor, data_name: str) -> bool:
  57. if len(data.shape) not in (3, 4) and data.shape[-2:] != (4, 4): # Shouldn't this be an OR logic than AND?
  58. raise ValueError(
  59. f"Argument {data_name} shape must be in the following shape Bx4x4 or BxNx4x4. Got {data.shape}"
  60. )
  61. return True
  62. @staticmethod
  63. def _check_valid_shape(data: Tensor, data_name: str) -> bool:
  64. if not len(data.shape) == 1:
  65. raise ValueError(f"Argument {data_name} shape must be in the following shape B. Got {data.shape}")
  66. return True
  67. @staticmethod
  68. def _check_consistent_device(data_iter: List[Tensor]) -> None:
  69. first = data_iter[0]
  70. for data in data_iter:
  71. KORNIA_CHECK_SAME_DEVICE(data, first)
  72. def device(self) -> torch.device:
  73. r"""Return the device for camera buffers.
  74. Returns:
  75. Device type
  76. """
  77. return self._intrinsics.device
  78. @property
  79. def intrinsics(self) -> Tensor:
  80. r"""The full 4x4 intrinsics matrix.
  81. Returns:
  82. tensor of shape :math:`(B, 4, 4)`.
  83. """
  84. if not self._check_valid_params(self._intrinsics, "intrinsics"):
  85. raise AssertionError
  86. return self._intrinsics
  87. @property
  88. def extrinsics(self) -> Tensor:
  89. r"""The full 4x4 extrinsics matrix.
  90. Returns:
  91. tensor of shape :math:`(B, 4, 4)`.
  92. """
  93. if not self._check_valid_params(self._extrinsics, "extrinsics"):
  94. raise AssertionError
  95. return self._extrinsics
  96. @property
  97. def batch_size(self) -> int:
  98. r"""Return the batch size of the storage.
  99. Returns:
  100. scalar with the batch size.
  101. """
  102. return self.intrinsics.shape[0]
  103. @property
  104. def fx(self) -> Tensor:
  105. r"""Return the focal length in the x-direction.
  106. Returns:
  107. tensor of shape :math:`(B)`.
  108. """
  109. return self.intrinsics[..., 0, 0]
  110. @property
  111. def fy(self) -> Tensor:
  112. r"""Return the focal length in the y-direction.
  113. Returns:
  114. tensor of shape :math:`(B)`.
  115. """
  116. return self.intrinsics[..., 1, 1]
  117. @property
  118. def cx(self) -> Tensor:
  119. r"""Return the x-coordinate of the principal point.
  120. Returns:
  121. tensor of shape :math:`(B)`.
  122. """
  123. return self.intrinsics[..., 0, 2]
  124. @property
  125. def cy(self) -> Tensor:
  126. r"""Return the y-coordinate of the principal point.
  127. Returns:
  128. tensor of shape :math:`(B)`.
  129. """
  130. return self.intrinsics[..., 1, 2]
  131. @property
  132. def tx(self) -> Tensor:
  133. r"""Return the x-coordinate of the translation vector.
  134. Returns:
  135. tensor of shape :math:`(B)`.
  136. """
  137. return self.extrinsics[..., 0, -1]
  138. @tx.setter
  139. def tx(self, value: Union[Tensor, float]) -> "PinholeCamera":
  140. r"""Set the x-coordinate of the translation vector with the given value."""
  141. self.extrinsics[..., 0, -1] = value
  142. return self
  143. @property
  144. def ty(self) -> Tensor:
  145. r"""Return the y-coordinate of the translation vector.
  146. Returns:
  147. tensor of shape :math:`(B)`.
  148. """
  149. return self.extrinsics[..., 1, -1]
  150. @ty.setter
  151. def ty(self, value: Union[Tensor, float]) -> "PinholeCamera":
  152. r"""Set the y-coordinate of the translation vector with the given value."""
  153. self.extrinsics[..., 1, -1] = value
  154. return self
  155. @property
  156. def tz(self) -> Tensor:
  157. r"""Returns the z-coordinate of the translation vector.
  158. Returns:
  159. tensor of shape :math:`(B)`.
  160. """
  161. return self.extrinsics[..., 2, -1]
  162. @tz.setter
  163. def tz(self, value: Union[Tensor, float]) -> "PinholeCamera":
  164. r"""Set the y-coordinate of the translation vector with the given value."""
  165. self.extrinsics[..., 2, -1] = value
  166. return self
  167. @property
  168. def rt_matrix(self) -> Tensor:
  169. r"""Return the 3x4 rotation-translation matrix.
  170. Returns:
  171. tensor of shape :math:`(B, 3, 4)`.
  172. """
  173. return self.extrinsics[..., :3, :4]
  174. @property
  175. def camera_matrix(self) -> Tensor:
  176. r"""Return the 3x3 camera matrix containing the intrinsics.
  177. Returns:
  178. tensor of shape :math:`(B, 3, 3)`.
  179. """
  180. return self.intrinsics[..., :3, :3]
  181. @property
  182. def rotation_matrix(self) -> Tensor:
  183. r"""Return the 3x3 rotation matrix from the extrinsics.
  184. Returns:
  185. tensor of shape :math:`(B, 3, 3)`.
  186. """
  187. return self.extrinsics[..., :3, :3]
  188. @property
  189. def translation_vector(self) -> Tensor:
  190. r"""Return the translation vector from the extrinsics.
  191. Returns:
  192. tensor of shape :math:`(B, 3, 1)`.
  193. """
  194. return self.extrinsics[..., :3, -1:]
  195. def clone(self) -> "PinholeCamera":
  196. r"""Return a deep copy of the current object instance."""
  197. height: Tensor = self.height.clone()
  198. width: Tensor = self.width.clone()
  199. intrinsics: Tensor = self.intrinsics.clone()
  200. extrinsics: Tensor = self.extrinsics.clone()
  201. return PinholeCamera(intrinsics, extrinsics, height, width)
  202. def intrinsics_inverse(self) -> Tensor:
  203. r"""Return the inverse of the 4x4 instrisics matrix.
  204. Returns:
  205. tensor of shape :math:`(B, 4, 4)`.
  206. """
  207. return self.intrinsics.inverse()
  208. def scale(self, scale_factor: Tensor) -> "PinholeCamera":
  209. r"""Scale the pinhole model.
  210. Args:
  211. scale_factor: a tensor with the scale factor. It has
  212. to be broadcastable with class members. The expected shape is
  213. :math:`(B)` or :math:`(1)`.
  214. Returns:
  215. the camera model with scaled parameters.
  216. """
  217. # scale the intrinsic parameters
  218. intrinsics: Tensor = self.intrinsics.clone()
  219. intrinsics[..., 0, 0] *= scale_factor
  220. intrinsics[..., 1, 1] *= scale_factor
  221. intrinsics[..., 0, 2] *= scale_factor
  222. intrinsics[..., 1, 2] *= scale_factor
  223. # scale the image height/width
  224. height: Tensor = scale_factor * self.height.clone()
  225. width: Tensor = scale_factor * self.width.clone()
  226. return PinholeCamera(intrinsics, self.extrinsics, height, width)
  227. def scale_(self, scale_factor: Union[float, Tensor]) -> "PinholeCamera":
  228. r"""Scale the pinhole model in-place.
  229. Args:
  230. scale_factor: a tensor with the scale factor. It has
  231. to be broadcastable with class members. The expected shape is
  232. :math:`(B)` or :math:`(1)`.
  233. Returns:
  234. the camera model with scaled parameters.
  235. """
  236. # scale the intrinsic parameters
  237. self.intrinsics[..., 0, 0] *= scale_factor
  238. self.intrinsics[..., 1, 1] *= scale_factor
  239. self.intrinsics[..., 0, 2] *= scale_factor
  240. self.intrinsics[..., 1, 2] *= scale_factor
  241. # scale the image height/width
  242. self.height *= scale_factor
  243. self.width *= scale_factor
  244. return self
  245. def project(self, point_3d: Tensor) -> Tensor:
  246. r"""Project a 3d point in world coordinates onto the 2d camera plane.
  247. Args:
  248. point_3d: tensor containing the 3d points to be projected
  249. to the camera plane. The shape of the tensor can be :math:`(*, 3)`.
  250. Returns:
  251. tensor of (u, v) cam coordinates with shape :math:`(*, 2)`.
  252. Example:
  253. >>> _ = torch.manual_seed(0)
  254. >>> X = torch.rand(1, 3)
  255. >>> K = torch.eye(4)[None]
  256. >>> E = torch.eye(4)[None]
  257. >>> h = torch.ones(1)
  258. >>> w = torch.ones(1)
  259. >>> pinhole = kornia.geometry.camera.PinholeCamera(K, E, h, w)
  260. >>> pinhole.project(X)
  261. tensor([[5.6088, 8.6827]])
  262. """
  263. P = self.intrinsics @ self.extrinsics
  264. return convert_points_from_homogeneous(transform_points(P, point_3d))
  265. def unproject(self, point_2d: Tensor, depth: Tensor) -> Tensor:
  266. r"""Unproject a 2d point in 3d.
  267. Transform coordinates in the pixel frame to the world frame.
  268. Args:
  269. point_2d: tensor containing the 2d to be projected to
  270. world coordinates. The shape of the tensor can be :math:`(*, 2)`.
  271. depth: tensor containing the depth value of each 2d
  272. points. The tensor shape must be equal to point2d :math:`(*, 1)`.
  273. normalize: whether to normalize the pointcloud. This
  274. must be set to `True` when the depth is represented as the Euclidean
  275. ray length from the camera position.
  276. Returns:
  277. tensor of (x, y, z) world coordinates with shape :math:`(*, 3)`.
  278. Example:
  279. >>> _ = torch.manual_seed(0)
  280. >>> x = torch.rand(1, 2)
  281. >>> depth = torch.ones(1, 1)
  282. >>> K = torch.eye(4)[None]
  283. >>> E = torch.eye(4)[None]
  284. >>> h = torch.ones(1)
  285. >>> w = torch.ones(1)
  286. >>> pinhole = kornia.geometry.camera.PinholeCamera(K, E, h, w)
  287. >>> pinhole.unproject(x, depth)
  288. tensor([[0.4963, 0.7682, 1.0000]])
  289. """
  290. P = self.intrinsics @ self.extrinsics
  291. P_inv = _torch_inverse_cast(P)
  292. return transform_points(P_inv, convert_points_to_homogeneous(point_2d) * depth)
  293. # NOTE: just for test. Decide if we keep it.
  294. @classmethod
  295. def from_parameters(
  296. self,
  297. fx: Tensor,
  298. fy: Tensor,
  299. cx: Tensor,
  300. cy: Tensor,
  301. height: int,
  302. width: int,
  303. tx: Tensor,
  304. ty: Tensor,
  305. tz: Tensor,
  306. batch_size: int,
  307. device: Device,
  308. dtype: torch.dtype,
  309. ) -> "PinholeCamera":
  310. # create the camera matrix
  311. intrinsics = zeros(batch_size, 4, 4, device=device, dtype=dtype)
  312. intrinsics[..., 0, 0] += fx
  313. intrinsics[..., 1, 1] += fy
  314. intrinsics[..., 0, 2] += cx
  315. intrinsics[..., 1, 2] += cy
  316. intrinsics[..., 2, 2] += 1.0
  317. intrinsics[..., 3, 3] += 1.0
  318. # create the pose matrix
  319. extrinsics = eye(4, device=device, dtype=dtype).repeat(batch_size, 1, 1)
  320. extrinsics[..., 0, -1] += tx
  321. extrinsics[..., 1, -1] += ty
  322. extrinsics[..., 2, -1] += tz
  323. # create image hegith and width
  324. height_tmp = zeros(batch_size, device=device, dtype=dtype)
  325. height_tmp[..., 0] += height
  326. width_tmp = zeros(batch_size, device=device, dtype=dtype)
  327. width_tmp[..., 0] += width
  328. return self(intrinsics, extrinsics, height_tmp, width_tmp)
  329. class PinholeCamerasList(PinholeCamera):
  330. r"""Class that represents a list of pinhole cameras.
  331. The class inherits from :class:`~kornia.PinholeCamera` meaning that
  332. it will keep the same class properties but with an extra dimension.
  333. .. note::
  334. The underlying data tensor will be stacked in the first dimension.
  335. That's it, given a list of two camera instances, the intrinsics tensor
  336. will have a shape :math:`(B, N, 4, 4)` where :math:`B` is the batch
  337. size and :math:`N` is the numbers of cameras (in this case two).
  338. Args:
  339. pinholes_list: a python tuple or list containing a set of `PinholeCamera` instances.
  340. """
  341. def __init__(self, pinholes_list: Iterable[PinholeCamera]) -> None:
  342. self._initialize_parameters(pinholes_list)
  343. def _initialize_parameters(self, pinholes: Iterable[PinholeCamera]) -> "PinholeCamerasList":
  344. r"""Initialise the class attributes given a cameras list."""
  345. if not isinstance(pinholes, (list, tuple)):
  346. raise TypeError(f"pinhole must of type list or tuple. Got {type(pinholes)}")
  347. height, width = [], []
  348. intrinsics, extrinsics = [], []
  349. for pinhole in pinholes:
  350. if not isinstance(pinhole, PinholeCamera):
  351. raise TypeError(f"Argument pinhole must be from type PinholeCamera. Got {type(pinhole)}")
  352. height.append(pinhole.height)
  353. width.append(pinhole.width)
  354. intrinsics.append(pinhole.intrinsics)
  355. extrinsics.append(pinhole.extrinsics)
  356. # concatenate and set members. We will assume BxNx4x4
  357. self.height: Tensor = stack(height, dim=1)
  358. self.width: Tensor = stack(width, dim=1)
  359. self._intrinsics: Tensor = stack(intrinsics, dim=1)
  360. self._extrinsics: Tensor = stack(extrinsics, dim=1)
  361. return self
  362. @property
  363. def num_cameras(self) -> int:
  364. r"""Return the number of pinholes cameras per batch."""
  365. num_cameras: int = -1
  366. if self.intrinsics is not None:
  367. num_cameras = int(self.intrinsics.shape[1])
  368. return num_cameras
  369. def get_pinhole(self, idx: int) -> PinholeCamera:
  370. r"""Return a PinholeCamera object with parameters such as Bx4x4."""
  371. height: Tensor = self.height[..., idx]
  372. width: Tensor = self.width[..., idx]
  373. intrinsics: Tensor = self.intrinsics[:, idx]
  374. extrinsics: Tensor = self.extrinsics[:, idx]
  375. return PinholeCamera(intrinsics, extrinsics, height, width)
  376. def pinhole_matrix(pinholes: Tensor, eps: float = 1e-6) -> Tensor:
  377. r"""Return the pinhole matrix from a pinhole model.
  378. .. note::
  379. This method is going to be deprecated in version 0.2 in favour of
  380. :attr:`kornia.PinholeCamera.camera_matrix`.
  381. Args:
  382. pinholes: tensor of pinhole models.
  383. eps: epsilon for numerical stability.
  384. Returns:
  385. tensor of pinhole matrices.
  386. Shape:
  387. - Input: :math:`(N, 12)`
  388. - Output: :math:`(N, 4, 4)`
  389. Example:
  390. >>> rng = torch.manual_seed(0)
  391. >>> pinhole = torch.rand(1, 12) # Nx12
  392. >>> pinhole_matrix(pinhole) # Nx4x4
  393. tensor([[[4.9626e-01, 1.0000e-06, 8.8477e-02, 1.0000e-06],
  394. [1.0000e-06, 7.6822e-01, 1.3203e-01, 1.0000e-06],
  395. [1.0000e-06, 1.0000e-06, 1.0000e+00, 1.0000e-06],
  396. [1.0000e-06, 1.0000e-06, 1.0000e-06, 1.0000e+00]]])
  397. """
  398. # warnings.warn("pinhole_matrix will be deprecated in version 0.2, "
  399. # "use PinholeCamera.camera_matrix instead",
  400. # PendingDeprecationWarning)
  401. if not (len(pinholes.shape) == 2 and pinholes.shape[1] == 12):
  402. raise AssertionError(pinholes.shape)
  403. # unpack pinhole values
  404. fx, fy, cx, cy = torch.chunk(pinholes[..., :4], 4, dim=1) # Nx1
  405. # create output container
  406. k = eye(4, device=pinholes.device, dtype=pinholes.dtype) + eps
  407. k = k.view(1, 4, 4).repeat(pinholes.shape[0], 1, 1) # Nx4x4
  408. # fill output with pinhole values
  409. k[..., 0, 0:1] = fx
  410. k[..., 0, 2:3] = cx
  411. k[..., 1, 1:2] = fy
  412. k[..., 1, 2:3] = cy
  413. return k
  414. def inverse_pinhole_matrix(pinhole: Tensor, eps: float = 1e-6) -> Tensor:
  415. r"""Return the inverted pinhole matrix from a pinhole model.
  416. .. note::
  417. This method is going to be deprecated in version 0.2 in favour of
  418. :attr:`kornia.PinholeCamera.intrinsics_inverse()`.
  419. Args:
  420. pinhole: tensor with pinhole models.
  421. eps: epsilon for numerical stability.
  422. Returns:
  423. tensor of inverted pinhole matrices.
  424. Shape:
  425. - Input: :math:`(N, 12)`
  426. - Output: :math:`(N, 4, 4)`
  427. Example:
  428. >>> rng = torch.manual_seed(0)
  429. >>> pinhole = torch.rand(1, 12) # Nx12
  430. >>> inverse_pinhole_matrix(pinhole) # Nx4x4
  431. tensor([[[ 2.0151, 0.0000, -0.1783, 0.0000],
  432. [ 0.0000, 1.3017, -0.1719, 0.0000],
  433. [ 0.0000, 0.0000, 1.0000, 0.0000],
  434. [ 0.0000, 0.0000, 0.0000, 1.0000]]])
  435. """
  436. # warnings.warn("inverse_pinhole_matrix will be deprecated in version 0.2, "
  437. # "use PinholeCamera.intrinsics_inverse() instead",
  438. # PendingDeprecationWarning)
  439. if not (len(pinhole.shape) == 2 and pinhole.shape[1] == 12):
  440. raise AssertionError(pinhole.shape)
  441. # unpack pinhole values
  442. fx, fy, cx, cy = torch.chunk(pinhole[..., :4], 4, dim=1) # Nx1
  443. # create output container
  444. k = eye(4, device=pinhole.device, dtype=pinhole.dtype)
  445. k = k.view(1, 4, 4).repeat(pinhole.shape[0], 1, 1) # Nx4x4
  446. # fill output with inverse values
  447. k[..., 0, 0:1] = 1.0 / (fx + eps)
  448. k[..., 1, 1:2] = 1.0 / (fy + eps)
  449. k[..., 0, 2:3] = -1.0 * cx / (fx + eps)
  450. k[..., 1, 2:3] = -1.0 * cy / (fy + eps)
  451. return k
  452. def scale_pinhole(pinholes: Tensor, scale: Tensor) -> Tensor:
  453. r"""Scale the pinhole matrix for each pinhole model.
  454. .. note::
  455. This method is going to be deprecated in version 0.2 in favour of
  456. :attr:`kornia.PinholeCamera.scale()`.
  457. Args:
  458. pinholes: tensor with the pinhole model.
  459. scale: tensor of scales.
  460. Returns:
  461. tensor of scaled pinholes.
  462. Shape:
  463. - Input: :math:`(N, 12)` and :math:`(N, 1)`
  464. - Output: :math:`(N, 12)`
  465. Example:
  466. >>> rng = torch.manual_seed(0)
  467. >>> pinhole_i = torch.rand(1, 12) # Nx12
  468. >>> scales = 2.0 * torch.ones(1) # N
  469. >>> scale_pinhole(pinhole_i, scales) # Nx12
  470. tensor([[0.9925, 1.5364, 0.1770, 0.2641, 0.6148, 1.2682, 0.4901, 0.8964, 0.4556,
  471. 0.6323, 0.3489, 0.4017]])
  472. """
  473. # warnings.warn("scale_pinhole will be deprecated in version 0.2, "
  474. # "use PinholeCamera.scale() instead",
  475. # PendingDeprecationWarning)
  476. if not (len(pinholes.shape) == 2 and pinholes.shape[1] == 12):
  477. raise AssertionError(pinholes.shape)
  478. if len(scale.shape) != 1:
  479. raise AssertionError(scale.shape)
  480. pinholes_scaled = pinholes.clone()
  481. pinholes_scaled[..., :6] = pinholes[..., :6] * scale.unsqueeze(-1)
  482. return pinholes_scaled
  483. def get_optical_pose_base(pinholes: Tensor) -> Tensor:
  484. """Compute extrinsic transformation matrices for pinholes.
  485. Args:
  486. pinholes: tensor of form [fx fy cx cy h w rx ry rz tx ty tz]
  487. of size (N, 12).
  488. Returns:
  489. tensor of extrinsic transformation matrices of size (N, 4, 4).
  490. """
  491. if not (len(pinholes.shape) == 2 and pinholes.shape[1] == 12):
  492. raise AssertionError(pinholes.shape)
  493. # TODO: where is rtvec_to_pose?
  494. raise NotImplementedError
  495. # TODO: We have rtvec_to_pose in torchgeometry
  496. # https://github.com/whh14/torchgeometry/blob/master/torchgeometry/conversions.py#L240
  497. # But it relies on axis_angle_to_rotation_matrix
  498. # And since then, it was changed from returning Nx4x4 matrix to Nx3x3
  499. # return rtvec_to_pose(optical_pose_parent) type: ignore
  500. def homography_i_H_ref(pinhole_i: Tensor, pinhole_ref: Tensor) -> Tensor:
  501. r"""Homography from reference to ith pinhole.
  502. .. note::
  503. The pinhole model is represented in a single vector as follows:
  504. .. math::
  505. pinhole = (f_x, f_y, c_x, c_y, height, width,
  506. r_x, r_y, r_z, t_x, t_y, t_z)
  507. where:
  508. :math:`(r_x, r_y, r_z)` is the rotation vector in angle-axis
  509. convention.
  510. :math:`(t_x, t_y, t_z)` is the translation vector.
  511. .. math::
  512. H_{ref}^{i} = K_{i} * T_{ref}^{i} * K_{ref}^{-1}
  513. Args:
  514. pinhole_i: tensor with pinhole model for ith frame.
  515. pinhole_ref: tensor with pinhole model for reference frame.
  516. Returns:
  517. tensors that convert depth points (u, v, d) from pinhole_ref to pinhole_i.
  518. Shape:
  519. - Input: :math:`(N, 12)` and :math:`(N, 12)`
  520. - Output: :math:`(N, 4, 4)`
  521. Example:
  522. pinhole_i = torch.rand(1, 12) # Nx12
  523. pinhole_ref = torch.rand(1, 12) # Nx12
  524. homography_i_H_ref(pinhole_i, pinhole_ref) # Nx4x4
  525. """
  526. # TODO: Add doctest once having `rtvec_to_pose`.
  527. if not (len(pinhole_i.shape) == 2 and pinhole_i.shape[1] == 12):
  528. raise AssertionError(pinhole_i.shape)
  529. if pinhole_i.shape != pinhole_ref.shape:
  530. raise AssertionError(pinhole_ref.shape)
  531. i_pose_base = get_optical_pose_base(pinhole_i)
  532. ref_pose_base = get_optical_pose_base(pinhole_ref)
  533. i_pose_ref = torch.matmul(i_pose_base, inverse_transformation(ref_pose_base))
  534. return torch.matmul(pinhole_matrix(pinhole_i), torch.matmul(i_pose_ref, inverse_pinhole_matrix(pinhole_ref)))
  535. # based on:
  536. # https://github.com/ClementPinard/SfmLearner-Pytorch/blob/master/inverse_warp.py#L26
  537. def pixel2cam(depth: Tensor, intrinsics_inv: Tensor, pixel_coords: Tensor) -> Tensor:
  538. r"""Transform coordinates in the pixel frame to the camera frame.
  539. Args:
  540. depth: the source depth maps. Shape must be Bx1xHxW.
  541. intrinsics_inv: the inverse intrinsics camera matrix. Shape must be Bx4x4.
  542. pixel_coords: the grid with (u, v, 1) pixel coordinates. Shape must be BxHxWx3.
  543. Returns:
  544. tensor of shape BxHxWx3 with (x, y, z) cam coordinates.
  545. """
  546. if not len(depth.shape) == 4 and depth.shape[1] == 1:
  547. raise ValueError(f"Input depth has to be in the shape of Bx1xHxW. Got {depth.shape}")
  548. if not len(intrinsics_inv.shape) == 3:
  549. raise ValueError(f"Input intrinsics_inv has to be in the shape of Bx4x4. Got {intrinsics_inv.shape}")
  550. if not len(pixel_coords.shape) == 4 and pixel_coords.shape[3] == 3:
  551. raise ValueError(f"Input pixel_coords has to be in the shape of BxHxWx3. Got {intrinsics_inv.shape}")
  552. cam_coords: Tensor = transform_points(intrinsics_inv[:, None], pixel_coords)
  553. return cam_coords * depth.permute(0, 2, 3, 1)
  554. # based on
  555. # https://github.com/ClementPinard/SfmLearner-Pytorch/blob/master/inverse_warp.py#L43
  556. def cam2pixel(cam_coords_src: Tensor, dst_proj_src: Tensor, eps: float = 1e-12) -> Tensor:
  557. r"""Transform coordinates in the camera frame to the pixel frame.
  558. Args:
  559. cam_coords_src: (x, y, z) coordinates defined in the first camera coordinates system. Shape must be BxHxWx3.
  560. dst_proj_src: the projection matrix between the
  561. reference and the non reference camera frame. Shape must be Bx4x4.
  562. eps: small value to avoid division by zero error.
  563. Returns:
  564. tensor of shape BxHxWx2 with (u, v) pixel coordinates.
  565. """
  566. if not len(cam_coords_src.shape) == 4 and cam_coords_src.shape[3] == 3:
  567. raise ValueError(f"Input cam_coords_src has to be in the shape of BxHxWx3. Got {cam_coords_src.shape}")
  568. if not len(dst_proj_src.shape) == 3 and dst_proj_src.shape[-2:] == (4, 4):
  569. raise ValueError(f"Input dst_proj_src has to be in the shape of Bx4x4. Got {dst_proj_src.shape}")
  570. # apply projection matrix to points
  571. point_coords: Tensor = transform_points(dst_proj_src[:, None], cam_coords_src)
  572. x_coord: Tensor = point_coords[..., 0]
  573. y_coord: Tensor = point_coords[..., 1]
  574. z_coord: Tensor = point_coords[..., 2]
  575. # compute pixel coordinates
  576. u_coord: Tensor = x_coord / (z_coord + eps)
  577. v_coord: Tensor = y_coord / (z_coord + eps)
  578. # stack and return the coordinates, that's the actual flow
  579. pixel_coords_dst: Tensor = stack([u_coord, v_coord], dim=-1)
  580. return pixel_coords_dst # BxHxWx2
  581. # layer api
  582. '''class PinholeMatrix(nn.Module):
  583. r"""Create an object that returns the pinhole matrix from a pinhole model
  584. Args:
  585. pinholes (Tensor): tensor of pinhole models.
  586. Returns:
  587. Tensor: tensor of pinhole matrices.
  588. Shape:
  589. - Input: :math:`(N, 12)`
  590. - Output: :math:`(N, 4, 4)`
  591. Example:
  592. >>> pinhole = torch.rand(1, 12) # Nx12
  593. >>> transform = PinholeMatrix()
  594. >>> pinhole_matrix = transform(pinhole) # Nx4x4
  595. """
  596. def __init__(self):
  597. super(PinholeMatrix, self).__init__()
  598. def forward(self, input):
  599. return pinhole_matrix(input)
  600. class InversePinholeMatrix(nn.Module):
  601. r"""Return and object that inverts a pinhole matrix from a pinhole model
  602. Args:
  603. pinholes (Tensor): tensor with pinhole models.
  604. Returns:
  605. Tensor: tensor of inverted pinhole matrices.
  606. Shape:
  607. - Input: :math:`(N, 12)`
  608. - Output: :math:`(N, 4, 4)`
  609. Example:
  610. >>> pinhole = torch.rand(1, 12) # Nx12
  611. >>> transform = kornia.InversePinholeMatrix()
  612. >>> pinhole_matrix_inv = transform(pinhole) # Nx4x4
  613. """
  614. def __init__(self):
  615. super(InversePinholeMatrix, self).__init__()
  616. def forward(self, input):
  617. return inverse_pinhole_matrix(input)'''