Started writing new multispectral alignment algorithm

pull/1210/head
Piero Toffanin 2020-12-02 13:04:12 -05:00
rodzic a2040b2274
commit 509035d5e9
5 zmienionych plików z 75 dodań i 28 usunięć

Wyświetl plik

@ -156,6 +156,15 @@ def config(argv=None, parser=None):
help=('Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. '
'Can be one of: %(choices)s. Default: '
'%(default)s'))
parser.add_argument('--feature-matcher',
metavar='<string>',
action=StoreValue,
default='flann',
choices=['flann', 'bow'],
help=('Set feature matcher algorithm. FLANN is more robust, but slower. BOW is much faster, but can miss some valid matches. '
'Can be one of: %(choices)s. Default: '
'%(default)s'))
parser.add_argument('--matcher-neighbors',
metavar='<integer>',
@ -755,6 +764,15 @@ def config(argv=None, parser=None):
'points will be re-classified and gaps will be filled. Useful for generating DTMs. '
'Default: %(default)s'))
parser.add_argument('--primary-band',
metavar='<string>',
action=StoreValue,
default="auto",
type=str,
help=('When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. '
'It\'s recommended to choose a band which has sharp details and is in focus. '
'Default: %(default)s'))
args = parser.parse_args(argv)
# check that the project path setting has been set properly

Wyświetl plik

@ -150,4 +150,19 @@ def compute_irradiance(photo, use_sun_sensor=True):
elif use_sun_sensor:
log.ODM_WARNING("No sun sensor values found for %s" % photo.filename)
return 1.0
return 1.0
def get_photos_by_band(multi_camera, band_name):
if len(multi_camera) < 1:
raise Exception("Invalid multi_camera list")
# multi_camera is already sorted by band_index
if band_name == "auto":
return multi_camera[0]['photos']
for band in multi_camera:
if band['name'].lower() == band_name.lower():
return band['photos']
logger.ODM_WARNING("Cannot find band name \"%s\", will use \"auto\" instead" % band_name)
return multi_camera[0]['photos']

Wyświetl plik

@ -15,6 +15,7 @@ from opensfm.large import metadataset
from opensfm.large import tools
from opensfm.actions import undistort
from opensfm.dataset import DataSet
from opendm.multispectral import get_photos_by_band
class OSFMContext:
def __init__(self, opensfm_project_path):
@ -68,6 +69,14 @@ class OSFMContext:
list_path = os.path.join(self.opensfm_project_path, 'image_list.txt')
if not io.file_exists(list_path) or rerun:
if reconstruction.multi_camera:
photos = get_photos_by_band(reconstruction.multi_camera, args.primary_band)
if len(photos) < 1:
raise Exception("Not enough images in selected band %s" % args.primary_band.lower())
logger.ODM_INFO("Reconstruction will use %s images from %s band" % (len(photos), args.primary_band.lower()))
else:
photos = reconstruction.photos
# create file list
has_alt = True
has_gps = False
@ -77,6 +86,7 @@ class OSFMContext:
has_alt = False
if photo.latitude is not None and photo.longitude is not None:
has_gps = True
fout.write('%s\n' % os.path.join(images_path, photo.filename))
# check for image_groups.txt (split-merge)
@ -95,16 +105,9 @@ class OSFMContext:
except Exception as e:
log.ODM_WARNING("Cannot set camera_models_overrides.json: %s" % str(e))
use_bow = False
use_bow = args.feature_matcher == "bow"
feature_type = "SIFT"
matcher_neighbors = args.matcher_neighbors
if matcher_neighbors != 0 and reconstruction.multi_camera is not None:
matcher_neighbors *= len(reconstruction.multi_camera)
log.ODM_INFO("Increasing matcher neighbors to %s to accomodate multi-camera setup" % matcher_neighbors)
log.ODM_INFO("Multi-camera setup, using BOW matching")
use_bow = True
# GPSDOP override if we have GPS accuracy information (such as RTK)
if 'gps_accuracy_is_set' in args:
log.ODM_INFO("Forcing GPS DOP to %s for all images" % args.gps_accuracy)
@ -188,8 +191,7 @@ class OSFMContext:
"undistorted_image_format: tif",
"bundle_outlier_filtering_type: AUTO",
"align_orientation_prior: vertical",
"triangulation_type: ROBUST",
"bundle_common_position_constraints: %s" % ('no' if reconstruction.multi_camera is None else 'yes'),
"triangulation_type: ROBUST"
]
if args.camera_lens != 'auto':

