-
Notifications
You must be signed in to change notification settings - Fork 15
/
run_colmap.py
186 lines (138 loc) · 6.56 KB
/
run_colmap.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 os
import sys
import subprocess
import shutil
from extract_sfm import extract_all_to_dir
from normalize_cam_dict import normalize_cam_dict
#########################################################################
# Note: configure the colmap_bin to the colmap executable on your machine
#########################################################################
def bash_run(cmd):
colmap_bin = '/path/to/colmap/build/src/exe/colmap'
cmd = colmap_bin + ' ' + cmd
print('\nRunning cmd: ', cmd)
subprocess.check_call(['/bin/bash', '-c', cmd])
# gpu_index = '-1'
gpu_index = '0,1,2'
def run_sift_matching(img_dir, db_file, remove_exist=False):
print('Running sift matching...')
if remove_exist and os.path.exists(db_file):
os.remove(db_file) # otherwise colmap will skip sift matching
# feature extraction
# if there's no attached display, cannot use feature extractor with GPU
cmd = ' feature_extractor --database_path {} \
--image_path {} \
--ImageReader.single_camera 0 \
--ImageReader.camera_model SIMPLE_RADIAL \
--SiftExtraction.max_image_size 1280 \
--SiftExtraction.estimate_affine_shape 0 \
--SiftExtraction.domain_size_pooling 1 \
--SiftExtraction.use_gpu 1 \
--SiftExtraction.max_num_features 16384 \
--SiftExtraction.gpu_index {}'.format(db_file, img_dir, gpu_index)
bash_run(cmd)
# feature matching
cmd = ' exhaustive_matcher --database_path {} \
--SiftMatching.guided_matching 1 \
--SiftMatching.use_gpu 1 \
--SiftMatching.max_num_matches 65536 \
--SiftMatching.max_error 3 \
--SiftMatching.gpu_index {}'.format(db_file, gpu_index)
bash_run(cmd)
def run_sfm(img_dir, db_file, out_dir):
print('Running SfM...')
cmd = ' mapper \
--database_path {} \
--image_path {} \
--output_path {} \
--Mapper.tri_min_angle 3.0 \
--Mapper.filter_min_tri_angle 3.0 \
--Mapper.ba_global_use_pba 1'.format(db_file, img_dir, out_dir)
bash_run(cmd)
def prepare_mvs(img_dir, sparse_dir, mvs_dir):
print('Preparing for MVS...')
cmd = ' image_undistorter \
--image_path {} \
--input_path {} \
--output_path {} \
--output_type COLMAP \
--max_image_size 1280'.format(img_dir, sparse_dir, mvs_dir)
bash_run(cmd)
def run_photometric_mvs(mvs_dir, window_radius):
print('Running photometric MVS...')
cmd = ' patch_match_stereo --workspace_path {} \
--PatchMatchStereo.window_radius {} \
--PatchMatchStereo.min_triangulation_angle 3.0 \
--PatchMatchStereo.filter 1 \
--PatchMatchStereo.geom_consistency 1 \
--PatchMatchStereo.gpu_index={} \
--PatchMatchStereo.num_samples 15 \
--PatchMatchStereo.num_iterations 12'.format(mvs_dir,
window_radius, gpu_index)
bash_run(cmd)
def run_fuse(mvs_dir, out_ply):
print('Running depth fusion...')
cmd = ' stereo_fusion --workspace_path {} \
--output_path {} \
--input_type geometric'.format(mvs_dir, out_ply)
bash_run(cmd)
def run_possion_mesher(in_ply, out_ply, trim):
print('Running possion mesher...')
cmd = ' poisson_mesher \
--input_path {} \
--output_path {} \
--PoissonMeshing.trim {}'.format(in_ply, out_ply, trim)
bash_run(cmd)
def main(img_dir, out_dir, base_dir, run_mvs=False):
os.makedirs(out_dir, exist_ok=True)
#### run sfm
sfm_dir = os.path.join(out_dir, 'sfm')
os.makedirs(sfm_dir, exist_ok=True)
img_dir_link = os.path.join(sfm_dir, 'images')
if os.path.exists(img_dir_link):
os.remove(img_dir_link)
os.symlink(img_dir, img_dir_link)
db_file = os.path.join(sfm_dir, 'database.db')
run_sift_matching(img_dir, db_file, remove_exist=False)
sparse_dir = os.path.join(sfm_dir, 'sparse')
os.makedirs(sparse_dir, exist_ok=True)
run_sfm(img_dir, db_file, sparse_dir)
sparse_dir = os.path.join(sfm_dir, 'sparse', '0')
# undistort images
mvs_dir = os.path.join(out_dir, 'mvs')
os.makedirs(mvs_dir, exist_ok=True)
prepare_mvs(img_dir, sparse_dir, mvs_dir)
# extract camera parameters and undistorted images
os.makedirs(os.path.join(out_dir, 'posed_images'), exist_ok=True)
extract_all_to_dir(os.path.join(mvs_dir, 'sparse'), os.path.join(out_dir, 'posed_images'))
undistorted_img_dir = os.path.join(mvs_dir, 'images')
posed_img_dir_link = os.path.join(out_dir, 'posed_images/images')
if os.path.exists(posed_img_dir_link):
os.remove(posed_img_dir_link)
os.symlink(undistorted_img_dir, posed_img_dir_link)
# normalize average camera center to origin, and put all cameras inside the unit sphere
normalize_cam_dict(os.path.join(out_dir, 'posed_images/kai_cameras.json'),
os.path.join(out_dir, 'posed_images/kai_cameras_normalized.json'))
# copy stuff over
shutil.copy2(os.path.join(out_dir, 'posed_images/kai_cameras.json'), base_dir)
shutil.copy2(os.path.join(out_dir, 'posed_images/kai_cameras_normalized.json'), base_dir)
targetdir = os.path.join(base_dir, 'rgb')
os.makedirs(targetdir)
imgdir = os.path.join(out_dir, 'posed_images/images')
for img in os.listdir(imgdir):
shutil.copy2(os.path.join(imgdir, img), targetdir)
if run_mvs:
# run mvs
run_photometric_mvs(mvs_dir, window_radius=7)
out_ply = os.path.join(out_dir, 'mvs/fused.ply')
run_fuse(mvs_dir, out_ply)
out_mesh_ply = os.path.join(out_dir, 'mvs/meshed_trim_3.ply')
run_possion_mesher(out_ply, out_mesh_ply, trim=3)
if __name__ == '__main__':
### note: this script is intended for the case where all images are taken by the same camera, i.e., intrinisics are shared.
### note: not anymore.
base_dir = sys.argv[1]
img_dir = os.path.join(base_dir, 'source')
out_dir = os.path.join(base_dir, 'out')
run_mvs = False
main(img_dir, out_dir, base_dir, run_mvs=run_mvs)