From 07be35270f4ffc13fb45404ce5e3401854873529 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Wed, 2 Mar 2022 13:49:02 -0500 Subject: [PATCH 1/4] Dynamic depthmap resolution --- opendm/config.py | 4 ++-- opendm/osfm.py | 3 --- opendm/photo.py | 14 ++++++++++++++ opendm/utils.py | 36 +++++++++++++++++++++++------------- stages/openmvs.py | 4 +++- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/opendm/config.py b/opendm/config.py index f33fd029..a0d683e2 100755 --- a/opendm/config.py +++ b/opendm/config.py @@ -218,8 +218,8 @@ def config(argv=None, parser=None): action=StoreValue, type=float, default=640, - help=('Legacy option (use --pc-quality instead). Controls the density of the point cloud by setting the resolution of the depthmap images. Higher values take longer to compute ' - 'but produce denser point clouds. ' + help=('Controls the density of the point cloud by setting the resolution of the depthmap images. Higher values take longer to compute ' + 'but produce denser point clouds. Overrides the value calculated by --pc-quality.' 'Default: %(default)s')) parser.add_argument('--use-hybrid-bundle-adjustment', diff --git a/opendm/osfm.py b/opendm/osfm.py index 447709d0..91f64fef 100644 --- a/opendm/osfm.py +++ b/opendm/osfm.py @@ -13,7 +13,6 @@ from opendm import system from opendm import context from opendm import camera from opendm import location -from opendm.utils import get_depthmap_resolution from opendm.photo import find_largest_photo_dim, find_largest_photo from opensfm.large import metadataset from opensfm.large import tools @@ -195,8 +194,6 @@ class OSFMContext: else: log.ODM_WARNING("Cannot compute max image dimensions, going with defaults") - depthmap_resolution = get_depthmap_resolution(args, photos) - # create config file for OpenSfM config = [ "use_exif_size: no", diff --git a/opendm/photo.py b/opendm/photo.py index 13ee1c6b..0d8be6bb 100644 --- a/opendm/photo.py +++ b/opendm/photo.py @@ -20,6 +20,20 @@ from opensfm.geo import ecef_from_lla projections = ['perspective', 'fisheye', 'brown', 'dual', 'equirectangular', 'spherical'] +def find_largest_photo_dims(photos): + max_mp = 0 + max_dims = None + + for p in photos: + if p.width is None or p.height is None: + continue + mp = p.width * p.height + if mp > max_mp: + max_mp = mp + max_dims = (p.width, p.height) + + return max_dims + def find_largest_photo_dim(photos): max_dim = 0 for p in photos: diff --git a/opendm/utils.py b/opendm/utils.py index 8028287e..c01dfe8a 100644 --- a/opendm/utils.py +++ b/opendm/utils.py @@ -1,28 +1,38 @@ import os, shutil from opendm import log -from opendm.photo import find_largest_photo_dim +from opendm.photo import find_largest_photo_dims from osgeo import gdal from opendm.loghelpers import double_quote def get_depthmap_resolution(args, photos): if 'depthmap_resolution_is_set' in args: - # Legacy - log.ODM_WARNING("Legacy option --depthmap-resolution (this might be removed in a future version). Use --pc-quality instead.") + # Override pc-quality return int(args.depthmap_resolution) else: - max_dim = find_largest_photo_dim(photos) + max_dims = find_largest_photo_dims(photos) min_dim = 320 # Never go lower than this - pc_quality_scale = { - 'ultra': 0.5, - 'high': 0.25, - 'medium': 0.125, - 'low': 0.0675, - 'lowest': 0.03375 - } + if max_dims is not None: + w, h = max_dims + max_dim = max(w, h) - if max_dim > 0: - return max(min_dim, int(max_dim * pc_quality_scale[args.pc_quality])) + megapixels = (w * h) / 1e6 + multiplier = 1 + + if megapixels < 6: + multiplier = 2 + elif megapixels > 42: + multiplier = 0.5 + + pc_quality_scale = { + 'ultra': 0.5, + 'high': 0.25, + 'medium': 0.125, + 'low': 0.0675, + 'lowest': 0.03375 + } + + return max(min_dim, int(max_dim * pc_quality_scale[args.pc_quality] * multiplier)) else: log.ODM_WARNING("Cannot compute max image dimensions, going with default depthmap_resolution of 640") return 640 # Sensible default diff --git a/stages/openmvs.py b/stages/openmvs.py index aaa6ad32..6594c158 100644 --- a/stages/openmvs.py +++ b/stages/openmvs.py @@ -46,8 +46,10 @@ class ODMOpenMVSStage(types.ODM_Stage): if not io.dir_exists(depthmaps_dir): os.mkdir(depthmaps_dir) - + depthmap_resolution = get_depthmap_resolution(args, photos) + log.ODM_INFO("Depthmap resolution set to: %spx" % depthmap_resolution) + if outputs["undist_image_max_size"] <= depthmap_resolution: resolution_level = 0 else: From e65afba7b63639543e9086de26b45683b24608f9 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 3 Mar 2022 10:35:29 -0500 Subject: [PATCH 2/4] Catch landmark duplicate error during merge --- opendm/osfm.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/opendm/osfm.py b/opendm/osfm.py index 91f64fef..7657fca1 100644 --- a/opendm/osfm.py +++ b/opendm/osfm.py @@ -88,8 +88,12 @@ class OSFMContext: merged.add_camera(camera) for point in rec.points.values(): - new_point = merged.create_point(point.id, point.coordinates) - new_point.color = point.color + try: + new_point = merged.create_point(point.id, point.coordinates) + new_point.color = point.color + except RuntimeError as e: + log.ODM_WARNING("Cannot merge shot id %s (%s)" % (shot.id, str(e))) + continue for shot in rec.shots.values(): merged.add_shot(shot) From c1da63b4dd962f5e1f921b2002042021a10a119c Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 3 Mar 2022 10:43:45 -0500 Subject: [PATCH 3/4] Fix edge case in get_geojson_shots_from_opensfm --- opendm/shots.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opendm/shots.py b/opendm/shots.py index 0718f839..0440257e 100644 --- a/opendm/shots.py +++ b/opendm/shots.py @@ -46,6 +46,10 @@ def get_geojson_shots_from_opensfm(reconstruction_file, utm_srs=None, utm_offset [0, 0, 0, 1]]) raster = None pseudo = True + + # Couldn't get a SRS? + if utm_srs is None: + return None crstrans = transformer(CRS.from_proj4(utm_srs), CRS.from_epsg("4326")) From da276c22119bde6e643f8d378919318e0ddc6850 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 3 Mar 2022 15:10:31 -0500 Subject: [PATCH 4/4] More aggressive smoothing, interpolate mesh DSM --- opendm/dem/commands.py | 9 +-------- opendm/mesh.py | 2 ++ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/opendm/dem/commands.py b/opendm/dem/commands.py index 152e84d5..a1d09bf0 100755 --- a/opendm/dem/commands.py +++ b/opendm/dem/commands.py @@ -322,14 +322,7 @@ def median_smoothing(geotiff_path, output_path, smoothing_iterations=1): # these edge cases, but it's slower. for i in range(smoothing_iterations): log.ODM_INFO("Smoothing iteration %s" % str(i + 1)) - arr = ndimage.median_filter(arr, size=5, output=dtype) - - # Fill corner points with nearest value - if arr.shape >= (4, 4): - arr[0][:2] = arr[1][0] = arr[1][1] - arr[0][-2:] = arr[1][-1] = arr[2][-1] - arr[-1][:2] = arr[-2][0] = arr[-2][1] - arr[-1][-2:] = arr[-2][-1] = arr[-2][-2] + arr = ndimage.median_filter(arr, size=9, output=dtype, mode='nearest') # Median filter leaves a bunch of zeros in nodata areas arr[nodata_locs] = nodata diff --git a/opendm/mesh.py b/opendm/mesh.py index 1a0e7834..f0c524a6 100644 --- a/opendm/mesh.py +++ b/opendm/mesh.py @@ -20,6 +20,8 @@ def create_25dmesh(inPointCloud, outMesh, dsm_radius=0.07, dsm_resolution=0.05, log.ODM_INFO('Created temporary directory: %s' % tmp_directory) radius_steps = [dsm_radius] + for _ in range(2): + radius_steps.append(radius_steps[-1] * 2) # 2 is arbitrary log.ODM_INFO('Creating DSM for 2.5D mesh')