From 3249a196fa732458453290631ab691e73c54558e Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 30 May 2019 15:58:37 -0400 Subject: [PATCH] Point to 061 branch in OpenDroneMap/OpenSfM, undistort logic adaptation Former-commit-id: 05d0706da1a50542d33ea8dfbf080f33284702ea --- SuperBuild/cmake/External-OpenSfM.cmake | 4 ++-- opendm/gsd.py | 24 +++++++++++++++++++++ opendm/osfm.py | 24 ++++++++++++++++++++- stages/run_opensfm.py | 28 ++++++++++--------------- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/SuperBuild/cmake/External-OpenSfM.cmake b/SuperBuild/cmake/External-OpenSfM.cmake index 98234278..be02cbea 100644 --- a/SuperBuild/cmake/External-OpenSfM.cmake +++ b/SuperBuild/cmake/External-OpenSfM.cmake @@ -8,8 +8,8 @@ ExternalProject_Add(${_proj_name} STAMP_DIR ${_SB_BINARY_DIR}/stamp #--Download step-------------- DOWNLOAD_DIR ${SB_DOWNLOAD_DIR} - GIT_REPOSITORY https://github.com/mapillary/OpenSfM/ - GIT_TAG 00655397be3e27c490492ae01197bf66c51fa493 + GIT_REPOSITORY https://github.com/OpenDroneMap/OpenSfM/ + GIT_TAG 061 #--Update/Patch step---------- UPDATE_COMMAND git submodule update --init --recursive #--Configure step------------- diff --git a/opendm/gsd.py b/opendm/gsd.py index 73835e5b..9bdc18b7 100644 --- a/opendm/gsd.py +++ b/opendm/gsd.py @@ -1,6 +1,7 @@ import os import json import numpy as np +import math from repoze.lru import lru_cache from opendm import log @@ -20,6 +21,29 @@ def rounded_gsd(reconstruction_json, default_value=None, ndigits=0, ignore_gsd=F return default_value +def image_max_size(photos, target_resolution, reconstruction_json, gsd_error_estimate = 0.1, ignore_gsd=False): + """ + :param photos images database + :param target_resolution resolution the user wants have in cm / pixel + :param reconstruction_json path to OpenSfM's reconstruction.json + :param gsd_error_estimate percentage of estimated error in the GSD calculation to set an upper bound on resolution. + :param ignore_gsd if set to True, simply return the largest side of the largest image in the images database. + :return A dimension in pixels calculated by taking the image_scale_factor and applying it to the size of the largest image. + Returned value is never higher than the size of the largest side of the largest image. + """ + max_width = 0 + max_height = 0 + if ignore_gsd: + isf = 1.0 + else: + isf = image_scale_factor(target_resolution, reconstruction_json, gsd_error_estimate) + + for p in photos: + max_width = max(p.width, max_width) + max_height = max(p.height, max_height) + + return int(math.ceil(max(max_width, max_height) * isf)) + def image_scale_factor(target_resolution, reconstruction_json, gsd_error_estimate = 0.1): """ :param target_resolution resolution the user wants have in cm / pixel diff --git a/opendm/osfm.py b/opendm/osfm.py index de928095..ab9b2f39 100644 --- a/opendm/osfm.py +++ b/opendm/osfm.py @@ -3,6 +3,7 @@ OpenSfM related utils """ import os, shutil, sys +import yaml from opendm import io from opendm import log from opendm import system @@ -85,6 +86,7 @@ class OSFMContext: "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 args.use_fixed_camera_params else 'yes'), + "undistorted_image_format: png" # mvs-texturing exhibits artifacts with JPG ] if has_alt: @@ -109,7 +111,7 @@ class OSFMContext: # write config file log.ODM_DEBUG(config) - config_filename = io.join_paths(self.opensfm_project_path, 'config.yaml') + config_filename = self.get_config_file_path() with open(config_filename, 'w') as fout: fout.write("\n".join(config)) @@ -121,6 +123,9 @@ class OSFMContext: else: log.ODM_WARNING("%s already exists, not rerunning OpenSfM setup" % list_path) + def get_config_file_path(self): + return io.join_paths(self.opensfm_project_path, 'config.yaml') + def extract_metadata(self, rerun=False): metadata_dir = self.path("exif") if not io.dir_exists(metadata_dir) or rerun: @@ -157,6 +162,23 @@ class OSFMContext: def path(self, *paths): return os.path.join(self.opensfm_project_path, *paths) + def update_config(self, cfg_dict): + cfg_file = self.get_config_file_path() + log.ODM_DEBUG("Updating %s" % cfg_file) + if os.path.exists(cfg_file): + try: + with open(cfg_file) as fin: + cfg = yaml.safe_load(fin) + for k, v in cfg_dict.items(): + cfg[k] = v + log.ODM_DEBUG("%s: %s" % (k, v)) + with open(cfg_file, 'w') as fout: + fout.write(yaml.dump(cfg, default_flow_style=False)) + except Exception as e: + log.ODM_WARNING("Cannot update configuration file %s: %s" % (cfg_file, str(e))) + else: + log.ODM_WARNING("Tried to update configuration, but %s does not exist." % cfg_file) + def save_absolute_image_list_to(self, file): """ Writes a copy of the image_list.txt file and makes sure that all paths diff --git a/stages/run_opensfm.py b/stages/run_opensfm.py index 311561be..4bac8928 100644 --- a/stages/run_opensfm.py +++ b/stages/run_opensfm.py @@ -42,29 +42,26 @@ class ODMOpenSfMStage(types.ODM_Stage): else: output_file = tree.opensfm_reconstruction - # 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 - - if not io.file_exists(tree.opensfm_reconstruction_nvm) or self.rerun(): - octx.run('export_visualsfm --image_extension png --scale_focal %s' % image_scale) - else: - log.ODM_WARNING('Found a valid OpenSfM NVM reconstruction file in: %s' % - tree.opensfm_reconstruction_nvm) + octx.update_config({'undistorted_image_max_size': gsd.image_max_size(photos, args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd)}) # These will be used for texturing undistorted_images_path = octx.path("undistorted") if not io.dir_exists(undistorted_images_path) or self.rerun(): - octx.run('undistort --image_format png --image_scale %s' % image_scale) + octx.run('undistort') else: log.ODM_WARNING("Found an undistorted directory in %s" % undistorted_images_path) self.update_progress(80) + if not io.file_exists(tree.opensfm_reconstruction_nvm) or self.rerun(): + octx.run('export_visualsfm') + else: + log.ODM_WARNING('Found a valid OpenSfM NVM reconstruction file in: %s' % + tree.opensfm_reconstruction_nvm) + + self.update_progress(85) + # Skip dense reconstruction if necessary and export # sparse reconstruction instead if args.fast_orthophoto: @@ -74,11 +71,8 @@ class ODMOpenSfMStage(types.ODM_Stage): log.ODM_WARNING("Found a valid PLY reconstruction in %s" % output_file) 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) if not io.file_exists(output_file) or self.rerun(): - octx.run('undistort') + #octx.run('undistort') # TODO can we use PNGs here? octx.run('compute_depthmaps') else: log.ODM_WARNING("Found a valid dense reconstruction in %s" % output_file)