diff --git a/instantsfm/processors/view_graph_calibration.py b/instantsfm/processors/view_graph_calibration.py index bd2f807..e133be5 100644 --- a/instantsfm/processors/view_graph_calibration.py +++ b/instantsfm/processors/view_graph_calibration.py @@ -15,30 +15,39 @@ from bae.optim import LM from bae.autograd.function import TrackingTensor -def SolveViewGraphCalibration(view_graph:ViewGraph, cameras, images, VIEW_GRAPH_CALIBRATOR_OPTIONS): - valid_image_pairs = {pair_id: image_pair for pair_id, image_pair in view_graph.image_pairs.items() - if image_pair.is_valid and image_pair.config in [ConfigurationType.CALIBRATED, ConfigurationType.UNCALIBRATED]} - focals = np.array([np.mean(cam.focal_length) for cam in cameras]) - - problem = pyceres.Problem() - options = pyceres.SolverOptions() - loss_function = pyceres.CauchyLoss(VIEW_GRAPH_CALIBRATOR_OPTIONS['thres_loss_function']) +def SolveViewGraphCalibration(view_graph:ViewGraph, cameras, images, VIEW_GRAPH_CALIBRATOR_OPTIONS): + valid_image_pairs = {pair_id: image_pair for pair_id, image_pair in view_graph.image_pairs.items() + if image_pair.is_valid and image_pair.config in [ConfigurationType.CALIBRATED, ConfigurationType.UNCALIBRATED]} + if len(valid_image_pairs) == 0: + print('No valid image pairs for view graph calibration; skipping.') + return + focals = np.array([np.mean(cam.focal_length) for cam in cameras]) + + problem = pyceres.Problem() + options = pyceres.SolverOptions() + loss_function = pyceres.CauchyLoss(VIEW_GRAPH_CALIBRATOR_OPTIONS['thres_loss_function']) if len(cameras) < 50: options.linear_solver_type = pyceres.LinearSolverType.DENSE_NORMAL_CHOLESKY else: options.linear_solver_type = pyceres.LinearSolverType.SPARSE_NORMAL_CHOLESKY - for image_pair in valid_image_pairs.values(): - image1, image2 = images[image_pair.image_id1], images[image_pair.image_id2] - cam1, cam2 = cameras[image1.cam_id], cameras[image2.cam_id] - cam_id1, cam_id2 = image1.cam_id, image2.cam_id - if cam_id1 == cam_id2: - cost_function = FetzerFocalLengthSameCameraCostFunction(image_pair.F, cam1.principal_point) - problem.add_residual_block(cost_function, loss_function, [focals[cam_id1:cam_id1+1]]) - else: - cost_function = FetzerFocalLengthCostFunction(image_pair.F, cam1.principal_point, cam2.principal_point) - problem.add_residual_block(cost_function, loss_function, [focals[cam_id1:cam_id1+1], focals[cam_id2:cam_id2+1]]) - problem.set_parameter_lower_bound(focals, 0, 1e-3) + bounded_cam_ids = set() + for image_pair in valid_image_pairs.values(): + image1, image2 = images[image_pair.image_id1], images[image_pair.image_id2] + cam1, cam2 = cameras[image1.cam_id], cameras[image2.cam_id] + cam_id1, cam_id2 = image1.cam_id, image2.cam_id + if cam_id1 == cam_id2: + cost_function = FetzerFocalLengthSameCameraCostFunction(image_pair.F, cam1.principal_point) + problem.add_residual_block(cost_function, loss_function, [focals[cam_id1:cam_id1+1]]) + else: + cost_function = FetzerFocalLengthCostFunction(image_pair.F, cam1.principal_point, cam2.principal_point) + problem.add_residual_block(cost_function, loss_function, [focals[cam_id1:cam_id1+1], focals[cam_id2:cam_id2+1]]) + if cam_id1 not in bounded_cam_ids: + problem.set_parameter_lower_bound(focals[cam_id1:cam_id1+1], 0, 1e-3) + bounded_cam_ids.add(cam_id1) + if cam_id2 not in bounded_cam_ids: + problem.set_parameter_lower_bound(focals[cam_id2:cam_id2+1], 0, 1e-3) + bounded_cam_ids.add(cam_id2) options.max_num_iterations = VIEW_GRAPH_CALIBRATOR_OPTIONS['max_num_iterations'] options.function_tolerance = VIEW_GRAPH_CALIBRATOR_OPTIONS['function_tolerance'] @@ -179,4 +188,4 @@ def forward(self, ds, camera_indices1, camera_indices2): if loss_sq[idx*2] > thres_two_view_error_sq: invalid_counter += 1 view_graph.image_pairs[pair_id].is_valid = False - print(f'invalid / total number of two view geometry: {invalid_counter} / {len(valid_image_pairs)}') \ No newline at end of file + print(f'invalid / total number of two view geometry: {invalid_counter} / {len(valid_image_pairs)}') diff --git a/instantsfm/scripts/feat.py b/instantsfm/scripts/feat.py index fc3718c..264d2b7 100644 --- a/instantsfm/scripts/feat.py +++ b/instantsfm/scripts/feat.py @@ -1,3 +1,4 @@ +import os import time from argparse import ArgumentParser @@ -19,9 +20,12 @@ def run_feature_handler(): if not path_info: print('Invalid data path, please check the provided path') return - if path_info.database_exists: - print('Database path already exists') - return + force_rebuild = os.environ.get("INSTANTSFM_FORCE_REBUILD_DB", "1").lower() in ("1", "true", "yes") + if path_info.database_exists and not force_rebuild: + if os.path.getsize(path_info.database_path) > 0: + print('Database already exists; skipping feature extraction/matching.') + return + print('Database file exists but is empty; rebuilding.') start_time = time.time() config = Config(handler_args.feature_handler, handler_args.manual_config_name)