kopia lustrzana https://github.com/OpenDroneMap/ODM
rodzic
554e559a43
commit
bc0a73a2ad
|
@ -479,21 +479,16 @@ def config():
|
|||
version='OpenDroneMap {0}'.format(__version__),
|
||||
help='Displays version number and exits. ')
|
||||
|
||||
parser.add_argument('--submodel-size',
|
||||
parser.add_argument('--split',
|
||||
type=int,
|
||||
default=80,
|
||||
default=999999,
|
||||
help='Average number of images per submodel. When '
|
||||
'splitting a large dataset into smaller '
|
||||
'submodels, images are grouped into clusters. '
|
||||
'This value regulates the number of images that '
|
||||
'each cluster should have on average.')
|
||||
|
||||
parser.add_argument('--large',
|
||||
help='Run large-scale split-merge process',
|
||||
action='store_true',
|
||||
default=False)
|
||||
|
||||
parser.add_argument('--submodel-overlap',
|
||||
parser.add_argument('--split-overlap',
|
||||
type=float,
|
||||
metavar='<positive integer>',
|
||||
default=150,
|
||||
|
@ -503,14 +498,13 @@ def config():
|
|||
'are added to the cluster. This is done to ensure '
|
||||
'that neighboring submodels overlap.')
|
||||
|
||||
parser.add_argument('--run-matching',
|
||||
help='Run matching for each submodel',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--merge-overwrite',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Force overwrite of generated files')
|
||||
parser.add_argument('--split-distributed',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Optimizes the split-merge workflow for a distributed computing environment. '
|
||||
'When this is turned on, feature detection and matching on images is '
|
||||
'performed independently on the submodels instead of being computed at once. '
|
||||
'Default: %(default)s')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
"""
|
||||
OpenSfM related utils
|
||||
"""
|
||||
|
||||
from opendm import io
|
||||
from opendm import log
|
||||
from opendm import system
|
||||
from opendm import context
|
||||
|
||||
def run(command, opensfm_project_path):
|
||||
system.run('%s/bin/opensfm %s %s' %
|
||||
(context.opensfm_path, command, opensfm_project_path))
|
||||
|
||||
def setup(args, params, images_path, opensfm_path, photos, gcp_path=None, append_config = []):
|
||||
"""
|
||||
Setup a OpenSfM project
|
||||
"""
|
||||
if not io.dir_exists(opensfm_path):
|
||||
system.mkdir_p(opensfm_path)
|
||||
|
||||
# create file list
|
||||
list_path = io.join_paths(opensfm_path, 'image_list.txt')
|
||||
has_alt = True
|
||||
with open(list_path, 'w') as fout:
|
||||
for photo in photos:
|
||||
if not photo.altitude:
|
||||
has_alt = False
|
||||
fout.write('%s\n' % io.join_paths(images_path, photo.filename))
|
||||
|
||||
# create config file for OpenSfM
|
||||
config = [
|
||||
"use_exif_size: %s" % ('no' if not params.get('use_exif_size') else 'yes'),
|
||||
"feature_process_size: %s" % params.get('feature_process_size'),
|
||||
"feature_min_frames: %s" % params.get('feature_min_frames'),
|
||||
"processes: %s" % params.get('processes'),
|
||||
"matching_gps_neighbors: %s" % params.get('matching_gps_neighbors'),
|
||||
"depthmap_method: %s" % args.opensfm_depthmap_method,
|
||||
"depthmap_resolution: %s" % args.depthmap_resolution,
|
||||
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
|
||||
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
|
||||
"optimize_camera_parameters: %s" % ('no' if params.get('fixed_camera_params') else 'yes')
|
||||
]
|
||||
|
||||
if has_alt:
|
||||
log.ODM_DEBUG("Altitude data detected, enabling it for GPS alignment")
|
||||
config.append("use_altitude_tag: yes")
|
||||
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" % params.get('matching_gps_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))
|
|
@ -8,6 +8,7 @@ from opendm import context
|
|||
from opendm import gsd
|
||||
from opendm import point_cloud
|
||||
from opendm import types
|
||||
from opendm import osfm
|
||||
|
||||
class ODMOpenSfMStage(types.ODM_Stage):
|
||||
def process(self, args, outputs):
|
||||
|
@ -31,65 +32,16 @@ class ODMOpenSfMStage(types.ODM_Stage):
|
|||
|
||||
# check if reconstruction was done before
|
||||
if not io.file_exists(output_file) or self.rerun():
|
||||
# create file list
|
||||
list_path = io.join_paths(tree.opensfm, 'image_list.txt')
|
||||
has_alt = True
|
||||
with open(list_path, 'w') as fout:
|
||||
for photo in photos:
|
||||
if not photo.altitude:
|
||||
has_alt = False
|
||||
fout.write('%s\n' % io.join_paths(tree.dataset_raw, photo.filename))
|
||||
|
||||
# create config file for OpenSfM
|
||||
config = [
|
||||
"use_exif_size: %s" % ('no' if not self.params.get('use_exif_size') else 'yes'),
|
||||
"feature_process_size: %s" % self.params.get('feature_process_size'),
|
||||
"feature_min_frames: %s" % self.params.get('feature_min_frames'),
|
||||
"processes: %s" % self.params.get('processes'),
|
||||
"matching_gps_neighbors: %s" % self.params.get('matching_gps_neighbors'),
|
||||
"depthmap_method: %s" % args.opensfm_depthmap_method,
|
||||
"depthmap_resolution: %s" % args.depthmap_resolution,
|
||||
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
|
||||
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
|
||||
"optimize_camera_parameters: %s" % ('no' if self.params.get('fixed_camera_params') else 'yes')
|
||||
]
|
||||
|
||||
if has_alt:
|
||||
log.ODM_DEBUG("Altitude data detected, enabling it for GPS alignment")
|
||||
config.append("use_altitude_tag: yes")
|
||||
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" % self.params.get('matching_gps_distance'))
|
||||
|
||||
if tree.odm_georeferencing_gcp:
|
||||
config.append("bundle_use_gcp: yes")
|
||||
io.copy(tree.odm_georeferencing_gcp, tree.opensfm)
|
||||
|
||||
# write config file
|
||||
log.ODM_DEBUG(config)
|
||||
config_filename = io.join_paths(tree.opensfm, 'config.yaml')
|
||||
with open(config_filename, 'w') as fout:
|
||||
fout.write("\n".join(config))
|
||||
osfm.setup(args, self.params, tree.dataset_raw, tree.opensfm, photos, gcp_path=tree.odm_georeferencing_gcp)
|
||||
|
||||
# run OpenSfM reconstruction
|
||||
matched_done_file = io.join_paths(tree.opensfm, 'matching_done.txt')
|
||||
if not io.file_exists(matched_done_file) or self.rerun():
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm extract_metadata %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm detect_features %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm match_features %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
osfm.run('extract_metadata', tree.opensfm)
|
||||
osfm.run('detect_features', tree.opensfm)
|
||||
osfm.run('match_features', tree.opensfm)
|
||||
|
||||
with open(matched_done_file, 'w') as fout:
|
||||
fout.write("Matching done!\n")
|
||||
else:
|
||||
|
@ -97,15 +49,13 @@ class ODMOpenSfMStage(types.ODM_Stage):
|
|||
matched_done_file)
|
||||
|
||||
if not io.file_exists(tree.opensfm_tracks) or self.rerun():
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm create_tracks %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
osfm.run('create_tracks', tree.opensfm)
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid OpenSfM tracks file in: %s' %
|
||||
tree.opensfm_tracks)
|
||||
|
||||
if not io.file_exists(tree.opensfm_reconstruction) or self.rerun():
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm reconstruct %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
osfm.run('reconstruct', tree.opensfm)
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
|
||||
tree.opensfm_reconstruction)
|
||||
|
@ -129,29 +79,24 @@ class ODMOpenSfMStage(types.ODM_Stage):
|
|||
image_scale = 1.0
|
||||
|
||||
if not io.file_exists(tree.opensfm_reconstruction_nvm) or self.rerun():
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm export_visualsfm --image_extension png --scale_focal %s %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, image_scale, tree.opensfm))
|
||||
osfm.run('export_visualsfm --image_extension png --scale_focal %s' % image_scale, tree.opensfm)
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid OpenSfM NVM reconstruction file in: %s' %
|
||||
tree.opensfm_reconstruction_nvm)
|
||||
|
||||
# These will be used for texturing
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm undistort --image_format png --image_scale %s %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, image_scale, tree.opensfm))
|
||||
osfm.run('undistort --image_format png --image_scale %s' % image_scale, tree.opensfm)
|
||||
|
||||
# Skip dense reconstruction if necessary and export
|
||||
# sparse reconstruction instead
|
||||
if args.fast_orthophoto:
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm export_ply --no-cameras %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
osfm.run('export_ply --no-cameras' % image_scale, tree.opensfm)
|
||||
elif args.use_opensfm_dense:
|
||||
# Undistort images at full scale in JPG
|
||||
# (TODO: we could compare the size of the PNGs if they are < than depthmap_resolution
|
||||
# and use those instead of re-exporting full resolution JPGs)
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm undistort %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm compute_depthmaps %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
osfm.run('undistort', tree.opensfm)
|
||||
osfm.run('compute_depthmaps', tree.opensfm)
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
|
||||
tree.opensfm_reconstruction)
|
||||
|
@ -159,12 +104,10 @@ class ODMOpenSfMStage(types.ODM_Stage):
|
|||
# check if reconstruction was exported to bundler before
|
||||
if not io.file_exists(tree.opensfm_bundle_list) or self.rerun():
|
||||
# convert back to bundler's format
|
||||
system.run('PYTHONPATH=%s %s/bin/export_bundler %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
|
||||
osfm.run('export_bundler', tree.opensfm)
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid Bundler file in: %s' %
|
||||
tree.opensfm_reconstruction)
|
||||
|
||||
if reconstruction.georef:
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm export_geocoords %s --transformation --proj \'%s\'' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm, reconstruction.georef.projection.srs))
|
||||
osfm.run('export_geocoords --transformation --proj \'%s\'' % reconstruction.georef.projection.srs, tree.opensfm)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
from opendm import log
|
||||
from opendm import osfm
|
||||
|
||||
class ODMSplitStage(types.ODM_Stage):
|
||||
def process(self, args, outputs):
|
||||
tree = outputs['tree']
|
||||
reconstruction = outputs['reconstruction']
|
||||
photos = reconstruction.photos
|
||||
|
||||
config = [
|
||||
"submodels_relpath: ../submodels/opensfm",
|
||||
"submodel_relpath_template: ../submodels/submodel_%04d/opensfm",
|
||||
"submodel_images_relpath_template: ../submodels/submodel_%04d/images",
|
||||
"submodel_size: %s" % args.split,
|
||||
"submodel_overlap: %s" % args.split_overlap,
|
||||
]
|
||||
|
||||
osfm.setup(args, self.params, tree.dataset_raw, tree.opensfm, photos, gcp_path=tree.odm_georeferencing_gcp, append_config=config)
|
||||
|
||||
|
Ładowanie…
Reference in New Issue