// Copyright (c) 2022, ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de) #define TEST_NAME "optim/bundle_adjustment" #include "util/testing.h" #include "base/camera_models.h" #include "base/correspondence_graph.h" #include "base/projection.h" #include "optim/bundle_adjustment.h" #include "util/random.h" #define CheckVariableCamera(camera, orig_camera) \ { \ const size_t focal_length_idx = \ SimpleRadialCameraModel::focal_length_idxs[0]; \ const size_t extra_param_idx = \ SimpleRadialCameraModel::extra_params_idxs[0]; \ BOOST_CHECK_NE(camera.Params(focal_length_idx), \ orig_camera.Params(focal_length_idx)); \ BOOST_CHECK_NE(camera.Params(extra_param_idx), \ orig_camera.Params(extra_param_idx)); \ } #define CheckConstantCamera(camera, orig_camera) \ { \ const size_t focal_length_idx = \ SimpleRadialCameraModel::focal_length_idxs[0]; \ const size_t extra_param_idx = \ SimpleRadialCameraModel::extra_params_idxs[0]; \ BOOST_CHECK_EQUAL(camera.Params(focal_length_idx), \ orig_camera.Params(focal_length_idx)); \ BOOST_CHECK_EQUAL(camera.Params(extra_param_idx), \ orig_camera.Params(extra_param_idx)); \ } #define CheckVariableImage(image, orig_image) \ { \ BOOST_CHECK_NE(image.Qvec(), orig_image.Qvec()); \ BOOST_CHECK_NE(image.Tvec(), orig_image.Tvec()); \ } #define CheckConstantImage(image, orig_image) \ { \ BOOST_CHECK_EQUAL(image.Qvec(), orig_image.Qvec()); \ BOOST_CHECK_EQUAL(image.Tvec(), orig_image.Tvec()); \ } #define CheckConstantXImage(image, orig_image) \ { \ CheckVariableImage(image, orig_image); \ BOOST_CHECK_EQUAL(image.Tvec(0), orig_image.Tvec(0)); \ } #define CheckConstantCameraRig(camera_rig, orig_camera_rig, camera_id) \ { \ BOOST_CHECK_EQUAL(camera_rig.RelativeQvec(camera_id), \ orig_camera_rig.RelativeQvec(camera_id)); \ BOOST_CHECK_EQUAL(camera_rig.RelativeTvec(camera_id), \ orig_camera_rig.RelativeTvec(camera_id)); \ } #define CheckVariableCameraRig(camera_rig, orig_camera_rig, camera_id) \ { \ if (camera_rig.RefCameraId() == camera_id) { \ CheckConstantCameraRig(camera_rig, orig_camera_rig, camera_id); \ } else { \ BOOST_CHECK_NE(camera_rig.RelativeQvec(camera_id), \ orig_camera_rig.RelativeQvec(camera_id)); \ BOOST_CHECK_NE(camera_rig.RelativeTvec(camera_id), \ orig_camera_rig.RelativeTvec(camera_id)); \ } \ } #define CheckVariablePoint(point, orig_point) \ { BOOST_CHECK_NE(point.XYZ(), orig_point.XYZ()); } #define CheckConstantPoint(point, orig_point) \ { BOOST_CHECK_EQUAL(point.XYZ(), orig_point.XYZ()); } using namespace colmap; void GeneratePointCloud(const size_t num_points, const Eigen::Vector3d& min, const Eigen::Vector3d& max, Reconstruction* reconstruction) { for (size_t i = 0; i < num_points; ++i) { Eigen::Vector3d xyz; xyz.x() = RandomReal(min.x(), max.x()); xyz.y() = RandomReal(min.y(), max.y()); xyz.z() = RandomReal(min.z(), max.z()); reconstruction->AddPoint3D(xyz, Track()); } } void GenerateReconstruction(const size_t num_images, const size_t num_points, Reconstruction* reconstruction, CorrespondenceGraph* correspondence_graph) { SetPRNGSeed(0); GeneratePointCloud(num_points, Eigen::Vector3d(-1, -1, -1), Eigen::Vector3d(1, 1, 1), reconstruction); const double kFocalLengthFactor = 1.2; const size_t kImageSize = 1000; for (size_t i = 0; i < num_images; ++i) { const camera_t camera_id = static_cast(i); const image_t image_id = static_cast(i); Camera camera; camera.InitializeWithId(SimpleRadialCameraModel::model_id, kFocalLengthFactor * kImageSize, kImageSize, kImageSize); camera.SetCameraId(camera_id); reconstruction->AddCamera(camera); Image image; image.SetImageId(image_id); image.SetCameraId(camera_id); image.SetName(std::to_string(i)); image.Qvec() = ComposeIdentityQuaternion(); image.Tvec() = Eigen::Vector3d(RandomReal(-1.0, 1.0), RandomReal(-1.0, 1.0), 10); image.SetRegistered(true); reconstruction->AddImage(image); const Eigen::Matrix3x4d proj_matrix = image.ProjectionMatrix(); std::vector points2D; for (const auto& point3D : reconstruction->Points3D()) { BOOST_CHECK(HasPointPositiveDepth(proj_matrix, point3D.second.XYZ())); // Get exact projection of 3D point. Eigen::Vector2d point2D = ProjectPointToImage(point3D.second.XYZ(), proj_matrix, camera); // Add some uniform noise. point2D += Eigen::Vector2d(RandomReal(-2.0, 2.0), RandomReal(-2.0, 2.0)); points2D.push_back(point2D); } correspondence_graph->AddImage(image_id, num_points); reconstruction->Image(image_id).SetPoints2D(points2D); } reconstruction->SetUp(correspondence_graph); for (size_t i = 0; i < num_images; ++i) { const image_t image_id = static_cast(i); TrackElement track_el; track_el.image_id = image_id; track_el.point2D_idx = 0; for (const auto& point3D : reconstruction->Points3D()) { reconstruction->AddObservation(point3D.first, track_el); track_el.point2D_idx += 1; } } } BOOST_AUTO_TEST_CASE(TestConfigNumObservations) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(4, 100, &reconstruction, &correspondence_graph); BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); BOOST_CHECK_EQUAL(config.NumResiduals(reconstruction), 400); config.AddVariablePoint(1); BOOST_CHECK_EQUAL(config.NumResiduals(reconstruction), 404); config.AddConstantPoint(2); BOOST_CHECK_EQUAL(config.NumResiduals(reconstruction), 408); config.AddImage(2); BOOST_CHECK_EQUAL(config.NumResiduals(reconstruction), 604); config.AddImage(3); BOOST_CHECK_EQUAL(config.NumResiduals(reconstruction), 800); } BOOST_AUTO_TEST_CASE(TestTwoView) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantTvec(1, {0}); BundleAdjustmentOptions options; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 5 image parameters (pose of second image) // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 309); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantXImage(reconstruction.Image(1), orig_reconstruction.Image(1)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestTwoViewConstantCamera) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantPose(1); config.SetConstantCamera(0); BundleAdjustmentOptions options; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 302); CheckConstantCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantImage(reconstruction.Image(1), orig_reconstruction.Image(1)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestPartiallyContainedTracks) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(3, 100, &reconstruction, &correspondence_graph); const auto variable_point3D_id = reconstruction.Image(2).Point2D(0).Point3DId(); reconstruction.DeleteObservation(2, 0); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantPose(1); BundleAdjustmentOptions options; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 1 x 3 point parameters // 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 7); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckConstantCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); CheckConstantImage(reconstruction.Image(2), orig_reconstruction.Image(2)); for (const auto& point3D : reconstruction.Points3D()) { if (point3D.first == variable_point3D_id) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } else { CheckConstantPoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } } BOOST_AUTO_TEST_CASE(TestPartiallyContainedTracksForceToOptimizePoint) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(3, 100, &reconstruction, &correspondence_graph); const point3D_t variable_point3D_id = reconstruction.Image(2).Point2D(0).Point3DId(); const point3D_t add_variable_point3D_id = reconstruction.Image(2).Point2D(1).Point3DId(); const point3D_t add_constant_point3D_id = reconstruction.Image(2).Point2D(2).Point3DId(); reconstruction.DeleteObservation(2, 0); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantPose(1); config.AddVariablePoint(add_variable_point3D_id); config.AddConstantPoint(add_constant_point3D_id); BundleAdjustmentOptions options; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image // + 2 residuals in 3rd image for added variable 3D point // (added constant point does not add residuals since the image/camera // is also constant). BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 402); // 2 x 3 point parameters // 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 10); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckConstantCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); CheckConstantImage(reconstruction.Image(2), orig_reconstruction.Image(2)); for (const auto& point3D : reconstruction.Points3D()) { if (point3D.first == variable_point3D_id || point3D.first == add_variable_point3D_id) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } else { CheckConstantPoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } } BOOST_AUTO_TEST_CASE(TestConstantPoints) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; const point3D_t constant_point3D_id1 = 1; const point3D_t constant_point3D_id2 = 2; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantPose(1); config.AddConstantPoint(constant_point3D_id1); config.AddConstantPoint(constant_point3D_id2); BundleAdjustmentOptions options; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 98 x 3 point parameters // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 298); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantImage(reconstruction.Image(1), orig_reconstruction.Image(1)); for (const auto& point3D : reconstruction.Points3D()) { if (point3D.first == constant_point3D_id1 || point3D.first == constant_point3D_id2) { CheckConstantPoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } else { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } } BOOST_AUTO_TEST_CASE(TestVariableImage) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(3, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.AddImage(2); config.SetConstantPose(0); config.SetConstantTvec(1, {0}); BundleAdjustmentOptions options; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 3 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 600); // 100 x 3 point parameters // + 5 image parameters (pose of second image) // + 6 image parameters (pose of third image) // + 3 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 317); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckConstantXImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckVariableCamera(reconstruction.Camera(2), orig_reconstruction.Camera(2)); CheckVariableImage(reconstruction.Image(2), orig_reconstruction.Image(2)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestConstantFocalLength) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantTvec(1, {0}); BundleAdjustmentOptions options; options.refine_focal_length = false; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 3 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 5 image parameters (pose of second image) // + 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 307); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckConstantXImage(reconstruction.Image(1), orig_reconstruction.Image(1)); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; const auto& camera0 = reconstruction.Camera(0); const auto& orig_camera0 = orig_reconstruction.Camera(0); BOOST_CHECK(camera0.Params(focal_length_idx) == orig_camera0.Params(focal_length_idx)); BOOST_CHECK(camera0.Params(extra_param_idx) != orig_camera0.Params(extra_param_idx)); const auto& camera1 = reconstruction.Camera(1); const auto& orig_camera1 = orig_reconstruction.Camera(1); BOOST_CHECK(camera1.Params(focal_length_idx) == orig_camera1.Params(focal_length_idx)); BOOST_CHECK(camera1.Params(extra_param_idx) != orig_camera1.Params(extra_param_idx)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestVariablePrincipalPoint) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantTvec(1, {0}); BundleAdjustmentOptions options; options.refine_principal_point = true; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 3 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 5 image parameters (pose of second image) // + 8 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 313); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckConstantXImage(reconstruction.Image(1), orig_reconstruction.Image(1)); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t principal_point_idx_x = SimpleRadialCameraModel::principal_point_idxs[0]; const size_t principal_point_idx_y = SimpleRadialCameraModel::principal_point_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; const auto& camera0 = reconstruction.Camera(0); const auto& orig_camera0 = orig_reconstruction.Camera(0); BOOST_CHECK(camera0.Params(focal_length_idx) != orig_camera0.Params(focal_length_idx)); BOOST_CHECK(camera0.Params(principal_point_idx_x) != orig_camera0.Params(principal_point_idx_x)); BOOST_CHECK(camera0.Params(principal_point_idx_y) != orig_camera0.Params(principal_point_idx_y)); BOOST_CHECK(camera0.Params(extra_param_idx) != orig_camera0.Params(extra_param_idx)); const auto& camera1 = reconstruction.Camera(1); const auto& orig_camera1 = orig_reconstruction.Camera(1); BOOST_CHECK(camera1.Params(focal_length_idx) != orig_camera1.Params(focal_length_idx)); BOOST_CHECK(camera1.Params(principal_point_idx_x) != orig_camera1.Params(principal_point_idx_x)); BOOST_CHECK(camera1.Params(principal_point_idx_y) != orig_camera1.Params(principal_point_idx_y)); BOOST_CHECK(camera1.Params(extra_param_idx) != orig_camera1.Params(extra_param_idx)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestConstantExtraParam) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.SetConstantPose(0); config.SetConstantTvec(1, {0}); BundleAdjustmentOptions options; options.refine_extra_params = false; BundleAdjuster bundle_adjuster(options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 3 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 5 image parameters (pose of second image) // + 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 307); CheckConstantImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckConstantXImage(reconstruction.Image(1), orig_reconstruction.Image(1)); const size_t focal_length_idx = SimpleRadialCameraModel::focal_length_idxs[0]; const size_t extra_param_idx = SimpleRadialCameraModel::extra_params_idxs[0]; const auto& camera0 = reconstruction.Camera(0); const auto& orig_camera0 = orig_reconstruction.Camera(0); BOOST_CHECK(camera0.Params(focal_length_idx) != orig_camera0.Params(focal_length_idx)); BOOST_CHECK(camera0.Params(extra_param_idx) == orig_camera0.Params(extra_param_idx)); const auto& camera1 = reconstruction.Camera(1); const auto& orig_camera1 = orig_reconstruction.Camera(1); BOOST_CHECK(camera1.Params(focal_length_idx) != orig_camera1.Params(focal_length_idx)); BOOST_CHECK(camera1.Params(extra_param_idx) == orig_camera1.Params(extra_param_idx)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestParallelReconstructionSupported) { BundleAdjustmentOptions options; options.refine_focal_length = true; options.refine_principal_point = false; options.refine_extra_params = true; Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); BOOST_CHECK(ParallelBundleAdjuster::IsSupported(options, reconstruction)); reconstruction.Camera(0).SetModelIdFromName("SIMPLE_PINHOLE"); BOOST_CHECK(!ParallelBundleAdjuster::IsSupported(options, reconstruction)); reconstruction.Camera(0).SetModelIdFromName("SIMPLE_RADIAL"); BOOST_CHECK(ParallelBundleAdjuster::IsSupported(options, reconstruction)); options.refine_principal_point = true; BOOST_CHECK(!ParallelBundleAdjuster::IsSupported(options, reconstruction)); options.refine_principal_point = false; options.refine_focal_length = false; BOOST_CHECK(!ParallelBundleAdjuster::IsSupported(options, reconstruction)); options.refine_extra_params = false; BOOST_CHECK(ParallelBundleAdjuster::IsSupported(options, reconstruction)); options.refine_focal_length = true; BOOST_CHECK(!ParallelBundleAdjuster::IsSupported(options, reconstruction)); } BOOST_AUTO_TEST_CASE(TestParallelTwoViewVariableIntrinsics) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); ParallelBundleAdjuster::Options options; BundleAdjustmentOptions ba_options; ba_options.refine_focal_length = true; ba_options.refine_principal_point = false; ba_options.refine_extra_params = true; ParallelBundleAdjuster bundle_adjuster(options, ba_options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 12 image parameters // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 316); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckVariableImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableImage(reconstruction.Image(1), orig_reconstruction.Image(1)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestParallelTwoViewConstantIntrinsics) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); ParallelBundleAdjuster::Options options; BundleAdjustmentOptions ba_options; ba_options.refine_focal_length = false; ba_options.refine_principal_point = false; ba_options.refine_extra_params = false; ParallelBundleAdjuster bundle_adjuster(options, ba_options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 12 image parameters // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 316); CheckConstantCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckVariableImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckConstantCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableImage(reconstruction.Image(1), orig_reconstruction.Image(1)); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestRigTwoView) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(2, 100, &reconstruction, &correspondence_graph); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); std::vector camera_rigs; camera_rigs.emplace_back(); camera_rigs[0].AddCamera(0, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddCamera(1, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddSnapshot({0, 1}); camera_rigs[0].SetRefCameraId(0); const auto orig_camera_rigs = camera_rigs; BundleAdjustmentOptions options; RigBundleAdjuster::Options rig_options; RigBundleAdjuster bundle_adjuster(options, rig_options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction, &camera_rigs)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 400); // 100 x 3 point parameters // + 6 pose parameters for camera rig // + 1 x 6 relative pose parameters for camera rig // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 316); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckVariableImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckVariableCameraRig(camera_rigs[0], orig_camera_rigs[0], 0); CheckVariableCameraRig(camera_rigs[0], orig_camera_rigs[0], 1); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestRigFourView) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(4, 100, &reconstruction, &correspondence_graph); reconstruction.Image(2).SetCameraId(0); reconstruction.Image(3).SetCameraId(1); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.AddImage(2); config.AddImage(3); std::vector camera_rigs; camera_rigs.emplace_back(); camera_rigs[0].AddCamera(0, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddCamera(1, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddSnapshot({0, 1}); camera_rigs[0].AddSnapshot({2, 3}); camera_rigs[0].SetRefCameraId(0); const auto orig_camera_rigs = camera_rigs; BundleAdjustmentOptions options; RigBundleAdjuster::Options rig_options; RigBundleAdjuster bundle_adjuster(options, rig_options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction, &camera_rigs)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 800); // 100 x 3 point parameters // + 2 x 6 pose parameters for camera rig // + 1 x 6 relative pose parameters for camera rig // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 322); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckVariableImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckVariableCameraRig(camera_rigs[0], orig_camera_rigs[0], 0); CheckVariableCameraRig(camera_rigs[0], orig_camera_rigs[0], 1); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestConstantRigFourView) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(4, 100, &reconstruction, &correspondence_graph); reconstruction.Image(2).SetCameraId(0); reconstruction.Image(3).SetCameraId(1); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.AddImage(2); config.AddImage(3); std::vector camera_rigs; camera_rigs.emplace_back(); camera_rigs[0].AddCamera(0, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddCamera(1, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddSnapshot({0, 1}); camera_rigs[0].AddSnapshot({2, 3}); camera_rigs[0].SetRefCameraId(0); const auto orig_camera_rigs = camera_rigs; BundleAdjustmentOptions options; RigBundleAdjuster::Options rig_options; rig_options.refine_relative_poses = false; RigBundleAdjuster bundle_adjuster(options, rig_options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction, &camera_rigs)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 800); // 100 x 3 point parameters // + 2 x 6 pose parameters for camera rig // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 316); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckVariableImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckConstantCameraRig(camera_rigs[0], orig_camera_rigs[0], 0); CheckConstantCameraRig(camera_rigs[0], orig_camera_rigs[0], 1); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } } BOOST_AUTO_TEST_CASE(TestRigFourViewPartial) { Reconstruction reconstruction; CorrespondenceGraph correspondence_graph; GenerateReconstruction(4, 100, &reconstruction, &correspondence_graph); reconstruction.Image(2).SetCameraId(0); reconstruction.Image(3).SetCameraId(1); const auto orig_reconstruction = reconstruction; BundleAdjustmentConfig config; config.AddImage(0); config.AddImage(1); config.AddImage(2); config.AddImage(3); std::vector camera_rigs; camera_rigs.emplace_back(); camera_rigs[0].AddCamera(0, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddCamera(1, ComposeIdentityQuaternion(), Eigen::Vector3d(0, 0, 0)); camera_rigs[0].AddSnapshot({0, 1}); camera_rigs[0].AddSnapshot({2}); camera_rigs[0].SetRefCameraId(0); const auto orig_camera_rigs = camera_rigs; BundleAdjustmentOptions options; RigBundleAdjuster::Options rig_options; RigBundleAdjuster bundle_adjuster(options, rig_options, config); BOOST_REQUIRE(bundle_adjuster.Solve(&reconstruction, &camera_rigs)); const auto summary = bundle_adjuster.Summary(); // 100 points, 2 images, 2 residuals per point per image BOOST_CHECK_EQUAL(summary.num_residuals_reduced, 800); // 100 x 3 point parameters // + 2 x 6 pose parameters for camera rig // + 1 x 6 relative pose parameters for camera rig // + 1 x 6 pose parameters for individual image // + 2 x 2 camera parameters BOOST_CHECK_EQUAL(summary.num_effective_parameters_reduced, 328); CheckVariableCamera(reconstruction.Camera(0), orig_reconstruction.Camera(0)); CheckVariableImage(reconstruction.Image(0), orig_reconstruction.Image(0)); CheckVariableCamera(reconstruction.Camera(1), orig_reconstruction.Camera(1)); CheckVariableImage(reconstruction.Image(1), orig_reconstruction.Image(1)); CheckVariableCameraRig(camera_rigs[0], orig_camera_rigs[0], 0); CheckVariableCameraRig(camera_rigs[0], orig_camera_rigs[0], 1); for (const auto& point3D : reconstruction.Points3D()) { CheckVariablePoint(point3D.second, orig_reconstruction.Point3D(point3D.first)); } }