kopia lustrzana https://github.com/OpenDroneMap/ODM
Merge pull request #1464 from pierotofy/3dtiles
Adds support for OGC 3D Tiles outputs + Multi Subresolution Patch Match OpenMVSpull/1470/head
commit
41ad541aa2
|
@ -138,17 +138,14 @@ set(custom_libs OpenSfM
|
|||
LASzip
|
||||
PDAL
|
||||
Untwine
|
||||
Entwine
|
||||
MvsTexturing
|
||||
OpenMVS
|
||||
FPCFilter
|
||||
PyPopsift
|
||||
Obj2Tiles
|
||||
)
|
||||
|
||||
# Build entwine only on Linux
|
||||
if (NOT WIN32)
|
||||
set(custom_libs ${custom_libs} Entwine)
|
||||
endif()
|
||||
|
||||
externalproject_add(mve
|
||||
GIT_REPOSITORY https://github.com/OpenDroneMap/mve.git
|
||||
GIT_TAG 262
|
||||
|
|
|
@ -13,7 +13,7 @@ ExternalProject_Add(${_proj_name}
|
|||
#--Download step--------------
|
||||
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
|
||||
GIT_REPOSITORY https://github.com/OpenDroneMap/entwine/
|
||||
GIT_TAG 250
|
||||
GIT_TAG 285
|
||||
#--Update/Patch step----------
|
||||
UPDATE_COMMAND ""
|
||||
#--Configure step-------------
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
set(_proj_name obj2tiles)
|
||||
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
||||
|
||||
set(OBJ2TILES_VERSION v1.0.7)
|
||||
set(OBJ2TILES_EXT "")
|
||||
|
||||
set(OBJ2TILES_ARCH "Linux64")
|
||||
if (WIN32)
|
||||
set(OBJ2TILES_ARCH "Win64")
|
||||
set(OBJ2TILES_EXT ".exe")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
|
||||
set(OBJ2TILES_ARCH "LinuxArm")
|
||||
endif()
|
||||
|
||||
|
||||
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}
|
||||
URL https://github.com/OpenDroneMap/Obj2Tiles/releases/download/${OBJ2TILES_VERSION}/Obj2Tiles-${OBJ2TILES_ARCH}.zip
|
||||
SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
|
||||
UPDATE_COMMAND ""
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${SB_SOURCE_DIR}/${_proj_name}/Obj2Tiles${OBJ2TILES_EXT} ${SB_INSTALL_DIR}/bin
|
||||
#--Output logging-------------
|
||||
LOG_DOWNLOAD OFF
|
||||
LOG_CONFIGURE OFF
|
||||
LOG_BUILD OFF
|
||||
)
|
|
@ -3,7 +3,7 @@ set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
|||
|
||||
externalproject_add(vcg
|
||||
GIT_REPOSITORY https://github.com/OpenDroneMap/VCG.git
|
||||
GIT_TAG 280
|
||||
GIT_TAG 285
|
||||
UPDATE_COMMAND ""
|
||||
SOURCE_DIR ${SB_SOURCE_DIR}/vcg
|
||||
CONFIGURE_COMMAND ""
|
||||
|
@ -52,7 +52,7 @@ ExternalProject_Add(${_proj_name}
|
|||
#--Download step--------------
|
||||
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
|
||||
GIT_REPOSITORY https://github.com/OpenDroneMap/openMVS
|
||||
GIT_TAG 270
|
||||
GIT_TAG 285
|
||||
#--Update/Patch step----------
|
||||
UPDATE_COMMAND ""
|
||||
#--Configure step-------------
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2.8.4
|
||||
2.8.5
|
||||
|
|
|
@ -270,6 +270,12 @@ def config(argv=None, parser=None):
|
|||
'resizes images when necessary, resulting in faster processing and '
|
||||
'lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--no-gpu',
|
||||
action=StoreTrue,
|
||||
nargs=0,
|
||||
default=False,
|
||||
help='Do not use GPU acceleration, even if it\'s available. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--mesh-size',
|
||||
metavar='<positive integer>',
|
||||
action=StoreValue,
|
||||
|
@ -608,6 +614,12 @@ def config(argv=None, parser=None):
|
|||
'suitable for viewers like Leaflet or OpenLayers. '
|
||||
'Default: %(default)s')
|
||||
|
||||
parser.add_argument('--3d-tiles',
|
||||
action=StoreTrue,
|
||||
nargs=0,
|
||||
default=False,
|
||||
help='Generate OGC 3D Tiles outputs. Default: %(default)s')
|
||||
|
||||
parser.add_argument('--build-overviews',
|
||||
action=StoreTrue,
|
||||
nargs=0,
|
||||
|
@ -773,9 +785,6 @@ def config(argv=None, parser=None):
|
|||
if args.fast_orthophoto:
|
||||
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --skip-3dmodel')
|
||||
args.skip_3dmodel = True
|
||||
# if not 'sfm_algorithm_is_set' in args:
|
||||
# log.ODM_INFO('Fast orthophoto is turned on, automatically setting --sfm-algorithm to triangulation')
|
||||
# args.sfm_algorithm = 'triangulation'
|
||||
|
||||
if args.pc_rectify and not args.pc_classify:
|
||||
log.ODM_INFO("Ground rectify is turned on, automatically turning on point cloud classification")
|
||||
|
|
|
@ -23,6 +23,16 @@ import threading
|
|||
from .ground_rectification.rectify import run_rectification
|
||||
from . import pdal
|
||||
|
||||
try:
|
||||
# GDAL >= 3.3
|
||||
from osgeo_utils.gdal_proximity import main as gdal_proximity
|
||||
except ModuleNotFoundError:
|
||||
# GDAL <= 3.2
|
||||
try:
|
||||
from osgeo.utils.gdal_proximity import main as gdal_proximity
|
||||
except:
|
||||
pass
|
||||
|
||||
def classify(lasFile, scalar, slope, threshold, window, verbose=False):
|
||||
start = datetime.now()
|
||||
|
||||
|
@ -290,12 +300,20 @@ def compute_euclidean_map(geotiff_path, output_path, overwrite=False):
|
|||
|
||||
if not os.path.exists(output_path) or overwrite:
|
||||
log.ODM_INFO("Computing euclidean distance: %s" % output_path)
|
||||
run('gdal_proximity.py "%s" "%s" -values %s' % (geotiff_path, output_path, nodata))
|
||||
|
||||
if os.path.exists(output_path):
|
||||
return output_path
|
||||
if gdal_proximity is not None:
|
||||
try:
|
||||
gdal_proximity(['gdal_proximity.py', geotiff_path, output_path, '-values', str(nodata)])
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot compute euclidean distance: %s" % str(e))
|
||||
|
||||
if os.path.exists(output_path):
|
||||
return output_path
|
||||
else:
|
||||
log.ODM_WARNING("Cannot compute euclidean distance file: %s" % output_path)
|
||||
else:
|
||||
log.ODM_WARNING("Cannot compute euclidean distance file: %s" % output_path)
|
||||
log.ODM_WARNING("Cannot compute euclidean map, gdal_proximity is missing")
|
||||
|
||||
else:
|
||||
log.ODM_INFO("Found a euclidean distance map: %s" % output_path)
|
||||
return output_path
|
||||
|
|
|
@ -28,34 +28,28 @@ def build(input_point_cloud_files, output_path, max_concurrency=8, rerun=False):
|
|||
if rerun:
|
||||
dir_cleanup()
|
||||
|
||||
# On Windows we always use Untwine
|
||||
if sys.platform == 'win32':
|
||||
# Attempt with entwine (faster, more memory hungry)
|
||||
try:
|
||||
build_entwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=max_concurrency)
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot build EPT using entwine (%s), attempting with untwine..." % str(e))
|
||||
dir_cleanup()
|
||||
build_untwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=max_concurrency)
|
||||
else:
|
||||
# Attempt with entwine (faster, more memory hungry)
|
||||
try:
|
||||
build_entwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=max_concurrency)
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot build EPT using entwine (%s), attempting with untwine..." % str(e))
|
||||
dir_cleanup()
|
||||
build_untwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=max_concurrency)
|
||||
|
||||
if os.path.exists(tmpdir):
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
def build_entwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=8):
|
||||
def build_entwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=8, reproject=None):
|
||||
kwargs = {
|
||||
'threads': max_concurrency,
|
||||
'tmpdir': tmpdir,
|
||||
'all_inputs': "-i " + " ".join(map(double_quote, input_point_cloud_files)),
|
||||
'outputdir': output_path
|
||||
'outputdir': output_path,
|
||||
'reproject': (" -r %s " % reproject) if reproject is not None else ""
|
||||
}
|
||||
|
||||
# for _ in range(len(input_point_cloud_files)):
|
||||
# # One at a time
|
||||
# system.run('entwine build --threads {threads} --tmp "{tmpdir}" -i "{input}" -o "{outputdir}"'.format(**kwargs))
|
||||
system.run('entwine build --threads {threads} --tmp "{tmpdir}" {all_inputs} -o "{outputdir}"'.format(**kwargs))
|
||||
system.run('entwine build --threads {threads} --tmp "{tmpdir}" {all_inputs} -o "{outputdir}" {reproject}'.format(**kwargs))
|
||||
|
||||
def build_untwine(input_point_cloud_files, tmpdir, output_path, max_concurrency=8, rerun=False):
|
||||
kwargs = {
|
||||
|
|
|
@ -5,7 +5,7 @@ import ctypes
|
|||
from opendm import log
|
||||
from repoze.lru import lru_cache
|
||||
|
||||
def gpu_disabled_by_user():
|
||||
def gpu_disabled_by_user_env():
|
||||
return bool(os.environ.get('ODM_NO_GPU'))
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
|
@ -68,11 +68,13 @@ def get_cuda_compute_version(device_id = 0):
|
|||
|
||||
return (compute_major.value, compute_minor.value)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def has_gpu():
|
||||
if gpu_disabled_by_user():
|
||||
def has_gpu(args):
|
||||
if gpu_disabled_by_user_env():
|
||||
log.ODM_INFO("Disabling GPU features (ODM_NO_GPU is set)")
|
||||
return False
|
||||
if args.no_gpu:
|
||||
log.ODM_INFO("Disabling GPU features (--no-gpu is set)")
|
||||
return False
|
||||
|
||||
if sys.platform == 'win32':
|
||||
nvcuda_path = os.path.join(os.environ.get('SYSTEMROOT'), 'system32', 'nvcuda.dll')
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import json
|
||||
import math
|
||||
from opendm.utils import double_quote
|
||||
from opendm import io
|
||||
from opendm import log
|
||||
from opendm import system
|
||||
from opendm.entwine import build_entwine
|
||||
import fiona
|
||||
from shapely.geometry import shape
|
||||
|
||||
def build_textured_model(input_obj, output_path, reference_lla = None, model_bounds_file=None, rerun=False):
|
||||
if not os.path.isfile(input_obj):
|
||||
log.ODM_WARNING("No input OBJ file to process")
|
||||
return
|
||||
|
||||
if rerun and io.dir_exists(output_path):
|
||||
log.ODM_WARNING("Removing previous 3D tiles directory: %s" % output_path)
|
||||
shutil.rmtree(output_path)
|
||||
|
||||
log.ODM_INFO("Generating OGC 3D Tiles textured model")
|
||||
lat = lon = alt = 0
|
||||
|
||||
# Read reference_lla.json (if provided)
|
||||
if reference_lla is not None and os.path.isfile(reference_lla):
|
||||
try:
|
||||
with open(reference_lla) as f:
|
||||
reference_lla = json.loads(f.read())
|
||||
lat = reference_lla['latitude']
|
||||
lon = reference_lla['longitude']
|
||||
alt = reference_lla['altitude']
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot read %s: %s" % (reference_lla, str(e)))
|
||||
|
||||
# Read model bounds (if provided)
|
||||
divisions = 1 # default
|
||||
DIV_THRESHOLD = 10000 # m^2 (this is somewhat arbitrary)
|
||||
|
||||
if model_bounds_file is not None and os.path.isfile(model_bounds_file):
|
||||
try:
|
||||
with fiona.open(model_bounds_file, 'r') as f:
|
||||
if len(f) == 1:
|
||||
poly = shape(f[1]['geometry'])
|
||||
area = poly.area
|
||||
log.ODM_INFO("Approximate area: %s m^2" % round(area, 2))
|
||||
|
||||
if area < DIV_THRESHOLD:
|
||||
divisions = 0
|
||||
else:
|
||||
divisions = math.ceil(math.log((area / DIV_THRESHOLD), 4))
|
||||
else:
|
||||
log.ODM_WARNING("Invalid boundary file: %s" % model_bounds_file)
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot read %s: %s" % (model_bounds_file, str(e)))
|
||||
|
||||
try:
|
||||
kwargs = {
|
||||
'input': input_obj,
|
||||
'output': output_path,
|
||||
'divisions': divisions,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'alt': alt,
|
||||
}
|
||||
system.run('Obj2Tiles "{input}" "{output}" --divisions {divisions} '.format(**kwargs))
|
||||
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot build 3D tiles textured model: %s" % str(e))
|
||||
|
||||
def build_pointcloud(input_pointcloud, output_path, max_concurrency, rerun=False):
|
||||
if not os.path.isfile(input_pointcloud):
|
||||
log.ODM_WARNING("No input point cloud file to process")
|
||||
return
|
||||
|
||||
if rerun and io.dir_exists(output_path):
|
||||
log.ODM_WARNING("Removing previous 3D tiles directory: %s" % output_path)
|
||||
shutil.rmtree(output_path)
|
||||
|
||||
log.ODM_INFO("Generating OGC 3D Tiles point cloud")
|
||||
|
||||
try:
|
||||
if not os.path.isdir(output_path):
|
||||
os.mkdir(output_path)
|
||||
|
||||
tmpdir = os.path.join(output_path, "tmp")
|
||||
entwine_output = os.path.join(output_path, "entwine")
|
||||
|
||||
build_entwine([input_pointcloud], tmpdir, entwine_output, max_concurrency, "EPSG:4978")
|
||||
|
||||
kwargs = {
|
||||
'input': entwine_output,
|
||||
'output': output_path,
|
||||
}
|
||||
system.run('entwine convert -i "{input}" -o "{output}"'.format(**kwargs))
|
||||
|
||||
for d in [tmpdir, entwine_output]:
|
||||
if os.path.isdir(d):
|
||||
shutil.rmtree(d)
|
||||
except Exception as e:
|
||||
log.ODM_WARNING("Cannot build 3D tiles point cloud: %s" % str(e))
|
||||
|
||||
|
||||
def build_3dtiles(args, tree, reconstruction, rerun=False):
|
||||
tiles_output_path = tree.ogc_tiles
|
||||
model_output_path = os.path.join(tiles_output_path, "model")
|
||||
pointcloud_output_path = os.path.join(tiles_output_path, "pointcloud")
|
||||
|
||||
if rerun and os.path.exists(tiles_output_path):
|
||||
shutil.rmtree(tiles_output_path)
|
||||
|
||||
if not os.path.isdir(tiles_output_path):
|
||||
os.mkdir(tiles_output_path)
|
||||
|
||||
# Model
|
||||
|
||||
if not os.path.isdir(model_output_path) or rerun:
|
||||
reference_lla = os.path.join(tree.opensfm, "reference_lla.json")
|
||||
model_bounds_file = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg')
|
||||
|
||||
input_obj = os.path.join(tree.odm_texturing, tree.odm_textured_model_obj)
|
||||
if not os.path.isfile(input_obj):
|
||||
input_obj = os.path.join(tree.odm_25dtexturing, tree.odm_textured_model_obj)
|
||||
|
||||
build_textured_model(input_obj, model_output_path, reference_lla, model_bounds_file, rerun)
|
||||
else:
|
||||
log.ODM_WARNING("OGC 3D Tiles model %s already generated" % model_output_path)
|
||||
|
||||
# Point cloud
|
||||
|
||||
if not os.path.isdir(pointcloud_output_path) or rerun:
|
||||
build_pointcloud(tree.odm_georeferencing_model_laz, pointcloud_output_path, args.max_concurrency, rerun)
|
||||
else:
|
||||
log.ODM_WARNING("OGC 3D Tiles model %s already generated" % model_output_path)
|
|
@ -251,7 +251,7 @@ class OSFMContext:
|
|||
config.append("matcher_type: %s" % osfm_matchers[matcher_type])
|
||||
|
||||
# GPU acceleration?
|
||||
if has_gpu():
|
||||
if has_gpu(args):
|
||||
max_photo = find_largest_photo(photos)
|
||||
w, h = max_photo.width, max_photo.height
|
||||
if w > h:
|
||||
|
|
|
@ -81,6 +81,11 @@ def get_mm_per_unit(resolution_unit):
|
|||
class PhotoCorruptedException(Exception):
|
||||
pass
|
||||
|
||||
class GPSRefMock:
|
||||
def __init__(self, ref):
|
||||
self.values = [ref]
|
||||
|
||||
|
||||
class ODM_Photo:
|
||||
"""ODMPhoto - a class for ODMPhotos"""
|
||||
|
||||
|
@ -209,8 +214,14 @@ class ODM_Photo:
|
|||
self.altitude *= -1
|
||||
if 'GPS GPSLatitude' in tags and 'GPS GPSLatitudeRef' in tags:
|
||||
self.latitude = self.dms_to_decimal(tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef'])
|
||||
elif 'GPS GPSLatitude' in tags:
|
||||
log.ODM_WARNING("GPS position for %s might be incorrect, GPSLatitudeRef tag is missing (assuming N)" % self.filename)
|
||||
self.latitude = self.dms_to_decimal(tags['GPS GPSLatitude'], GPSRefMock('N'))
|
||||
if 'GPS GPSLongitude' in tags and 'GPS GPSLongitudeRef' in tags:
|
||||
self.longitude = self.dms_to_decimal(tags['GPS GPSLongitude'], tags['GPS GPSLongitudeRef'])
|
||||
elif 'GPS GPSLongitude' in tags:
|
||||
log.ODM_WARNING("GPS position for %s might be incorrect, GPSLongitudeRef tag is missing (assuming E)" % self.filename)
|
||||
self.longitude = self.dms_to_decimal(tags['GPS GPSLongitude'], GPSRefMock('E'))
|
||||
if 'Image Orientation' in tags:
|
||||
self.orientation = self.int_value(tags['Image Orientation'])
|
||||
except (IndexError, ValueError) as e:
|
||||
|
|
|
@ -289,6 +289,7 @@ class ODM_Tree(object):
|
|||
|
||||
# Tiles
|
||||
self.entwine_pointcloud = self.path("entwine_pointcloud")
|
||||
self.ogc_tiles = self.path("3d_tiles")
|
||||
|
||||
def path(self, *args):
|
||||
return os.path.join(self.root_path, *args)
|
||||
|
|
|
@ -64,6 +64,7 @@ def get_processing_results_paths():
|
|||
"dsm_tiles",
|
||||
"dtm_tiles",
|
||||
"orthophoto_tiles",
|
||||
"3d_tiles",
|
||||
"images.json",
|
||||
"cameras.json",
|
||||
"log.json",
|
||||
|
|
|
@ -5,6 +5,7 @@ from opendm import io
|
|||
from opendm import log
|
||||
from opendm import types
|
||||
from opendm.utils import copy_paths, get_processing_results_paths
|
||||
from opendm.ogctiles import build_3dtiles
|
||||
|
||||
class ODMPostProcess(types.ODM_Stage):
|
||||
def process(self, args, outputs):
|
||||
|
@ -44,6 +45,9 @@ class ODMPostProcess(types.ODM_Stage):
|
|||
else:
|
||||
log.ODM_WARNING("Cannot open %s for writing, skipping GCP embedding" % product)
|
||||
|
||||
if getattr(args, '3d_tiles'):
|
||||
build_3dtiles(args, tree, reconstruction, self.rerun())
|
||||
|
||||
if args.copy_to:
|
||||
try:
|
||||
copy_paths([os.path.join(args.project_path, p) for p in get_processing_results_paths()], args.copy_to, self.rerun())
|
||||
|
|
|
@ -59,21 +59,25 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
|||
|
||||
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.
|
||||
|
||||
config = [
|
||||
" --resolution-level %s" % int(resolution_level),
|
||||
'--dense-config-file "%s"' % densify_ini_file,
|
||||
"--min-resolution %s" % depthmap_resolution,
|
||||
"--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,
|
||||
'-w "%s"' % depthmaps_dir,
|
||||
"-v 0"
|
||||
]
|
||||
|
||||
gpu_config = []
|
||||
|
||||
if not has_gpu():
|
||||
gpu_config.append("--cuda-device -1")
|
||||
if not has_gpu(args):
|
||||
gpu_config.append("--cuda-device -2")
|
||||
|
||||
if args.pc_tile:
|
||||
config.append("--fusion-mode 1")
|
||||
|
@ -81,6 +85,10 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
|||
if not args.pc_geometric:
|
||||
config.append("--geometric-iters 0")
|
||||
|
||||
sharp = args.pc_filter > 0
|
||||
with open(densify_ini_file, 'w+') as f:
|
||||
f.write("Optimize = %s\n" % (7 if sharp else 3))
|
||||
|
||||
def run_densify():
|
||||
system.run('"%s" "%s" %s' % (context.omvs_densify_path,
|
||||
openmvs_scene_file,
|
||||
|
@ -93,7 +101,7 @@ class ODMOpenMVSStage(types.ODM_Stage):
|
|||
# try to run it again without GPU
|
||||
if e.errorCode == 1 and len(gpu_config) == 0:
|
||||
log.ODM_WARNING("OpenMVS failed with GPU, is your graphics card driver up to date? Falling back to CPU.")
|
||||
gpu_config.append("--cuda-device -1")
|
||||
gpu_config.append("--cuda-device -2")
|
||||
run_densify()
|
||||
else:
|
||||
raise e
|
||||
|
|
Ładowanie…
Reference in New Issue