Dense reconstruction POC

Former-commit-id: 11fbc561d6
pull/1161/head
Piero Toffanin 2019-04-24 14:28:44 -04:00
rodzic 99bbff7ce5
commit d5b68fc6bc
4 zmienionych plików z 141 dodań i 67 usunięć

Wyświetl plik

@ -2,7 +2,7 @@
OpenSfM related utils OpenSfM related utils
""" """
import os import os, shutil, sys
from opendm import io from opendm import io
from opendm import log from opendm import log
from opendm import system from opendm import system
@ -47,78 +47,136 @@ def reconstruct(opensfm_project_path, rerun=False):
raise Exception("Reconstruction could not be generated") raise Exception("Reconstruction could not be generated")
def setup(args, images_path, opensfm_path, photos, gcp_path=None, append_config = []): def setup(args, images_path, opensfm_path, photos, gcp_path=None, append_config = [], rerun=False):
""" """
Setup a OpenSfM project Setup a OpenSfM project
""" """
if rerun and io.dir_exists(opensfm_path):
shutil.rmtree(opensfm_path)
if not io.dir_exists(opensfm_path): if not io.dir_exists(opensfm_path):
system.mkdir_p(opensfm_path) system.mkdir_p(opensfm_path)
# create file list # create file list
list_path = io.join_paths(opensfm_path, 'image_list.txt') list_path = io.join_paths(opensfm_path, 'image_list.txt')
has_alt = True if not io.file_exists(list_path) or rerun:
with open(list_path, 'w') as fout: has_alt = True
for photo in photos: with open(list_path, 'w') as fout:
if not photo.altitude: for photo in photos:
has_alt = False if not photo.altitude:
fout.write('%s\n' % io.join_paths(images_path, photo.filename)) has_alt = False
fout.write('%s\n' % io.join_paths(images_path, photo.filename))
# TODO: does this need to be a relative path? # TODO: does this need to be a relative path?
# create config file for OpenSfM # create config file for OpenSfM
config = [ config = [
"use_exif_size: no", "use_exif_size: no",
"feature_process_size: %s" % args.resize_to, "feature_process_size: %s" % args.resize_to,
"feature_min_frames: %s" % args.min_num_features, "feature_min_frames: %s" % args.min_num_features,
"processes: %s" % args.max_concurrency, "processes: %s" % args.max_concurrency,
"matching_gps_neighbors: %s" % args.matcher_neighbors, "matching_gps_neighbors: %s" % args.matcher_neighbors,
"depthmap_method: %s" % args.opensfm_depthmap_method, "depthmap_method: %s" % args.opensfm_depthmap_method,
"depthmap_resolution: %s" % args.depthmap_resolution, "depthmap_resolution: %s" % args.depthmap_resolution,
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd, "depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views, "depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
"optimize_camera_parameters: %s" % ('no' if args.use_fixed_camera_params else 'yes') "optimize_camera_parameters: %s" % ('no' if args.use_fixed_camera_params else 'yes')
] ]
if has_alt: if has_alt:
log.ODM_DEBUG("Altitude data detected, enabling it for GPS alignment") log.ODM_DEBUG("Altitude data detected, enabling it for GPS alignment")
config.append("use_altitude_tag: yes") config.append("use_altitude_tag: yes")
config.append("align_method: naive") config.append("align_method: naive")
else:
config.append("align_method: orientation_prior")
config.append("align_orientation_prior: vertical")
if args.use_hybrid_bundle_adjustment:
log.ODM_DEBUG("Enabling hybrid bundle adjustment")
config.append("bundle_interval: 100") # Bundle after adding 'bundle_interval' cameras
config.append("bundle_new_points_ratio: 1.2") # Bundle when (new points) / (bundled points) > bundle_new_points_ratio
config.append("local_bundle_radius: 1") # Max image graph distance for images to be included in local bundle adjustment
if args.matcher_distance > 0:
config.append("matching_gps_distance: %s" % args.matcher_distance)
if gcp_path:
config.append("bundle_use_gcp: yes")
io.copy(gcp_path, opensfm_path)
config = config + append_config
# write config file
log.ODM_DEBUG(config)
config_filename = io.join_paths(opensfm_path, 'config.yaml')
with open(config_filename, 'w') as fout:
fout.write("\n".join(config))
else: else:
config.append("align_method: orientation_prior") log.ODM_WARNING("%s already exists, not rerunning OpenSfM setup" % list_path)
config.append("align_orientation_prior: vertical")
if args.use_hybrid_bundle_adjustment:
log.ODM_DEBUG("Enabling hybrid bundle adjustment")
config.append("bundle_interval: 100") # Bundle after adding 'bundle_interval' cameras
config.append("bundle_new_points_ratio: 1.2") # Bundle when (new points) / (bundled points) > bundle_new_points_ratio
config.append("local_bundle_radius: 1") # Max image graph distance for images to be included in local bundle adjustment
if args.matcher_distance > 0:
config.append("matching_gps_distance: %s" % args.matcher_distance)
if gcp_path:
config.append("bundle_use_gcp: yes")
io.copy(gcp_path, opensfm_path)
config = config + append_config
# write config file
log.ODM_DEBUG(config)
config_filename = io.join_paths(opensfm_path, 'config.yaml')
with open(config_filename, 'w') as fout:
fout.write("\n".join(config))
def feature_matching(opensfm_project_path, rerun=False): def feature_matching(opensfm_project_path, rerun=False):
matched_done_file = io.join_paths(opensfm_project_path, 'matching_done.txt') if not feature_matching_done(opensfm_project_path) or rerun:
if not io.file_exists(matched_done_file) or rerun:
run('extract_metadata', opensfm_project_path) run('extract_metadata', opensfm_project_path)
# TODO: distributed workflow should do these two steps independently # TODO: distributed workflow should do these two steps independently
run('detect_features', opensfm_project_path) run('detect_features', opensfm_project_path)
run('match_features', opensfm_project_path) run('match_features', opensfm_project_path)
with open(matched_done_file, 'w') as fout: mark_feature_matching_done(opensfm_project_path)
fout.write("Matching done!\n")
else: else:
log.ODM_WARNING('Found a feature matching done progress file in: %s' % log.ODM_WARNING('Found a feature matching done progress file in: %s' %
matched_done_file) feature_matching_done_file(opensfm_project_path))
def feature_matching_done_file(opensfm_project_path):
return io.join_paths(opensfm_project_path, 'matching_done.txt')
def mark_feature_matching_done(opensfm_project_path):
with open(feature_matching_done_file(opensfm_project_path), 'w') as fout:
fout.write("Matching done!\n")
def feature_matching_done(opensfm_project_path):
return io.file_exists(feature_matching_done_file(opensfm_project_path))
def get_submodel_argv(args, submodels_path, submodel_name):
"""
:return the same as argv, but removing references to --split,
setting/replacing --project-path and name
"""
argv = sys.argv
result = [argv[0]]
i = 1
project_path_found = False
project_name_added = False
while i < len(argv):
arg = argv[i]
# Last?
if i == len(argv) - 1:
# Project name?
if arg == args.name:
result.append(submodel_name)
project_name_added = True
else:
result.append(arg)
i += 1
elif arg == '--project-path':
result.append(arg)
result.append(submodels_path)
project_path_found = True
i += 2
elif arg == '--split':
i += 2
else:
result.append(arg)
i += 1
if not project_path_found:
result.append('--project-path')
result.append(submodel_project_path)
if not project_name_added:
result.append(submodel_name)
return result

