OpenDroneMap-ODM/opendm/mesh.py

191 wiersze
6.4 KiB
Python

from __future__ import absolute_import
import os, shutil, sys, struct, random, math
from opendm.dem import commands
from opendm import system
from opendm import log
from opendm import context
from scipy import signal
import numpy as np
def create_25dmesh(inPointCloud, outMesh, dsm_radius=0.07, dsm_resolution=0.05, depth=8, samples=1, maxVertexCount=100000, verbose=False, available_cores=None, method='gridded', smooth_dsm=True):
# Create DSM from point cloud
# Create temporary directory
mesh_directory = os.path.dirname(outMesh)
tmp_directory = os.path.join(mesh_directory, 'tmp')
if os.path.exists(tmp_directory):
shutil.rmtree(tmp_directory)
os.mkdir(tmp_directory)
log.ODM_INFO('Created temporary directory: %s' % tmp_directory)
radius_steps = [dsm_radius]
log.ODM_INFO('Creating DSM for 2.5D mesh')
commands.create_dem(
inPointCloud,
'mesh_dsm',
output_type='max',
radiuses=list(map(str, radius_steps)),
gapfill=True,
outdir=tmp_directory,
resolution=dsm_resolution,
verbose=verbose,
max_workers=available_cores,
apply_smoothing=smooth_dsm
)
if method == 'gridded':
mesh = dem_to_mesh_gridded(os.path.join(tmp_directory, 'mesh_dsm.tif'), outMesh, maxVertexCount, verbose, maxConcurrency=max(1, available_cores))
elif method == 'poisson':
dsm_points = dem_to_points(os.path.join(tmp_directory, 'mesh_dsm.tif'), os.path.join(tmp_directory, 'dsm_points.ply'), verbose)
mesh = screened_poisson_reconstruction(dsm_points, outMesh, depth=depth,
samples=samples,
maxVertexCount=maxVertexCount,
threads=max(1, available_cores - 1), # poissonrecon can get stuck on some machines if --threads == all cores
verbose=verbose)
else:
raise 'Not a valid method: ' + method
# Cleanup tmp
if os.path.exists(tmp_directory):
shutil.rmtree(tmp_directory)
return mesh
def dem_to_points(inGeotiff, outPointCloud, verbose=False):
log.ODM_INFO('Sampling points from DSM: %s' % inGeotiff)
kwargs = {
'bin': context.dem2points_path,
'outfile': outPointCloud,
'infile': inGeotiff,
'verbose': '-verbose' if verbose else ''
}
system.run('{bin} -inputFile {infile} '
'-outputFile {outfile} '
'-skirtHeightThreshold 1.5 '
'-skirtIncrements 0.2 '
'-skirtHeightCap 100 '
' {verbose} '.format(**kwargs))
return outPointCloud
def dem_to_mesh_gridded(inGeotiff, outMesh, maxVertexCount, verbose=False, maxConcurrency=1):
log.ODM_INFO('Creating mesh from DSM: %s' % inGeotiff)
mesh_path, mesh_filename = os.path.split(outMesh)
# mesh_path = path/to
# mesh_filename = odm_mesh.ply
basename, ext = os.path.splitext(mesh_filename)
# basename = odm_mesh
# ext = .ply
outMeshDirty = os.path.join(mesh_path, "{}.dirty{}".format(basename, ext))
# This should work without issues most of the times,
# but just in case we lower maxConcurrency if it fails.
while True:
try:
kwargs = {
'bin': context.dem2mesh_path,
'outfile': outMeshDirty,
'infile': inGeotiff,
'maxVertexCount': maxVertexCount,
'maxConcurrency': maxConcurrency,
'verbose': '-verbose' if verbose else ''
}
system.run('{bin} -inputFile {infile} '
'-outputFile {outfile} '
'-maxTileLength 2000 '
'-maxVertexCount {maxVertexCount} '
'-maxConcurrency {maxConcurrency} '
' {verbose} '.format(**kwargs))
break
except Exception as e:
maxConcurrency = math.floor(maxConcurrency / 2)
if maxConcurrency >= 1:
log.ODM_WARNING("dem2mesh failed, retrying with lower concurrency (%s) in case this is a memory issue" % maxConcurrency)
else:
raise e
# Cleanup and reduce vertex count if necessary
# (as dem2mesh cannot guarantee that we'll have the target vertex count)
cleanupArgs = {
'reconstructmesh': context.omvs_reconstructmesh_path,
'outfile': outMesh,
'infile': outMeshDirty,
'max_faces': maxVertexCount * 2,
'verbose': '-verbose' if verbose else ''
}
system.run('{reconstructmesh} --mesh-file "{infile}" '
'-o "{outfile}" '
'--remove-spikes 0 --remove-spurious 0 --smooth 0 '
'--target-face-num {max_faces} '
'{verbose} '.format(**cleanupArgs))
# Delete intermediate results
os.remove(outMeshDirty)
return outMesh
def screened_poisson_reconstruction(inPointCloud, outMesh, depth = 8, samples = 1, maxVertexCount=100000, pointWeight=4, threads=context.num_cores, verbose=False):
mesh_path, mesh_filename = os.path.split(outMesh)
# mesh_path = path/to
# mesh_filename = odm_mesh.ply
basename, ext = os.path.splitext(mesh_filename)
# basename = odm_mesh
# ext = .ply
outMeshDirty = os.path.join(mesh_path, "{}.dirty{}".format(basename, ext))
poissonReconArgs = {
'bin': context.poisson_recon_path,
'outfile': outMeshDirty,
'infile': inPointCloud,
'depth': depth,
'samples': samples,
'pointWeight': pointWeight,
'threads': threads,
'verbose': '--verbose' if verbose else ''
}
# Run PoissonRecon
system.run('{bin} --in {infile} '
'--out {outfile} '
'--depth {depth} '
'--pointWeight {pointWeight} '
'--samplesPerNode {samples} '
'--threads {threads} '
'--linearFit '
'{verbose}'.format(**poissonReconArgs))
# Cleanup and reduce vertex count if necessary
cleanupArgs = {
'reconstructmesh': context.omvs_reconstructmesh_path,
'outfile': outMesh,
'infile': outMeshDirty,
'max_faces': maxVertexCount * 2,
'verbose': '-verbose' if verbose else ''
}
system.run('{reconstructmesh} --mesh-file "{infile}" '
'-o "{outfile}" '
'--remove-spikes 0 --remove-spurious 0 --smooth 0 '
'--target-face-num {max_faces} '
'{verbose} '.format(**cleanupArgs))
# Delete intermediate results
os.remove(outMeshDirty)
return outMesh