OpenDroneMap-ODM/stages/openmvs.py

252 wiersze
12 KiB
Python
Czysty Zwykły widok Historia

2021-12-07 18:26:18 +00:00
import shutil, os, glob, math, sys
2020-10-27 21:10:10 +00:00
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
2021-12-16 19:36:17 +00:00
from opendm.gpu import has_gpu
from opendm.utils import get_depthmap_resolution
2020-10-27 21:10:10 +00:00
from opendm.osfm import OSFMContext
from opendm.multispectral import get_primary_band_name
from opendm.point_cloud import fast_merge_ply
2020-10-27 21:10:10 +00:00
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)
2023-04-23 20:29:08 +00:00
pc_tile = False
2020-10-27 21:10:10 +00:00
if not photos:
2021-06-09 15:46:56 +00:00
raise system.ExitException('Not enough photos in photos array to start OpenMVS')
2020-10-27 21:10:10 +00:00
# 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)
2020-10-27 21:10:10 +00:00
# export reconstruction from opensfm
openmvs_scene_file = os.path.join(tree.openmvs, "scene.mvs")
2021-03-03 20:33:18 +00:00
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)
2020-10-27 21:10:10 +00:00
self.update_progress(10)
depthmaps_dir = os.path.join(tree.openmvs, "depthmaps")
2021-03-03 20:33:18 +00:00
if io.dir_exists(depthmaps_dir) and self.rerun():
shutil.rmtree(depthmaps_dir)
2020-10-27 21:10:10 +00:00
if not io.dir_exists(depthmaps_dir):
os.mkdir(depthmaps_dir)
2022-03-02 18:49:02 +00:00
depthmap_resolution = get_depthmap_resolution(args, photos)
2022-03-02 18:49:02 +00:00
log.ODM_INFO("Depthmap resolution set to: %spx" % depthmap_resolution)
if outputs["undist_image_max_size"] <= depthmap_resolution:
2020-10-27 23:30:11 +00:00
resolution_level = 0
else:
resolution_level = int(round(math.log(outputs['undist_image_max_size'] / float(depthmap_resolution)) / math.log(2)))
2020-10-27 21:10:10 +00:00
log.ODM_INFO("Running dense reconstruction. This might take a while.")
log.ODM_INFO("Estimating depthmaps")
2022-01-15 01:40:02 +00:00
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
2020-10-27 21:10:10 +00:00
config = [
2023-11-05 00:00:57 +00:00
"--resolution-level %s" % int(resolution_level),
'--dense-config-file "%s"' % densify_ini_file,
2020-11-03 18:03:03 +00:00
"--max-resolution %s" % int(outputs['undist_image_max_size']),
2020-10-27 21:10:10 +00:00
"--max-threads %s" % args.max_concurrency,
2022-01-15 01:40:02 +00:00
"--number-views-fuse %s" % number_views_fuse,
"--sub-resolution-levels %s" % subres_levels,
2023-08-21 19:42:21 +00:00
"--archive-type 3",
2020-10-27 21:10:10 +00:00
'-w "%s"' % depthmaps_dir,
2021-03-05 14:22:27 +00:00
"-v 0"
2020-10-27 21:10:10 +00:00
]
gpu_config = []
2022-10-17 17:26:31 +00:00
use_gpu = has_gpu(args)
if use_gpu:
2022-12-01 02:50:48 +00:00
gpu_config.append("--cuda-device -1")
2022-10-17 17:26:31 +00:00
else:
gpu_config.append("--cuda-device -2")
2021-11-12 15:41:41 +00:00
extra_config = []
2023-01-10 04:59:11 +00:00
if args.pc_skip_geometric:
extra_config.append("--geometric-iters 0")
2022-06-23 19:08:50 +00:00
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")
2020-11-03 17:07:20 +00:00
with open(densify_ini_file, 'w+') as f:
2024-04-10 15:13:58 +00:00
f.write("Optimize = 7\nMin Views Filter = 1\n")
2021-12-07 18:26:18 +00:00
def run_densify():
2023-01-10 05:05:42 +00:00
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
2021-12-07 18:26:18 +00:00
openmvs_scene_file,
' '.join(config + gpu_config + extra_config)))
2023-11-05 00:00:57 +00:00
2021-12-07 18:26:18 +00:00
try:
run_densify()
except system.SubprocessException as e:
2021-12-07 19:53:27 +00:00
# If the GPU was enabled and the program failed,
2021-12-07 18:26:18 +00:00
# try to run it again without GPU
2022-10-17 17:26:31 +00:00
if e.errorCode == 1 and use_gpu:
2021-12-07 18:26:18 +00:00
log.ODM_WARNING("OpenMVS failed with GPU, is your graphics card driver up to date? Falling back to CPU.")
2022-10-17 17:26:31 +00:00
gpu_config = ["--cuda-device -2"]
2021-12-07 18:26:18 +00:00
run_densify()
2024-04-11 18:18:20 +00:00
elif (e.errorCode == 137 or e.errorCode == 143 or e.errorCode == 3221226505) and not pc_tile:
2022-11-07 17:53:37 +00:00
log.ODM_WARNING("OpenMVS ran out of memory, we're going to turn on tiling to see if we can process this.")
2023-04-23 20:29:08 +00:00
pc_tile = True
2022-11-07 17:53:37 +00:00
config.append("--fusion-mode 1")
run_densify()
2021-12-07 18:26:18 +00:00
else:
raise e
2020-11-03 17:07:20 +00:00
2021-03-05 14:22:27 +00:00
self.update_progress(85)
files_to_remove = []
scene_dense = os.path.join(tree.openmvs, 'scene_dense.mvs')
2023-04-23 20:29:08 +00:00
if pc_tile:
2021-03-05 14:22:27 +00:00
log.ODM_INFO("Computing sub-scenes")
2022-01-15 01:40:02 +00:00
subscene_densify_ini_file = os.path.join(tree.openmvs, 'subscene-config.ini')
with open(subscene_densify_ini_file, 'w+') as f:
2024-04-11 18:18:20 +00:00
f.write("Optimize = 0\nEstimation Geometric Iters = 0\nMin Views Filter = 1\n")
2022-01-15 01:40:02 +00:00
2021-03-05 14:22:27 +00:00
config = [
2023-11-05 00:00:57 +00:00
"--sub-scene-area 660000", # 8000
2021-03-05 14:22:27 +00:00
"--max-threads %s" % args.max_concurrency,
'-w "%s"' % depthmaps_dir,
"-v 0",
]
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
2021-03-05 14:22:27 +00:00
openmvs_scene_file,
' '.join(config + gpu_config)))
2021-03-05 14:22:27 +00:00
scene_files = glob.glob(os.path.join(tree.openmvs, "scene_[0-9][0-9][0-9][0-9].mvs"))
if len(scene_files) == 0:
2021-06-09 15:46:56 +00:00
raise system.ExitException("No OpenMVS scenes found. This could be a bug, or the reconstruction could not be processed.")
2021-03-05 14:22:27 +00:00
log.ODM_INFO("Fusing depthmaps for %s scenes" % len(scene_files))
scene_ply_files = []
for sf in scene_files:
p, _ = os.path.splitext(sf)
2022-01-15 01:40:02 +00:00
scene_ply_unfiltered = p + "_dense.ply"
2021-03-05 14:22:27 +00:00
scene_ply = p + "_dense_dense_filtered.ply"
scene_dense_mvs = p + "_dense.mvs"
2022-01-15 01:40:02 +00:00
files_to_remove += [scene_ply, sf, scene_dense_mvs, scene_ply_unfiltered]
2021-03-05 14:22:27 +00:00
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']),
2023-11-05 00:00:57 +00:00
"--sub-resolution-levels %s" % subres_levels,
2022-01-15 01:40:02 +00:00
'--dense-config-file "%s"' % subscene_densify_ini_file,
'--number-views-fuse %s' % number_views_fuse,
2021-03-05 14:22:27 +00:00
'--max-threads %s' % args.max_concurrency,
2023-11-05 00:00:57 +00:00
'--archive-type 3',
'--postprocess-dmaps 0',
'--geometric-iters 0',
2021-03-05 14:22:27 +00:00
'-w "%s"' % depthmaps_dir,
'-v 0',
]
2022-11-23 17:20:52 +00:00
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)
2021-03-05 14:22:27 +00:00
2022-11-23 17:20:52 +00:00
if not io.file_exists(scene_ply_unfiltered):
2021-03-05 14:22:27 +00:00
scene_ply_files.pop()
log.ODM_WARNING("Could not compute PLY for subscene %s" % sf)
2022-11-23 17:20:52 +00:00
else:
# Filter
if args.pc_filter > 0:
2023-11-05 00:00:57 +00:00
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)
2021-03-05 14:22:27 +00:00
else:
log.ODM_WARNING("Found existing dense scene file %s" % scene_ply)
2021-03-05 14:22:27 +00:00
# Merge
log.ODM_INFO("Merging %s scene files" % len(scene_ply_files))
if len(scene_ply_files) == 0:
2022-11-23 17:20:52 +00:00
raise system.ExitException("Could not compute dense point cloud (no PLY files available).")
2021-03-05 14:22:27 +00:00
if len(scene_ply_files) == 1:
# Simply rename
2021-05-04 18:46:55 +00:00
os.replace(scene_ply_files[0], tree.openmvs_model)
2021-03-05 14:22:27 +00:00
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:
2024-04-11 18:18:20 +00:00
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()
2020-11-03 17:07:20 +00:00
self.update_progress(95)
2020-10-27 21:10:10 +00:00
if args.optimize_disk_space:
2020-11-03 17:07:20 +00:00
files = [scene_dense,
os.path.join(tree.openmvs, 'scene_dense.ply'),
2020-11-21 18:21:10 +00:00
os.path.join(tree.openmvs, 'scene_dense_dense_filtered.mvs'),
octx.path("undistorted", "tracks.csv"),
octx.path("undistorted", "reconstruction.json")
] + files_to_remove
2020-11-03 17:07:20 +00:00
for f in files:
if os.path.exists(f):
os.remove(f)
2020-10-27 21:10:10 +00:00
shutil.rmtree(depthmaps_dir)
else:
log.ODM_WARNING('Found a valid OpenMVS reconstruction file in: %s' %
tree.openmvs_model)