-
Notifications
You must be signed in to change notification settings - Fork 8
/
run_reconstruction.py
185 lines (131 loc) · 4.79 KB
/
run_reconstruction.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
import sys
import numpy as np
from plyfile import PlyData
from matplotlib import pyplot as plt
from tomasi_kanade import TomasiKanade
from visualization import plot3d, plot_result
import rigid_motion
def read_object(filename):
"""Read a 3D object from a PLY file"""
ply = PlyData.read(filename)
vertex = ply['vertex']
x, y, z = [vertex[t] for t in ('x', 'y', 'z')]
return np.vstack((x, y, z)).T
def normalize_object_size(X):
"""
Noramlize object size so that for
each object point :math:`\mathbf{x} \in X`
.. math::
\\frac{1}{|X|} \sum_{\mathbf{x} \in X} ||\mathbf{x}|| = 1
"""
return X / np.linalg.norm(X, axis=1).mean()
class Camera(object):
"""
Camera class
Args:
intrinsic_parameters: Intrinsic camera matrix
:math:`K \in R^{3 \times 3}`
"""
def __init__(self, intrinsic_parameters: np.ndarray):
self.intrinsic_parameters = intrinsic_parameters
self.rotation = np.eye(3)
self.translation = np.zeros(3)
def set_pose(self, rotation, translation):
self.rotation = rotation
self.translation = translation
class Object3D(object):
"""
3D object class.
This class wraps the observation process from a view point
Args:
points: Points of the 3D object
"""
def __init__(self, points: np.ndarray):
self.X = points
@property
def n_points(self):
"""The number of points in the object"""
return self.X.shape[0]
def observed(self, camera_rotation: np.ndarray,
camera_translation: np.ndarray):
"""
Return 2D points projected onto the image plane
Args:
camera_rotation: Rotation matrix
which represents the camera rotation
camera_translation: Translation vector
which represents the camera position
"""
R = camera_rotation
t = camera_translation
return rigid_motion.transform(1, R, t, self.X)
def take_picture(target_object: Object3D, camera: Camera, noise_std=0.0):
"""
Project 3D points in ``target_object`` onto the image plane defined
by `camera`
Args:
target_object: Object to be seen from the ``camera``
camera: Camera object which observes the target object
noise_std: Standard deviation of noise added in the observation process
"""
# Y: points seen from the camera coordinate
Y = target_object.observed(camera.rotation, camera.translation)
K = camera.intrinsic_parameters
image_points = np.dot(K, Y.T).T # project onto the image plane
if noise_std == 0.0:
return image_points
noise = np.random.normal(0, noise_std, size=image_points.shape)
return image_points + noise
def to_viewpoints(M):
x = np.array([1, 0, 0])
def to_viewpoint(M):
m = np.cross(M[0], M[1])
R = np.vstack((M, m))
return np.dot(R, x)
F = M.shape[0] // 2
return np.array([to_viewpoint(M_) for M_ in np.split(M, F)])
def main():
np.random.seed(1234)
if len(sys.argv) < 2:
print("Usage: $python3 run_reconstruction.py <path to PLY file>")
exit(0)
filename = sys.argv[1]
# Camera intrinsic matrix
# In this case, the image coordinate is represented in a non-homogeneous 2D
# vector, therefore the intrinsic matrix is represented in a 2x3 matrix.
# In this script, we assume the orthogonal projection as a camera
# projection model
intrinsic_parameters = np.array([
[1, 0, 0],
[0, 1, 0]
])
# Load the 3D object from the file
X_true = read_object(filename)
X_true = normalize_object_size(X_true)
# Number of viewpoints to be used for reconstruction
n_views = 128
# Standard deviation of noise
noise_std = 0.0
target_object = Object3D(X_true) # Create the target object
camera = Camera(intrinsic_parameters) # Camera object to observe the target
# The ground truth object `X_true` is passed to the TomasiKanade method,
# though, this is used only for the evaluation, not reconstruction
tomasi_kanade = TomasiKanade(X_eval=X_true, learning_rate=0.0027)
for i in range(n_views):
# Generate a random camera pose
R = rigid_motion.random_rotation_matrix_3d()
t = rigid_motion.random_vector_3d()
camera.set_pose(R, t)
# Observe the 3D object by projecting it onto the image plane
image_points = take_picture(target_object, camera, noise_std)
tomasi_kanade.add_image_points(image_points)
# Run reconstruction
# M is a stacked motion matrices
# X contains the reconstructed object
M, X = tomasi_kanade.run()
V = to_viewpoints(M)
plot3d(X, azim=180, elev=90)
plot_result(X, V)
plt.show()
if __name__ == '__main__':
main()