diff --git a/Dockerfile b/Dockerfile index ca9bb7db..888ed001 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,9 @@ RUN rm -rf \ /code/SuperBuild/src/opencv \ /code/SuperBuild/src/opengv \ /code/SuperBuild/src/pcl \ - /code/SuperBuild/src/pdal + /code/SuperBuild/src/pdal \ + /code/SuperBuild/src/openmvs \ + /code/SuperBuild/build/openmvs # Entry point ENTRYPOINT ["python3", "/code/run.py"] \ No newline at end of file diff --git a/SuperBuild/CMakeLists.txt b/SuperBuild/CMakeLists.txt index 98396f78..c20e7674 100644 --- a/SuperBuild/CMakeLists.txt +++ b/SuperBuild/CMakeLists.txt @@ -110,6 +110,7 @@ set(custom_libs OpenSfM PDAL Entwine MvsTexturing + OpenMVS ) foreach(lib ${custom_libs}) diff --git a/configure.sh b/configure.sh index d36a7519..b1b03015 100644 --- a/configure.sh +++ b/configure.sh @@ -91,6 +91,9 @@ install() { libboost-python-dev \ libboost-date-time-dev \ libboost-thread-dev + + echo "Installing OpenMVS Dependencies" + sudo apt-get install -y -qq --no-install-recommends libcgal-dev pip install --ignore-installed -r requirements.txt diff --git a/opendm/config.py b/opendm/config.py index 9ee82abe..640b552c 100755 --- a/opendm/config.py +++ b/opendm/config.py @@ -9,7 +9,7 @@ from pyodm import Node, exceptions import sys # parse arguments -processopts = ['dataset', 'split', 'merge', 'opensfm', 'mve', 'odm_filterpoints', +processopts = ['dataset', 'split', 'merge', 'opensfm', 'openmvs', 'odm_filterpoints', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_dem', 'odm_orthophoto', 'odm_report'] @@ -275,16 +275,6 @@ def config(argv=None, parser=None): help='Run local bundle adjustment for every image added to the reconstruction and a global ' 'adjustment every 100 images. Speeds up reconstruction for very large datasets.') - parser.add_argument('--mve-confidence', - metavar='', - action=StoreValue, - type=float, - default=0.60, - help=('Discard points that have less than a certain confidence threshold. ' - 'This only affects dense reconstructions performed with MVE. ' - 'Higher values discard more points. ' - 'Default: %(default)s')) - parser.add_argument('--use-3dmesh', action=StoreTrue, nargs=0, diff --git a/opendm/context.py b/opendm/context.py index c188938f..3fde1c4d 100644 --- a/opendm/context.py +++ b/opendm/context.py @@ -40,6 +40,9 @@ dem2points_path = os.path.join(superbuild_path, 'src', 'dem2points', 'dem2points # define mvstex path mvstex_path = os.path.join(superbuild_path, "install/bin/texrecon") +# openmvs paths +omvs_densify_path = os.path.join(superbuild_path, "install/bin/OpenMVS/DensifyPointCloud") + # define txt2las path txt2las_path = os.path.join(superbuild_path, 'src/las-tools/bin') pdal_path = os.path.join(superbuild_path, 'build/pdal/bin') diff --git a/opendm/io.py b/opendm/io.py index bb23a1ed..b8be1af0 100644 --- a/opendm/io.py +++ b/opendm/io.py @@ -12,8 +12,8 @@ def extract_path_from_file(file): return path -def join_paths(path1, path2): - return os.path.join(path1, path2) +def join_paths(*args): + return os.path.join(*args) def file_exists(path_file): diff --git a/opendm/types.py b/opendm/types.py index 341aa5ab..c6707f8e 100644 --- a/opendm/types.py +++ b/opendm/types.py @@ -214,6 +214,7 @@ class ODM_Tree(object): self.dataset_raw = io.join_paths(self.root_path, 'images') self.opensfm = io.join_paths(self.root_path, 'opensfm') self.mve = io.join_paths(self.root_path, 'mve') + self.openmvs = io.join_paths(self.opensfm, 'undistorted', 'openmvs') 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') @@ -244,6 +245,9 @@ class ODM_Tree(object): self.mve_model = io.join_paths(self.mve, 'mve_dense_point_cloud.ply') self.mve_views = io.join_paths(self.mve, 'views') + # OpenMVS + self.openmvs_model = io.join_paths(self.openmvs, 'scene_dense.ply') + # filter points self.filtered_point_cloud = io.join_paths(self.odm_filterpoints, "point_cloud.ply") diff --git a/portable.Dockerfile b/portable.Dockerfile index b279dc81..01e49167 100644 --- a/portable.Dockerfile +++ b/portable.Dockerfile @@ -27,7 +27,9 @@ RUN rm -rf \ /code/SuperBuild/src/opencv \ /code/SuperBuild/src/opengv \ /code/SuperBuild/src/pcl \ - /code/SuperBuild/src/pdal + /code/SuperBuild/src/pdal \ + /code/SuperBuild/src/openmvs \ + /code/SuperBuild/build/openmvs # Entry point ENTRYPOINT ["python3", "/code/run.py"] diff --git a/stages/odm_app.py b/stages/odm_app.py index f8822ca1..fff0244e 100644 --- a/stages/odm_app.py +++ b/stages/odm_app.py @@ -8,7 +8,7 @@ from opendm import log from stages.dataset import ODMLoadDatasetStage from stages.run_opensfm import ODMOpenSfMStage -from stages.mve import ODMMveStage +from stages.openmvs import ODMOpenMVSStage from stages.odm_slam import ODMSlamStage from stages.odm_meshing import ODMeshingStage from stages.mvstex import ODMMvsTexStage @@ -34,7 +34,7 @@ class ODMApp: merge = ODMMergeStage('merge', args, progress=100.0) opensfm = ODMOpenSfMStage('opensfm', args, progress=25.0) slam = ODMSlamStage('slam', args) - mve = ODMMveStage('mve', args, progress=50.0) + openmvs = ODMOpenMVSStage('openmvs', args, progress=50.0) filterpoints = ODMFilterPoints('odm_filterpoints', args, progress=52.0) meshing = ODMeshingStage('odm_meshing', args, progress=60.0, max_vertex=args.mesh_size, @@ -71,8 +71,8 @@ class ODMApp: if args.use_opensfm_dense or args.fast_orthophoto: opensfm.connect(filterpoints) else: - opensfm.connect(mve) \ - .connect(filterpoints) + opensfm.connect(openmvs) \ + .connect(filterpoints) filterpoints \ .connect(meshing) \ @@ -82,14 +82,5 @@ class ODMApp: .connect(orthophoto) \ .connect(report) - # # SLAM pipeline - # # TODO: this is broken and needs work - # log.ODM_WARNING("SLAM module is currently broken. We could use some help fixing this. If you know Python, get in touch at https://community.opendronemap.org.") - # self.first_stage = slam - - # slam.connect(mve) \ - # .connect(meshing) \ - # .connect(texturing) - def execute(self): self.first_stage.run() \ No newline at end of file diff --git a/stages/odm_filterpoints.py b/stages/odm_filterpoints.py index b979b469..418b0e93 100644 --- a/stages/odm_filterpoints.py +++ b/stages/odm_filterpoints.py @@ -21,7 +21,7 @@ class ODMFilterPoints(types.ODM_Stage): elif args.use_opensfm_dense: inputPointCloud = tree.opensfm_model else: - inputPointCloud = tree.mve_model + inputPointCloud = tree.openmvs_model point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, standard_deviation=args.pc_filter, diff --git a/stages/openmvs.py b/stages/openmvs.py new file mode 100644 index 00000000..5f1b5ce7 --- /dev/null +++ b/stages/openmvs.py @@ -0,0 +1,69 @@ +import shutil, os, glob, math + +from opendm import log +from opendm import io +from opendm import system +from opendm import context +from opendm import point_cloud +from opendm import types +from opendm.osfm import OSFMContext + +class ODMOpenMVSStage(types.ODM_Stage): + def process(self, args, outputs): + # get inputs + tree = outputs['tree'] + reconstruction = outputs['reconstruction'] + photos = reconstruction.photos + + if not photos: + log.ODM_ERROR('Not enough photos in photos array to start OpenMVS') + exit(1) + + # check if reconstruction was done before + if not io.file_exists(tree.openmvs_model) or self.rerun(): + if io.dir_exists(tree.openmvs): + shutil.rmtree(tree.openmvs) + + # export reconstruction from opensfm + octx = OSFMContext(tree.opensfm) + cmd = 'export_openmvs' + if reconstruction.multi_camera: + # Export only the primary band + primary = reconstruction.multi_camera[0] + image_list = os.path.join(tree.opensfm, "image_list_%s.txt" % primary['name'].lower()) + cmd += ' --image_list "%s"' % image_list + octx.run(cmd) + + self.update_progress(10) + + depthmaps_dir = os.path.join(tree.openmvs, "depthmaps") + if not io.dir_exists(depthmaps_dir): + os.mkdir(depthmaps_dir) + + resolution_level = int(float(outputs['undist_image_max_size']) / (2*args.depthmap_resolution)) + + config = [ + " --resolution-level %s" % resolution_level, + "--min-resolution %s" % args.depthmap_resolution, + "--max-resolution %s" % outputs['undist_image_max_size'], + "--max-threads %s" % args.max_concurrency, + '-w "%s"' % depthmaps_dir, + "-v 0", + ] + + log.ODM_INFO("Running dense reconstruction. This might take a while.") + + system.run('%s "%s" %s' % (context.omvs_densify_path, + os.path.join(tree.openmvs, 'scene.mvs'), + ' '.join(config))) + + self.update_progress(90) + + if args.optimize_disk_space: + dense_scene = os.path.join(tree.openmvs, 'scene_dense.mvs') + if os.path.exists(dense_scene): + os.remove(dense_scene) + shutil.rmtree(depthmaps_dir) + else: + log.ODM_WARNING('Found a valid OpenMVS reconstruction file in: %s' % + tree.openmvs_model)