forked from colmap/pycolmap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmvs.cc
307 lines (279 loc) · 12.7 KB
/
mvs.cc
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#include "colmap/base/camera_models.h"
#include "colmap/base/image_reader.h"
#include "colmap/base/reconstruction.h"
#include "colmap/controllers/incremental_mapper.h"
#include "colmap/exe/feature.h"
#include "colmap/exe/sfm.h"
#include "colmap/feature/extraction.h"
#include "colmap/feature/matching.h"
#include "colmap/feature/sift.h"
#include "colmap/util/misc.h"
#include "colmap/base/reconstruction.h"
#include "colmap/mvs/fusion.h"
#include "colmap/mvs/meshing.h"
#include "colmap/mvs/patch_match.h"
#include "colmap/util/misc.h"
using namespace colmap;
#include <pybind11/iostream.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
namespace py = pybind11;
using namespace pybind11::literals;
#include "helpers.h"
#include "log_exceptions.h"
void patch_match_stereo(py::object workspace_path_,
std::string workspace_format,
std::string pmvs_option_name,
mvs::PatchMatchOptions options,
std::string config_path,
bool verbose) {
#ifndef CUDA_ENABLED
THROW_EXCEPTION(
std::runtime_error,
"ERROR: Dense stereo reconstruction requires CUDA, which is not "
"available on your system.");
return;
#endif // CUDA_ENABLED
std::string workspace_path = py::str(workspace_path_).cast<std::string>();
THROW_CHECK_DIR_EXISTS(workspace_path);
StringToLower(&workspace_format);
THROW_CUSTOM_CHECK_MSG(
(workspace_format == "colmap" || workspace_format == "pmvs"),
std::invalid_argument,
"Invalid `workspace_format` - supported values are "
"'COLMAP' or 'PMVS'.")
std::stringstream oss;
std::streambuf* oldcout = nullptr;
if (!verbose) {
oldcout = std::cout.rdbuf(oss.rdbuf());
}
mvs::PatchMatchController controller(options,
workspace_path,
workspace_format,
pmvs_option_name,
config_path);
controller.Start();
PyWait(&controller);
// controller.Wait();
if (!verbose) {
std::cout.rdbuf(oldcout);
}
}
colmap::Reconstruction stereo_fusion(py::object output_path_,
py::object workspace_path_,
std::string workspace_format,
std::string pmvs_option_name,
std::string input_type,
mvs::StereoFusionOptions options,
bool verbose) {
std::string workspace_path = py::str(workspace_path_).cast<std::string>();
THROW_CHECK_DIR_EXISTS(workspace_path);
std::string output_path = py::str(output_path_).cast<std::string>();
StringToLower(&workspace_format);
THROW_CUSTOM_CHECK_MSG(
(workspace_format == "colmap" || workspace_format == "pmvs"),
std::invalid_argument,
"Invalid `workspace_format` - supported values are "
"'COLMAP' or 'PMVS'.");
StringToLower(&input_type);
THROW_CUSTOM_CHECK_MSG(
(input_type == "photometric" || input_type == "geometric"),
std::invalid_argument,
"Invalid input type - supported values are 'photometric' and "
"'geometric'.");
mvs::StereoFusion fuser(options,
workspace_path,
workspace_format,
pmvs_option_name,
input_type);
std::stringstream oss;
std::streambuf* oldcout = nullptr;
if (!verbose) {
oldcout = std::cout.rdbuf(oss.rdbuf());
}
py::gil_scoped_release release;
fuser.Start();
PyWait(&fuser);
if (!verbose) {
std::cout.rdbuf(oldcout);
}
Reconstruction reconstruction;
// read data from sparse reconstruction
if (workspace_format == "colmap") {
reconstruction.Read(JoinPaths(workspace_path, "sparse"));
}
// overwrite sparse point cloud with dense point cloud from fuser
reconstruction.ImportPLY(fuser.GetFusedPoints());
if (ExistsDir(output_path)) {
reconstruction.WriteBinary(output_path);
} else {
THROW_CHECK_HAS_FILE_EXTENSION(output_path, ".ply")
THROW_CHECK_FILE_OPEN(output_path);
WriteBinaryPlyPoints(output_path, fuser.GetFusedPoints());
mvs::WritePointsVisibility(output_path + ".vis",
fuser.GetFusedPointsVisibility());
}
return reconstruction;
}
void init_mvs(py::module& m) {
using PMOpts = mvs::PatchMatchOptions;
auto PyPatchMatchOptions =
py::class_<PMOpts>(m, "PatchMatchOptions")
.def(py::init<>())
.def_readwrite("max_image_size",
&PMOpts::max_image_size,
"Maximum image size in either dimension.")
.def_readwrite(
"gpu_index",
&PMOpts::gpu_index,
"Index of the GPU used for patch match. For multi-GPU usage, "
"you should separate multiple GPU indices by comma, e.g., "
"\"0,1,2,3\".")
.def_readwrite("depth_min", &PMOpts::depth_min)
.def_readwrite("depth_max", &PMOpts::depth_max)
.def_readwrite(
"window_radius",
&PMOpts::window_radius,
"Half window size to compute NCC photo-consistency cost.")
.def_readwrite("window_step",
&PMOpts::window_step,
"Number of pixels to skip when computing NCC.")
.def_readwrite("sigma_spatial",
&PMOpts::sigma_spatial,
"Spatial sigma for bilaterally weighted NCC.")
.def_readwrite("sigma_color",
&PMOpts::sigma_color,
"Color sigma for bilaterally weighted NCC.")
.def_readwrite(
"num_samples",
&PMOpts::num_samples,
"Number of random samples to draw in Monte Carlo sampling.")
.def_readwrite("ncc_sigma",
&PMOpts::ncc_sigma,
"Spread of the NCC likelihood function.")
.def_readwrite("min_triangulation_angle",
&PMOpts::min_triangulation_angle,
"Minimum triangulation angle in degrees.")
.def_readwrite("incident_angle_sigma",
&PMOpts::incident_angle_sigma,
"Spread of the incident angle likelihood function.")
.def_readwrite("num_iterations",
&PMOpts::num_iterations,
"Number of coordinate descent iterations.")
.def_readwrite("geom_consistency",
&PMOpts::geom_consistency,
"Whether to add a regularized geometric consistency "
"term to the cost function. If true, the "
"`depth_maps` and `normal_maps` must not be null.")
.def_readwrite("geom_consistency_regularizer",
&PMOpts::geom_consistency_regularizer,
"The relative weight of the geometric consistency "
"term w.r.t. to the photo-consistency term.")
.def_readwrite("geom_consistency_max_cost",
&PMOpts::geom_consistency_max_cost,
"Maximum geometric consistency cost in terms of the "
"forward-backward reprojection error in pixels.")
.def_readwrite(
"filter", &PMOpts::filter, "Whether to enable filtering.")
.def_readwrite(
"filter_min_ncc",
&PMOpts::filter_min_ncc,
"Minimum NCC coefficient for pixel to be photo-consistent.")
.def_readwrite("filter_min_triangulation_angle",
&PMOpts::filter_min_triangulation_angle,
"Minimum triangulation angle to be stable.")
.def_readwrite(
"filter_min_num_consistent",
&PMOpts::filter_min_num_consistent,
"Minimum number of source images have to be consistent "
"for pixel not to be filtered.")
.def_readwrite(
"filter_geom_consistency_max_cost",
&PMOpts::filter_geom_consistency_max_cost,
"Maximum forward-backward reprojection error for pixel "
"to be geometrically consistent.")
.def_readwrite("cache_size",
&PMOpts::cache_size,
"Cache size in gigabytes for patch match.")
.def_readwrite(
"allow_missing_files",
&PMOpts::allow_missing_files,
"Whether to tolerate missing images/maps in the problem setup")
.def_readwrite("write_consistency_graph",
&PMOpts::write_consistency_graph,
"Whether to write the consistency graph.");
make_dataclass(PyPatchMatchOptions);
auto patch_match_options = PyPatchMatchOptions().cast<PMOpts>();
using SFOpts = mvs::StereoFusionOptions;
auto PyStereoFusionOptions =
py::class_<SFOpts>(m, "StereoFusionOptions")
.def(py::init<>())
.def_readwrite("mask_path",
&SFOpts::mask_path,
"Path for PNG masks. Same format expected as "
"ImageReaderOptions.")
.def_readwrite("num_threads",
&SFOpts::num_threads,
"The number of threads to use during fusion.")
.def_readwrite("max_image_size",
&SFOpts::max_image_size,
"Maximum image size in either dimension.")
.def_readwrite("min_num_pixels",
&SFOpts::min_num_pixels,
"Minimum number of fused pixels to produce a point.")
.def_readwrite(
"max_num_pixels",
&SFOpts::max_num_pixels,
"Maximum number of pixels to fuse into a single point.")
.def_readwrite("max_traversal_depth",
&SFOpts::max_traversal_depth,
"Maximum depth in consistency graph traversal.")
.def_readwrite("max_reproj_error",
&SFOpts::max_reproj_error,
"Maximum relative difference between measured and "
"projected pixel.")
.def_readwrite("max_depth_error",
&SFOpts::max_depth_error,
"Maximum relative difference between measured and "
"projected depth.")
.def_readwrite("max_normal_error",
&SFOpts::max_normal_error,
"Maximum angular difference in degrees of normals "
"of pixels to be fused.")
.def_readwrite("check_num_images",
&SFOpts::check_num_images,
"Number of overlapping images to transitively check "
"for fusing points.")
.def_readwrite(
"use_cache",
&SFOpts::use_cache,
"Flag indicating whether to use LRU cache or pre-load all data")
.def_readwrite("cache_size",
&SFOpts::cache_size,
"Cache size in gigabytes for fusion.")
.def_readwrite("bounding_box",
&SFOpts::bounding_box,
"Bounding box Tuple[min, max]");
make_dataclass(PyStereoFusionOptions);
auto stereo_fusion_options = PyStereoFusionOptions().cast<SFOpts>();
m.def("patch_match_stereo",
&patch_match_stereo,
py::arg("workspace_path"),
py::arg("workspace_format") = "COLMAP",
py::arg("pmvs_option_name") = "option-all",
py::arg("options") = patch_match_options,
py::arg("config_path") = "",
py::arg("verbose") = true,
"Runs Patch-Match-Stereo (requires CUDA)");
m.def("stereo_fusion",
&stereo_fusion,
py::arg("output_path"),
py::arg("workspace_path"),
py::arg("workspace_format") = "COLMAP",
py::arg("pmvs_option_name") = "option-all",
py::arg("input_type") = "geometric",
py::arg("options") = stereo_fusion_options,
py::arg("verbose") = true,
"Stereo Fusion");
}