-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathcamera.py
211 lines (168 loc) · 6.87 KB
/
camera.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#
# For licensing see accompanying LICENSE file.
# Copyright (C) 2022 Apple Inc. All Rights Reserved.
#
import numpy as np
from typing import Tuple, Union
import torch
from lazy import lazy
class Camera:
def __init__(
self,
height: int = 480,
width: int = 640,
intrinsics: np.ndarray = None,
ray_from_pix_center: bool = False,
):
"""Initialize the camera class
Args:
height (int, optional): [description]. Defaults to 480.
width (int, optional): [description]. Defaults to 640.
intrinsics (np.ndarray, optional): [description]. Defaults to None.
"""
self._h = height
self._w = width
assert (
intrinsics.ndim == 2 and intrinsics.shape[0] == 3 and intrinsics.shape[1] == 3
), "[Camera] Expecting a 3x3 intrinsics matrix, but instead got {}".format(intrinsics.shape)
self._K = intrinsics
self._ray_dir_torch_cuda = None
self._ray_from_pix_center = ray_from_pix_center
@property
def intrinsic_matrix(self):
return self._K
@property
def height(self):
return self._h
@property
def width(self):
return self._w
def __repr__(self):
return f"Camera: height={self.height}, width={self.width}, intrinsics=\n{self.intrinsic_matrix}"
@lazy
def homogeneous_coordinates(self) -> np.ndarray:
"""Construct the homogeneous coordinates [x/z, y/z, 1] for every pixel
Returns:
np.ndarray: a 3 x H x W numpy ndarray corresponding to [x/z, y/z, 1]
"""
# construct arrays of pixel coordinates
xx, yy = np.meshgrid(range(int(self.width)), range(int(self.height)), indexing="xy")
if self._ray_from_pix_center:
# NOTE: we cast ray from pixel's center.
xx = xx + 0.5
yy = yy + 0.5
# [u, v, 1] of shape [3, H, W]
uv1 = np.stack([xx, yy, np.ones(xx.shape)])
# [x/z, y/z, 1] of shape [3, H, W]
inverse_K = np.linalg.inv(self.intrinsic_matrix)
xyz_div_z = np.matmul(inverse_K, uv1.reshape(3, -1))
xyz_div_z = xyz_div_z.reshape(3, self.height, self.width)
return xyz_div_z
@lazy
def homogeneous_coordinates_border(self) -> np.ndarray:
"""Construct the homogeneous coordinates [x/z, y/z, 1] for every pixel
Returns:
np.ndarray: a 3 x H x W numpy ndarray corresponding to [x/z, y/z, 1]
"""
# construct arrays of pixel coordinates
xx, yy = np.meshgrid(np.array([0, self.width]), np.array([0, self.height]), indexing="xy")
# [u, v, 1] of shape [3, H, W]
uv1 = np.stack([xx, yy, np.ones(xx.shape)])
# [x/z, y/z, 1] of shape [3, H, W]
inverse_K = np.linalg.inv(self.intrinsic_matrix)
xyz_div_z = np.matmul(inverse_K, uv1.reshape(3, -1))
xyz_div_z = xyz_div_z.reshape(3, 2, 2)
return xyz_div_z
@lazy
def ray_dir_np(self) -> np.ndarray:
# Construct unit-length ray directions
xyz_div_z = self.homogeneous_coordinates
row_l2_norms = np.linalg.norm(xyz_div_z, axis=0)
ray_dir = xyz_div_z / row_l2_norms
ray_dir = ray_dir.reshape(3, -1)
return ray_dir
@lazy
def ray_dir_border_np(self) -> np.ndarray:
# Construct unit-length ray directions
xyz_div_z = self.homogeneous_coordinates_border
row_l2_norms = np.linalg.norm(xyz_div_z, axis=0)
ray_dir = xyz_div_z / row_l2_norms
ray_dir = ray_dir.reshape(3, -1)
return ray_dir
@lazy
def ray_dir_torch(self) -> np.ndarray:
return torch.FloatTensor(self.ray_dir_np)
@lazy
def ray_dir_border_torch(self) -> np.ndarray:
return torch.FloatTensor(self.ray_dir_border_np)
def ray_dir_torch_cuda(self, device, border_only=False) -> torch.Tensor:
if self._ray_dir_torch_cuda is None:
if border_only:
self._ray_dir_torch_cuda = self.ray_dir_border_torch.to(device)
else:
self._ray_dir_torch_cuda = self.ray_dir_torch.to(device)
return self._ray_dir_torch_cuda
def generate_rays(
self,
tf_c2w: Union[np.ndarray, torch.Tensor],
border_only: bool = False,
) -> Tuple[Union[np.ndarray, torch.Tensor], Union[np.ndarray, torch.Tensor], Union[np.ndarray, torch.Tensor],]:
"""Generate camera rays in the world space, given the camera-to-world transformation
Args:
tf_c2w (np.ndarray): 4 x 4, camera-to-world transformation
Returns:
ray_dir (np.ndarray): a 3 x H x W tensor containing the unit-length ray directions for each pixel
eye_pos (np.ndarray): a 3-vector representing the eye position
z_dir (np.ndarray): a 3-vector representing the unit-length ray direction of the optical axis
"""
if isinstance(tf_c2w, np.ndarray):
return self._generate_rays_np(tf_c2w, border_only=border_only)
elif isinstance(tf_c2w, torch.Tensor):
return self._generate_rays_torch(tf_c2w, border_only=border_only)
else:
raise ValueError
def _generate_rays_np(
self,
tf_c2w: np.ndarray,
border_only: bool = False,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
# extract camera-to-world transformations
eye_pos = tf_c2w[:3, 3]
rot_mat = tf_c2w[:3, :3]
if border_only:
cur_ray_dir_np = self.ray_dir_border_np
else:
cur_ray_dir_np = self.ray_dir_np
# apply transformation from camera space to world space
ray_dir = rot_mat @ cur_ray_dir_np # 3x3 @ 3xN -> 3xN
if border_only:
ray_dir = ray_dir.reshape(3, 2, 2)
else:
ray_dir = ray_dir.reshape(3, self.height, self.width) # 3xHxW
# extract the z direction
z_dir = rot_mat[:, 2]
return ray_dir, eye_pos, z_dir
def _generate_rays_torch(
self,
tf_c2w: torch.Tensor,
border_only: bool = False,
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
# extract camera-to-world transformations
eye_pos = tf_c2w[:3, 3]
rot_mat = tf_c2w[:3, :3]
if tf_c2w.is_cuda:
cur_ray_dir_torch = self.ray_dir_torch_cuda(tf_c2w.device, border_only=border_only)
else:
if border_only:
cur_ray_dir_torch = self.ray_dir_border_torch
else:
cur_ray_dir_torch = self.ray_dir_torch
# apply transformation from camera space to world space
ray_dir = torch.matmul(rot_mat, cur_ray_dir_torch) # 3x3 @ 3xN -> 3xN
if border_only:
ray_dir = ray_dir.reshape(3, 2, 2) # 3xHxW
else:
ray_dir = ray_dir.reshape(3, self.height, self.width) # 3xHxW
# extract the z direction
z_dir = rot_mat[:, 2]
return ray_dir, eye_pos, z_dir