kopia lustrzana https://github.com/OpenDroneMap/ODM
Merge branch 'add_smvs' of ssh://git.masseranolabs.com:/home/odm/OpenDroneMap into add-poissonrecon
Former-commit-id: e99df871f7
pull/1161/head
commit
200b9ea436
|
@ -6,12 +6,9 @@ SuperBuild/install
|
|||
SuperBuild/src
|
||||
build
|
||||
opensfm
|
||||
pmvs
|
||||
odm_orthophoto
|
||||
odm_texturing
|
||||
odm_meshing
|
||||
odm_georeferencing
|
||||
images_resize
|
||||
.git
|
||||
|
||||
|
||||
|
|
12
README.md
12
README.md
|
@ -105,7 +105,7 @@ or
|
|||
|
||||
python run.py --rerun-from odm_meshing project-name
|
||||
|
||||
The options for rerunning are: 'resize', 'opensfm', 'slam', 'cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto'
|
||||
The options for rerunning are: 'resize', 'opensfm', 'slam', 'smvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto'
|
||||
|
||||
### View Results
|
||||
|
||||
|
@ -151,7 +151,7 @@ You can also view the orthophoto GeoTIFF in [QGIS](http://www.qgis.org/) or othe
|
|||
|
||||
## Build and Run Using Docker
|
||||
|
||||
(Instructions below apply to Ubuntu 14.04, but the Docker image workflow
|
||||
(Instructions below apply to Ubuntu 14.04, but the Docker image workflow
|
||||
has equivalent procedures for Mac OS X and Windows. See [docs.docker.com](https://docs.docker.com/))
|
||||
|
||||
OpenDroneMap is Dockerized, meaning you can use containerization to build and run it without tampering with the configuration of libraries and packages already
|
||||
|
@ -177,7 +177,7 @@ If you want to build your own Docker image from sources, type:
|
|||
Using this method, the containerized ODM will process the images in the OpenDroneMap/images directory and output results
|
||||
to the OpenDroneMap/odm_orthophoto and OpenDroneMap/odm_texturing directories as described in the [Viewing Results](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Output-and-Results) section.
|
||||
If you want to view other results outside the Docker image simply add which directories you're interested in to the run command in the same pattern
|
||||
established above. For example, if you're interested in the dense cloud results generated by PMVS and in the orthophoto,
|
||||
established above. For example, if you're interested in the dense cloud results generated by OpenSfM and in the orthophoto,
|
||||
simply use the following `docker run` command after building the image:
|
||||
|
||||
docker run -it --rm \
|
||||
|
@ -195,7 +195,7 @@ If you want to get all intermediate outputs, run the following command:
|
|||
-v "$(pwd)/odm_orthophoto:/code/odm_orthophoto" \
|
||||
-v "$(pwd)/odm_texturing:/code/odm_texturing" \
|
||||
-v "$(pwd)/opensfm:/code/opensfm" \
|
||||
-v "$(pwd)/pmvs:/code/pmvs" \
|
||||
-v "$(pwd)/smvs:/code/smvs" \
|
||||
opendronemap/opendronemap
|
||||
|
||||
To pass in custom parameters to the run.py script, simply pass it as arguments to the `docker run` command. For example:
|
||||
|
@ -222,7 +222,7 @@ When building your own Docker image, if image size is of importance to you, you
|
|||
This will clean up intermediate steps in the Docker build process, resulting in a significantly smaller image (about half the size).
|
||||
|
||||
Experimental flags need to be enabled in Docker to use the ```--squash``` flag. To enable this, insert the following into the file ```/etc/docker/daemon.json```:
|
||||
|
||||
|
||||
{
|
||||
"experimental": true
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ Coming soon...
|
|||
|
||||
## Documentation:
|
||||
|
||||
For documentation, everything is being moved to [http://docs.opendronemap.org/](http://docs.opendronemap.org/) but you can also take a look at our [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki). Check those places first if you are having problems. There's also help at [community forum](http://community.opendronemap.org/), and if you still need help and think you've found a bug or need an enhancement, look through the issue queue or create one.
|
||||
For documentation, everything is being moved to [http://docs.opendronemap.org/](http://docs.opendronemap.org/) but you can also take a look at our [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki). Check those places first if you are having problems. There's also help at [community forum](http://community.opendronemap.org/), and if you still need help and think you've found a bug or need an enhancement, look through the issue queue or create one.
|
||||
|
||||
## Developers
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ SETUP_EXTERNAL_PROJECT(Ceres ${ODM_Ceres_Version} ${ODM_BUILD_Ceres})
|
|||
# ---------------------------------------------------------------------------------------------
|
||||
# VTK7
|
||||
# We need to build VTK from sources because Debian packages
|
||||
# are built with DVTK_SMP_IMPLEMENTATION_TYPE set to
|
||||
# are built with DVTK_SMP_IMPLEMENTATION_TYPE set to
|
||||
# "Sequential" which means no multithread support.
|
||||
|
||||
set(ODM_VTK7_Version 7.1.1)
|
||||
|
@ -114,14 +114,12 @@ SETUP_EXTERNAL_PROJECT(Hexer 1.4 ON)
|
|||
# ---------------------------------------------------------------------------------------------
|
||||
# Open Geometric Vision (OpenGV)
|
||||
# Open Structure from Motion (OpenSfM)
|
||||
# Clustering Views for Multi-view Stereo (CMVS)
|
||||
# Catkin
|
||||
# Ecto
|
||||
#
|
||||
|
||||
set(custom_libs OpenGV
|
||||
OpenSfM
|
||||
CMVS
|
||||
Catkin
|
||||
Ecto
|
||||
LASzip
|
||||
|
@ -140,3 +138,24 @@ foreach(lib ${custom_libs})
|
|||
SETUP_EXTERNAL_PROJECT_CUSTOM(${lib})
|
||||
endforeach()
|
||||
|
||||
## Add smvs Build
|
||||
|
||||
externalproject_add(smvs
|
||||
GIT_REPOSITORY https://github.com/flanggut/smvs.git
|
||||
UPDATE_COMMAND ""
|
||||
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/smvs
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND make
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
||||
externalproject_add(mve
|
||||
GIT_REPOSITORY https://github.com/simonfuhrmann/mve.git
|
||||
UPDATE_COMMAND ""
|
||||
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/mve
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND make
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
set(_proj_name cmvs)
|
||||
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
||||
|
||||
ExternalProject_Add(${_proj_name}
|
||||
PREFIX ${_SB_BINARY_DIR}
|
||||
TMP_DIR ${_SB_BINARY_DIR}/tmp
|
||||
STAMP_DIR ${_SB_BINARY_DIR}/stamp
|
||||
#--Download step--------------
|
||||
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}/${_proj_name}
|
||||
URL https://github.com/edgarriba/CMVS-PMVS/archive/master.zip
|
||||
URL_MD5 dbb1493f49ca099b4208381bd20d1435
|
||||
#--Update/Patch step----------
|
||||
UPDATE_COMMAND ""
|
||||
#--Configure step-------------
|
||||
SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
|
||||
CONFIGURE_COMMAND cmake <SOURCE_DIR>/program
|
||||
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${SB_INSTALL_DIR}/bin
|
||||
-DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR}
|
||||
#--Build step-----------------
|
||||
BINARY_DIR ${_SB_BINARY_DIR}
|
||||
#--Install step---------------
|
||||
INSTALL_DIR ${SB_INSTALL_DIR}
|
||||
#--Output logging-------------
|
||||
LOG_DOWNLOAD OFF
|
||||
LOG_CONFIGURE OFF
|
||||
LOG_BUILD OFF
|
||||
)
|
||||
|
|
@ -102,6 +102,7 @@ install() {
|
|||
|
||||
pip install -U https://github.com/gipit/gippy/archive/v1.0.0.zip psutil
|
||||
|
||||
|
||||
echo "Compiling SuperBuild"
|
||||
cd ${RUNPATH}/SuperBuild
|
||||
mkdir -p build && cd build
|
||||
|
|
|
@ -10,8 +10,6 @@ Licensing for portions of OpenDroneMap are as follows:
|
|||
* libcv - BSD - http://opencv.org/license.html
|
||||
* libcvaux - BSD - http://opencv.org/license.html
|
||||
* bundler - GPLv3 - http://www.gnu.org/copyleft/gpl.html
|
||||
* cmvs - GPLv3 - http://www.gnu.org/copyleft/gpl.html
|
||||
* pmvs2 - GPLv3 - http://www.gnu.org/copyleft/gpl.html
|
||||
* parallel - GPLv3 - http://www.gnu.org/copyleft/gpl.html
|
||||
* PoissonRecon - BSD - http://www.cs.jhu.edu/~misha/Code/PoissonRecon/license.txt
|
||||
* vlfeat - BSD - http://www.vlfeat.org/license.html
|
||||
|
|
100
opendm/config.py
100
opendm/config.py
|
@ -7,7 +7,7 @@ from appsettings import SettingsParser
|
|||
import sys
|
||||
|
||||
# parse arguments
|
||||
processopts = ['dataset', 'opensfm', 'slam', 'cmvs', 'pmvs',
|
||||
processopts = ['dataset', 'opensfm', 'slam', 'smvs',
|
||||
'odm_meshing', 'odm_25dmeshing', 'mvs_texturing', 'odm_georeferencing',
|
||||
'odm_dem', 'odm_orthophoto']
|
||||
|
||||
|
@ -140,12 +140,13 @@ def config():
|
|||
default=False,
|
||||
help='Turn off camera parameter optimization during bundler')
|
||||
|
||||
parser.add_argument('--opensfm-processes',
|
||||
parser.add_argument('--max-concurrency',
|
||||
metavar='<positive integer>',
|
||||
default=context.num_cores,
|
||||
type=int,
|
||||
help=('The maximum number of processes to use in dense '
|
||||
'reconstruction. Default: %(default)s'))
|
||||
help=('The maximum number of processes to use in various '
|
||||
'processes. Peak memory requirement is ~1GB per '
|
||||
'thread and 2 megapixel image resolution. Default: %(default)s'))
|
||||
|
||||
parser.add_argument('--opensfm-depthmap-resolution',
|
||||
metavar='<positive float>',
|
||||
|
@ -192,67 +193,53 @@ def config():
|
|||
default=False,
|
||||
help='Use a 2.5D mesh to compute the orthophoto. This option tends to provide better results for planar surfaces. Experimental.')
|
||||
|
||||
parser.add_argument('--use-pmvs',
|
||||
parser.add_argument('--use-opensfm-dense',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use pmvs to compute point cloud alternatively')
|
||||
help='Use opensfm to compute dense point cloud alternatively')
|
||||
|
||||
parser.add_argument('--cmvs-maxImages',
|
||||
metavar='<integer>',
|
||||
default=500,
|
||||
type=int,
|
||||
help='The maximum number of images per cluster. '
|
||||
'Default: %(default)s')
|
||||
parser.add_argument('--smvs-alpha',
|
||||
metavar='<float>',
|
||||
default=1.0,
|
||||
type=float,
|
||||
help='Regularization parameter, a higher alpha leads to '
|
||||
'smoother surfaces. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--pmvs-level',
|
||||
metavar='<positive integer>',
|
||||
parser.add_argument('--smvs-scale',
|
||||
metavar='<non-negative integer>',
|
||||
default=1,
|
||||
type=int,
|
||||
help=('The level in the image pyramid that is used '
|
||||
'for the computation. see '
|
||||
'http://www.di.ens.fr/pmvs/documentation.html for '
|
||||
'more pmvs documentation. Default: %(default)s'))
|
||||
help='Scales the input images, which affects the output'
|
||||
' density. 0 is original scale but takes longer '
|
||||
'to process. 2 is 1/4 scale. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--pmvs-csize',
|
||||
parser.add_argument('--smvs-output-scale',
|
||||
metavar='<positive integer>',
|
||||
default=2,
|
||||
type=int,
|
||||
help='Cell size controls the density of reconstructions'
|
||||
'Default: %(default)s')
|
||||
help='The scale of the optimization - the '
|
||||
'finest resolution of the bicubic patches will have the'
|
||||
' size of the respective power of 2 (e.g. 2 will '
|
||||
'optimize patches covering down to 4x4 pixels). '
|
||||
'Default: %(default)s')
|
||||
|
||||
parser.add_argument('--pmvs-threshold',
|
||||
metavar='<float: -1.0 <= x <= 1.0>',
|
||||
default=0.7,
|
||||
type=float,
|
||||
help=('A patch reconstruction is accepted as a success '
|
||||
'and kept if its associated photometric consistency '
|
||||
'measure is above this threshold. Default: %(default)s'))
|
||||
parser.add_argument('--smvs-enable-shading',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use shading-based optimization. This model cannot '
|
||||
'handle complex scenes. Try to supply linear images to '
|
||||
'the reconstruction pipeline that are not tone mapped '
|
||||
'or altered as this can also have very negative effects '
|
||||
'on the reconstruction. If you have simple JPGs with SRGB '
|
||||
'gamma correction you can remove it with the --smvs-gamma-srgb '
|
||||
'option. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--pmvs-wsize',
|
||||
metavar='<positive integer>',
|
||||
default=7,
|
||||
type=int,
|
||||
help='pmvs samples wsize x wsize pixel colors from '
|
||||
'each image to compute photometric consistency '
|
||||
'score. For example, when wsize=7, 7x7=49 pixel '
|
||||
'colors are sampled in each image. Increasing the '
|
||||
'value leads to more stable reconstructions, but '
|
||||
'the program becomes slower. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--pmvs-min-images',
|
||||
metavar='<positive integer>',
|
||||
default=3,
|
||||
type=int,
|
||||
help=('Each 3D point must be visible in at least '
|
||||
'minImageNum images for being reconstructed. 3 is '
|
||||
'suggested in general. Default: %(default)s'))
|
||||
|
||||
parser.add_argument('--pmvs-num-cores',
|
||||
metavar='<positive integer>',
|
||||
default=context.num_cores,
|
||||
type=int,
|
||||
help=('The maximum number of cores to use in dense '
|
||||
'reconstruction. Default: %(default)s'))
|
||||
parser.add_argument('--smvs-gamma-srgb',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Apply inverse SRGB gamma correction. To be used '
|
||||
'with --smvs-enable-shading when you have simple JPGs with '
|
||||
'SRGB gamma correction. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--mesh-size',
|
||||
metavar='<positive integer>',
|
||||
|
@ -285,7 +272,7 @@ def config():
|
|||
'Increasing this value increases computation '
|
||||
'times slightly but helps reduce memory usage. '
|
||||
'Default: %(default)s'))
|
||||
|
||||
|
||||
parser.add_argument('--mesh-neighbors',
|
||||
metavar='<positive integer>',
|
||||
default=24,
|
||||
|
@ -568,11 +555,6 @@ def config():
|
|||
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --use-25dmesh')
|
||||
args.use_25dmesh = True
|
||||
|
||||
# Cannot use pmvs
|
||||
if args.use_pmvs:
|
||||
log.ODM_INFO('Fast orthophoto is turned on, cannot use pmvs (removing --use-pmvs)')
|
||||
args.use_pmvs = False
|
||||
|
||||
if args.dtm and args.pc_classify == 'none':
|
||||
log.ODM_INFO("DTM is turned on, automatically turning on point cloud classification")
|
||||
args.pc_classify = "smrf"
|
||||
|
|
|
@ -23,10 +23,9 @@ ccd_widths_path = os.path.join(opensfm_path, 'opensfm/data/sensor_data.json')
|
|||
# define orb_slam2 path
|
||||
orb_slam2_path = os.path.join(superbuild_path, "src/orb_slam2")
|
||||
|
||||
# define pmvs path
|
||||
cmvs_path = os.path.join(superbuild_path, "install/bin/cmvs")
|
||||
cmvs_opts_path = os.path.join(superbuild_path, "install/bin/genOption")
|
||||
pmvs2_path = os.path.join(superbuild_path, "install/bin/pmvs2")
|
||||
# define smvs join_paths
|
||||
makescene_path = os.path.join(superbuild_path, 'src', 'elibs', 'mve', 'apps', 'makescene', 'makescene') #TODO: don't install in source
|
||||
smvs_path = os.path.join(superbuild_path, 'src', 'elibs', 'smvs', 'app', 'smvsrecon')
|
||||
|
||||
# define mvstex path
|
||||
mvstex_path = os.path.join(superbuild_path, "install/bin/texrecon")
|
||||
|
@ -44,5 +43,5 @@ settings_path = os.path.join(root_path, 'settings.yaml')
|
|||
# Define supported image extensions
|
||||
supported_extensions = {'.jpg','.jpeg','.png'}
|
||||
|
||||
# Define the number of cores
|
||||
# Define the number of cores
|
||||
num_cores = multiprocessing.cpu_count()
|
||||
|
|
12
opendm/io.py
12
opendm/io.py
|
@ -34,13 +34,23 @@ def dir_exists(dirname):
|
|||
|
||||
|
||||
def copy(src, dst):
|
||||
try:
|
||||
try:
|
||||
shutil.copytree(src, dst)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOTDIR:
|
||||
shutil.copy(src, dst)
|
||||
else: raise
|
||||
|
||||
def rename_file(src, dst):
|
||||
try:
|
||||
os.rename(src, dst)
|
||||
return True
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
return False
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
# find a file in the root directory
|
||||
def find(filename, folder):
|
||||
|
|
|
@ -8,8 +8,6 @@ from scripts.opensfm import opensfm
|
|||
|
||||
# Define pipeline tasks
|
||||
tasks_dict = {'1': 'opensfm',
|
||||
'2': 'cmvs',
|
||||
'3': 'pmvs',
|
||||
'4': 'odm_meshing',
|
||||
'5': 'mvs_texturing',
|
||||
'6': 'odm_georeferencing',
|
||||
|
@ -52,7 +50,7 @@ class ODMTaskManager(object):
|
|||
'args': _odm_app.args,
|
||||
'photos': _odm_app.photos}
|
||||
|
||||
elif task_name in ['cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto', 'zip_results']:
|
||||
elif task_name in [ 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto', 'zip_results']:
|
||||
# setup this task
|
||||
command = None
|
||||
inputs = {}
|
||||
|
|
|
@ -61,7 +61,7 @@ class ODM_Photo:
|
|||
metadata.read()
|
||||
# loop over image tags
|
||||
for key in metadata:
|
||||
# try/catch tag value due to weird bug in pyexiv2
|
||||
# try/catch tag value due to weird bug in pyexiv2
|
||||
# ValueError: invalid literal for int() with base 10: ''
|
||||
GPS = 'Exif.GPSInfo.GPS'
|
||||
try:
|
||||
|
@ -274,7 +274,7 @@ class ODM_GeoRef(object):
|
|||
with open(json_file, 'w') as f:
|
||||
f.write(pipeline)
|
||||
|
||||
# call pdal
|
||||
# call pdal
|
||||
system.run('{bin}/pdal pipeline -i {json} --readers.ply.filename={f_in}'.format(**kwargs))
|
||||
|
||||
def utm_to_latlon(self, _file, _photo, idx):
|
||||
|
@ -389,7 +389,7 @@ class ODM_GeoRef(object):
|
|||
# Create a nested list for the transformation matrix
|
||||
with open(_file) as f:
|
||||
for line in f:
|
||||
# Handle matrix formats that either
|
||||
# Handle matrix formats that either
|
||||
# have leading or trailing brakets or just plain numbers.
|
||||
line = re.sub(r"[\[\],]", "", line).strip()
|
||||
self.transform += [[float(i) for i in line.split()]]
|
||||
|
@ -414,7 +414,7 @@ class ODM_Tree(object):
|
|||
# whole reconstruction process.
|
||||
self.dataset_raw = io.join_paths(self.root_path, 'images')
|
||||
self.opensfm = io.join_paths(self.root_path, 'opensfm')
|
||||
self.pmvs = io.join_paths(self.root_path, 'pmvs')
|
||||
self.smvs = io.join_paths(self.root_path, 'smvs')
|
||||
self.odm_meshing = io.join_paths(self.root_path, 'odm_meshing')
|
||||
self.odm_texturing = io.join_paths(self.root_path, 'odm_texturing')
|
||||
self.odm_25dtexturing = io.join_paths(self.root_path, 'odm_texturing_25d')
|
||||
|
@ -439,12 +439,12 @@ class ODM_Tree(object):
|
|||
self.opensfm_model = io.join_paths(self.opensfm, 'depthmaps/merged.ply')
|
||||
self.opensfm_transformation = io.join_paths(self.opensfm, 'geocoords_transformation.txt')
|
||||
|
||||
# pmvs
|
||||
self.pmvs_rec_path = io.join_paths(self.pmvs, 'recon0')
|
||||
self.pmvs_bundle = io.join_paths(self.pmvs_rec_path, 'bundle.rd.out')
|
||||
self.pmvs_visdat = io.join_paths(self.pmvs_rec_path, 'vis.dat')
|
||||
self.pmvs_options = io.join_paths(self.pmvs_rec_path, 'pmvs_options.txt')
|
||||
self.pmvs_model = io.join_paths(self.pmvs_rec_path, 'models/option-0000.ply')
|
||||
# smvs
|
||||
self.smvs_model = io.join_paths(self.smvs, 'smvs_dense_point_cloud.ply')
|
||||
self.mve_path = io.join_paths(self.opensfm, 'mve')
|
||||
self.mve_image_list = io.join_paths(self.mve_path, 'list.txt')
|
||||
self.mve_bundle = io.join_paths(self.mve_path, 'bundle/bundle.out')
|
||||
self.mve_views = io.join_paths(self.smvs, 'views')
|
||||
|
||||
# odm_meshing
|
||||
self.odm_mesh = io.join_paths(self.odm_meshing, 'odm_mesh.ply')
|
||||
|
|
2
run.py
2
run.py
|
@ -33,7 +33,7 @@ if __name__ == '__main__':
|
|||
+ args.project_path + "/odm_orthophoto "
|
||||
+ args.project_path + "/odm_texturing "
|
||||
+ args.project_path + "/opensfm "
|
||||
+ args.project_path + "/pmvs")
|
||||
+ args.project_path + "/smvs")
|
||||
|
||||
# create an instance of my App BlackBox
|
||||
# internally configure all tasks
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
import ecto
|
||||
|
||||
from opendm import io
|
||||
from opendm import log
|
||||
from opendm import system
|
||||
from opendm import context
|
||||
|
||||
|
||||
class ODMCmvsCell(ecto.Cell):
|
||||
|
||||
def declare_params(self, params):
|
||||
params.declare("max_images", 'The maximum number of images '
|
||||
'per cluster', 500)
|
||||
params.declare("cores", 'The maximum number of cores to use '
|
||||
'in dense reconstruction.', context.num_cores)
|
||||
|
||||
def declare_io(self, params, inputs, outputs):
|
||||
inputs.declare("tree", "Struct with paths", [])
|
||||
inputs.declare("args", "Struct with paths", [])
|
||||
inputs.declare("reconstruction", "list of ODMReconstructions", [])
|
||||
outputs.declare("reconstruction", "list of ODMReconstructions", [])
|
||||
|
||||
def process(self, inputs, outputs):
|
||||
|
||||
# Benchmarking
|
||||
start_time = system.now_raw()
|
||||
|
||||
log.ODM_INFO('Running ODM CMVS Cell')
|
||||
|
||||
# get inputs
|
||||
args = self.inputs.args
|
||||
tree = self.inputs.tree
|
||||
|
||||
# check if we rerun cell or not
|
||||
rerun_cell = (args.rerun is not None and
|
||||
args.rerun == 'cmvs') or \
|
||||
(args.rerun_all) or \
|
||||
(args.rerun_from is not None and
|
||||
'cmvs' in args.rerun_from)
|
||||
|
||||
if not io.file_exists(tree.pmvs_bundle) or rerun_cell:
|
||||
log.ODM_DEBUG('Writing CMVS vis in: %s' % tree.pmvs_bundle)
|
||||
|
||||
# copy bundle file to pmvs dir
|
||||
from shutil import copyfile
|
||||
copyfile(tree.opensfm_bundle,
|
||||
tree.pmvs_bundle)
|
||||
|
||||
kwargs = {
|
||||
'bin': context.cmvs_path,
|
||||
'prefix': self.inputs.tree.pmvs_rec_path,
|
||||
'max_images': self.params.max_images,
|
||||
'cores': self.params.cores
|
||||
}
|
||||
|
||||
# run cmvs
|
||||
system.run('{bin} {prefix}/ {max_images} {cores}'.format(**kwargs))
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid CMVS file in: %s' %
|
||||
tree.pmvs_bundle)
|
||||
|
||||
outputs.reconstruction = inputs.reconstruction
|
||||
|
||||
if args.time:
|
||||
system.benchmark(start_time, tree.benchmarking, 'CMVS')
|
||||
|
||||
log.ODM_INFO('Running ODM CMVS Cell - Finished')
|
||||
return ecto.OK if args.end_with != 'cmvs' else ecto.QUIT
|
|
@ -5,8 +5,6 @@ from opendm import io
|
|||
from opendm import system
|
||||
from opendm import context
|
||||
|
||||
import pmvs2nvmcams
|
||||
|
||||
class ODMMvsTexCell(ecto.Cell):
|
||||
def declare_params(self, params):
|
||||
params.declare("data_term", 'Data term: [area, gmi] default: gmi', "gmi")
|
||||
|
@ -38,7 +36,7 @@ class ODMMvsTexCell(ecto.Cell):
|
|||
|
||||
# define paths and create working directories
|
||||
system.mkdir_p(tree.odm_texturing)
|
||||
if args.use_25dmesh: system.mkdir_p(tree.odm_25dtexturing)
|
||||
if args.use_25dmesh: system.mkdir_p(tree.odm_25dtexturing)
|
||||
|
||||
# check if we rerun cell or not
|
||||
rerun_cell = (args.rerun is not None and
|
||||
|
@ -62,11 +60,11 @@ class ODMMvsTexCell(ecto.Cell):
|
|||
'model': tree.odm_25dmesh,
|
||||
|
||||
# We always skip the visibility test when using the 2.5D mesh
|
||||
# because many faces end up being narrow, and almost perpendicular
|
||||
# because many faces end up being narrow, and almost perpendicular
|
||||
# to the ground plane. The visibility test improperly classifies
|
||||
# them as "not seen" since the test is done on a single triangle vertex,
|
||||
# and while one vertex might be occluded, the other two might not.
|
||||
'force_skip_vis_test': True
|
||||
'force_skip_vis_test': True
|
||||
}]
|
||||
|
||||
for r in runs:
|
||||
|
@ -82,7 +80,7 @@ class ODMMvsTexCell(ecto.Cell):
|
|||
skipLocalSeamLeveling = ""
|
||||
skipHoleFilling = ""
|
||||
keepUnseenFaces = ""
|
||||
|
||||
|
||||
if (self.params.skip_vis_test or r['force_skip_vis_test']):
|
||||
skipGeometricVisibilityTest = "--skip_geometric_visibility_test"
|
||||
if (self.params.skip_glob_seam_leveling):
|
||||
|
@ -98,8 +96,6 @@ class ODMMvsTexCell(ecto.Cell):
|
|||
kwargs = {
|
||||
'bin': context.mvstex_path,
|
||||
'out_dir': io.join_paths(r['out_dir'], "odm_textured_model"),
|
||||
'pmvs_folder': tree.pmvs_rec_path,
|
||||
'nvm_file': io.join_paths(tree.pmvs_rec_path, "nvmCams.nvm"),
|
||||
'model': r['model'],
|
||||
'dataTerm': self.params.data_term,
|
||||
'outlierRemovalType': self.params.outlier_rem_type,
|
||||
|
@ -111,16 +107,11 @@ class ODMMvsTexCell(ecto.Cell):
|
|||
'toneMapping': self.params.tone_mapping
|
||||
}
|
||||
|
||||
if not args.use_pmvs:
|
||||
if args.use_opensfm_dense:
|
||||
kwargs['nvm_file'] = io.join_paths(tree.opensfm,
|
||||
"reconstruction.nvm")
|
||||
else:
|
||||
log.ODM_DEBUG('Generating .nvm file from pmvs output: %s'
|
||||
% '{nvm_file}'.format(**kwargs))
|
||||
|
||||
# Create .nvm camera file.
|
||||
pmvs2nvmcams.run('{pmvs_folder}'.format(**kwargs),
|
||||
'{nvm_file}'.format(**kwargs))
|
||||
kwargs['nvm_file'] = tree.smvs + "::undistorted"
|
||||
|
||||
# Make sure tmp directory is empty
|
||||
mvs_tmp_dir = os.path.join(r['out_dir'], 'tmp')
|
||||
|
|
|
@ -8,9 +8,8 @@ from opendm import system
|
|||
|
||||
from dataset import ODMLoadDatasetCell
|
||||
from run_opensfm import ODMOpenSfMCell
|
||||
from smvs import ODMSmvsCell
|
||||
from odm_slam import ODMSlamCell
|
||||
from pmvs import ODMPmvsCell
|
||||
from cmvs import ODMCmvsCell
|
||||
from odm_meshing import ODMeshingCell
|
||||
from mvstex import ODMMvsTexCell
|
||||
from odm_georeferencing import ODMGeoreferencingCell
|
||||
|
@ -44,19 +43,19 @@ class ODMApp(ecto.BlackBox):
|
|||
'opensfm': ODMOpenSfMCell(use_exif_size=False,
|
||||
feature_process_size=p.args.resize_to,
|
||||
feature_min_frames=p.args.min_num_features,
|
||||
processes=p.args.opensfm_processes,
|
||||
processes=p.args.max_concurrency,
|
||||
matching_gps_neighbors=p.args.matcher_neighbors,
|
||||
matching_gps_distance=p.args.matcher_distance,
|
||||
fixed_camera_params=p.args.use_fixed_camera_params,
|
||||
hybrid_bundle_adjustment=p.args.use_hybrid_bundle_adjustment),
|
||||
'slam': ODMSlamCell(),
|
||||
'cmvs': ODMCmvsCell(max_images=p.args.cmvs_maxImages),
|
||||
'pmvs': ODMPmvsCell(level=p.args.pmvs_level,
|
||||
csize=p.args.pmvs_csize,
|
||||
thresh=p.args.pmvs_threshold,
|
||||
wsize=p.args.pmvs_wsize,
|
||||
min_imgs=p.args.pmvs_min_images,
|
||||
cores=p.args.pmvs_num_cores),
|
||||
'smvs': ODMSmvsCell(alpha=p.args.smvs_alpha,
|
||||
scale=p.args.smvs_scale,
|
||||
threads=p.args.max_concurrency,
|
||||
output_scale=p.args.smvs_output_scale,
|
||||
shading=p.args.smvs_enable_shading,
|
||||
gamma_srgb=p.args.smvs_gamma_srgb,
|
||||
verbose=p.args.verbose),
|
||||
'meshing': ODMeshingCell(max_vertex=p.args.mesh_size,
|
||||
oct_tree=p.args.mesh_octree_depth,
|
||||
samples=p.args.mesh_samples,
|
||||
|
@ -73,13 +72,15 @@ class ODMApp(ecto.BlackBox):
|
|||
'georeferencing': ODMGeoreferencingCell(gcp_file=p.args.gcp,
|
||||
use_exif=p.args.use_exif,
|
||||
verbose=p.args.verbose),
|
||||
'dem': ODMDEMCell(verbose=p.args.verbose),
|
||||
'dem': ODMDEMCell(max_concurrency=p.args.max_concurrency,
|
||||
verbose=p.args.verbose),
|
||||
'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution,
|
||||
t_srs=p.args.orthophoto_target_srs,
|
||||
no_tiled=p.args.orthophoto_no_tiled,
|
||||
compress=p.args.orthophoto_compression,
|
||||
bigtiff=p.args.orthophoto_bigtiff,
|
||||
build_overviews=p.args.build_overviews,
|
||||
max_concurrency=p.args.max_concurrency,
|
||||
verbose=p.args.verbose)
|
||||
}
|
||||
|
||||
|
@ -116,26 +117,21 @@ class ODMApp(ecto.BlackBox):
|
|||
self.args[:] >> self.opensfm['args'],
|
||||
self.dataset['reconstruction'] >> self.opensfm['reconstruction']]
|
||||
|
||||
if not p.args.use_pmvs:
|
||||
if p.args.use_opensfm_dense:
|
||||
# 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 cmvs
|
||||
connections += [self.tree[:] >> self.cmvs['tree'],
|
||||
self.args[:] >> self.cmvs['args'],
|
||||
self.opensfm['reconstruction'] >> self.cmvs['reconstruction']]
|
||||
# run smvs
|
||||
connections += [self.tree[:] >> self.smvs['tree'],
|
||||
self.args[:] >> self.smvs['args'],
|
||||
self.opensfm['reconstruction'] >> self.smvs['reconstruction']]
|
||||
|
||||
# run pmvs
|
||||
connections += [self.tree[:] >> self.pmvs['tree'],
|
||||
self.args[:] >> self.pmvs['args'],
|
||||
self.cmvs['reconstruction'] >> self.pmvs['reconstruction']]
|
||||
|
||||
# create odm mesh from pmvs point cloud
|
||||
# create odm mesh from smvs point cloud
|
||||
connections += [self.tree[:] >> self.meshing['tree'],
|
||||
self.args[:] >> self.meshing['args'],
|
||||
self.pmvs['reconstruction'] >> self.meshing['reconstruction']]
|
||||
self.smvs['reconstruction'] >> self.meshing['reconstruction']]
|
||||
|
||||
# create odm texture
|
||||
connections += [self.tree[:] >> self.texturing['tree'],
|
||||
|
@ -166,20 +162,14 @@ class ODMApp(ecto.BlackBox):
|
|||
connections += [self.tree[:] >> self.slam['tree'],
|
||||
self.args[:] >> self.slam['args']]
|
||||
|
||||
# run cmvs
|
||||
connections += [self.tree[:] >> self.cmvs['tree'],
|
||||
self.args[:] >> self.cmvs['args'],
|
||||
self.slam['reconstruction'] >> self.cmvs['reconstruction']]
|
||||
|
||||
# run pmvs
|
||||
connections += [self.tree[:] >> self.pmvs['tree'],
|
||||
self.args[:] >> self.pmvs['args'],
|
||||
self.cmvs['reconstruction'] >> self.pmvs['reconstruction']]
|
||||
connections += [self.tree[:] >> self.smvs['tree'],
|
||||
self.args[:] >> self.smvs['args'],
|
||||
self.slam['reconstruction'] >> self.smvs['reconstruction']]
|
||||
|
||||
# create odm mesh
|
||||
connections += [self.tree[:] >> self.meshing['tree'],
|
||||
self.args[:] >> self.meshing['args'],
|
||||
self.pmvs['reconstruction'] >> self.meshing['reconstruction']]
|
||||
self.smvs['reconstruction'] >> self.meshing['reconstruction']]
|
||||
|
||||
# create odm texture
|
||||
connections += [self.tree[:] >> self.texturing['tree'],
|
||||
|
|
|
@ -13,6 +13,7 @@ from opendm.cropper import Cropper
|
|||
class ODMDEMCell(ecto.Cell):
|
||||
def declare_params(self, params):
|
||||
params.declare("verbose", 'print additional messages to console', False)
|
||||
params.declare("max_concurrency", "Number of threads", context.num_cores)
|
||||
|
||||
def declare_io(self, params, inputs, outputs):
|
||||
inputs.declare("tree", "Struct with paths", [])
|
||||
|
@ -44,12 +45,12 @@ class ODMDEMCell(ecto.Cell):
|
|||
|
||||
# Setup terrain parameters
|
||||
terrain_params_map = {
|
||||
'flatnonforest': (1, 3),
|
||||
'flatforest': (1, 2),
|
||||
'complexnonforest': (5, 2),
|
||||
'flatnonforest': (1, 3),
|
||||
'flatforest': (1, 2),
|
||||
'complexnonforest': (5, 2),
|
||||
'complexforest': (10, 2)
|
||||
}
|
||||
terrain_params = terrain_params_map[args.dem_terrain_type.lower()]
|
||||
terrain_params = terrain_params_map[args.dem_terrain_type.lower()]
|
||||
slope, cellsize = terrain_params
|
||||
|
||||
# define paths and create working directories
|
||||
|
@ -62,7 +63,7 @@ class ODMDEMCell(ecto.Cell):
|
|||
|
||||
if not io.file_exists(pc_classify_marker) or rerun_cell:
|
||||
log.ODM_INFO("Classifying {} using {}".format(tree.odm_georeferencing_model_laz, args.pc_classify))
|
||||
commands.classify(tree.odm_georeferencing_model_laz,
|
||||
commands.classify(tree.odm_georeferencing_model_laz,
|
||||
args.pc_classify == "smrf",
|
||||
slope,
|
||||
cellsize,
|
||||
|
@ -70,7 +71,7 @@ class ODMDEMCell(ecto.Cell):
|
|||
initialDistance=args.dem_initial_distance,
|
||||
verbose=args.verbose
|
||||
)
|
||||
with open(pc_classify_marker, 'w') as f:
|
||||
with open(pc_classify_marker, 'w') as f:
|
||||
f.write('Classify: {}\n'.format(args.pc_classify))
|
||||
f.write('Slope: {}\n'.format(slope))
|
||||
f.write('Cellsize: {}\n'.format(cellsize))
|
||||
|
@ -87,7 +88,7 @@ class ODMDEMCell(ecto.Cell):
|
|||
rerun_cell:
|
||||
|
||||
products = []
|
||||
if args.dsm: products.append('dsm')
|
||||
if args.dsm: products.append('dsm')
|
||||
if args.dtm: products.append('dtm')
|
||||
|
||||
radius_steps = [args.dem_resolution]
|
||||
|
@ -96,7 +97,7 @@ class ODMDEMCell(ecto.Cell):
|
|||
|
||||
for product in products:
|
||||
commands.create_dems(
|
||||
[tree.odm_georeferencing_model_laz],
|
||||
[tree.odm_georeferencing_model_laz],
|
||||
product,
|
||||
radius=map(str, radius_steps),
|
||||
gapfill=True,
|
||||
|
@ -116,7 +117,7 @@ class ODMDEMCell(ecto.Cell):
|
|||
'COMPRESS': 'LZW',
|
||||
'BLOCKXSIZE': 512,
|
||||
'BLOCKYSIZE': 512,
|
||||
'NUM_THREADS': 'ALL_CPUS'
|
||||
'NUM_THREADS': self.params.max_concurrency
|
||||
})
|
||||
else:
|
||||
log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root)
|
||||
|
|
|
@ -92,13 +92,13 @@ class ODMGeoreferencingCell(ecto.Cell):
|
|||
'verbose': verbose
|
||||
|
||||
}
|
||||
if not args.use_pmvs:
|
||||
if args.fast_orthophoto:
|
||||
kwargs['pc'] = os.path.join(tree.opensfm, 'reconstruction.ply')
|
||||
else:
|
||||
kwargs['pc'] = tree.opensfm_model
|
||||
|
||||
if args.fast_orthophoto:
|
||||
kwargs['pc'] = os.path.join(tree.opensfm, 'reconstruction.ply')
|
||||
elif args.use_opensfm_dense:
|
||||
kwargs['pc'] = tree.opensfm_model
|
||||
else:
|
||||
kwargs['pc'] = tree.pmvs_model
|
||||
kwargs['pc'] = tree.smvs_model
|
||||
|
||||
# Check to see if the GCP file exists
|
||||
|
||||
|
|
|
@ -50,9 +50,9 @@ class ODMeshingCell(ecto.Cell):
|
|||
(args.rerun_from is not None and
|
||||
'odm_meshing' in args.rerun_from)
|
||||
|
||||
infile = tree.opensfm_model
|
||||
if args.use_pmvs:
|
||||
infile = tree.pmvs_model
|
||||
infile = tree.smvs_model
|
||||
if args.use_opensfm_dense:
|
||||
infile = tree.opensfm_model
|
||||
elif args.fast_orthophoto:
|
||||
infile = os.path.join(tree.opensfm, 'reconstruction.ply')
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
|
|||
params.declare("bigtiff", 'Make BigTIFF orthophoto', 'IF_SAFER')
|
||||
params.declare("build_overviews", 'Build overviews', False)
|
||||
params.declare("verbose", 'print additional messages to console', False)
|
||||
params.declare("max_concurrency", "number of threads", context.num_cores)
|
||||
|
||||
def declare_io(self, params, inputs, outputs):
|
||||
inputs.declare("tree", "Struct with paths", [])
|
||||
|
@ -125,7 +126,8 @@ class ODMOrthoPhotoCell(ecto.Cell):
|
|||
'png': tree.odm_orthophoto_file,
|
||||
'tiff': tree.odm_orthophoto_tif,
|
||||
'log': tree.odm_orthophoto_tif_log,
|
||||
'max_memory': max(5, (100 - virtual_memory().percent) / 2)
|
||||
'max_memory': max(5, (100 - virtual_memory().percent) / 2),
|
||||
'threads': self.params.max_concurrency
|
||||
}
|
||||
|
||||
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
|
||||
|
@ -135,7 +137,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
|
|||
'{predictor} '
|
||||
'-co BLOCKXSIZE=512 '
|
||||
'-co BLOCKYSIZE=512 '
|
||||
'-co NUM_THREADS=ALL_CPUS '
|
||||
'-co NUM_THREADS={threads} '
|
||||
'-a_srs \"{proj}\" '
|
||||
'--config GDAL_CACHEMAX {max_memory}% '
|
||||
'{png} {tiff} > {log}'.format(**kwargs))
|
||||
|
@ -149,7 +151,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
|
|||
'BIGTIFF': self.params.bigtiff,
|
||||
'BLOCKXSIZE': 512,
|
||||
'BLOCKYSIZE': 512,
|
||||
'NUM_THREADS': 'ALL_CPUS'
|
||||
'NUM_THREADS': self.params.max_concurrency
|
||||
})
|
||||
|
||||
if self.params.build_overviews:
|
||||
|
|
|
@ -39,7 +39,6 @@ class ODMSlamCell(ecto.Cell):
|
|||
|
||||
# create working directories
|
||||
system.mkdir_p(tree.opensfm)
|
||||
system.mkdir_p(tree.pmvs)
|
||||
|
||||
vocabulary = os.path.join(context.orb_slam2_path,
|
||||
'Vocabulary/ORBvoc.txt')
|
||||
|
@ -96,16 +95,5 @@ class ODMSlamCell(ecto.Cell):
|
|||
'Found a valid Bundler file in: {}'.format(
|
||||
tree.opensfm_reconstruction))
|
||||
|
||||
# check if reconstruction was exported to pmvs before
|
||||
if not io.file_exists(tree.pmvs_visdat) or rerun_cell:
|
||||
# run PMVS converter
|
||||
system.run(
|
||||
'PYTHONPATH={} {}/bin/export_pmvs {} --output {}'.format(
|
||||
context.pyopencv_path, context.opensfm_path, tree.opensfm,
|
||||
tree.pmvs))
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid CMVS file in: {}'.format(
|
||||
tree.pmvs_visdat))
|
||||
|
||||
log.ODM_INFO('Running OMD Slam Cell - Finished')
|
||||
return ecto.OK if args.end_with != 'odm_slam' else ecto.QUIT
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
import ecto
|
||||
|
||||
from opendm import io
|
||||
from opendm import log
|
||||
from opendm import system
|
||||
from opendm import context
|
||||
|
||||
|
||||
class ODMPmvsCell(ecto.Cell):
|
||||
def declare_params(self, params):
|
||||
params.declare("level", 'The level in the image pyramid that is used '
|
||||
'for the computation', 1)
|
||||
params.declare("csize", 'Cell size controls the density of reconstructions', 2)
|
||||
params.declare("thresh", 'A patch reconstruction is accepted as a success '
|
||||
'and kept, if its associcated photometric consistency '
|
||||
'measure is above this threshold.', 0.7)
|
||||
params.declare("wsize", 'pmvs samples wsize x wsize pixel colors from '
|
||||
'each image to compute photometric consistency '
|
||||
'score. For example, when wsize=7, 7x7=49 pixel '
|
||||
'colors are sampled in each image. Increasing the '
|
||||
'value leads to more stable reconstructions, but '
|
||||
'the program becomes slower.', 7)
|
||||
params.declare("min_imgs", 'Each 3D point must be visible in at least '
|
||||
'minImageNum images for being reconstructed. 3 is '
|
||||
'suggested in general.', 3)
|
||||
params.declare("cores", 'The maximum number of cores to use in dense '
|
||||
' reconstruction.', context.num_cores)
|
||||
|
||||
def declare_io(self, params, inputs, outputs):
|
||||
inputs.declare("tree", "Struct with paths", [])
|
||||
inputs.declare("args", "The application arguments.", {})
|
||||
inputs.declare("reconstruction", "list of ODMReconstructions", [])
|
||||
outputs.declare("reconstruction", "list of ODMReconstructions", [])
|
||||
|
||||
def process(self, inputs, outputs):
|
||||
|
||||
# Benchmarking
|
||||
start_time = system.now_raw()
|
||||
|
||||
log.ODM_INFO('Running OMD PMVS Cell')
|
||||
|
||||
# get inputs
|
||||
args = self.inputs.args
|
||||
tree = self.inputs.tree
|
||||
|
||||
# check if we rerun cell or not
|
||||
rerun_cell = (args.rerun is not None and
|
||||
args.rerun == 'pmvs') or \
|
||||
(args.rerun_all) or \
|
||||
(args.rerun_from is not None and
|
||||
'pmvs' in args.rerun_from)
|
||||
|
||||
if not io.file_exists(tree.pmvs_model) or rerun_cell:
|
||||
log.ODM_DEBUG('Creating dense pointcloud in: %s' % tree.pmvs_model)
|
||||
|
||||
kwargs = {
|
||||
'bin': context.cmvs_opts_path,
|
||||
'prefix': tree.pmvs_rec_path,
|
||||
'level': self.params.level,
|
||||
'csize': self.params.csize,
|
||||
'thresh': self.params.thresh,
|
||||
'wsize': self.params.wsize,
|
||||
'min_imgs': self.params.min_imgs,
|
||||
'cores': self.params.cores
|
||||
}
|
||||
|
||||
# generate pmvs2 options
|
||||
system.run('{bin} {prefix}/ {level} {csize} {thresh} {wsize} '
|
||||
'{min_imgs} {cores}'.format(**kwargs))
|
||||
|
||||
# run pmvs2
|
||||
system.run('%s %s/ option-0000' %
|
||||
(context.pmvs2_path, tree.pmvs_rec_path))
|
||||
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid PMVS file in %s' % tree.pmvs_model)
|
||||
|
||||
outputs.reconstruction = inputs.reconstruction
|
||||
|
||||
if args.time:
|
||||
system.benchmark(start_time, tree.benchmarking, 'PMVS')
|
||||
|
||||
log.ODM_INFO('Running ODM PMVS Cell - Finished')
|
||||
return ecto.OK if args.end_with != 'pmvs' else ecto.QUIT
|
|
@ -1,144 +0,0 @@
|
|||
import os
|
||||
import numpy as np
|
||||
|
||||
from opendm import log
|
||||
|
||||
# Go from QR-factorizatoin to corresponding RQ-factorization.
|
||||
def rq(A):
|
||||
Q,R = np.linalg.qr(np.flipud(A).T)
|
||||
R = np.flipud(R.T)
|
||||
Q = Q.T
|
||||
return R[:,::-1],Q[::-1,:]
|
||||
|
||||
# Create a unit quaternion from rotation matrix.
|
||||
def rot2quat(R):
|
||||
|
||||
# Float epsilon (use square root to be well with the stable region).
|
||||
eps = np.sqrt(np.finfo(float).eps)
|
||||
|
||||
# If the determinant is not 1, it's not a rotation matrix
|
||||
if np.abs(np.linalg.det(R) - 1.0) > eps:
|
||||
log.ODM_ERROR('Matrix passed to rot2quat was not a rotation matrix, det != 1.0')
|
||||
|
||||
tr = np.trace(R)
|
||||
|
||||
quat = np.zeros((1,4))
|
||||
|
||||
# Is trace big enough be computationally stable?
|
||||
if tr > eps:
|
||||
S = 0.5 / np.sqrt(tr + 1.0)
|
||||
quat[0,0] = 0.25 / S
|
||||
quat[0,1] = (R[2,1] - R[1,2]) * S
|
||||
quat[0,2] = (R[0,2] - R[2,0]) * S
|
||||
quat[0,3] = (R[1,0] - R[0,1]) * S
|
||||
else: # It's not, use the largest diagonal.
|
||||
if R[0,0] > R[1,1] and R[0,0] > R[2,2]:
|
||||
S = np.sqrt(1.0 + R[0,0] - R[1,1] - R[2,2]) * 2.0
|
||||
quat[0,0] = (R[2,1] - R[1,2]) / S
|
||||
quat[0,1] = 0.25 * S
|
||||
quat[0,2] = (R[0,1] + R[1,0]) / S
|
||||
quat[0,3] = (R[0,2] + R[2,0]) / S
|
||||
elif R[1,1] > R[2,2]:
|
||||
S = np.sqrt(1.0 - R[0,0] + R[1,1] - R[2,2]) * 2.0
|
||||
quat[0,0] = (R[0,2] - R[2,0]) / S
|
||||
quat[0,1] = (R[0,1] + R[1,0]) / S
|
||||
quat[0,2] = 0.25 * S
|
||||
quat[0,3] = (R[1,2] + R[2,1]) / S
|
||||
else:
|
||||
S = np.sqrt(1.0 - R[0,0] - R[1,1] + R[2,2]) * 2.0
|
||||
quat[0,0] = (R[1,0] - R[0,1]) / S
|
||||
quat[0,1] = (R[0,2] + R[2,0]) / S
|
||||
quat[0,2] = (R[1,2] + R[2,1]) / S
|
||||
quat[0,3] = 0.25 * S
|
||||
|
||||
return quat
|
||||
|
||||
# Decompose a projection matrix into parts
|
||||
# (Intrinsic projection, Rotation, Camera position)
|
||||
def decomposeProjection(projectionMatrix):
|
||||
|
||||
# Check input:
|
||||
if projectionMatrix.shape != (3,4):
|
||||
log.ODM_ERROR('Unable to decompose projection matrix, shape != (3,4)')
|
||||
|
||||
RQ = rq(projectionMatrix[:,:3])
|
||||
|
||||
# Fix sign, since we know K is upper triangular and has a positive diagonal.
|
||||
signMat = np.diag(np.diag(np.sign(RQ[0])))
|
||||
K = signMat*RQ[0]
|
||||
R = signMat*RQ[1]
|
||||
|
||||
# Calculate camera position from translation vector.
|
||||
t = np.linalg.inv(-1.0*projectionMatrix[:,:3])*projectionMatrix[:,3]
|
||||
|
||||
return K, R, t
|
||||
|
||||
# Parses pvms contour file.
|
||||
def parseContourFile(filePath):
|
||||
|
||||
with open(filePath, 'r') as contourFile:
|
||||
if (contourFile.readline().strip() != "CONTOUR"):
|
||||
return np.array([])
|
||||
else:
|
||||
pMatData = np.loadtxt(contourFile, float, '#', None, None, 0)
|
||||
if pMatData.shape == (3,4):
|
||||
return pMatData
|
||||
return np.array([])
|
||||
|
||||
|
||||
|
||||
# Creates a .nvm camera file in the pmvs folder.
|
||||
def run(pmvsFolder, outputFile):
|
||||
|
||||
projectionFolder = pmvsFolder + "/txt"
|
||||
imageFolder = pmvsFolder + "/visualize"
|
||||
|
||||
pMatrices = []
|
||||
imageFileNames = []
|
||||
|
||||
# for all files in the visualize folder:
|
||||
for imageFileName in os.listdir(imageFolder):
|
||||
fileNameNoExt = os.path.splitext(imageFileName)[0]
|
||||
|
||||
# look for corresponding projection matrix txt file
|
||||
projectionFilePath = os.path.join(projectionFolder, fileNameNoExt)
|
||||
projectionFilePath += ".txt"
|
||||
if os.path.isfile(projectionFilePath):
|
||||
pMatData = parseContourFile(projectionFilePath)
|
||||
if pMatData.size == 0:
|
||||
log.ODM_WARNING('Unable to parse contour file, skipping: %s'
|
||||
% projectionFilePath)
|
||||
else:
|
||||
pMatrices.append(np.matrix(pMatData))
|
||||
imageFileNames.append(imageFileName)
|
||||
|
||||
|
||||
# Decompose projection matrices
|
||||
focals = []
|
||||
rotations = []
|
||||
translations = []
|
||||
for projection in pMatrices:
|
||||
KRt = decomposeProjection(projection)
|
||||
focals.append(KRt[0][0,0])
|
||||
rotations.append(rot2quat(KRt[1]))
|
||||
translations.append(KRt[2])
|
||||
|
||||
# Create .nvm file
|
||||
with open (outputFile, 'w') as nvmFile:
|
||||
nvmFile.write("NVM_V3\n\n")
|
||||
nvmFile.write('%d' % len(rotations) + "\n")
|
||||
|
||||
for idx, imageFileName in enumerate(imageFileNames):
|
||||
nvmFile.write(os.path.join("visualize", imageFileName))
|
||||
nvmFile.write(" " + '%f' % focals[idx])
|
||||
nvmFile.write(" " + '%f' % rotations[idx][0,0] +
|
||||
" " + '%f' % rotations[idx][0,1] +
|
||||
" " + '%f' % rotations[idx][0,2] +
|
||||
" " + '%f' % rotations[idx][0,3])
|
||||
nvmFile.write(" " + '%f' % translations[idx][0] +
|
||||
" " + '%f' % translations[idx][1] +
|
||||
" " + '%f' % translations[idx][2])
|
||||
nvmFile.write(" 0 0\n")
|
||||
nvmFile.write("0\n\n")
|
||||
nvmFile.write("0\n\n")
|
||||
nvmFile.write("0")
|
|
@ -40,9 +40,8 @@ class ODMOpenSfMCell(ecto.Cell):
|
|||
log.ODM_ERROR('Not enough photos in photos array to start OpenSfM')
|
||||
return ecto.QUIT
|
||||
|
||||
# create working directories
|
||||
# create working directories
|
||||
system.mkdir_p(tree.opensfm)
|
||||
system.mkdir_p(tree.pmvs)
|
||||
|
||||
# check if we rerun cell or not
|
||||
rerun_cell = (args.rerun is not None and
|
||||
|
@ -51,7 +50,7 @@ class ODMOpenSfMCell(ecto.Cell):
|
|||
(args.rerun_from is not None and
|
||||
'opensfm' in args.rerun_from)
|
||||
|
||||
if not args.use_pmvs:
|
||||
if args.use_opensfm_dense:
|
||||
output_file = tree.opensfm_model
|
||||
if args.fast_orthophoto:
|
||||
output_file = io.join_paths(tree.opensfm, 'reconstruction.ply')
|
||||
|
@ -136,7 +135,7 @@ class ODMOpenSfMCell(ecto.Cell):
|
|||
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
|
||||
tree.opensfm_reconstruction)
|
||||
|
||||
if not args.use_pmvs:
|
||||
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))
|
||||
|
@ -146,7 +145,7 @@ class ODMOpenSfMCell(ecto.Cell):
|
|||
|
||||
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:
|
||||
|
@ -169,15 +168,6 @@ class ODMOpenSfMCell(ecto.Cell):
|
|||
log.ODM_WARNING('Found a valid Bundler file in: %s' %
|
||||
tree.opensfm_reconstruction)
|
||||
|
||||
if args.use_pmvs:
|
||||
# check if reconstruction was exported to pmvs before
|
||||
if not io.file_exists(tree.pmvs_visdat) or rerun_cell:
|
||||
# run PMVS converter
|
||||
system.run('PYTHONPATH=%s %s/bin/export_pmvs %s --output %s' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm, tree.pmvs))
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid CMVS file in: %s' % tree.pmvs_visdat)
|
||||
|
||||
if reconstruction.georef:
|
||||
system.run('PYTHONPATH=%s %s/bin/opensfm export_geocoords %s --transformation --proj \'%s\'' %
|
||||
(context.pyopencv_path, context.opensfm_path, tree.opensfm, reconstruction.georef.projection.srs))
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import ecto
|
||||
|
||||
from opendm import log
|
||||
from opendm import io
|
||||
from opendm import system
|
||||
from opendm import context
|
||||
|
||||
|
||||
class ODMSmvsCell(ecto.Cell):
|
||||
def declare_params(self, params):
|
||||
params.declare("threads", "max number of threads", context.num_cores)
|
||||
params.declare("alpha", "Regularization parameter", 1)
|
||||
params.declare("scale", "input scale", 1)
|
||||
params.declare("output_scale", "scale of optimization", 2)
|
||||
params.declare("shading", "Enable shading-aware model", False)
|
||||
params.declare("gamma_srgb", "Apply inverse SRGB gamma correction", False)
|
||||
params.declare("verbose", "Increase debug level", False)
|
||||
|
||||
def declare_io(self, params, inputs, outputs):
|
||||
inputs.declare("tree", "Struct with paths", [])
|
||||
inputs.declare("args", "The application arguments.", {})
|
||||
inputs.declare("reconstruction", "ODMReconstruction", [])
|
||||
outputs.declare("reconstruction", "list of ODMReconstructions", [])
|
||||
|
||||
def process(self, inputs, outputs):
|
||||
|
||||
# Benchmarking
|
||||
start_time = system.now_raw()
|
||||
|
||||
log.ODM_INFO('Running SMVS Cell')
|
||||
|
||||
# get inputs
|
||||
tree = inputs.tree
|
||||
args = inputs.args
|
||||
reconstruction = inputs.reconstruction
|
||||
photos = reconstruction.photos
|
||||
|
||||
if not photos:
|
||||
log.ODM_ERROR('Not enough photos in photos array to start SMVS')
|
||||
return ecto.QUIT
|
||||
|
||||
system.mkdir_p(tree.smvs)
|
||||
|
||||
# check if we rerun cell or not
|
||||
rerun_cell = (args.rerun is not None and
|
||||
args.rerun == 'smvs') or \
|
||||
(args.rerun_all) or \
|
||||
(args.rerun_from is not None and
|
||||
'smvs' in args.rerun_from)
|
||||
|
||||
# check if reconstruction was done before
|
||||
if not io.file_exists(tree.smvs_model) or rerun_cell:
|
||||
|
||||
# make bundle directory
|
||||
if not io.file_exists(tree.mve_bundle):
|
||||
system.mkdir_p(tree.mve_path) #TODO: check permissions/what happens when rerun
|
||||
system.mkdir_p(io.join_paths(tree.mve_path, 'bundle'))
|
||||
io.copy(tree.opensfm_image_list, tree.mve_image_list)
|
||||
io.copy(tree.opensfm_bundle, tree.mve_bundle)
|
||||
|
||||
# run mve makescene
|
||||
if not io.dir_exists(tree.mve_views):
|
||||
system.run('%s %s %s' % (context.makescene_path, tree.mve_path, tree.smvs))
|
||||
|
||||
# config
|
||||
config = [
|
||||
"-t%s" % self.params.threads,
|
||||
"-a%s" % self.params.alpha,
|
||||
"-s%s" % self.params.scale,
|
||||
"-o%s" % self.params.output_scale,
|
||||
"--debug-lvl=%s" % ('1' if self.params.verbose else '0'),
|
||||
"%s" % '-S' if self.params.shading else '',
|
||||
"%s" % '-g' if self.params.gamma_srgb and self.params.shading else '',
|
||||
"--force" if rerun_cell else ''
|
||||
]
|
||||
|
||||
# run smvs
|
||||
system.run('%s %s %s' % (context.smvs_path, ' '.join(config), tree.smvs))
|
||||
# rename the file for simplicity
|
||||
old_file = io.join_paths(tree.smvs, 'smvs-%s%s.ply' %
|
||||
('S' if self.params.shading else 'B', self.params.scale))
|
||||
if not (io.rename_file(old_file, tree.smvs_model)):
|
||||
log.ODM_WARNING("File %s does not exist, cannot be renamed. " % old_file)
|
||||
|
||||
|
||||
else:
|
||||
log.ODM_WARNING('Found a valid SMVS reconstruction file in: %s' %
|
||||
tree.smvs_model)
|
||||
|
||||
outputs.reconstruction = reconstruction
|
||||
|
||||
if args.time:
|
||||
system.benchmark(start_time, tree.benchmarking, 'SMVS')
|
||||
|
||||
log.ODM_INFO('Running ODM SMVS Cell - Finished')
|
||||
return ecto.OK if args.end_with != 'smvs' else ecto.QUIT
|
Ładowanie…
Reference in New Issue