kopia lustrzana https://github.com/OpenDroneMap/ODM
GSD optimization, Dockerfile updates, ignore-gsd arg, small refactoring
Former-commit-id: f4352e3fee
pull/1161/head
rodzic
722c321626
commit
54c26bfff3
|
@ -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"
|
||||
|
|
|
@ -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-------------
|
||||
|
|
|
@ -78,7 +78,8 @@ install() {
|
|||
gpxpy \
|
||||
xmltodict \
|
||||
appsettings \
|
||||
loky
|
||||
loky \
|
||||
repoze.lru
|
||||
|
||||
echo "Installing Ecto Dependencies"
|
||||
pip install -U catkin-pkg
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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']]
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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' %
|
||||
|
|
Ładowanie…
Reference in New Issue