Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@B1ueber2y
Copy link
Contributor

@B1ueber2y B1ueber2y commented Jun 21, 2024

This PR contains the covariance estimation for bundle adjustment with handcrafted Schur elimination, which has been forever missing from the ceres solver. Also, the interface for world_from_cam to cam_from_world conversion for the 6x6 covariance is provided with the adjoint feature from #2598

This is a practically extremely useful feature since the ceres covariance computation will generally fail for large-scale bundle adjustment problem mainly due to poorly conditioned 3D points. Many of these points generally cannot be easily identified with heuristics such as triangulation angles, making the ceres covariance computation intractable. Here are some examples of this long standing issue in the ceres development thread (seems that they stop developing this feature):

https://groups.google.com/g/ceres-solver/c/719fVUZqOZk
https://groups.google.com/g/ceres-solver/c/1SrLa1Pr8D4

With this new feature we will be able to reliably extract covariance from the bundle adjustment problem as long as the camera poses are well constrained (i.e. without Gauge ambiguity), even with poorly conditioned points. The covariance computation will also be faster with the use of Schur elimination.

Here is a minimal testing example in Python (If we feel necessary I can also try to write a unit test in C++). To be able to run the test one only needs a bundle adjusted COLMAP SfM model without rank deficiency. I am using the SfM model produced by the pycolmap example from here: https://github.com/colmap/colmap/blob/main/pycolmap/example.py

import pycolmap
import pyceres
import numpy as np

# setup bundle adjustment
recon = pycolmap.Reconstruction("example/sfm/0")
ba_options = pycolmap.BundleAdjustmentOptions()
ba_config = pycolmap.BundleAdjustmentConfig()
for img_id in recon.images:
    ba_config.add_image(img_id)
bundle_adjuster = pycolmap.BundleAdjuster(ba_options, ba_config)
bundle_adjuster.set_up_problem(recon, pyceres.TrivialLoss())
problem = bundle_adjuster.problem 

# if needed we can add more constraints here (e.g. PGO, control points).

# constrain problem (must-do for visual-only SfM to remove gauge)
image1_id, image2_id = 1, 2
problem.set_parameter_block_constant(recon.images[image1_id].cam_from_world.rotation.quat)
problem.set_parameter_block_constant(recon.images[image1_id].cam_from_world.translation)
problem.set_manifold(recon.images[image2_id].cam_from_world.translation, pyceres.SubsetManifold(3, np.array([0])))

# solve
solver_options = pyceres.SolverOptions()
summary = pyceres.SolverSummary()
pyceres.solve(solver_options, problem, summary)

# [TEST] covariance: direct interface
covs = pycolmap.estimate_pose_covariance_from_ba(problem, recon)
covs_from_ceres = pycolmap.estimate_pose_covariance_from_ba_ceres_backend(bundle_adjuster.problem, recon)
for img_id, _ in covs.items():
    assert covs[img_id].shape[0] == 5 if img_id == image2_id else 6 
    np.testing.assert_allclose(covs[img_id], covs_from_ceres[img_id], atol=1e-5)

# [TEST] cross-image covariance: pybind classes
cov_estimator = pycolmap.BundleAdjustmentCovarianceEstimator(problem, recon)
cov_estimator.compute()
cov_estimator_ceres = pycolmap.BundleAdjustmentCovarianceEstimatorCeresBackend(problem, recon)
cov_estimator_ceres.compute()
for i in np.arange(2, 10): # in this example model image_ids are from 1 to 11.
    cov = cov_estimator.get_pose_covariance(i, i + 1)
    cov_from_ceres = cov_estimator_ceres.get_pose_covariance(i, i + 1)
    np.testing.assert_allclose(cov, cov_from_ceres, atol=1e-5)

# [TEST] multi-image covariance
cov = cov_estimator.get_pose_covariance([3, 5, 7, 8])
cov_from_ceres = cov_estimator.get_pose_covariance([3, 5, 7, 8])
assert cov.shape[0] == 24 # 6 * 4
np.testing.assert_allclose(cov, cov_from_ceres, atol=1e-5)

