From 3c20a2508498c7080d92ae82cb1703754b507a01 Mon Sep 17 00:00:00 2001 From: Pau Gargallo Date: Tue, 16 May 2017 15:38:12 +0200 Subject: [PATCH] Add scripts to setup/split/reconstruct/align a metadataset --- scripts/metadataset/align.py | 25 +++++ scripts/metadataset/run_matching.py | 27 ++++++ scripts/metadataset/run_reconstructions.py | 69 ++++++++++++++ scripts/metadataset/setup.py | 101 +++++++++++++++++++++ scripts/metadataset/split.py | 25 +++++ 5 files changed, 247 insertions(+) create mode 100644 scripts/metadataset/align.py create mode 100644 scripts/metadataset/run_matching.py create mode 100644 scripts/metadataset/run_reconstructions.py create mode 100644 scripts/metadataset/setup.py create mode 100644 scripts/metadataset/split.py diff --git a/scripts/metadataset/align.py b/scripts/metadataset/align.py new file mode 100644 index 00000000..2ba818a4 --- /dev/null +++ b/scripts/metadataset/align.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import argparse +import os +import subprocess + +from opendm import context + + +def run_command(args): + result = subprocess.Popen(args).wait() + if result != 0: + raise RuntimeError(result) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Align metadaset submodels') + parser.add_argument('dataset', + help='path to the dataset to be processed') + args = parser.parse_args() + + command = os.path.join(context.opensfm_path, 'bin', 'opensfm') + path = os.path.join(args.dataset, 'opensfm') + + run_command([command, 'align_submodels', path]) diff --git a/scripts/metadataset/run_matching.py b/scripts/metadataset/run_matching.py new file mode 100644 index 00000000..db019367 --- /dev/null +++ b/scripts/metadataset/run_matching.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +import argparse +import os +import subprocess + +from opendm import context + + +def run_command(args): + result = subprocess.Popen(args).wait() + if result != 0: + raise RuntimeError(result) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Run matching on a metadataset') + parser.add_argument('dataset', + help='path to the dataset to be processed') + args = parser.parse_args() + + command = os.path.join(context.opensfm_path, 'bin', 'opensfm') + path = os.path.join(args.dataset, 'opensfm') + + run_command([command, 'extract_metadata', path]) + run_command([command, 'detect_features', path]) + run_command([command, 'match_features', path]) diff --git a/scripts/metadataset/run_reconstructions.py b/scripts/metadataset/run_reconstructions.py new file mode 100644 index 00000000..ab334399 --- /dev/null +++ b/scripts/metadataset/run_reconstructions.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +import argparse +import logging +import multiprocessing +import os +import subprocess + +from opensfm.large import metadataset + +from opendm import context + +logger = logging.getLogger(__name__) + +logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) + + +def run_command(args): + result = subprocess.Popen(args).wait() + if result != 0: + raise RuntimeError(result) + + +class Reconstructor: + def __init__(self, command, run_matching): + self.command = command + self.run_matching = run_matching + + def __call__(self, submodel_path): + logger.info("=======================================================") + logger.info("Reconstructing submodel {}".format(submodel_path)) + logger.info("=======================================================") + + if self.run_matching: + run_command([self.command, 'extract_metadata', submodel_path]) + run_command([self.command, 'detect_features', submodel_path]) + run_command([self.command, 'match_features', submodel_path]) + + run_command([self.command, 'create_tracks', submodel_path]) + run_command([self.command, 'reconstruct', submodel_path]) + + logger.info("=======================================================") + logger.info("Submodel {} reconstructed".format(submodel_path)) + logger.info("=======================================================") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Reconstruct all submodels') + parser.add_argument('dataset', + help='path to the dataset to be processed') + parser.add_argument('--run-matching', + help='Run matching for each submodel', + action='store_true') + args = parser.parse_args() + + path = os.path.join(args.dataset, 'opensfm') + meta_data = metadataset.MetaDataSet(path) + command = os.path.join(context.opensfm_path, 'bin', 'opensfm') + + submodel_paths = meta_data.get_submodel_paths() + reconstructor = Reconstructor(command, args.run_matching) + + processes = meta_data.config['processes'] + if processes == 1: + for submodel_path in submodel_paths: + reconstructor(submodel_path) + else: + p = multiprocessing.Pool(processes) + p.map(reconstructor, submodel_paths) diff --git a/scripts/metadataset/setup.py b/scripts/metadataset/setup.py new file mode 100644 index 00000000..9af423f6 --- /dev/null +++ b/scripts/metadataset/setup.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +"""Setup an ODM metadataset. + +A metadatase will be split into multiple submodel folders. +Each submodel is reconstructed independently. Before dense +reconstruction the different submodels are aligned to each +other. +""" + +import argparse +import os + +import yaml + +from opensfm.io import mkdir_p + + +def is_image_file(filename): + extensions = {'jpg', 'jpeg', 'png', 'tif', 'tiff', 'pgm', 'pnm', 'gif'} + return filename.split('.')[-1].lower() in extensions + + +def create_image_list(image_path, opensfm_path): + image_files = filter(is_image_file, os.listdir(image_path)) + + lines = [] + relpath = os.path.relpath(image_path, opensfm_path) + for image in image_files: + lines.append(os.path.join(relpath, image)) + + with open(os.path.join(opensfm_path, 'image_list.txt'), 'w') as fout: + fout.write("\n".join(lines)) + + +def create_config(opensfm_path, args): + config = { + "submodels_relpath": "../submodels/opensfm", + "submodel_relpath_template": "../submodels/submodel_%04d/opensfm", + + "feature_process_size": args.resize_to, + "feature_min_frames": args.min_num_features, + "processes": args.num_cores, + "matching_gps_neighbors": args.matcher_neighbors, + } + with open(os.path.join(opensfm_path, 'config.yaml'), 'w') as fout: + yaml.dump(config, fout, default_flow_style=False) + + +def parse_command_line(): + parser = argparse.ArgumentParser(description='Setup an ODM metadataset') + parser.add_argument('dataset', + help='path to the dataset to be processed') + + # TODO(pau): reduce redundancy with OpenDroneMap/opendm/config.py + + parser.add_argument('--resize-to', # currently doesn't support 'orig' + metavar='', + default=2400, + type=int, + help='resizes images by the largest side') + + parser.add_argument('--min-num-features', + metavar='', + default=4000, + type=int, + help=('Minimum number of features to extract per image. ' + 'More features leads to better results but slower ' + 'execution. Default: %(default)s')) + + parser.add_argument('--num-cores', + metavar='', + default=4, + type=int, + help=('The maximum number of cores to use. ' + 'Default: %(default)s')) + + parser.add_argument('--matcher-neighbors', + type=int, + metavar='', + default=8, + help='Number of nearest images to pre-match based on GPS ' + 'exif data. Set to 0 to skip pre-matching. ' + 'Neighbors works together with Distance parameter, ' + 'set both to 0 to not use pre-matching. OpenSFM ' + 'uses both parameters at the same time, Bundler ' + 'uses only one which has value, prefering the ' + 'Neighbors parameter. Default: %(default)s') + + return parser.parse_args() + + +if __name__ == '__main__': + args = parse_command_line() + data_path = args.dataset + image_path = os.path.join(data_path, 'images') + opensfm_path = os.path.join(data_path, 'opensfm') + + mkdir_p(opensfm_path) + create_image_list(image_path, opensfm_path) + create_config(opensfm_path, args) diff --git a/scripts/metadataset/split.py b/scripts/metadataset/split.py new file mode 100644 index 00000000..c91bfb0d --- /dev/null +++ b/scripts/metadataset/split.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import argparse +import os +import subprocess + +from opendm import context + + +def run_command(args): + result = subprocess.Popen(args).wait() + if result != 0: + raise RuntimeError(result) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Split metadaset into submodels') + parser.add_argument('dataset', + help='path to the dataset to be processed') + args = parser.parse_args() + + command = os.path.join(context.opensfm_path, 'bin', 'opensfm') + path = os.path.join(args.dataset, 'opensfm') + + run_command([command, 'create_submodels', path, '--size', '20', '--dist', '20'])