Wyświetl plik

@ -25,7 +25,7 @@ class ODMOpenSfMStage(types.ODM_Stage):
exit(1)
octx = OSFMContext(tree.opensfm)
octx.setup(args, tree.dataset_raw, photos, reconstruction=reconstruction, rerun=self.rerun())
octx.setup(args, tree.dataset_raw, reconstruction=reconstruction, rerun=self.rerun())
octx.extract_metadata(self.rerun())
self.update_progress(20)
octx.feature_matching(self.rerun())
@ -48,13 +48,6 @@ class ODMOpenSfMStage(types.ODM_Stage):
self.next_stage = None
return
if args.fast_orthophoto:
output_file = octx.path('reconstruction.ply')
elif args.use_opensfm_dense:
output_file = tree.opensfm_model
else:
output_file = tree.opensfm_reconstruction
updated_config_flag_file = octx.path('updated_config.txt')
# Make sure it's capped by the depthmap-resolution arg,
@ -68,15 +61,29 @@ class ODMOpenSfMStage(types.ODM_Stage):
octx.update_config({'undistorted_image_max_size': outputs['undist_image_max_size']})
octx.touch(updated_config_flag_file)
# These will be used for texturing / MVS
if args.radiometric_calibration == "none":
octx.convert_and_undistort(self.rerun())
else:
def radiometric_calibrate(shot_id, image):
photo = reconstruction.get_photo(shot_id)
return multispectral.dn_to_reflectance(photo, image, use_sun_sensor=args.radiometric_calibration=="camera+sun")
# Undistorted images will be used for texturing / MVS
undistort_pipeline = []
octx.convert_and_undistort(self.rerun(), radiometric_calibrate)
def undistort_callback(shot_id, image):
for func in undistort_pipeline:
image = func(shot_id, image)
return image
def radiometric_calibrate(shot_id, image):
photo = reconstruction.get_photo(shot_id)
return multispectral.dn_to_reflectance(photo, image, use_sun_sensor=args.radiometric_calibration=="camera+sun")
def align_to_primary_band(shot_id, image):
# TODO
return image
if args.radiometric_calibration != "none"
undistort_pipeline.append(radiometric_calibrate)
if reconstruction.multi_camera:
undistort_pipeline.append(align_to_primary_band)
octx.convert_and_undistort(self.rerun(), undistort_callback)
self.update_progress(80)
@ -94,6 +101,7 @@ class ODMOpenSfMStage(types.ODM_Stage):
else:
log.ODM_WARNING("Found a valid image list in %s for %s band" % (image_list_file, band['name']))
# TODO!! CHANGE
nvm_file = octx.path("undistorted", "reconstruction_%s.nvm" % band['name'].lower())
if not io.file_exists(nvm_file) or self.rerun():
octx.run('export_visualsfm --points --image_list "%s"' % image_list_file)
@ -112,12 +120,16 @@ class ODMOpenSfMStage(types.ODM_Stage):
# Skip dense reconstruction if necessary and export
# sparse reconstruction instead
if args.fast_orthophoto:
output_file = octx.path('reconstruction.ply')
if not io.file_exists(output_file) or self.rerun():
octx.run('export_ply --no-cameras')
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:

Wyświetl plik

@ -50,7 +50,7 @@ class ODMSplitStage(types.ODM_Stage):
"submodel_overlap: %s" % args.split_overlap,
]
octx.setup(args, tree.dataset_raw, photos, reconstruction=reconstruction, append_config=config, rerun=self.rerun())
octx.setup(args, tree.dataset_raw, reconstruction=reconstruction, append_config=config, rerun=self.rerun())
octx.extract_metadata(self.rerun())
self.update_progress(5)