diff --git a/opendm/gsd.py b/opendm/gsd.py index 4f4b0bb0..8c2f64e1 100644 --- a/opendm/gsd.py +++ b/opendm/gsd.py @@ -5,6 +5,7 @@ import math from repoze.lru import lru_cache from opendm import log + def rounded_gsd(reconstruction_json, default_value=None, ndigits=0, ignore_gsd=False): """ :param reconstruction_json path to OpenSfM's reconstruction.json @@ -61,12 +62,15 @@ def image_scale_factor(target_resolution, reconstruction_json, gsd_error_estimat return 1 -def cap_resolution(resolution, reconstruction_json, gsd_error_estimate = 0.1, ignore_gsd=False, ignore_resolution=False, has_gcp=False): +def cap_resolution(resolution, reconstruction_json, gsd_error_estimate = 0.1, gsd_scaling = 1.0, ignore_gsd=False, + ignore_resolution=False, has_gcp=False): """ :param resolution resolution 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 gsd_scaling scaling of estimated GSD. :param ignore_gsd when set to True, forces the function to just return resolution. + :param ignore_resolution when set to True, forces the function to return a value based on GSD. :return The max value between resolution and the GSD computed from the reconstruction. If a GSD cannot be computed, or ignore_gsd is set to True, it just returns resolution. Units are in cm / pixel. """ @@ -76,14 +80,16 @@ def cap_resolution(resolution, reconstruction_json, gsd_error_estimate = 0.1, ig gsd = opensfm_reconstruction_average_gsd(reconstruction_json, use_all_shots=has_gcp or ignore_resolution) if gsd is not None: - gsd = gsd * (1 - gsd_error_estimate) + gsd = gsd * (1 - gsd_error_estimate) * gsd_scaling if gsd > resolution or ignore_resolution: - log.ODM_WARNING('Maximum resolution set to GSD - {}% ({} cm / pixel, requested resolution was {} cm / pixel)'.format(gsd_error_estimate * 100, round(gsd, 2), round(resolution, 2))) + log.ODM_WARNING('Maximum resolution set to {} * (GSD - {}%) ' + '({:.2f} cm / pixel, requested resolution was {:.2f} cm / pixel)' + .format(gsd_scaling, gsd_error_estimate * 100, gsd, resolution)) return gsd else: return resolution else: - log.ODM_WARNING('Cannot calculate GSD, using requested resolution of {}'.format(round(resolution, 2))) + log.ODM_WARNING('Cannot calculate GSD, using requested resolution of {:.2f}'.format(resolution)) return resolution @@ -134,6 +140,7 @@ def opensfm_reconstruction_average_gsd(reconstruction_json, use_all_shots=False) return None + def calculate_gsd(sensor_width, flight_height, focal_length, image_width): """ :param sensor_width in millimeters @@ -154,6 +161,7 @@ def calculate_gsd(sensor_width, flight_height, focal_length, image_width): else: return None + def calculate_gsd_from_focal_ratio(focal_ratio, flight_height, image_width): """ :param focal_ratio focal length (mm) / sensor_width (mm) diff --git a/stages/odm_dem.py b/stages/odm_dem.py index a29e4b9a..248bfffe 100755 --- a/stages/odm_dem.py +++ b/stages/odm_dem.py @@ -13,6 +13,7 @@ from opendm import pseudogeo from opendm.tiles.tiler import generate_dem_tiles from opendm.cogeo import convert_to_cogeo + class ODMDEMStage(types.ODM_Stage): def process(self, args, outputs): tree = outputs['tree'] @@ -28,11 +29,15 @@ class ODMDEMStage(types.ODM_Stage): ignore_resolution = True pseudo_georeference = True + # It is probably not reasonable to have accurate DEMs a the same resolution as the source photos, so reduce it + # by a factor! + gsd_scaling = 2.0 + resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, - gsd_error_estimate=-1, - ignore_gsd=args.ignore_gsd, - ignore_resolution=ignore_resolution, - has_gcp=reconstruction.has_gcp()) + gsd_scaling=gsd_scaling, + ignore_gsd=args.ignore_gsd, + ignore_resolution=ignore_resolution, + has_gcp=reconstruction.has_gcp()) log.ODM_INFO('Classify: ' + str(args.pc_classify)) log.ODM_INFO('Create DSM: ' + str(args.dsm)) diff --git a/stages/odm_orthophoto.py b/stages/odm_orthophoto.py index 1d54f39c..c55e2769 100644 --- a/stages/odm_orthophoto.py +++ b/stages/odm_orthophoto.py @@ -13,6 +13,7 @@ from opendm.utils import double_quote from opendm import pseudogeo from opendm.multispectral import get_primary_band_name + class ODMOrthoPhotoStage(types.ODM_Stage): def process(self, args, outputs): tree = outputs['tree'] @@ -23,18 +24,11 @@ class ODMOrthoPhotoStage(types.ODM_Stage): system.mkdir_p(tree.odm_orthophoto) if not io.file_exists(tree.odm_orthophoto_tif) or self.rerun(): - gsd_error_estimate = 0.1 - ignore_resolution = False - if not reconstruction.is_georeferenced(): - # Match DEMs - gsd_error_estimate = -3 - ignore_resolution = True resolution = 1.0 / (gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, - gsd_error_estimate=gsd_error_estimate, - ignore_gsd=args.ignore_gsd, - ignore_resolution=ignore_resolution, - has_gcp=reconstruction.has_gcp()) / 100.0) + ignore_gsd=args.ignore_gsd, + ignore_resolution=not reconstruction.is_georeferenced(), + has_gcp=reconstruction.has_gcp()) / 100.0) # odm_orthophoto definitions kwargs = {