Skip to content

Commit 2480723

Browse files
davegreenwoodfacebook-github-bot
authored andcommitted
create extrinsic from eye point (#65)
Summary: Create extrinsic parameters from eye point. Create the rotation and translation from an eye point, look-at point and up vector. see: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml It is arguably easier to initialise a camera position as a point in the world rather than an angle. Pull Request resolved: #65 Reviewed By: bottler Differential Revision: D20419652 Pulled By: nikhilaravi fbshipit-source-id: 9caa1330860bb8bde1fb5c3864ed4cde836a5d19
1 parent c9742d0 commit 2480723

File tree

2 files changed

+52
-11
lines changed

2 files changed

+52
-11
lines changed

pytorch3d/renderer/cameras.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import math
44
import numpy as np
5-
from typing import Tuple
5+
from typing import Tuple, Optional, Sequence
66
import torch
77
import torch.nn.functional as F
88

@@ -997,10 +997,11 @@ def look_at_rotation(
997997

998998

999999
def look_at_view_transform(
1000-
dist,
1001-
elev,
1002-
azim,
1000+
dist=1.0,
1001+
elev=0.0,
1002+
azim=0.0,
10031003
degrees: bool = True,
1004+
eye: Optional[Sequence] = None,
10041005
at=((0, 0, 0),), # (1, 3)
10051006
up=((0, 1, 0),), # (1, 3)
10061007
device="cpu",
@@ -1019,10 +1020,12 @@ def look_at_view_transform(
10191020
reference vector at (1, 0, 0) on the reference plane.
10201021
dist, elem and azim can be of shape (1), (N).
10211022
degrees: boolean flag to indicate if the elevation and azimuth
1022-
angles are specified in degrees or raidans.
1023+
angles are specified in degrees or radians.
1024+
eye: the position of the camera(s) in world coordinates. If eye is not
1025+
None, it will overide the camera position derived from dist, elev, azim.
10231026
up: the direction of the x axis in the world coordinate system.
10241027
at: the position of the object(s) in world coordinates.
1025-
up and at can be of shape (1, 3) or (N, 3).
1028+
eye, up and at can be of shape (1, 3) or (N, 3).
10261029
10271030
Returns:
10281031
2-element tuple containing
@@ -1033,11 +1036,19 @@ def look_at_view_transform(
10331036
References:
10341037
[0] https://www.scratchapixel.com
10351038
"""
1036-
broadcasted_args = convert_to_tensors_and_broadcast(
1037-
dist, elev, azim, at, up, device=device
1038-
)
1039-
dist, elev, azim, at, up = broadcasted_args
1040-
C = camera_position_from_spherical_angles(dist, elev, azim, device=device)
1039+
1040+
if eye is not None:
1041+
broadcasted_args = convert_to_tensors_and_broadcast(
1042+
eye, at, up, device=device)
1043+
eye, at, up = broadcasted_args
1044+
C = eye
1045+
else:
1046+
broadcasted_args = convert_to_tensors_and_broadcast(
1047+
dist, elev, azim, at, up, device=device)
1048+
dist, elev, azim, at, up = broadcasted_args
1049+
C = camera_position_from_spherical_angles(
1050+
dist, elev, azim, degrees=degrees, device=device)
1051+
10411052
R = look_at_rotation(C, at, up, device=device)
10421053
T = -torch.bmm(R.transpose(1, 2), C[:, :, None])[:, :, 0]
10431054
return R, T

tests/test_cameras.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
camera_position_from_spherical_angles,
3939
get_world_to_view_transform,
4040
look_at_rotation,
41+
look_at_view_transform,
4142
)
4243
from pytorch3d.transforms import Transform3d
4344
from pytorch3d.transforms.so3 import so3_exponential_map
@@ -116,6 +117,35 @@ def setUp(self) -> None:
116117
torch.manual_seed(42)
117118
np.random.seed(42)
118119

120+
def test_look_at_view_transform_from_eye_point_tuple(self):
121+
dist = math.sqrt(2)
122+
elev = math.pi / 4
123+
azim = 0.0
124+
eye = ((0.0, 1.0, 1.0), )
125+
# using passed values for dist, elev, azim
126+
R, t = look_at_view_transform(dist, elev, azim, degrees=False)
127+
# using other values for dist, elev, azim - eye overrides
128+
R_eye, t_eye = look_at_view_transform(dist=3, elev=2, azim=1, eye=eye)
129+
# using only eye value
130+
131+
R_eye_only, t_eye_only = look_at_view_transform(eye=eye)
132+
self.assertTrue(torch.allclose(R, R_eye, atol=2e-7))
133+
self.assertTrue(torch.allclose(t, t_eye, atol=2e-7))
134+
self.assertTrue(torch.allclose(R, R_eye_only, atol=2e-7))
135+
self.assertTrue(torch.allclose(t, t_eye_only, atol=2e-7))
136+
137+
def test_look_at_view_transform_default_values(self):
138+
dist = 1.0
139+
elev = 0.0
140+
azim = 0.0
141+
# Using passed values for dist, elev, azim
142+
R, t = look_at_view_transform(dist, elev, azim)
143+
# Using default dist=1.0, elev=0.0, azim=0.0
144+
R_default, t_default = look_at_view_transform()
145+
# test default = passed = expected
146+
self.assertTrue(torch.allclose(R, R_default, atol=2e-7))
147+
self.assertTrue(torch.allclose(t, t_default, atol=2e-7))
148+
119149
def test_camera_position_from_angles_python_scalar(self):
120150
dist = 2.7
121151
elev = 90.0

0 commit comments

Comments
 (0)