google-research
260 строк · 8.0 Кб
1# coding=utf-8
2# Copyright 2024 The Google Research Authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import numpy as np
17
18
19def generate_camera_noise(num_poses=100, noise_std=0.15):
20"""Generate camera noise."""
21se3_noise = np.random.randn(num_poses, 6) * noise_std
22return se3_noise
23
24
25def add_camera_noise(poses, se3_noise):
26"""Add camera noise."""
27num_poses = len(poses)
28se3_noise = se3_exp(se3_noise[:num_poses])
29noisy_poses = compose([se3_noise, poses[:, :3]])
30new_poses = np.copy(poses)
31new_poses[:, :3] = noisy_poses
32return new_poses
33
34
35def define_pose(rot=None, trans=None):
36"""Construct pose matrix."""
37assert(rot is not None or trans is not None)
38if rot is None:
39rot = np.repeat(np.eye(3)[None, Ellipsis], repeats=trans.shape[0], axis=0)
40if trans is None:
41trans = np.zeros(rot.shape[:-1]) # Bx3
42pose = np.concatenate([rot, trans[Ellipsis, None]], axis=-1) # [..., 3, 4]
43assert pose.shape[-2:] == (3, 4)
44return pose
45
46
47def invert_pose(pose):
48"""Invert pose."""
49# invert a camera pose
50rot, trans = pose[Ellipsis, :3], pose[Ellipsis, 3:]
51r_inv = np.swapaxes(rot, -1, -2)
52t_inv = (-r_inv@trans)[Ellipsis, 0]
53pose_inv = define_pose(rot=r_inv, trans=t_inv)
54return pose_inv
55
56
57def compose(pose_list):
58"""Compose sequence of poses."""
59# compose a sequence of poses together
60# pose_new(x) = poseN o ... o pose2 o pose1(x)
61pose_new = pose_list[0]
62for pose in pose_list[1:]:
63pose_new = compose_pair(pose_new, pose)
64return pose_new
65
66
67def compose_pair(pose_a, pose_b):
68"""Compose poses."""
69# pose_new(x) = pose_b o pose_a(x)
70r_a, t_a = pose_a[Ellipsis, :3], pose_a[Ellipsis, 3:]
71r_b, t_b = pose_b[Ellipsis, :3], pose_b[Ellipsis, 3:]
72r_new = r_b@r_a
73t_new = (r_b@t_a+t_b)[Ellipsis, 0]
74pose_new = define_pose(rot=r_new, trans=t_new)
75return pose_new
76
77
78def se3_exp(wu): # [..., 3]
79"""Exponential map."""
80w_vec, u_vec = np.split(wu, 2, axis=1)
81wx = skew_symmetric(w_vec)
82theta = np.linalg.norm(w_vec, axis=-1)[Ellipsis, None, None]
83i_mat = np.eye(3)
84a_mat = taylor_a(theta)
85b_mat = taylor_b(theta)
86c_mat = taylor_c(theta)
87r_mat = i_mat+a_mat*wx+b_mat*wx@wx
88v_mat = i_mat+b_mat*wx+c_mat*wx@wx
89rt = np.concatenate([r_mat, (v_mat@u_vec[Ellipsis, None])], axis=-1) # Bx3x4
90return rt
91
92
93def skew_symmetric(w_vec):
94"""Skey symmetric matrix from vectors."""
95w0, w1, w2 = np.split(w_vec, 3, axis=-1)
96w0 = w0.squeeze()
97w1 = w1.squeeze()
98w2 = w2.squeeze()
99zeros = np.zeros(w0.shape, np.float32)
100wx = np.stack([np.stack([zeros, -w2, w1], axis=-1),
101np.stack([w2, zeros, -w0], axis=-1),
102np.stack([-w1, w0, zeros], axis=-1)], axis=-2)
103return wx
104
105
106def taylor_a(x_val, nth=10):
107"""Taylor expansion."""
108# Taylor expansion of sin(x)/x
109ans = np.zeros_like(x_val)
110denom = 1.
111for i in range(nth+1):
112if i > 0:
113denom *= (2*i)*(2*i+1)
114ans = ans+(-1)**i*x_val**(2*i)/denom
115return ans
116
117
118def taylor_b(x_val, nth=10):
119"""Taylor expansion."""
120# Taylor expansion of (1-cos(x))/x**2
121ans = np.zeros_like(x_val)
122denom = 1.
123for i in range(nth+1):
124denom *= (2*i+1)*(2*i+2)
125ans = ans+(-1)**i*x_val**(2*i)/denom
126return ans
127
128
129def taylor_c(x_val, nth=10):
130"""Taylor expansion."""
131# Taylor expansion of (x-sin(x))/x**3
132ans = np.zeros_like(x_val)
133denom = 1.
134for i in range(nth+1):
135denom *= (2*i+2)*(2*i+3)
136ans = ans+(-1)**i*x_val**(2*i)/denom
137return ans
138
139
140def to_hom(x_mat):
141"""Homogenous coordinates."""
142# get homogeneous coordinates of the input
143x_hom = np.concatenate([x_mat, np.ones_like(x_mat[Ellipsis, :1])], axis=-1)
144return x_hom
145
146
147def world2cam(x_mat, pose): # [B, N, 3]
148"""Coordinate transformations."""
149x_hom = to_hom(x_mat)
150return x_hom@np.swapaxes(pose, -1, -2)
151
152
153def cam2img(x_mat, cam_intr):
154"""Coordinate transformations."""
155return x_mat@np.swapaxes(cam_intr, -1, -2)
156
157
158def img2cam(x_mat, cam_intr):
159"""Coordinate transformations."""
160return x_mat@np.swapaxes(np.linalg.inv(cam_intr), -1, -2)
161
162
163def cam2world(x_mat, pose):
164"""Coordinate transformations."""
165x_hom = to_hom(x_mat)
166pose_inv = invert_pose(pose)
167return x_hom@np.swapaxes(pose_inv, -1, -2)
168
169
170def angle_to_rotation_matrix(angle, axis):
171"""Angle to rotation matrix."""
172# get the rotation matrix from Euler angle around specific axis
173roll = dict(X=1, Y=2, Z=0)[axis]
174zeros = np.zeros_like(angle)
175eye = np.ones_like(angle)
176mat = np.stack([np.stack([angle.cos(), -angle.sin(), zeros], axis=-1),
177np.stack([angle.sin(), angle.cos(), zeros], axis=-1),
178np.stack([zeros, zeros, eye], axis=-1)], axis=-2)
179mat = mat.roll((roll, roll), axis=(-2, -1))
180return mat
181
182
183def rotation_distance(r1, r2, eps=1e-7):
184"""Rotation distance."""
185# http://www.boris-belousov.net/2016/12/01/quat-dist/
186r_diff = r1@np.swapaxes(r2, -2, -1)
187trace = r_diff[Ellipsis, 0, 0]+r_diff[Ellipsis, 1, 1]+r_diff[Ellipsis, 2, 2]
188angle = np.arccos(((trace-1)/2).clip(-1+eps, 1-eps))
189return angle
190
191
192def procrustes_analysis(x0, x1): # [N, 3]
193"""Procrustes."""
194# translation
195t0 = x0.mean(axis=0, keepdims=True)
196t1 = x1.mean(axis=0, keepdims=True)
197x0c = x0-t0
198x1c = x1-t1
199# scale
200s0 = np.sqrt((x0c**2).sum(axis=-1).mean())
201s1 = np.sqrt((x1c**2).sum(axis=-1).mean())
202x0cs = x0c/s0
203x1cs = x1c/s1
204# rotation (use double for SVD, float loses precision)
205# TODO do we need float64?
206u_mat, _, v_mat = np.linalg.svd((x0cs.transpose()@x1cs).astype(np.float64),
207full_matrices=False)
208rot = (u_mat@v_mat).astype(np.float32)
209if np.linalg.det(rot) < 0:
210rot[2] *= -1
211# align X1 to X0: X1to0 = (X1-t1)/s1@R.t()*s0+t0
212sim3 = {"t0": t0[0], "t1": t1[0], "s0": s0, "s1": s1, "R": rot}
213return sim3
214
215
216def prealign_cameras(poses_pred, poses_gt):
217"""Prealign cameras."""
218center = np.zeros((1, 1, 3))
219centers_pred = cam2world(center, poses_pred)[:, 0]
220centers_gt = cam2world(center, poses_gt)[:, 0]
221sim3 = procrustes_analysis(centers_gt, centers_pred)
222centers_aligned = (centers_pred-sim3["t1"])/(
223sim3["s1"]@sim3["R"].transpose()*sim3["s0"]+sim3["t0"])
224r_aligned = poses_pred[Ellipsis, :3]@sim3["R"].transpose()
225t_aligned = (-r_aligned@centers_aligned[Ellipsis, None])[Ellipsis, 0]
226poses_aligned = define_pose(rot=r_aligned, trans=t_aligned)
227return poses_aligned, sim3
228
229
230def refine_test_cameras(poses_init, sim3):
231"""Refine test cameras."""
232center = np.zeros((1, 1, 3))
233centers = cam2world(center, poses_init)[:, 0]
234centers_aligned = (
235centers-sim3["t0"])/sim3["s0"]@sim3["R"]*sim3["s1"]+sim3["t1"]
236r_aligned = poses_init[Ellipsis, :3]@sim3["R"]
237t_aligned = (-r_aligned@centers_aligned[Ellipsis, None])[Ellipsis, 0]
238poses_aligned = define_pose(rot=r_aligned, trans=t_aligned)
239return poses_aligned
240
241
242def evaluate_camera(pose, pose_gt):
243"""Evaluate cameras."""
244# evaluate the distance between pose and pose_ref; pose_ref is fixed
245pose_aligned, _ = prealign_cameras(pose, pose_gt)
246r_aligned, t_aligned = np.split(pose_aligned, [3,], axis=-1)
247r_gt, t_gt = np.split(pose_gt, [3,], axis=-1)
248r_error = rotation_distance(r_aligned, r_gt)
249t_error = np.linalg.norm((t_aligned-t_gt)[Ellipsis, 0], axis=-1)
250return r_error, t_error
251
252
253def evaluate_aligned_camera(pose_aligned, pose_gt):
254"""Evaluate aligned cameras."""
255# evaluate the distance between pose and pose_ref; pose_ref is fixed
256r_aligned, t_aligned = np.split(pose_aligned, [3,], axis=-1)
257r_gt, t_gt = np.split(pose_gt, [3,], axis=-1)
258r_error = rotation_distance(r_aligned, r_gt)
259t_error = np.linalg.norm((t_aligned-t_gt)[Ellipsis, 0], axis=-1)
260return r_error, t_error
261