Remove --use-opensfm-dense, add more stats data for report, diagram

pull/1225/head
Piero Toffanin 2021-01-13 22:22:44 +00:00
rodzic 72816ceaf5
commit 29a346c6aa
11 zmienionych plików z 82 dodań i 78 usunięć

Wyświetl plik

@ -236,35 +236,6 @@ def config(argv=None, parser=None):
'but produce denser point clouds. '
'Default: %(default)s'))
parser.add_argument('--opensfm-depthmap-min-consistent-views',
metavar='<integer: 2 <= x <= 9>',
action=StoreValue,
type=int,
default=3,
help=('Minimum number of views that should reconstruct a point for it to be valid. Use lower values '
'if your images have less overlap. Lower values result in denser point clouds '
'but with more noise. '
'Default: %(default)s'))
parser.add_argument('--opensfm-depthmap-method',
metavar='<string>',
action=StoreValue,
default='PATCH_MATCH',
choices=['PATCH_MATCH', 'BRUTE_FORCE', 'PATCH_MATCH_SAMPLE'],
help=('Raw depthmap computation algorithm. '
'PATCH_MATCH and PATCH_MATCH_SAMPLE are faster, but might miss some valid points. '
'BRUTE_FORCE takes longer but produces denser reconstructions. '
'Default: %(default)s'))
parser.add_argument('--opensfm-depthmap-min-patch-sd',
metavar='<positive float>',
action=StoreValue,
type=float,
default=1,
help=('When using PATCH_MATCH or PATCH_MATCH_SAMPLE, controls the standard deviation threshold to include patches. '
'Patches with lower standard deviation are ignored. '
'Default: %(default)s'))
parser.add_argument('--use-hybrid-bundle-adjustment',
action=StoreTrue,
nargs=0,
@ -284,12 +255,6 @@ def config(argv=None, parser=None):
default=False,
help='Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs. Default: %(default)s')
parser.add_argument('--use-opensfm-dense',
action=StoreTrue,
nargs=0,
default=False,
help='Use OpenSfM to compute the dense point cloud instead of OpenMVS. Default: %(default)s')
parser.add_argument('--ignore-gsd',
action=StoreTrue,
nargs=0,

Wyświetl plik

@ -185,10 +185,6 @@ class OSFMContext:
"processes: %s" % args.max_concurrency,
"matching_gps_neighbors: %s" % args.matcher_neighbors,
"matching_gps_distance: %s" % args.matcher_distance,
"depthmap_method: %s" % args.opensfm_depthmap_method,
"depthmap_resolution: %s" % depthmap_resolution,
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
"optimize_camera_parameters: %s" % ('no' if args.use_fixed_camera_params or args.cameras else 'yes'),
"undistorted_image_format: tif",
"bundle_outlier_filtering_type: AUTO",

Wyświetl plik

@ -0,0 +1,7 @@
# QGIS Generated Color Map Export File
2,215,25,28,255,2
3,246,201,5,255,3
4,117,188,39,255,4
5,26,150,65,255,5+
inf,26,150,65,255,> 5
nv 0 0 0 0

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 7.9 KiB

Wyświetl plik

@ -243,7 +243,6 @@ class ODM_Tree(object):
self.opensfm_image_list = os.path.join(self.opensfm, 'image_list.txt')
self.opensfm_reconstruction = os.path.join(self.opensfm, 'reconstruction.json')
self.opensfm_reconstruction_nvm = os.path.join(self.opensfm, 'undistorted/reconstruction.nvm')
self.opensfm_model = os.path.join(self.opensfm, 'undistorted/depthmaps/merged.ply')
self.opensfm_transformation = os.path.join(self.opensfm, 'geocoords_transformation.txt')
# OpenMVS

Wyświetl plik

@ -135,22 +135,6 @@ class ODMLoadDatasetStage(types.ODM_Stage):
log.ODM_INFO('Found %s usable images' % len(photos))
# TODO: add support for masks in OpenMVS
has_mask = False
for p in photos:
if p.mask is not None:
has_mask = True
break
if has_mask and not args.use_opensfm_dense and not args.fast_orthophoto:
log.ODM_WARNING("Image masks found, will use OpenSfM for dense reconstruction")
args.use_opensfm_dense = True
# Remove OpenMVS from pipeline. Yep.
opensfm_stage = self.next_stage.next_stage.next_stage
opensfm_stage.next_stage = opensfm_stage.next_stage.next_stage
opensfm_stage.next_stage.prev_stage = opensfm_stage
# Create reconstruction object
reconstruction = types.ODM_Reconstruction(photos)

Wyświetl plik

@ -65,7 +65,7 @@ class ODMApp:
.connect(merge) \
.connect(opensfm)
if args.use_opensfm_dense or args.fast_orthophoto:
if args.fast_orthophoto:
opensfm.connect(filterpoints)
else:
opensfm.connect(openmvs) \

Wyświetl plik

@ -18,8 +18,6 @@ class ODMFilterPoints(types.ODM_Stage):
if not io.file_exists(tree.filtered_point_cloud) or self.rerun():
if args.fast_orthophoto:
inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
inputPointCloud = tree.opensfm_model
else:
inputPointCloud = tree.openmvs_model

Wyświetl plik

@ -1,4 +1,7 @@
import os
import json
import math
import shutil
from opendm import log
from opendm import io
@ -8,7 +11,6 @@ from opendm.shots import get_geojson_shots_from_opensfm
from opendm.osfm import OSFMContext
from opendm import gsd
from opendm.point_cloud import export_summary_json
import json
def hms(seconds):
@ -23,6 +25,14 @@ def hms(seconds):
return '{}s'.format(s)
def generate_point_cloud_stats(input_point_cloud, pc_summary_file):
if not os.path.exists(pc_summary_file):
export_summary_json(input_point_cloud, pc_summary_file)
if os.path.exists(pc_summary_file):
with open(pc_summary_file, 'r') as f:
return json.loads(f.read())
class ODMReport(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
@ -56,6 +66,8 @@ class ODMReport(types.ODM_Stage):
octx = OSFMContext(tree.opensfm)
osfm_stats_json = octx.path("stats", "stats.json")
odm_stats = None
point_cloud_file = None
views_dimension = None
if not os.path.exists(odm_stats_json) or self.rerun():
if os.path.exists(osfm_stats_json):
@ -63,15 +75,25 @@ class ODMReport(types.ODM_Stage):
odm_stats = json.loads(f.read())
# Add point cloud stats
pc_summary_file = os.path.join(tree.odm_georeferencing, "odm_georeferenced_model.summary.json")
# This should have been generated by cropper, but in case it hasn't..
if not os.path.exists(pc_summary_file) and os.path.exists(tree.odm_georeferencing_model_laz):
export_summary_json(tree.odm_georeferencing_model_laz, pc_summary_file)
if os.path.exists(pc_summary_file):
with open(pc_summary_file, 'r') as f:
odm_stats['point_cloud_statistics'] = json.loads(f.read())
if os.path.exists(tree.odm_georeferencing_model_laz):
point_cloud_file = tree.odm_georeferencing_model_laz
views_dimension = "UserData"
# pc_summary_file should have been generated by cropper
pc_summary_file = os.path.join(tree.odm_georeferencing, "odm_georeferenced_model.summary.json")
odm_stats['point_cloud_statistics'] = generate_point_cloud_stats(tree.odm_georeferencing_model_laz, pc_summary_file)
else:
ply_pc = os.path.join(tree.odm_filterpoints, "point_cloud.ply")
if os.path.exists(ply_pc):
point_cloud_file = ply_pc
views_dimension = "views"
pc_summary_file = os.path.join(tree.odm_filterpoints, "point_cloud.summary.json")
odm_stats['point_cloud_statistics'] = generate_point_cloud_stats(ply_pc, pc_summary_file)
else:
log.ODM_WARNING("No point cloud found")
odm_stats['point_cloud_statistics']['dense'] = not args.fast_orthophoto
# Add runtime stats
odm_stats['odm_processing_statistics'] = {
@ -89,4 +111,43 @@ class ODMReport(types.ODM_Stage):
with open(odm_stats_json, 'r') as f:
odm_stats = json.loads(f.read())
# Generate overlap diagram
if odm_stats.get('point_cloud_statistics') and point_cloud_file and views_dimension:
bounds = odm_stats['point_cloud_statistics'].get('summary', {}).get('bounds')
if bounds:
diagram_target_size = 1600 # pixels
osfm_stats_dir = os.path.join(tree.opensfm, "stats")
diagram_tiff = os.path.join(osfm_stats_dir, "overlap.tif")
diagram_png = os.path.join(osfm_stats_dir, "overlap.png")
width = bounds.get('maxx') - bounds.get('minx')
height = bounds.get('maxy') - bounds.get('miny')
max_dim = max(width, height)
resolution = float(max_dim) / float(diagram_target_size)
radius = resolution * math.sqrt(2)
# Larger radius for sparse point cloud diagram
if not odm_stats['point_cloud_statistics']['dense']:
radius *= 10
system.run("pdal translate -i \"{}\" "
"-o \"{}\" "
"--writer gdal "
"--writers.gdal.resolution={} "
"--writers.gdal.data_type=uint8_t "
"--writers.gdal.dimension={} "
"--writers.gdal.output_type=max "
"--writers.gdal.radius={} ".format(point_cloud_file, diagram_tiff,
resolution, views_dimension, radius))
report_assets = os.path.abspath(os.path.join(os.path.dirname(__file__), "../opendm/report"))
overlap_color_map = os.path.join(report_assets, "overlap_color_map.txt")
system.run("gdaldem color-relief \"{}\" \"{}\" \"{}\" -of PNG -alpha".format(diagram_tiff, overlap_color_map, diagram_png))
# Copy legend
shutil.copy(os.path.join(report_assets, "overlap_diagram_legend.png"), os.path.join(osfm_stats_dir, "overlap_diagram_legend.png"))
else:
log.ODM_WARNING("Cannot generate overlap diagram, cannot compute point cloud bounds")
else:
log.ODM_WARNING("Cannot generate overlap diagram, point cloud stats missing")
octx.export_report(os.path.join(tree.odm_report, "report.pdf"), odm_stats, self.rerun())

Wyświetl plik

@ -55,6 +55,8 @@ class ODMOpenMVSStage(types.ODM_Stage):
log.ODM_INFO("Running dense reconstruction. This might take a while.")
# TODO: add support for image masks
system.run('%s "%s" %s' % (context.omvs_densify_path,
os.path.join(tree.openmvs, 'scene.mvs'),
' '.join(config)))

Wyświetl plik

@ -193,14 +193,6 @@ class ODMOpenSfMStage(types.ODM_Stage):
else:
log.ODM_WARNING("Found a valid PLY reconstruction in %s" % output_file)
elif args.use_opensfm_dense:
output_file = tree.opensfm_model
if not io.file_exists(output_file) or self.rerun():
octx.run('compute_depthmaps')
else:
log.ODM_WARNING("Found a valid dense reconstruction in %s" % output_file)
self.update_progress(90)
if reconstruction.is_georeferenced() and (not io.file_exists(tree.opensfm_transformation) or self.rerun()):
@ -222,7 +214,7 @@ class ODMOpenSfMStage(types.ODM_Stage):
os.remove(f)
# Keep these if using OpenMVS
if args.fast_orthophoto or args.use_opensfm_dense:
if args.fast_orthophoto:
files = [octx.path("undistorted", "tracks.csv"),
octx.path("undistorted", "reconstruction.json")
]