diff --git a/.dockerignore b/.dockerignore index e82fb5df..e1c69c15 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,12 +6,9 @@ SuperBuild/install SuperBuild/src build opensfm -pmvs odm_orthophoto odm_texturing odm_meshing odm_georeferencing images_resize .git - - diff --git a/README.md b/README.md index 7495cc03..28bbabd9 100644 --- a/README.md +++ b/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 diff --git a/SuperBuild/CMakeLists.txt b/SuperBuild/CMakeLists.txt index 51187869..18c4ea7b 100644 --- a/SuperBuild/CMakeLists.txt +++ b/SuperBuild/CMakeLists.txt @@ -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 diff --git a/SuperBuild/cmake/External-CMVS.cmake b/SuperBuild/cmake/External-CMVS.cmake deleted file mode 100644 index a58beb5f..00000000 --- a/SuperBuild/cmake/External-CMVS.cmake +++ /dev/null @@ -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 /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 -) - diff --git a/licenses/license.md b/licenses/license.md index 7cd375fd..8648350f 100644 --- a/licenses/license.md +++ b/licenses/license.md @@ -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 diff --git a/opendm/config.py b/opendm/config.py index ede85923..dec035c4 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -7,7 +7,7 @@ from appsettings import SettingsParser import sys # parse arguments -processopts = ['dataset', 'opensfm', 'slam', 'cmvs', 'pmvs', 'smvs', +processopts = ['dataset', 'opensfm', 'slam', 'smvs', 'odm_meshing', 'odm_25dmeshing', 'mvs_texturing', 'odm_georeferencing', 'odm_dem', 'odm_orthophoto'] @@ -241,63 +241,6 @@ def config(): 'with --smvs-enable-shading when you have simple JPGs with ' 'SRGB gamma correction. Default: %(default)s') - parser.add_argument('--cmvs-maxImages', - metavar='', - default=500, - type=int, - help='The maximum number of images per cluster. ' - 'Default: %(default)s') - - parser.add_argument('--pmvs-level', - metavar='', - 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')) - - parser.add_argument('--pmvs-csize', - metavar='', - default=2, - type=int, - help='Cell size controls the density of reconstructions' - 'Default: %(default)s') - - parser.add_argument('--pmvs-threshold', - metavar='', - 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('--pmvs-wsize', - metavar='', - 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='', - 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='', - default=context.num_cores, - type=int, - help=('The maximum number of cores to use in dense ' - 'reconstruction. Default: %(default)s')) - parser.add_argument('--mesh-size', metavar='', default=100000, @@ -612,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" diff --git a/opendm/context.py b/opendm/context.py index 49365ee4..9f7756b0 100644 --- a/opendm/context.py +++ b/opendm/context.py @@ -23,11 +23,6 @@ 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') diff --git a/opendm/tasks.py b/opendm/tasks.py index 91135ce7..6859db26 100644 --- a/opendm/tasks.py +++ b/opendm/tasks.py @@ -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 = {} diff --git a/opendm/types.py b/opendm/types.py index e4d20036..34a8170c 100644 --- a/opendm/types.py +++ b/opendm/types.py @@ -274,14 +274,8 @@ class ODM_GeoRef(object): with open(json_file, 'w') as f: f.write(pipeline) -<<<<<<< HEAD # call pdal - system.run('{bin}/pdal pipeline -i {json} --readers.ply.filename={f_in} ' - '--writers.las.filename={f_out}'.format(**kwargs)) -======= - # call pdal system.run('{bin}/pdal pipeline -i {json} --readers.ply.filename={f_in}'.format(**kwargs)) ->>>>>>> 4a00c4d8411befc0c1b01a36f713e61633f002b3 def utm_to_latlon(self, _file, _photo, idx): @@ -421,7 +415,6 @@ class ODM_Tree(object): self.dataset_raw = io.join_paths(self.root_path, 'images') self.opensfm = io.join_paths(self.root_path, 'opensfm') self.smvs = io.join_paths(self.root_path, 'smvs') - self.pmvs = io.join_paths(self.root_path, 'pmvs') 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') @@ -446,13 +439,6 @@ 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') diff --git a/run.py b/run.py index 4a60471d..43d7a1e3 100644 --- a/run.py +++ b/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 diff --git a/scripts/cmvs.py b/scripts/cmvs.py deleted file mode 100644 index f3e3f3b5..00000000 --- a/scripts/cmvs.py +++ /dev/null @@ -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 diff --git a/scripts/mvstex.py b/scripts/mvstex.py index 6b6123fb..cb399522 100644 --- a/scripts/mvstex.py +++ b/scripts/mvstex.py @@ -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,8 @@ class ODMMvsTexCell(ecto.Cell): 'toneMapping': self.params.tone_mapping } - if not args.use_pmvs: - 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'] = io.join_paths(tree.opensfm, + "reconstruction.nvm") # Make sure tmp directory is empty mvs_tmp_dir = os.path.join(r['out_dir'], 'tmp') diff --git a/scripts/odm_app.py b/scripts/odm_app.py index 4d80451a..2a7f8a65 100644 --- a/scripts/odm_app.py +++ b/scripts/odm_app.py @@ -10,8 +10,6 @@ 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 @@ -51,13 +49,6 @@ class ODMApp(ecto.BlackBox): 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, @@ -135,7 +126,7 @@ class ODMApp(ecto.BlackBox): self.args[:] >> self.smvs['args'], self.opensfm['reconstruction'] >> self.smvs['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.smvs['reconstruction'] >> self.meshing['reconstruction']] @@ -169,20 +160,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'], diff --git a/scripts/odm_georeferencing.py b/scripts/odm_georeferencing.py index 69a519e3..700e3db2 100644 --- a/scripts/odm_georeferencing.py +++ b/scripts/odm_georeferencing.py @@ -92,13 +92,12 @@ 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') else: - kwargs['pc'] = tree.pmvs_model + kwargs['pc'] = tree.opensfm_model + # Check to see if the GCP file exists diff --git a/scripts/odm_slam.py b/scripts/odm_slam.py index b905f4a5..5e532971 100644 --- a/scripts/odm_slam.py +++ b/scripts/odm_slam.py @@ -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 diff --git a/scripts/pmvs.py b/scripts/pmvs.py deleted file mode 100644 index 948877b4..00000000 --- a/scripts/pmvs.py +++ /dev/null @@ -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 diff --git a/scripts/pmvs2nvmcams.py b/scripts/pmvs2nvmcams.py deleted file mode 100644 index f9754579..00000000 --- a/scripts/pmvs2nvmcams.py +++ /dev/null @@ -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") diff --git a/scripts/run_opensfm.py b/scripts/run_opensfm.py index 7b3bc616..e4d5c2c8 100644 --- a/scripts/run_opensfm.py +++ b/scripts/run_opensfm.py @@ -42,7 +42,6 @@ class ODMOpenSfMCell(ecto.Cell): # 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 @@ -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)) diff --git a/scripts/smvs.py b/scripts/smvs.py index 60d22f77..bdb46bf2 100644 --- a/scripts/smvs.py +++ b/scripts/smvs.py @@ -66,7 +66,7 @@ class ODMSmvsCell(ecto.Cell): "-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', + "--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 ''