Skip to content

Commit 34f8c9c

Browse files
authored
Merge pull request #8 from playcanvas/resize-speedup
Logging and mask updates, resize speedup
2 parents a719a09 + 487d0d2 commit 34f8c9c

File tree

1 file changed

+104
-117
lines changed

1 file changed

+104
-117
lines changed

convert.py

Lines changed: 104 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#
1111

1212
import os
13+
import subprocess
1314
import logging
1415
from argparse import ArgumentParser
1516
import shutil
@@ -37,7 +38,17 @@
3738
magick_command = '"{}"'.format(args.magick_executable) if len(args.magick_executable) > 0 else "magick"
3839
use_gpu = 1 if not args.no_gpu else 0
3940

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)
4152

4253
if not args.skip_matching:
4354
os.makedirs(args.source_path + "/distorted/sparse", exist_ok=True)
@@ -49,19 +60,13 @@
4960
--ImageReader.single_camera 1 \
5061
--ImageReader.camera_model " + args.camera + " \
5162
--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)
5664

5765
## Feature matching
5866
feat_matching_cmd = colmap_command + " exhaustive_matcher \
5967
--database_path " + args.source_path + "/distorted/database.db \
6068
--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)
6570

6671
### Bundle adjustment
6772
# The default Mapper tolerance is unnecessarily large,
@@ -71,11 +76,7 @@
7176
--image_path " + args.source_path + "/input \
7277
--output_path " + args.source_path + "/distorted/sparse \
7378
--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)
7980

8081
# select the largest submodel
8182
i = 0
@@ -107,99 +108,94 @@
107108
--input_path " + distorted_sparse_path + " \
108109
--output_path " + args.source_path + "\
109110
--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)
115112

116113

117-
def remove_dir_if_exist(path):
118-
if Path(path).exists():
119-
shutil.rmtree(path)
120-
114+
# Handle masks
121115

122116
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+
126122
# First convert model to text format
127123
model_converter_cmd = (colmap_command + " model_converter \
128124
--input_path " + distorted_sparse_path + " \
129-
--output_path " + args.source_path + "/alpha_distorted_sparse_txt/ \
125+
--output_path " + mask_model_path + " \
130126
--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
145156
seg_undist_cmd = (colmap_command + " image_undistorter \
146157
--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 \
149160
--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):
203199
print("Copying and resizing...")
204200

205201
# Resize images.
@@ -211,26 +207,17 @@ def concat_alpha(seg_path):
211207
# Copy each file from the source directory to the destination directory
212208
for file in files:
213209
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)
235222

236223
print("Done.")

0 commit comments

Comments
 (0)