OpenDroneMap-ODM/scripts/run_opensfm.py

171 wiersze
8.8 KiB
Python
Czysty Zwykły widok Historia

import sys
2019-03-06 02:45:08 +00:00
import os
2015-11-26 12:15:02 +00:00
2015-11-20 10:00:43 +00:00
from opendm import log
2015-11-26 12:15:02 +00:00
from opendm import io
2015-11-20 10:00:43 +00:00
from opendm import system
from opendm import context
from opendm import gsd
2019-03-06 00:38:46 +00:00
from opendm import point_cloud
2019-04-22 19:14:39 +00:00
from opendm import types
2019-04-22 19:14:39 +00:00
class ODMOpenSfMStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
photos = reconstruction.photos
2015-11-26 12:15:02 +00:00
2015-11-27 16:51:21 +00:00
if not photos:
log.ODM_ERROR('Not enough photos in photos array to start OpenSfM')
2019-04-22 19:14:39 +00:00
exit(1)
2015-11-27 16:51:21 +00:00
# create working directories
system.mkdir_p(tree.opensfm)
2015-11-26 12:15:02 +00:00
if args.fast_orthophoto:
output_file = io.join_paths(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
output_file = tree.opensfm_model
else:
output_file = tree.opensfm_reconstruction
2015-11-26 12:15:02 +00:00
# check if reconstruction was done before
2019-04-22 19:14:39 +00:00
if not io.file_exists(output_file) or self.rerun():
# create file list
list_path = io.join_paths(tree.opensfm, 'image_list.txt')
2017-07-31 16:52:25 +00:00
has_alt = True
with open(list_path, 'w') as fout:
for photo in photos:
2017-07-31 16:52:25 +00:00
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 = [
2019-04-22 19:14:39 +00:00
"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'),
2018-05-16 19:41:36 +00:00
"depthmap_method: %s" % args.opensfm_depthmap_method,
"depthmap_resolution: %s" % args.depthmap_resolution,
2018-05-16 19:41:36 +00:00
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
2019-04-22 19:14:39 +00:00
"optimize_camera_parameters: %s" % ('no' if self.params.get('fixed_camera_params') else 'yes')
]
2017-07-31 16:52:25 +00:00
if has_alt:
2017-08-30 19:07:29 +00:00
log.ODM_DEBUG("Altitude data detected, enabling it for GPS alignment")
config.append("use_altitude_tag: yes")
2017-07-31 16:52:25 +00:00
config.append("align_method: naive")
else:
config.append("align_method: orientation_prior")
config.append("align_orientation_prior: vertical")
2017-07-31 16:52:25 +00:00
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
2016-03-08 18:55:58 +00:00
if args.matcher_distance > 0:
2019-04-22 19:14:39 +00:00
config.append("matching_gps_distance: %s" % self.params.get('matching_gps_distance'))
2015-12-30 14:36:56 +00:00
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))
# run OpenSfM reconstruction
2017-03-22 22:22:24 +00:00
matched_done_file = io.join_paths(tree.opensfm, 'matching_done.txt')
2019-04-22 19:14:39 +00:00
if not io.file_exists(matched_done_file) or self.rerun():
2017-03-22 22:22:24 +00:00
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))
with open(matched_done_file, 'w') as fout:
fout.write("Matching done!\n")
else:
log.ODM_WARNING('Found a feature matching done progress file in: %s' %
matched_done_file)
2019-04-22 19:14:39 +00:00
if not io.file_exists(tree.opensfm_tracks) or self.rerun():
2017-03-22 22:22:24 +00:00
system.run('PYTHONPATH=%s %s/bin/opensfm create_tracks %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
else:
log.ODM_WARNING('Found a valid OpenSfM tracks file in: %s' %
tree.opensfm_tracks)
2017-03-22 22:22:24 +00:00
2019-04-22 19:14:39 +00:00
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))
else:
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
tree.opensfm_reconstruction)
# Check that a reconstruction file has been created
if not io.file_exists(tree.opensfm_reconstruction):
log.ODM_ERROR("The program could not process this dataset using the current settings. "
2019-03-03 21:41:03 +00:00
"Check that the images have enough overlap, "
"that there are enough recognizable features "
"and that the images are in focus. "
"You could also try to increase the --min-num-features parameter."
"The program will now exit.")
sys.exit(1)
# Always export VisualSFM's reconstruction and undistort images
# as we'll use these for texturing (after GSD estimation and resizing)
if not args.ignore_gsd:
image_scale = gsd.image_scale_factor(args.orthophoto_resolution, tree.opensfm_reconstruction)
else:
image_scale = 1.0
2019-04-22 19:14:39 +00:00
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))
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))
# 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))
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))
else:
2017-03-22 22:22:24 +00:00
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
tree.opensfm_reconstruction)
# check if reconstruction was exported to bundler before
2019-04-22 19:14:39 +00:00
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))
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))