GSD optimization, Dockerfile updates, ignore-gsd arg, small refactoring

Former-commit-id: f4352e3fee
pull/1161/head
Piero Toffanin 2018-08-11 12:45:21 -04:00
rodzic 722c321626
commit 54c26bfff3
12 zmienionych plików z 94 dodań i 52 usunięć

Wyświetl plik

@ -21,7 +21,7 @@ libexiv2-dev liblas-bin python-matplotlib libatlas-base-dev swig2.0 python-wheel
RUN apt-get remove libdc1394-22-dev
RUN pip install --upgrade pip
RUN pip install setuptools
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings https://github.com/gipit/gippy/archive/v1.0.0.zip loky shapely numpy pyproj psutil && pip install -U scipy --ignore-installed
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings https://github.com/gipit/gippy/archive/v1.0.0.zip loky shapely numpy pyproj psutil repoze.lru && pip install -U scipy --ignore-installed
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/install/lib/python2.7/dist-packages"
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/src/opensfm"

Wyświetl plik

@ -8,8 +8,7 @@ ExternalProject_Add(${_proj_name}
STAMP_DIR ${_SB_BINARY_DIR}/stamp
#--Download step--------------
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
URL https://github.com/mapillary/OpenSfM/archive/93ae099862297c36ae94dd56fca1c062aa2bb60d.zip
URL_MD5 f0d8ec8a8dc9e0f6fd55f77d5407e50f
URL https://github.com/pierotofy/OpenSfM/archive/pngscale.zip
#--Update/Patch step----------
UPDATE_COMMAND ""
#--Configure step-------------

Wyświetl plik

@ -78,7 +78,8 @@ install() {
gpxpy \
xmltodict \
appsettings \
loky
loky \
repoze.lru
echo "Installing Ecto Dependencies"
pip install -U catkin-pkg

Wyświetl plik

@ -19,7 +19,7 @@ libexiv2-dev liblas-bin python-matplotlib libatlas-base-dev swig2.0 python-wheel
RUN apt-get remove libdc1394-22-dev
RUN pip install --upgrade pip
RUN pip install setuptools
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings https://github.com/gipit/gippy/archive/v1.0.0.zip loky shapely numpy pyproj psutil && pip install -U scipy --ignore-installed
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings https://github.com/gipit/gippy/archive/v1.0.0.zip loky shapely numpy pyproj psutil repoze.lru && pip install -U scipy --ignore-installed
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/install/lib/python2.7/dist-packages"
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/src/opensfm"

Wyświetl plik

@ -203,6 +203,14 @@ def config():
default=False,
help='Use opensfm to compute dense point cloud alternatively')
parser.add_argument('--ignore-gsd',
action='store_true',
default=False,
help='Ignore Ground Sampling Distance (GSD). GSD '
'caps the maximum resolution of image outputs and '
'resizes images when necessary, resulting in faster processing and '
'lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality.')
parser.add_argument('--smvs-alpha',
metavar='<float>',
default=1.0,
@ -540,9 +548,8 @@ def config():
sys.exit(1)
if args.fast_orthophoto:
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --skip-3dmodel and --use-opensfm-dense')
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --skip-3dmodel')
args.skip_3dmodel = True
args.use_opensfm_dense = True
if args.dtm and args.pc_classify == 'none':
log.ODM_INFO("DTM is turned on, automatically turning on point cloud classification")

Wyświetl plik

@ -1,31 +1,53 @@
import os
import json
import numpy as np
import functools
from repoze.lru import lru_cache
from opendm import log
def cap_resolution(resolution, reconstruction_json):
def image_scale_factor(target_resolution, reconstruction_json, gsd_error_estimate = 0.1):
"""
:param resolution resolution in cm / pixel
:param target_resolution resolution the user wants have in cm / pixel
:param reconstruction_json path to OpenSfM's reconstruction.json
:return The max value between resolution and the GSD computed from the reconstruction.
If a GSD cannot be computed, it just returns resolution. Units are in cm / pixel.
:param gsd_error_estimate percentage of estimated error in the GSD calculation to set an upper bound on resolution.
:return A down-scale (<= 1) value to apply to images to achieve the target resolution by comparing the current GSD of the reconstruction.
If a GSD cannot be computed, it just returns 1. Returned scale values are never higher than 1.
"""
gsd = opensfm_reconstruction_average_gsd(reconstruction_json)
if gsd is not None and target_resolution > 0:
gsd = gsd * (1 + gsd_error_estimate)
return min(1, gsd / target_resolution)
else:
return 1
def cap_resolution(resolution, reconstruction_json, gsd_error_estimate = 0.1, ignore_gsd=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 ignore_gsd when set to True, forces the function to just return resolution.
: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.
"""
if ignore_gsd:
return resolution
gsd = opensfm_reconstruction_average_gsd(reconstruction_json)
if gsd is not None:
log.ODM_INFO('Ground Sampling Distance: {} cm / pixel'.format(round(gsd, 2))
gsd = gsd * (1 - gsd_error_estimate)
if gsd > resolution:
log.ODM_WARNING('Maximum resolution set to GSD (requested resolution was {})'.format(round(resolution, 2)))
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)))
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 {}'.format(round(resolution, 2)))
return resolution
@functools.lru_cache(maxsize=None, typed=False)
@lru_cache(maxsize=None)
def opensfm_reconstruction_average_gsd(reconstruction_json):
"""
Computes the average Ground Sampling Distance of an OpenSfM reconstruction.
@ -62,7 +84,12 @@ def opensfm_reconstruction_average_gsd(reconstruction_json):
shot_height - ground_height,
camera['width']))
return np.mean(gsds) if len(gsds) > 0 else None
if len(gsds) > 0:
mean = np.mean(gsds)
if mean > 0:
return mean
return None
def calculate_gsd(sensor_width, flight_height, focal_length, image_width):
"""

Wyświetl plik

@ -103,15 +103,10 @@ class ODMMvsTexCell(ecto.Cell):
'keepUnseenFaces': keepUnseenFaces,
'toneMapping': self.params.tone_mapping,
'nadirMode': nadir,
'nadirWeight': 2 ** args.texturing_nadir_weight - 1
'nadirWeight': 2 ** args.texturing_nadir_weight - 1,
'nvm_file': io.join_paths(tree.opensfm, "reconstruction.nvm")
}
if args.use_opensfm_dense:
kwargs['nvm_file'] = io.join_paths(tree.opensfm,
"reconstruction.nvm")
else:
kwargs['nvm_file'] = tree.smvs + "::undistorted"
# Make sure tmp directory is empty
mvs_tmp_dir = os.path.join(r['out_dir'], 'tmp')
if io.dir_exists(mvs_tmp_dir):

Wyświetl plik

@ -118,13 +118,14 @@ class ODMApp(ecto.BlackBox):
self.args[:] >> self.opensfm['args'],
self.dataset['reconstruction'] >> self.opensfm['reconstruction']]
if p.args.use_opensfm_dense:
if p.args.use_opensfm_dense or p.args.fast_orthophoto:
# create odm mesh from opensfm point cloud
connections += [self.tree[:] >> self.meshing['tree'],
self.args[:] >> self.meshing['args'],
self.opensfm['reconstruction'] >> self.meshing['reconstruction']]
else:
# run smvs
connections += [self.tree[:] >> self.smvs['tree'],
self.args[:] >> self.smvs['args'],
self.opensfm['reconstruction'] >> self.smvs['reconstruction']]

Wyświetl plik

@ -92,7 +92,7 @@ class ODMDEMCell(ecto.Cell):
if args.dsm: products.append('dsm')
if args.dtm: products.append('dtm')
resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction)
resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd)
radius_steps = [(resolution / 100.0) / 2.0]
for _ in range(args.dem_gapfill_steps - 1):
radius_steps.append(radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value?

Wyświetl plik

@ -50,10 +50,10 @@ class ODMeshingCell(ecto.Cell):
'odm_meshing' in args.rerun_from)
infile = tree.smvs_model
if args.use_opensfm_dense:
infile = tree.opensfm_model
elif args.fast_orthophoto:
if args.fast_orthophoto:
infile = os.path.join(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
infile = tree.opensfm_model
# Create full 3D model unless --skip-3dmodel is set
if not args.skip_3dmodel:
@ -79,7 +79,7 @@ class ODMeshingCell(ecto.Cell):
if not io.file_exists(tree.odm_25dmesh) or rerun_cell:
log.ODM_DEBUG('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh)
dsm_resolution = gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction) / 100.0
dsm_resolution = gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0
# Create reference DSM at half ortho resolution
dsm_resolution *= 2

Wyświetl plik

@ -57,7 +57,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
'log': tree.odm_orthophoto_log,
'ortho': tree.odm_orthophoto_file,
'corners': tree.odm_orthophoto_corners,
'res': 1.0 / (gsd.cap_resolution(self.params.resolution, tree.reconstruction_json) / 100.0),
'res': 1.0 / (gsd.cap_resolution(self.params.resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0),
'verbose': verbose
}

Wyświetl plik

@ -4,7 +4,7 @@ from opendm import log
from opendm import io
from opendm import system
from opendm import context
from opendm import gsd
class ODMOpenSfMCell(ecto.Cell):
def declare_params(self, params):
@ -50,10 +50,10 @@ class ODMOpenSfMCell(ecto.Cell):
(args.rerun_from is not None and
'opensfm' in args.rerun_from)
if args.use_opensfm_dense:
if args.fast_orthophoto:
output_file = io.join_paths(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
output_file = tree.opensfm_model
if args.fast_orthophoto:
output_file = io.join_paths(tree.opensfm, 'reconstruction.ply')
else:
output_file = tree.opensfm_reconstruction
@ -135,25 +135,37 @@ class ODMOpenSfMCell(ecto.Cell):
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
tree.opensfm_reconstruction)
if args.use_opensfm_dense:
if not io.file_exists(tree.opensfm_reconstruction_nvm) or rerun_cell:
system.run('PYTHONPATH=%s %s/bin/opensfm export_visualsfm %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
else:
log.ODM_WARNING('Found a valid OpenSfM NVM reconstruction file in: %s' %
tree.opensfm_reconstruction_nvm)
# 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 rerun_cell:
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))
# 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))
else:
system.run('PYTHONPATH=%s %s/bin/opensfm compute_depthmaps %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
(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:
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %