Merge pull request #1610 from pierotofy/305

Point spacing estimation
pull/1613/head
Piero Toffanin 2023-02-26 13:24:50 -05:00 zatwierdzone przez GitHub
commit b392c7a09d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 50 dodań i 38 usunięć

Wyświetl plik

@ -8,7 +8,7 @@ ExternalProject_Add(${_proj_name}
#--Download step-------------- #--Download step--------------
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR} DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
GIT_REPOSITORY https://github.com/OpenDroneMap/FPCFilter GIT_REPOSITORY https://github.com/OpenDroneMap/FPCFilter
GIT_TAG main GIT_TAG 305
#--Update/Patch step---------- #--Update/Patch step----------
UPDATE_COMMAND "" UPDATE_COMMAND ""
#--Configure step------------- #--Configure step-------------

Wyświetl plik

@ -371,3 +371,11 @@ def window_filter_2d(arr, nodata, window, kernel_size, filter):
win_arr[nodata_locs] = nodata win_arr[nodata_locs] = nodata
win_arr = win_arr[window[0] - expanded_window[0] : window[2] - expanded_window[0], window[1] - expanded_window[1] : window[3] - expanded_window[1]] win_arr = win_arr[window[0] - expanded_window[0] : window[2] - expanded_window[0], window[1] - expanded_window[1] : window[3] - expanded_window[1]]
return win_arr return win_arr
def get_dem_radius_steps(stats_file, steps, resolution, multiplier = 1.0):
radius_steps = [point_cloud.get_spacing(stats_file, resolution) * multiplier]
for _ in range(steps - 1):
radius_steps.append(radius_steps[-1] * math.sqrt(2))
return radius_steps

Wyświetl plik

@ -5,10 +5,11 @@ from opendm import system
from opendm import log from opendm import log
from opendm import context from opendm import context
from opendm import concurrency from opendm import concurrency
from opendm import point_cloud
from scipy import signal from scipy import signal
import numpy as np import numpy as np
def create_25dmesh(inPointCloud, outMesh, dsm_radius=0.07, dsm_resolution=0.05, depth=8, samples=1, maxVertexCount=100000, available_cores=None, method='gridded', smooth_dsm=True): def create_25dmesh(inPointCloud, outMesh, radius_steps=["0.05"], dsm_resolution=0.05, depth=8, samples=1, maxVertexCount=100000, available_cores=None, method='gridded', smooth_dsm=True):
# Create DSM from point cloud # Create DSM from point cloud
# Create temporary directory # Create temporary directory
@ -19,17 +20,13 @@ def create_25dmesh(inPointCloud, outMesh, dsm_radius=0.07, dsm_resolution=0.05,
os.mkdir(tmp_directory) os.mkdir(tmp_directory)
log.ODM_INFO('Created temporary directory: %s' % tmp_directory) log.ODM_INFO('Created temporary directory: %s' % tmp_directory)
radius_steps = [dsm_radius]
for _ in range(2):
radius_steps.append(radius_steps[-1] * math.sqrt(2)) # sqrt(2) is arbitrary
log.ODM_INFO('Creating DSM for 2.5D mesh') log.ODM_INFO('Creating DSM for 2.5D mesh')
commands.create_dem( commands.create_dem(
inPointCloud, inPointCloud,
'mesh_dsm', 'mesh_dsm',
output_type='max', output_type='max',
radiuses=list(map(str, radius_steps)), radiuses=radius_steps,
gapfill=True, gapfill=True,
outdir=tmp_directory, outdir=tmp_directory,
resolution=dsm_resolution, resolution=dsm_resolution,

Wyświetl plik

@ -71,7 +71,7 @@ def split(input_point_cloud, outdir, filename_template, capacity, dims=None):
return [os.path.join(outdir, f) for f in os.listdir(outdir)] return [os.path.join(outdir, f) for f in os.listdir(outdir)]
def filter(input_point_cloud, output_point_cloud, standard_deviation=2.5, meank=16, sample_radius=0, boundary=None, max_concurrency=1): def filter(input_point_cloud, output_point_cloud, output_stats, standard_deviation=2.5, sample_radius=0, boundary=None, max_concurrency=1):
""" """
Filters a point cloud Filters a point cloud
""" """
@ -89,10 +89,11 @@ def filter(input_point_cloud, output_point_cloud, standard_deviation=2.5, meank=
log.ODM_INFO("Sampling points around a %sm radius" % sample_radius) log.ODM_INFO("Sampling points around a %sm radius" % sample_radius)
args.append('--radius %s' % sample_radius) args.append('--radius %s' % sample_radius)
if standard_deviation > 0 and meank > 0: meank = 16
log.ODM_INFO("Filtering {} (statistical, meanK {}, standard deviation {})".format(input_point_cloud, meank, standard_deviation)) log.ODM_INFO("Filtering {} (statistical, meanK {}, standard deviation {})".format(input_point_cloud, meank, standard_deviation))
args.append('--meank %s' % meank) args.append('--meank %s' % meank)
args.append('--std %s' % standard_deviation) args.append('--std %s' % standard_deviation)
args.append('--stats "%s"' % output_stats)
if boundary is not None: if boundary is not None:
log.ODM_INFO("Boundary {}".format(boundary)) log.ODM_INFO("Boundary {}".format(boundary))
@ -107,6 +108,26 @@ def filter(input_point_cloud, output_point_cloud, standard_deviation=2.5, meank=
if not os.path.exists(output_point_cloud): if not os.path.exists(output_point_cloud):
log.ODM_WARNING("{} not found, filtering has failed.".format(output_point_cloud)) log.ODM_WARNING("{} not found, filtering has failed.".format(output_point_cloud))
def get_spacing(stats_file, resolution_fallback=5.0):
def fallback():
log.ODM_WARNING("Cannot read %s, falling back to resolution estimate" % stats_file)
return (resolution_fallback / 100.0) / 2.0
if not os.path.isfile(stats_file):
return fallback()
with open(stats_file, 'r') as f:
j = json.loads(f.read())
if "spacing" in j:
d = j["spacing"]
if d > 0:
return round(d, 3)
else:
return fallback()
else:
return fallback()
def export_info_json(pointcloud_path, info_file_path): def export_info_json(pointcloud_path, info_file_path):
system.run('pdal info --dimensions "X,Y,Z" "{0}" > "{1}"'.format(pointcloud_path, info_file_path)) system.run('pdal info --dimensions "X,Y,Z" "{0}" > "{1}"'.format(pointcloud_path, info_file_path))

Wyświetl plik

@ -290,6 +290,7 @@ class ODM_Tree(object):
# filter points # filter points
self.filtered_point_cloud = os.path.join(self.odm_filterpoints, "point_cloud.ply") self.filtered_point_cloud = os.path.join(self.odm_filterpoints, "point_cloud.ply")
self.filtered_point_cloud_stats = os.path.join(self.odm_filterpoints, "point_cloud_stats.json")
# odm_meshing # odm_meshing
self.odm_mesh = os.path.join(self.odm_meshing, 'odm_mesh.ply') self.odm_mesh = os.path.join(self.odm_meshing, 'odm_mesh.ply')

Wyświetl plik

@ -29,12 +29,8 @@ class ODMDEMStage(types.ODM_Stage):
ignore_resolution = True ignore_resolution = True
pseudo_georeference = True pseudo_georeference = True
# It is probably not reasonable to have accurate DEMs a the same resolution as the source photos, so reduce it
# by a factor!
gsd_scaling = 2.0
resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction,
gsd_scaling=gsd_scaling, gsd_scaling=1.0,
ignore_gsd=args.ignore_gsd, ignore_gsd=args.ignore_gsd,
ignore_resolution=ignore_resolution and args.ignore_gsd, ignore_resolution=ignore_resolution and args.ignore_gsd,
has_gcp=reconstruction.has_gcp()) has_gcp=reconstruction.has_gcp())
@ -88,9 +84,7 @@ class ODMDEMStage(types.ODM_Stage):
if args.dsm or (args.dtm and args.dem_euclidean_map): products.append('dsm') if args.dsm or (args.dtm and args.dem_euclidean_map): products.append('dsm')
if args.dtm: products.append('dtm') if args.dtm: products.append('dtm')
radius_steps = [(resolution / 100.0) / 2.0] radius_steps = commands.get_dem_radius_steps(tree.filtered_point_cloud_stats, args.dem_gapfill_steps, resolution)
for _ in range(args.dem_gapfill_steps - 1):
radius_steps.append(radius_steps[-1] * math.sqrt(2)) # sqrt(2) is arbitrary, maybe there's a better value?
for product in products: for product in products:
commands.create_dem( commands.create_dem(

Wyświetl plik

@ -49,7 +49,7 @@ class ODMFilterPoints(types.ODM_Stage):
else: else:
log.ODM_WARNING("Not a georeferenced reconstruction, will ignore --auto-boundary") log.ODM_WARNING("Not a georeferenced reconstruction, will ignore --auto-boundary")
point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, tree.filtered_point_cloud_stats,
standard_deviation=args.pc_filter, standard_deviation=args.pc_filter,
sample_radius=args.pc_sample, sample_radius=args.pc_sample,
boundary=boundary_offset(outputs.get('boundary'), reconstruction.get_proj_offset()), boundary=boundary_offset(outputs.get('boundary'), reconstruction.get_proj_offset()),

Wyświetl plik

@ -7,6 +7,7 @@ from opendm import context
from opendm import mesh from opendm import mesh
from opendm import gsd from opendm import gsd
from opendm import types from opendm import types
from opendm.dem import commands
class ODMeshingStage(types.ODM_Stage): class ODMeshingStage(types.ODM_Stage):
def process(self, args, outputs): def process(self, args, outputs):
@ -40,28 +41,18 @@ class ODMeshingStage(types.ODM_Stage):
if not io.file_exists(tree.odm_25dmesh) or self.rerun(): if not io.file_exists(tree.odm_25dmesh) or self.rerun():
log.ODM_INFO('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) log.ODM_INFO('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh)
ortho_resolution = gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction,
ignore_gsd=args.ignore_gsd,
ignore_resolution=(not reconstruction.is_georeferenced()) and args.ignore_gsd,
has_gcp=reconstruction.has_gcp()) / 100.0
dsm_multiplier = max(1.0, gsd.rounded_gsd(tree.opensfm_reconstruction, default_value=4, ndigits=3, ignore_gsd=args.ignore_gsd))
# A good DSM size depends on the flight altitude.
# Flights at low altitude need more details (higher resolution)
# Flights at higher altitude benefit from smoother surfaces (lower resolution)
dsm_resolution = ortho_resolution * dsm_multiplier
dsm_radius = dsm_resolution * math.sqrt(2)
if args.fast_orthophoto: multiplier = math.pi / 2.0
dsm_radius *= 2 radius_steps = commands.get_dem_radius_steps(tree.filtered_point_cloud_stats, 3, args.orthophoto_resolution, multiplier=multiplier)
dsm_resolution *= 8 dsm_resolution = radius_steps[0] / multiplier
log.ODM_INFO('ODM 2.5D DSM resolution: %s' % dsm_resolution) log.ODM_INFO('ODM 2.5D DSM resolution: %s' % dsm_resolution)
if args.fast_orthophoto:
dsm_resolution *= 8.0
mesh.create_25dmesh(tree.filtered_point_cloud, tree.odm_25dmesh, mesh.create_25dmesh(tree.filtered_point_cloud, tree.odm_25dmesh,
dsm_radius=dsm_radius, radius_steps,
dsm_resolution=dsm_resolution, dsm_resolution=dsm_resolution,
depth=self.params.get('oct_tree'), depth=self.params.get('oct_tree'),
maxVertexCount=self.params.get('max_vertex'), maxVertexCount=self.params.get('max_vertex'),