3
run.py
Wyświetl plik

@ -36,7 +36,8 @@ if __name__ == '__main__':
quote(os.path.join(args.project_path, "odm_25dmeshing")), quote(os.path.join(args.project_path, "odm_25dmeshing")),
quote(os.path.join(args.project_path, "odm_25dtexturing")), quote(os.path.join(args.project_path, "odm_25dtexturing")),
quote(os.path.join(args.project_path, "mve")), quote(os.path.join(args.project_path, "mve")),
]) + "") quote(os.path.join(args.project_path, "submodels")),
]))
app = ODMApp(args) app = ODMApp(args)
app.execute() app.execute()

Wyświetl plik

@ -30,14 +30,13 @@ class ODMOpenSfMStage(types.ODM_Stage):
# check if reconstruction was done before # check if reconstruction was done before
# TODO: more granularity for each step (setup/featurematch/reconstruction/etc.) # TODO: more granularity for each step (setup/featurematch/reconstruction/etc.)
osfm.setup(args, tree.dataset_raw, tree.opensfm, photos, gcp_path=tree.odm_georeferencing_gcp, rerun=self.rerun())
osfm.feature_matching(tree.opensfm, self.rerun())
osfm.reconstruct(tree.opensfm, self.rerun())
if not io.file_exists(output_file) or self.rerun(): if not io.file_exists(output_file) or self.rerun():
osfm.setup(args, tree.dataset_raw, tree.opensfm, photos, gcp_path=tree.odm_georeferencing_gcp)
osfm.feature_matching(tree.opensfm, self.rerun())
osfm.reconstruction(tree.opensfm, self.rerun())
# Always export VisualSFM's reconstruction and undistort images # Always export VisualSFM's reconstruction and undistort images
# as we'll use these for texturing (after GSD estimation and resizing) # as we'll use these for texturing (after GSD estimation and resizing)
if not args.ignore_gsd: if not args.ignore_gsd:

