10
10
#
11
11
12
12
import os
13
+ import subprocess
13
14
import logging
14
15
from argparse import ArgumentParser
15
16
import shutil
37
38
magick_command = '"{}"' .format (args .magick_executable ) if len (args .magick_executable ) > 0 else "magick"
38
39
use_gpu = 1 if not args .no_gpu else 0
39
40
40
- EXIT_FAIL = 1
41
+ # configure logging
42
+ logging .basicConfig (level = logging .INFO )
43
+
44
+ # execute a command after logging it and propagate failure correctly
45
+ def exec (cmd ):
46
+ logging .info (f"Executing: { cmd } " )
47
+ try :
48
+ subprocess .check_call (cmd , stdout = subprocess .DEVNULL , stderr = subprocess .STDOUT , shell = True )
49
+ except subprocess .CalledProcessError as e :
50
+ logging .error (f"Command failed with code { e .returncode } . Exiting." )
51
+ exit (e .returncode )
41
52
42
53
if not args .skip_matching :
43
54
os .makedirs (args .source_path + "/distorted/sparse" , exist_ok = True )
49
60
--ImageReader.single_camera 1 \
50
61
--ImageReader.camera_model " + args .camera + " \
51
62
--SiftExtraction.use_gpu " + str (use_gpu )
52
- exit_code = os .system (feat_extracton_cmd )
53
- if exit_code != 0 :
54
- logging .error (f"Feature extraction failed with code { exit_code } . Exiting." )
55
- exit (EXIT_FAIL )
63
+ exec (feat_extracton_cmd )
56
64
57
65
## Feature matching
58
66
feat_matching_cmd = colmap_command + " exhaustive_matcher \
59
67
--database_path " + args .source_path + "/distorted/database.db \
60
68
--SiftMatching.use_gpu " + str (use_gpu )
61
- exit_code = os .system (feat_matching_cmd )
62
- if exit_code != 0 :
63
- logging .error (f"Feature matching failed with code { exit_code } . Exiting." )
64
- exit (EXIT_FAIL )
69
+ exec (feat_matching_cmd )
65
70
66
71
### Bundle adjustment
67
72
# The default Mapper tolerance is unnecessarily large,
71
76
--image_path " + args .source_path + "/input \
72
77
--output_path " + args .source_path + "/distorted/sparse \
73
78
--Mapper.ba_global_function_tolerance=0.000001" )
74
- exit_code = os .system (mapper_cmd )
75
- if exit_code != 0 :
76
- logging .error (f"Mapper failed with code { exit_code } . Exiting." )
77
- exit (EXIT_FAIL )
78
-
79
+ exec (mapper_cmd )
79
80
80
81
# select the largest submodel
81
82
i = 0
107
108
--input_path " + distorted_sparse_path + " \
108
109
--output_path " + args .source_path + "\
109
110
--output_type COLMAP" )
110
-
111
- exit_code = os .system (img_undist_cmd )
112
- if exit_code != 0 :
113
- logging .error (f"image_undistorter failed with code { exit_code } . Exiting." )
114
- exit (EXIT_FAIL )
111
+ exec (img_undist_cmd )
115
112
116
113
117
- def remove_dir_if_exist (path ):
118
- if Path (path ).exists ():
119
- shutil .rmtree (path )
120
-
114
+ # Handle masks
121
115
122
116
if args .masks_path is not None :
123
- remove_dir_if_exist (args .source_path + "/alpha_distorted_sparse_txt/" )
124
- Path (args .source_path + "/alpha_distorted_sparse_txt/" ).mkdir (exist_ok = True )
125
- # We need to "hack" colmap to undistort segmentation maps modify paths
117
+ # We need to modify the colmap database to reference the mask images
118
+ # which are always in png format.
119
+ mask_model_path = args .masks_path + "/model"
120
+ Path (mask_model_path ).mkdir (exist_ok = True )
121
+
126
122
# First convert model to text format
127
123
model_converter_cmd = (colmap_command + " model_converter \
128
124
--input_path " + distorted_sparse_path + " \
129
- --output_path " + args . source_path + "/alpha_distorted_sparse_txt/ \
125
+ --output_path " + mask_model_path + " \
130
126
--output_type TXT" )
131
- exit_code = os .system (model_converter_cmd )
132
- if exit_code != 0 :
133
- logging .error (f"model_converter failed with code { exit_code } . Exiting." )
134
- exit (EXIT_FAIL )
135
-
136
- # replace '.jpg' to '.png'
137
- with open (args .source_path + "/alpha_distorted_sparse_txt/images.txt" , "r+" ) as f :
138
- images_txt = f .read ()
139
- images_txt = images_txt .replace ('.jpg' , '.png' )
140
- f .seek (0 )
141
- f .write (images_txt )
142
- f .truncate ()
143
-
144
- # Undistort alpha masks
127
+ exec (model_converter_cmd )
128
+
129
+ # read images.txt
130
+ with open (mask_model_path + "/images.txt" , 'r' ) as file :
131
+ lines = file .readlines ()
132
+
133
+ # replace image filenames with png extensions (and keep the list of renames for later)
134
+ filenames = []
135
+ l = 0
136
+ for i in range (len (lines )):
137
+ if lines [i ].startswith ("#" ):
138
+ # skip comments
139
+ continue
140
+ if l % 2 == 0 :
141
+ # handle every second line
142
+ words = lines [i ].rstrip ().split (" " )
143
+ filename = words [- 1 ].split ("." )
144
+ filename [- 1 ] = "png"
145
+ new_filename = "." .join (filename )
146
+ filenames .append ([words [- 1 ], new_filename ])
147
+ words [- 1 ] = new_filename
148
+ lines [i ] = " " .join (words ) + "\n "
149
+ l += 1
150
+
151
+ # write modified images.txt
152
+ with open (mask_model_path + "/images.txt" , 'w' ) as file :
153
+ file .writelines (lines )
154
+
155
+ # Undistort mask images
145
156
seg_undist_cmd = (colmap_command + " image_undistorter \
146
157
--image_path " + args .masks_path + " \
147
- --input_path " + args . source_path + "/alpha_distorted_sparse_txt/ \
148
- --output_path " + args .source_path + "/alpha_undistorted_sparse \
158
+ --input_path " + mask_model_path + " \
159
+ --output_path " + args .masks_path + "/undistorted \
149
160
--output_type COLMAP" )
150
- exit_code = os .system (seg_undist_cmd )
151
- if exit_code != 0 :
152
- logging .error (f"image_undistorter for segs failed with code { exit_code } . Exiting." )
153
- exit (EXIT_FAIL )
154
-
155
- # switch images
156
- remove_dir_if_exist (f'{ args .source_path } /alpha_undistorted_sparse/alphas' )
157
- Path (f'{ args .source_path } /alpha_undistorted_sparse/images' ).replace (f'{ args .source_path } /alpha_undistorted_sparse/alphas' )
158
- remove_dir_if_exist (f'{ args .source_path } /images_src/' )
159
- Path (f'{ args .source_path } /images/' ).replace (f'{ args .source_path } /images_src/' )
160
-
161
- # concat undistorted images with undistorted alpha masks - TODO: make parallel
162
- remove_dir_if_exist (f'{ args .source_path } /images/' )
163
- Path (f'{ args .source_path } /images/' ).mkdir ()
164
-
165
- def concat_alpha (seg_path ):
166
- seg = Image .open (seg_path ).convert ('L' )
167
- img = Image .open (f'{ args .source_path } /images_src/{ Path (seg_path ).stem } .jpg' )
168
- img .putalpha (seg )
169
- img .save (f'{ args .source_path } /images/{ Path (seg_path ).stem } .png' )
170
-
171
- all_masks_paths = glob (args .source_path + "/alpha_undistorted_sparse/alphas/*.png" )
172
- with mp .Pool () as pool :
173
- list (tqdm (pool .imap_unordered (concat_alpha , all_masks_paths ), total = len (all_masks_paths )))
174
-
175
- # switch models
176
- remove_dir_if_exist (f'{ args .source_path } /sparse_src/' )
177
- Path (f'{ args .source_path } /sparse' ).replace (f'{ args .source_path } /sparse_src/' )
178
- Path (f'{ args .source_path } /alpha_undistorted_sparse/sparse' ).replace (f'{ args .source_path } /sparse/' )
179
-
180
- if args .generate_text_model :
181
- ### Convert model to text format so we can read cameras
182
- convert_cmd = (colmap_command + " model_converter \
183
- --input_path " + args .source_path + "/sparse" + " \
184
- --output_path " + args .source_path + "/sparse" + " \
185
- --output_type TXT" )
186
- exit_code = os .system (convert_cmd )
187
- if exit_code != 0 :
188
- logging .error (f"Convert failed with code { exit_code } . Exiting." )
189
- exit (exit_code )
190
-
191
- # move all files from sparse into sparse/0, as train.py expects it
192
- files = os .listdir (args .source_path + "/sparse" )
193
- os .makedirs (args .source_path + "/sparse/0" , exist_ok = True )
194
- # Copy each file from the source directory to the destination directory
195
- for file in files :
196
- if file == "0" :
197
- continue
198
- source_file = os .path .join (args .source_path , "sparse" , file )
199
- destination_file = os .path .join (args .source_path , "sparse" , "0" , file )
200
- shutil .move (source_file , destination_file )
201
-
202
- if (args .resize ):
161
+ exec (seg_undist_cmd )
162
+
163
+ # combine undistorted color and mask images
164
+ def combine (color_path , alpha_path , output_path ):
165
+ alpha = Image .open (alpha_path ).convert ('L' )
166
+ clr = Image .open (color_path )
167
+ clr .putalpha (alpha )
168
+ clr .save (output_path )
169
+
170
+ for i in range (len (filenames )):
171
+ color_image = args .source_path + "/images/" + filenames [i ][0 ]
172
+ mask_image = args .masks_path + "/undistorted/images/" + filenames [i ][1 ]
173
+ output_image = args .source_path + "/images/" + filenames [i ][1 ]
174
+ combine (color_image , mask_image , output_image )
175
+
176
+ # copy the modified database to final location for use in training
177
+ target_path = args .source_path + "/sparse/0"
178
+ Path (target_path ).mkdir (exist_ok = True )
179
+
180
+ source_path = args .masks_path + "/undistorted/sparse"
181
+ files = os .listdir (source_path )
182
+ for file in files :
183
+ source_file = os .path .join (source_path , file )
184
+ destination_file = os .path .join (target_path , file )
185
+ shutil .move (source_file , destination_file )
186
+ else :
187
+ # move all files from sparse into sparse/0, as train.py expects it
188
+ files = os .listdir (args .source_path + "/sparse" )
189
+ os .makedirs (args .source_path + "/sparse/0" , exist_ok = True )
190
+ # Copy each file from the source directory to the destination directory
191
+ for file in files :
192
+ if file == "0" :
193
+ continue
194
+ source_file = os .path .join (args .source_path , "sparse" , file )
195
+ destination_file = os .path .join (args .source_path , "sparse" , "0" , file )
196
+ shutil .move (source_file , destination_file )
197
+
198
+ if (args .resize ):
203
199
print ("Copying and resizing..." )
204
200
205
201
# Resize images.
@@ -211,26 +207,17 @@ def concat_alpha(seg_path):
211
207
# Copy each file from the source directory to the destination directory
212
208
for file in files :
213
209
source_file = os .path .join (args .source_path , "images" , file )
214
-
215
- destination_file = os .path .join (args .source_path , "images_2" , file )
216
- shutil .copy2 (source_file , destination_file )
217
- exit_code = os .system ("mogrify -resize 50% " + destination_file )
218
- if exit_code != 0 :
219
- logging .error (f"50% resize failed with code { exit_code } . Exiting." )
220
- exit (EXIT_FAIL )
221
-
222
- destination_file = os .path .join (args .source_path , "images_4" , file )
223
- shutil .copy2 (source_file , destination_file )
224
- exit_code = os .system ("mogrify -resize 25% " + destination_file )
225
- if exit_code != 0 :
226
- logging .error (f"25% resize failed with code { exit_code } . Exiting." )
227
- exit (EXIT_FAIL )
228
-
229
- destination_file = os .path .join (args .source_path , "images_8" , file )
230
- shutil .copy2 (source_file , destination_file )
231
- exit_code = os .system ("mogrify -resize 12.5% " + destination_file )
232
- if exit_code != 0 :
233
- logging .error (f"12.5% resize failed with code { exit_code } . Exiting." )
234
- exit (EXIT_FAIL )
210
+ output_file2 = os .path .join (args .source_path , "images_2" , file )
211
+ output_file4 = os .path .join (args .source_path , "images_4" , file )
212
+ output_file8 = os .path .join (args .source_path , "images_8" , file )
213
+
214
+ # generate the resized images in a single call
215
+ generate_thumbnails_cmd = ("convert "
216
+ # resize input file, uses less memory
217
+ f"{ source_file } [50%]"
218
+ f" -write mpr:thumb -write { output_file2 } +delete"
219
+ f" mpr:thumb -resize 50% -write mpr:thumb -write { output_file4 } +delete"
220
+ f" mpr:thumb -resize 50% { output_file8 } " )
221
+ exec (generate_thumbnails_cmd )
235
222
236
223
print ("Done." )
0 commit comments