# [TEST] covariance on intrinsics
cov_estimator.compute_full()
cov_estimator_ceres.compute_full()
for camera_id in recon.cameras:
    params = recon.cameras[camera_id].params
    cov_params = cov_estimator.get_covariance(params)
    assert cov_params.shape[0] == 2 # (f, k1)
    cov_params_from_ceres = cov_estimator.get_covariance(params)
    np.testing.assert_allclose(cov_params, cov_params_from_ceres, atol=1e-5)

@sarlinpe
Copy link
Member

Very nice, thanks! I will review this in more details on Monday.

@B1ueber2y B1ueber2y marked this pull request as draft June 22, 2024 10:54
@B1ueber2y
Copy link
Contributor Author

Let me restructure this now to support cross-pose covariance and also covariance for other parameters.

@B1ueber2y
Copy link
Contributor Author

New feature arised from the new update:

  1. cross-image covariance
  2. covariance on other variables (except for points)
  3. error handling for rank deficient jacobian.

The testing example is updated in the PR description to incorporate tests for the first two features in the new update.

@B1ueber2y B1ueber2y marked this pull request as ready for review June 22, 2024 15:45
return true;
}

bool BundleAdjustmentCovarianceEstimatorCeresBackend::Compute() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to call the two methods "ComputePoseCov" and "ComputePoseAndPointCov" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first method is to indeed compute pose covariance, but the second is to compute the covariance of all variables except for the points, as described in the header, and I felt that ComputePoseAndOtherVariablesCov is too long here.

@ahojnnes ahojnnes merged commit 0a1ae65 into colmap:main Jun 26, 2024
@ahojnnes
Copy link
Contributor

Thanks, great work.

@B1ueber2y B1ueber2y deleted the features/covariance_schur branch June 26, 2024 15:31
@cdcseacave
Copy link

@B1ueber2y thank you for this contribution. I am trying to port most of your math into my custom SfM pipeline for estimating cov for poses, and it works great for normal scenes; however for some scenes it either fails or the cov is inf, and I am not sure why. These seems to happen in scenes where there are some poses not well constrained, and I can figure out the exact cause: weak connections (constraints) for these poses, or something else. The failure is in "Simplicial LDLT for computing L_inv failed" with "NumericalIssue if the matrix.appears to be negative."
Any idea how to solve this? I do not know the math for this part well enough.

@B1ueber2y
Copy link
Contributor Author

@B1ueber2y thank you for this contribution. I am trying to port most of your math into my custom SfM pipeline for estimating cov for poses, and it works great for normal scenes; however for some scenes it either fails or the cov is inf, and I am not sure why. These seems to happen in scenes where there are some poses not well constrained, and I can figure out the exact cause: weak connections (constraints) for these poses, or something else. The failure is in "Simplicial LDLT for computing L_inv failed" with "NumericalIssue if the matrix.appears to be negative." Any idea how to solve this? I do not know the math for this part well enough.

Hey. For those poses that are not well constrained, it is possible that there is rank deficiency (or large condition number) when inverting the Hessian, resulting in failures. This actually makes sense since the covariance on certain direction would be infinite, and is not useful to inspect for downstream applications. You would need to either remove the ambiguity by adding additional constraints, or resort to more advanced techniques to deal with the nullspace (e.g., https://dspace.cvut.cz/bitstream/handle/10467/108057/F3-D-2023-Polic-Michal-Polic%20-%20disertacni%20prace.pdf). Thanks!

@cdcseacave
Copy link

cdcseacave commented May 22, 2025

@B1ueber2y Makes sens, thank you. Is it possible to detect only which poses are not well constrained? That in itself would be a valuable info to have for the SfM

@B1ueber2y
Copy link
Contributor Author

@B1ueber2y Makes sens, thank you. Is it possible to detect only which poses are not well constrained? That in itself would be a valuable info to have for the SfM

The rank deficiency may not be specific to one pose, but can be a global behavior. For example, if the Gauge is not constrained, all the poses can be ambiguous up to a similarity transformation, but the relative poses are well constrained.

@cdcseacave
Copy link

Gauge is constrained on all degrees of freedom (first camera pose is constant), except explicit lock on scale (though it is constrained too indirectly by the relative pose loss)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants