kopia lustrzana https://github.com/OpenDroneMap/ODM
Transform shots.geojson, bump opensfm, bump version
rodzic
28c48c34e6
commit
9f8eca2b83
|
@ -25,7 +25,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/OpenSfM/
|
GIT_REPOSITORY https://github.com/OpenDroneMap/OpenSfM/
|
||||||
GIT_TAG 292
|
GIT_TAG 302
|
||||||
#--Update/Patch step----------
|
#--Update/Patch step----------
|
||||||
UPDATE_COMMAND git submodule update --init --recursive
|
UPDATE_COMMAND git submodule update --init --recursive
|
||||||
#--Configure step-------------
|
#--Configure step-------------
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
3.0.1
|
3.0.2
|
||||||
|
|
|
@ -471,12 +471,12 @@ def config(argv=None, parser=None):
|
||||||
'image_name geo_x geo_y geo_z [omega (degrees)] [phi (degrees)] [kappa (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]\n'
|
'image_name geo_x geo_y geo_z [omega (degrees)] [phi (degrees)] [kappa (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]\n'
|
||||||
'Default: %(default)s'))
|
'Default: %(default)s'))
|
||||||
|
|
||||||
parser.add_argument('--align-to',
|
parser.add_argument('--align',
|
||||||
metavar='<path string>',
|
metavar='<path string>',
|
||||||
action=StoreValue,
|
action=StoreValue,
|
||||||
default=None,
|
default=None,
|
||||||
help=('Path to a GeoTIFF DEM or a LAS/LAZ point cloud '
|
help=('Path to a GeoTIFF DEM or a LAS/LAZ point cloud '
|
||||||
'that the reconstruction outputs should be automatically aligned to. '
|
'that the reconstruction outputs should be automatically aligned to. Experimental. '
|
||||||
'Default: %(default)s'))
|
'Default: %(default)s'))
|
||||||
|
|
||||||
parser.add_argument('--use-exif',
|
parser.add_argument('--use-exif',
|
||||||
|
|
|
@ -23,7 +23,7 @@ def get_origin(shot):
|
||||||
"""The origin of the pose in world coordinates."""
|
"""The origin of the pose in world coordinates."""
|
||||||
return -get_rotation_matrix(np.array(shot['rotation'])).T.dot(np.array(shot['translation']))
|
return -get_rotation_matrix(np.array(shot['rotation'])).T.dot(np.array(shot['translation']))
|
||||||
|
|
||||||
def get_geojson_shots_from_opensfm(reconstruction_file, utm_srs=None, utm_offset=None, pseudo_geotiff=None):
|
def get_geojson_shots_from_opensfm(reconstruction_file, utm_srs=None, utm_offset=None, pseudo_geotiff=None, a_matrix=None):
|
||||||
"""
|
"""
|
||||||
Extract shots from OpenSfM's reconstruction.json
|
Extract shots from OpenSfM's reconstruction.json
|
||||||
"""
|
"""
|
||||||
|
@ -92,6 +92,11 @@ def get_geojson_shots_from_opensfm(reconstruction_file, utm_srs=None, utm_offset
|
||||||
utm_coords = [origin[0] + utm_offset[0],
|
utm_coords = [origin[0] + utm_offset[0],
|
||||||
origin[1] + utm_offset[1],
|
origin[1] + utm_offset[1],
|
||||||
origin[2]]
|
origin[2]]
|
||||||
|
|
||||||
|
if a_matrix is not None:
|
||||||
|
rotation = list(np.array(rotation).dot(a_matrix[:3,:3]))
|
||||||
|
utm_coords = list(a_matrix.dot(np.hstack((np.array(utm_coords), 1)))[:-1])
|
||||||
|
|
||||||
translation = utm_coords
|
translation = utm_coords
|
||||||
trans_coords = crstrans.TransformPoint(utm_coords[0], utm_coords[1], utm_coords[2])
|
trans_coords = crstrans.TransformPoint(utm_coords[0], utm_coords[1], utm_coords[2])
|
||||||
|
|
||||||
|
|
|
@ -307,6 +307,9 @@ class ODM_Tree(object):
|
||||||
self.odm_georeferencing, 'odm_georeferenced_model.laz')
|
self.odm_georeferencing, 'odm_georeferenced_model.laz')
|
||||||
self.odm_georeferencing_model_las = os.path.join(
|
self.odm_georeferencing_model_las = os.path.join(
|
||||||
self.odm_georeferencing, 'odm_georeferenced_model.las')
|
self.odm_georeferencing, 'odm_georeferenced_model.las')
|
||||||
|
self.odm_georeferencing_alignment_matrix = os.path.join(
|
||||||
|
self.odm_georeferencing, 'alignment_matrix.json'
|
||||||
|
)
|
||||||
|
|
||||||
# odm_orthophoto
|
# odm_orthophoto
|
||||||
self.odm_orthophoto_render = os.path.join(self.odm_orthophoto, 'odm_orthophoto_render.tif')
|
self.odm_orthophoto_render = os.path.join(self.odm_orthophoto, 'odm_orthophoto_render.tif')
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import os, shutil
|
import os, shutil
|
||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
from opendm import log
|
from opendm import log
|
||||||
from opendm.photo import find_largest_photo_dims
|
from opendm.photo import find_largest_photo_dims
|
||||||
from osgeo import gdal
|
from osgeo import gdal
|
||||||
from opendm.loghelpers import double_quote
|
from opendm.loghelpers import double_quote
|
||||||
|
|
||||||
|
class NumpyEncoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
if isinstance(obj, np.ndarray):
|
||||||
|
return obj.tolist()
|
||||||
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
|
||||||
def get_depthmap_resolution(args, photos):
|
def get_depthmap_resolution(args, photos):
|
||||||
max_dims = find_largest_photo_dims(photos)
|
max_dims = find_largest_photo_dims(photos)
|
||||||
min_dim = 320 # Never go lower than this
|
min_dim = 320 # Never go lower than this
|
||||||
|
@ -98,4 +107,10 @@ def rm_r(path):
|
||||||
elif os.path.exists(path):
|
elif os.path.exists(path):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
except:
|
except:
|
||||||
log.ODM_WARNING("Cannot remove %s" % path)
|
log.ODM_WARNING("Cannot remove %s" % path)
|
||||||
|
|
||||||
|
def np_to_json(arr):
|
||||||
|
return json.dumps(arr, cls=NumpyEncoder)
|
||||||
|
|
||||||
|
def np_from_json(json_dump):
|
||||||
|
return np.asarray(json.loads(json_dump))
|
|
@ -46,7 +46,7 @@ def load_images_database(database_file):
|
||||||
class ODMLoadDatasetStage(types.ODM_Stage):
|
class ODMLoadDatasetStage(types.ODM_Stage):
|
||||||
def process(self, args, outputs):
|
def process(self, args, outputs):
|
||||||
outputs['start_time'] = system.now_raw()
|
outputs['start_time'] = system.now_raw()
|
||||||
tree = types.ODM_Tree(args.project_path, args.gcp, args.geo, args.align_to)
|
tree = types.ODM_Tree(args.project_path, args.gcp, args.geo, args.align)
|
||||||
outputs['tree'] = tree
|
outputs['tree'] = tree
|
||||||
|
|
||||||
if io.file_exists(tree.benchmarking):
|
if io.file_exists(tree.benchmarking):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
import pipes
|
import pipes
|
||||||
import fiona
|
import fiona
|
||||||
|
@ -19,6 +20,7 @@ from opendm.multispectral import get_primary_band_name
|
||||||
from opendm.osfm import OSFMContext
|
from opendm.osfm import OSFMContext
|
||||||
from opendm.boundary import as_polygon, export_to_bounds_files
|
from opendm.boundary import as_polygon, export_to_bounds_files
|
||||||
from opendm.align import compute_alignment_matrix, transform_point_cloud, transform_obj
|
from opendm.align import compute_alignment_matrix, transform_point_cloud, transform_obj
|
||||||
|
from opendm.utils import np_to_json
|
||||||
|
|
||||||
class ODMGeoreferencingStage(types.ODM_Stage):
|
class ODMGeoreferencingStage(types.ODM_Stage):
|
||||||
def process(self, args, outputs):
|
def process(self, args, outputs):
|
||||||
|
@ -172,11 +174,17 @@ class ODMGeoreferencingStage(types.ODM_Stage):
|
||||||
system.run(cmd + ' ' + ' '.join(stages) + ' ' + ' '.join(params))
|
system.run(cmd + ' ' + ' '.join(stages) + ' ' + ' '.join(params))
|
||||||
|
|
||||||
|
|
||||||
if tree.odm_align_file is not None:
|
stats_dir = tree.path("opensfm", "stats", "codem")
|
||||||
alignment_file = os.path.join(tree.odm_georeferencing, 'alignment_done.txt')
|
if os.path.exists(stats_dir) and self.rerun():
|
||||||
|
shutil.rmtree(stats_dir)
|
||||||
|
|
||||||
|
if tree.odm_align_file is not None:
|
||||||
|
alignment_file_exists = io.file_exists(tree.odm_georeferencing_alignment_matrix)
|
||||||
|
|
||||||
|
if not alignment_file_exists or self.rerun():
|
||||||
|
if alignment_file_exists:
|
||||||
|
os.unlink(tree.odm_georeferencing_alignment_matrix)
|
||||||
|
|
||||||
if not io.file_exists(alignment_file) or self.rerun():
|
|
||||||
stats_dir = tree.path("opensfm", "stats", "codem")
|
|
||||||
a_matrix = compute_alignment_matrix(tree.odm_georeferencing_model_laz, tree.odm_align_file, stats_dir)
|
a_matrix = compute_alignment_matrix(tree.odm_georeferencing_model_laz, tree.odm_align_file, stats_dir)
|
||||||
if a_matrix is not None:
|
if a_matrix is not None:
|
||||||
log.ODM_INFO("Alignment matrix: %s" % a_matrix)
|
log.ODM_INFO("Alignment matrix: %s" % a_matrix)
|
||||||
|
@ -207,12 +215,15 @@ class ODMGeoreferencingStage(types.ODM_Stage):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.ODM_WARNING("Cannot transform textured model: %s" % str(e))
|
log.ODM_WARNING("Cannot transform textured model: %s" % str(e))
|
||||||
os.rename(unaligned_obj, obj)
|
os.rename(unaligned_obj, obj)
|
||||||
|
|
||||||
|
with open(tree.odm_georeferencing_alignment_matrix, "w") as f:
|
||||||
|
f.write(np_to_json(a_matrix))
|
||||||
else:
|
else:
|
||||||
log.ODM_WARNING("Alignment to %s will be skipped." % tree.odm_align_file)
|
log.ODM_WARNING("Alignment to %s will be skipped." % tree.odm_align_file)
|
||||||
|
|
||||||
io.touch(alignment_file)
|
|
||||||
else:
|
else:
|
||||||
log.ODM_WARNING("Already computed alignment")
|
log.ODM_WARNING("Already computed alignment")
|
||||||
|
elif io.file_exists(tree.odm_georeferencing_alignment_matrix):
|
||||||
|
os.unlink(tree.odm_georeferencing_alignment_matrix)
|
||||||
|
|
||||||
point_cloud.post_point_cloud_steps(args, tree, self.rerun())
|
point_cloud.post_point_cloud_steps(args, tree, self.rerun())
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -14,7 +14,7 @@ from opendm.point_cloud import export_info_json
|
||||||
from opendm.cropper import Cropper
|
from opendm.cropper import Cropper
|
||||||
from opendm.orthophoto import get_orthophoto_vars, get_max_memory, generate_png
|
from opendm.orthophoto import get_orthophoto_vars, get_max_memory, generate_png
|
||||||
from opendm.tiles.tiler import generate_colored_hillshade
|
from opendm.tiles.tiler import generate_colored_hillshade
|
||||||
from opendm.utils import get_raster_stats
|
from opendm.utils import get_raster_stats, np_from_json
|
||||||
|
|
||||||
def hms(seconds):
|
def hms(seconds):
|
||||||
h = seconds // 3600
|
h = seconds // 3600
|
||||||
|
@ -49,7 +49,14 @@ class ODMReport(types.ODM_Stage):
|
||||||
if not io.file_exists(shots_geojson) or self.rerun():
|
if not io.file_exists(shots_geojson) or self.rerun():
|
||||||
# Extract geographical camera shots
|
# Extract geographical camera shots
|
||||||
if reconstruction.is_georeferenced():
|
if reconstruction.is_georeferenced():
|
||||||
shots = get_geojson_shots_from_opensfm(tree.opensfm_reconstruction, utm_srs=reconstruction.get_proj_srs(), utm_offset=reconstruction.georef.utm_offset())
|
# Check if alignment has been performed (we need to transform our shots if so)
|
||||||
|
a_matrix = None
|
||||||
|
if io.file_exists(tree.odm_georeferencing_alignment_matrix):
|
||||||
|
with open(tree.odm_georeferencing_alignment_matrix, 'r') as f:
|
||||||
|
a_matrix = np_from_json(f.read())
|
||||||
|
log.ODM_INFO("Aligning shots to %s" % a_matrix)
|
||||||
|
|
||||||
|
shots = get_geojson_shots_from_opensfm(tree.opensfm_reconstruction, utm_srs=reconstruction.get_proj_srs(), utm_offset=reconstruction.georef.utm_offset(), a_matrix=a_matrix)
|
||||||
else:
|
else:
|
||||||
# Pseudo geo
|
# Pseudo geo
|
||||||
shots = get_geojson_shots_from_opensfm(tree.opensfm_reconstruction, pseudo_geotiff=tree.odm_orthophoto_tif)
|
shots = get_geojson_shots_from_opensfm(tree.opensfm_reconstruction, pseudo_geotiff=tree.odm_orthophoto_tif)
|
||||||
|
@ -73,6 +80,7 @@ class ODMReport(types.ODM_Stage):
|
||||||
odm_stats_json = os.path.join(tree.odm_report, "stats.json")
|
odm_stats_json = os.path.join(tree.odm_report, "stats.json")
|
||||||
octx = OSFMContext(tree.opensfm)
|
octx = OSFMContext(tree.opensfm)
|
||||||
osfm_stats_json = octx.path("stats", "stats.json")
|
osfm_stats_json = octx.path("stats", "stats.json")
|
||||||
|
codem_stats_json = octx.path("stats", "codem", "registration.json")
|
||||||
odm_stats = None
|
odm_stats = None
|
||||||
point_cloud_file = None
|
point_cloud_file = None
|
||||||
views_dimension = None
|
views_dimension = None
|
||||||
|
@ -111,6 +119,11 @@ class ODMReport(types.ODM_Stage):
|
||||||
'average_gsd': gsd.opensfm_reconstruction_average_gsd(octx.recon_file(), use_all_shots=reconstruction.has_gcp()),
|
'average_gsd': gsd.opensfm_reconstruction_average_gsd(octx.recon_file(), use_all_shots=reconstruction.has_gcp()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add CODEM stats
|
||||||
|
if os.path.exists(codem_stats_json):
|
||||||
|
with open(codem_stats_json, 'r') as f:
|
||||||
|
odm_stats['align'] = json.loads(f.read())
|
||||||
|
|
||||||
with open(odm_stats_json, 'w') as f:
|
with open(odm_stats_json, 'w') as f:
|
||||||
f.write(json.dumps(odm_stats))
|
f.write(json.dumps(odm_stats))
|
||||||
else:
|
else:
|
||||||
|
|
Ładowanie…
Reference in New Issue