OpenDroneMap-ODM/stages/openmvs.py

252 wiersze
12 KiB
Python

import shutil, os, glob, math, sys
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.gpu import has_gpu
from opendm.utils import get_depthmap_resolution
from opendm.osfm import OSFMContext
from opendm.multispectral import get_primary_band_name
from opendm.point_cloud import fast_merge_ply
class ODMOpenMVSStage(types.ODM_Stage):
def process(self, args, outputs):
# get inputs
tree = outputs['tree']
reconstruction = outputs['reconstruction']
photos = reconstruction.photos
octx = OSFMContext(tree.opensfm)
pc_tile = False
if not photos:
raise system.ExitException('Not enough photos in photos array to start OpenMVS')
# check if reconstruction was done before
if not io.file_exists(tree.openmvs_model) or self.rerun():
if self.rerun():
if io.dir_exists(tree.openmvs):
shutil.rmtree(tree.openmvs)
# export reconstruction from opensfm
openmvs_scene_file = os.path.join(tree.openmvs, "scene.mvs")
if not io.file_exists(openmvs_scene_file) or self.rerun():
cmd = 'export_openmvs'
octx.run(cmd)
else:
log.ODM_WARNING("Found existing %s" % openmvs_scene_file)
self.update_progress(10)
depthmaps_dir = os.path.join(tree.openmvs, "depthmaps")
if io.dir_exists(depthmaps_dir) and self.rerun():
shutil.rmtree(depthmaps_dir)
if not io.dir_exists(depthmaps_dir):
os.mkdir(depthmaps_dir)
depthmap_resolution = get_depthmap_resolution(args, photos)
log.ODM_INFO("Depthmap resolution set to: %spx" % depthmap_resolution)
if outputs["undist_image_max_size"] <= depthmap_resolution:
resolution_level = 0
else:
resolution_level = int(round(math.log(outputs['undist_image_max_size'] / float(depthmap_resolution)) / math.log(2)))
log.ODM_INFO("Running dense reconstruction. This might take a while.")
log.ODM_INFO("Estimating depthmaps")
number_views_fuse = 2
densify_ini_file = os.path.join(tree.openmvs, 'Densify.ini')
subres_levels = 2 # The number of lower resolutions to process before estimating output resolution depthmap.
filter_point_th = -20
config = [
"--resolution-level %s" % int(resolution_level),
'--dense-config-file "%s"' % densify_ini_file,
"--max-resolution %s" % int(outputs['undist_image_max_size']),
"--max-threads %s" % args.max_concurrency,
"--number-views-fuse %s" % number_views_fuse,
"--sub-resolution-levels %s" % subres_levels,
"--archive-type 3",
'-w "%s"' % depthmaps_dir,
"-v 0"
]
gpu_config = []
use_gpu = has_gpu(args)
if use_gpu:
gpu_config.append("--cuda-device -1")
else:
gpu_config.append("--cuda-device -2")
extra_config = []
if args.pc_skip_geometric:
extra_config.append("--geometric-iters 0")
masks_dir = os.path.join(tree.opensfm, "undistorted", "masks")
masks = os.path.exists(masks_dir) and len(os.listdir(masks_dir)) > 0
if masks:
extra_config.append("--ignore-mask-label 0")
with open(densify_ini_file, 'w+') as f:
f.write("Optimize = 7\nMin Views Filter = 1\n")
def run_densify():
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
openmvs_scene_file,
' '.join(config + gpu_config + extra_config)))
try:
run_densify()
except system.SubprocessException as e:
# If the GPU was enabled and the program failed,
# try to run it again without GPU
if e.errorCode == 1 and use_gpu:
log.ODM_WARNING("OpenMVS failed with GPU, is your graphics card driver up to date? Falling back to CPU.")
gpu_config = ["--cuda-device -2"]
run_densify()
elif (e.errorCode == 137 or e.errorCode == 143 or e.errorCode == 3221226505) and not pc_tile:
log.ODM_WARNING("OpenMVS ran out of memory, we're going to turn on tiling to see if we can process this.")
pc_tile = True
config.append("--fusion-mode 1")
run_densify()
else:
raise e
self.update_progress(85)
files_to_remove = []
scene_dense = os.path.join(tree.openmvs, 'scene_dense.mvs')
if pc_tile:
log.ODM_INFO("Computing sub-scenes")
subscene_densify_ini_file = os.path.join(tree.openmvs, 'subscene-config.ini')
with open(subscene_densify_ini_file, 'w+') as f:
f.write("Optimize = 0\nEstimation Geometric Iters = 0\nMin Views Filter = 1\n")
config = [
"--sub-scene-area 660000", # 8000
"--max-threads %s" % args.max_concurrency,
'-w "%s"' % depthmaps_dir,
"-v 0",
]
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
openmvs_scene_file,
' '.join(config + gpu_config)))
scene_files = glob.glob(os.path.join(tree.openmvs, "scene_[0-9][0-9][0-9][0-9].mvs"))
if len(scene_files) == 0:
raise system.ExitException("No OpenMVS scenes found. This could be a bug, or the reconstruction could not be processed.")
log.ODM_INFO("Fusing depthmaps for %s scenes" % len(scene_files))
scene_ply_files = []
for sf in scene_files:
p, _ = os.path.splitext(sf)
scene_ply_unfiltered = p + "_dense.ply"
scene_ply = p + "_dense_dense_filtered.ply"
scene_dense_mvs = p + "_dense.mvs"
files_to_remove += [scene_ply, sf, scene_dense_mvs, scene_ply_unfiltered]
scene_ply_files.append(scene_ply)
if not io.file_exists(scene_ply) or self.rerun():
# Fuse
config = [
'--resolution-level %s' % int(resolution_level),
'--max-resolution %s' % int(outputs['undist_image_max_size']),
"--sub-resolution-levels %s" % subres_levels,
'--dense-config-file "%s"' % subscene_densify_ini_file,
'--number-views-fuse %s' % number_views_fuse,
'--max-threads %s' % args.max_concurrency,
'--archive-type 3',
'--postprocess-dmaps 0',
'--geometric-iters 0',
'-w "%s"' % depthmaps_dir,
'-v 0',
]
try:
system.run('"%s" "%s" %s' % (context.omvs_densify_path, sf, ' '.join(config + gpu_config + extra_config)))
except:
log.ODM_WARNING("Sub-scene %s could not be reconstructed, skipping..." % sf)
if not io.file_exists(scene_ply_unfiltered):
scene_ply_files.pop()
log.ODM_WARNING("Could not compute PLY for subscene %s" % sf)
else:
# Filter
if args.pc_filter > 0:
system.run('"%s" "%s" --filter-point-cloud %s -v 0 --archive-type 3 %s' % (context.omvs_densify_path, scene_dense_mvs, filter_point_th, ' '.join(gpu_config)))
else:
# Just rename
log.ODM_INFO("Skipped filtering, %s --> %s" % (scene_ply_unfiltered, scene_ply))
os.rename(scene_ply_unfiltered, scene_ply)
else:
log.ODM_WARNING("Found existing dense scene file %s" % scene_ply)
# Merge
log.ODM_INFO("Merging %s scene files" % len(scene_ply_files))
if len(scene_ply_files) == 0:
raise system.ExitException("Could not compute dense point cloud (no PLY files available).")
if len(scene_ply_files) == 1:
# Simply rename
os.replace(scene_ply_files[0], tree.openmvs_model)
log.ODM_INFO("%s --> %s"% (scene_ply_files[0], tree.openmvs_model))
else:
# Merge
fast_merge_ply(scene_ply_files, tree.openmvs_model)
else:
def skip_filtering():
# Just rename
scene_dense_ply = os.path.join(tree.openmvs, 'scene_dense.ply')
if not os.path.exists(scene_dense_ply):
raise system.ExitException("Dense reconstruction failed. This could be due to poor georeferencing or insufficient image overlap.")
log.ODM_INFO("Skipped filtering, %s --> %s" % (scene_dense_ply, tree.openmvs_model))
os.rename(scene_dense_ply, tree.openmvs_model)
# Filter all at once
if args.pc_filter > 0:
if os.path.exists(scene_dense):
config = [
"--filter-point-cloud %s" % filter_point_th,
'-i "%s"' % scene_dense,
"-v 0"
]
try:
system.run('"%s" %s' % (context.omvs_densify_path, ' '.join(config + gpu_config + extra_config)))
except system.SubprocessException as e:
if e.errorCode == 137 or e.errorCode == 143 or e.errorCode == 3221226505:
log.ODM_WARNING("OpenMVS filtering ran out of memory, visibility checks will be skipped.")
skip_filtering()
else:
raise e
else:
raise system.ExitException("Cannot find scene_dense.mvs, dense reconstruction probably failed. Exiting...")
else:
skip_filtering()
self.update_progress(95)
if args.optimize_disk_space:
files = [scene_dense,
os.path.join(tree.openmvs, 'scene_dense.ply'),
os.path.join(tree.openmvs, 'scene_dense_dense_filtered.mvs'),
octx.path("undistorted", "tracks.csv"),
octx.path("undistorted", "reconstruction.json")
] + files_to_remove
for f in files:
if os.path.exists(f):
os.remove(f)
shutil.rmtree(depthmaps_dir)
else:
log.ODM_WARNING('Found a valid OpenMVS reconstruction file in: %s' %
tree.openmvs_model)