forked from mapillary/OpenSfM
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmesh.py
186 lines (154 loc) · 5.8 KB
/
mesh.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
#!/usr/bin/env python3
import itertools
import logging
from typing import Any, Tuple, List
import numpy as np
import scipy.spatial
from opensfm import pygeometry, pymap, types
logger: logging.Logger = logging.getLogger(__name__)
def triangle_mesh(
shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager
) -> Tuple[List[Any], List[Any]]:
"""
Create triangle meshes in a list
"""
if shot_id not in r.shots or shot_id not in tracks_manager.get_shot_ids():
return [], []
shot = r.shots[shot_id]
if shot.camera.projection_type in [
"perspective",
"brown",
"radial",
"simple_radial",
]:
return triangle_mesh_perspective(shot_id, r, tracks_manager)
elif shot.camera.projection_type in [
"fisheye",
"fisheye_opencv",
"fisheye62",
"fisheye624",
"dual",
]:
return triangle_mesh_fisheye(shot_id, r, tracks_manager)
elif pygeometry.Camera.is_panorama(shot.camera.projection_type):
return triangle_mesh_spherical(shot_id, r, tracks_manager)
else:
raise NotImplementedError(
f"triangle_mesh not implemented for projection type {shot.camera.projection_type}"
)
def triangle_mesh_perspective(
shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager
) -> Tuple[List[Any], List[Any]]:
shot = r.shots[shot_id]
cam = shot.camera
dx = float(cam.width) / 2 / max(cam.width, cam.height)
dy = float(cam.height) / 2 / max(cam.width, cam.height)
pixels = [[-dx, -dy], [-dx, dy], [dx, dy], [dx, -dy]]
vertices = [None for i in range(4)]
for track_id in tracks_manager.get_shot_observations(shot_id):
if track_id in r.points:
point = r.points[track_id]
pixel = shot.project(point.coordinates)
nonans = not np.isnan(pixel).any()
if nonans and -dx <= pixel[0] <= dx and -dy <= pixel[1] <= dy:
vertices.append(point.coordinates)
pixels.append(pixel.tolist())
try:
tri = scipy.spatial.Delaunay(pixels)
except Exception as e:
logger.error("Delaunay triangulation failed for input: {}".format(repr(pixels)))
raise e
sums = [0.0, 0.0, 0.0, 0.0]
depths = [0.0, 0.0, 0.0, 0.0]
for t in tri.simplices:
for i in range(4):
if i in t:
for j in t:
if j >= 4:
depths[i] += shot.pose.transform(vertices[j])[2]
sums[i] += 1
for i in range(4):
if sums[i] > 0:
d = depths[i] / sums[i]
else:
d = 50.0
vertices[i] = back_project_no_distortion(shot, pixels[i], d).tolist()
faces = tri.simplices.tolist()
return vertices, faces
def back_project_no_distortion(
shot: pymap.Shot, pixel: List[float], depth: float
) -> np.ndarray:
"""
Back-project a pixel of a perspective camera ignoring its radial distortion
"""
K = shot.camera.get_K()
K1 = np.linalg.inv(K)
p = np.dot(K1, [pixel[0], pixel[1], 1])
p *= depth / p[2]
return shot.pose.transform_inverse(p)
def triangle_mesh_fisheye(
shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager
) -> Tuple[List[Any], List[Any]]:
shot = r.shots[shot_id]
bearings = []
vertices = []
# Add boundary vertices
num_circle_points = 20
for i in range(num_circle_points):
a = 2 * np.pi * float(i) / num_circle_points
point = 30 * np.array([np.cos(a), np.sin(a), 0])
bearing = point / np.linalg.norm(point)
point = shot.pose.transform_inverse(point)
vertices.append(point.tolist())
bearings.append(bearing)
# Add a single vertex in front of the camera
point = 30 * np.array([0, 0, 1])
bearing = 0.3 * point / np.linalg.norm(point)
point = shot.pose.transform_inverse(point)
vertices.append(point.tolist())
bearings.append(bearing)
# Add reconstructed points
for track_id in tracks_manager.get_shot_observations(shot_id):
if track_id in r.points:
point = r.points[track_id].coordinates
direction = shot.pose.transform(point)
pixel = direction / np.linalg.norm(direction)
if not np.isnan(pixel).any():
vertices.append(point)
bearings.append(pixel.tolist())
# Triangulate
tri = scipy.spatial.ConvexHull(bearings)
faces = tri.simplices.tolist()
# Remove faces having only boundary vertices
def good_face(face: List[Any]) -> bool:
return (
face[0] >= num_circle_points
or face[1] >= num_circle_points
or face[2] >= num_circle_points
)
faces = list(filter(good_face, faces))
return vertices, faces
def triangle_mesh_spherical(
shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager
) -> Tuple[List[Any], List[Any]]:
shot = r.shots[shot_id]
bearings = []
vertices = []
# Add vertices to ensure that the camera is inside the convex hull
# of the points
for point in itertools.product([-1, 1], repeat=3): # vertices of a cube
bearing = 0.3 * np.array(point) / np.linalg.norm(point)
bearings.append(bearing)
point = shot.pose.transform_inverse(bearing)
vertices.append(point.tolist())
for track_id in tracks_manager.get_shot_observations(shot_id):
if track_id in r.points:
point = r.points[track_id].coordinates
direction = shot.pose.transform(point)
pixel = direction / np.linalg.norm(direction)
if not np.isnan(pixel).any():
vertices.append(point)
bearings.append(pixel.tolist())
tri = scipy.spatial.ConvexHull(bearings)
faces = tri.simplices.tolist()
return vertices, faces