Wyświetl plik

@ -1,9 +1,11 @@
import os import os, sys
from opendm import log from opendm import log
from opendm import osfm from opendm import osfm
from opendm import types from opendm import types
from opendm import io from opendm import io
from opendm import system
from opensfm.large import metadataset from opensfm.large import metadataset
from pipes import quote
class ODMSplitStage(types.ODM_Stage): class ODMSplitStage(types.ODM_Stage):
def process(self, args, outputs): def process(self, args, outputs):
@ -23,7 +25,7 @@ class ODMSplitStage(types.ODM_Stage):
"submodel_overlap: %s" % args.split_overlap, "submodel_overlap: %s" % args.split_overlap,
] ]
osfm.setup(args, tree.dataset_raw, tree.opensfm, photos, gcp_path=tree.odm_georeferencing_gcp, append_config=config) osfm.setup(args, tree.dataset_raw, tree.opensfm, photos, gcp_path=tree.odm_georeferencing_gcp, append_config=config, rerun=self.rerun())
osfm.feature_matching(tree.opensfm, self.rerun()) osfm.feature_matching(tree.opensfm, self.rerun())
@ -50,14 +52,28 @@ class ODMSplitStage(types.ODM_Stage):
for sp in submodel_paths: for sp in submodel_paths:
log.ODM_INFO("Reconstructing %s" % sp) log.ODM_INFO("Reconstructing %s" % sp)
osfm.reconstruct(sp, self.rerun()) #osfm.reconstruct(sp, self.rerun())
# Align # Align
log.ODM_INFO("Aligning submodels...") log.ODM_INFO("Aligning submodels...")
osfm.run('align_submodels', tree.opensfm) #osfm.run('align_submodels', tree.opensfm)
# Dense reconstruction for each submodel # Dense reconstruction for each submodel
# TODO for sp in submodel_paths:
# TODO: network workflow
# We have already done matching
osfm.mark_feature_matching_done(sp)
submodel_name = os.path.basename(os.path.abspath(os.path.join(sp, "..")))
log.ODM_INFO("====================")
log.ODM_INFO("Processing %s" % submodel_name)
log.ODM_INFO("====================")
argv = osfm.get_submodel_argv(args, tree.submodels_path, submodel_name)
system.run(" ".join(map(quote, argv)), env_vars=os.environ.copy())
exit(1) exit(1)
else: else: