kopia lustrzana https://github.com/OpenDroneMap/ODM
Remove --use-opensfm-dense, add more stats data for report, diagram
rodzic
72816ceaf5
commit
29a346c6aa
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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())
|
|
@ -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)))
|
||||
|
|
|
@ -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")
|
||||
]
|
||||
|
|
Ładowanie…
Reference in New Issue