// 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) #include "estimators/pose.h" #include "base/camera_models.h" #include "base/cost_functions.h" #include "base/essential_matrix.h" #include "base/pose.h" #include "estimators/absolute_pose.h" #include "estimators/essential_matrix.h" #include "optim/bundle_adjustment.h" #include "util/matrix.h" #include "util/misc.h" #include "util/threading.h" namespace colmap { namespace { typedef LORANSAC AbsolutePoseRANSAC; void EstimateAbsolutePoseKernel(const Camera& camera, const double focal_length_factor, const std::vector& points2D, const std::vector& points3D, const RANSACOptions& options, AbsolutePoseRANSAC::Report* report) { // Scale the focal length by the given factor. Camera scaled_camera = camera; const std::vector& focal_length_idxs = camera.FocalLengthIdxs(); for (const size_t idx : focal_length_idxs) { scaled_camera.Params(idx) *= focal_length_factor; } // Normalize image coordinates with current camera hypothesis. std::vector points2D_N(points2D.size()); for (size_t i = 0; i < points2D.size(); ++i) { points2D_N[i] = scaled_camera.ImageToWorld(points2D[i]); } // Estimate pose for given focal length. auto custom_options = options; custom_options.max_error = scaled_camera.ImageToWorldThreshold(options.max_error); AbsolutePoseRANSAC ransac(custom_options); *report = ransac.Estimate(points2D_N, points3D); } } // namespace bool EstimateAbsolutePose(const AbsolutePoseEstimationOptions& options, const std::vector& points2D, const std::vector& points3D, Eigen::Vector4d* qvec, Eigen::Vector3d* tvec, Camera* camera, size_t* num_inliers, std::vector* inlier_mask) { options.Check(); std::vector focal_length_factors; if (options.estimate_focal_length) { // Generate focal length factors using a quadratic function, // such that more samples are drawn for small focal lengths focal_length_factors.reserve(options.num_focal_length_samples + 1); const double fstep = 1.0 / options.num_focal_length_samples; const double fscale = options.max_focal_length_ratio - options.min_focal_length_ratio; for (double f = 0; f <= 1.0; f += fstep) { focal_length_factors.push_back(options.min_focal_length_ratio + fscale * f * f); } } else { focal_length_factors.reserve(1); focal_length_factors.push_back(1); } std::vector> futures; futures.resize(focal_length_factors.size()); std::vector> reports; reports.resize(focal_length_factors.size()); ThreadPool thread_pool(std::min( options.num_threads, static_cast(focal_length_factors.size()))); for (size_t i = 0; i < focal_length_factors.size(); ++i) { futures[i] = thread_pool.AddTask( EstimateAbsolutePoseKernel, *camera, focal_length_factors[i], points2D, points3D, options.ransac_options, &reports[i]); } double focal_length_factor = 0; Eigen::Matrix3x4d proj_matrix; *num_inliers = 0; inlier_mask->clear(); // Find best model among all focal lengths. for (size_t i = 0; i < focal_length_factors.size(); ++i) { futures[i].get(); const auto report = reports[i]; if (report.success && report.support.num_inliers > *num_inliers) { *num_inliers = report.support.num_inliers; proj_matrix = report.model; *inlier_mask = report.inlier_mask; focal_length_factor = focal_length_factors[i]; } } if (*num_inliers == 0) { return false; } // Scale output camera with best estimated focal length. if (options.estimate_focal_length && *num_inliers > 0) { const std::vector& focal_length_idxs = camera->FocalLengthIdxs(); for (const size_t idx : focal_length_idxs) { camera->Params(idx) *= focal_length_factor; } } // Extract pose parameters. *qvec = RotationMatrixToQuaternion(proj_matrix.leftCols<3>()); *tvec = proj_matrix.rightCols<1>(); if (IsNaN(*qvec) || IsNaN(*tvec)) { return false; } return true; } size_t EstimateRelativePose(const RANSACOptions& ransac_options, const std::vector& points1, const std::vector& points2, Eigen::Vector4d* qvec, Eigen::Vector3d* tvec) { RANSAC ransac(ransac_options); const auto report = ransac.Estimate(points1, points2); if (!report.success) { return 0; } std::vector inliers1(report.support.num_inliers); std::vector inliers2(report.support.num_inliers); size_t j = 0; for (size_t i = 0; i < points1.size(); ++i) { if (report.inlier_mask[i]) { inliers1[j] = points1[i]; inliers2[j] = points2[i]; j += 1; } } Eigen::Matrix3d R; std::vector points3D; PoseFromEssentialMatrix(report.model, inliers1, inliers2, &R, tvec, &points3D); *qvec = RotationMatrixToQuaternion(R); if (IsNaN(*qvec) || IsNaN(*tvec)) { return 0; } return points3D.size(); } bool RefineAbsolutePose(const AbsolutePoseRefinementOptions& options, const std::vector& inlier_mask, const std::vector& points2D, const std::vector& points3D, Eigen::Vector4d* qvec, Eigen::Vector3d* tvec, Camera* camera, Eigen::Matrix6d* rot_tvec_covariance) { CHECK_EQ(inlier_mask.size(), points2D.size()); CHECK_EQ(points2D.size(), points3D.size()); options.Check(); ceres::LossFunction* loss_function = new ceres::CauchyLoss(options.loss_function_scale); double* camera_params_data = camera->ParamsData(); double* qvec_data = qvec->data(); double* tvec_data = tvec->data(); std::vector points3D_copy = points3D; ceres::Problem problem; for (size_t i = 0; i < points2D.size(); ++i) { // Skip outlier observations if (!inlier_mask[i]) { continue; } ceres::CostFunction* cost_function = nullptr; switch (camera->ModelId()) { #define CAMERA_MODEL_CASE(CameraModel) \ case CameraModel::kModelId: \ cost_function = \ BundleAdjustmentCostFunction::Create(points2D[i]); \ break; CAMERA_MODEL_SWITCH_CASES #undef CAMERA_MODEL_CASE } problem.AddResidualBlock(cost_function, loss_function, qvec_data, tvec_data, points3D_copy[i].data(), camera_params_data); problem.SetParameterBlockConstant(points3D_copy[i].data()); } if (problem.NumResiduals() > 0) { SetQuaternionManifold(&problem, qvec_data); // Camera parameterization. if (!options.refine_focal_length && !options.refine_extra_params) { problem.SetParameterBlockConstant(camera->ParamsData()); } else { // Always set the principal point as fixed. std::vector camera_params_const; const std::vector& principal_point_idxs = camera->PrincipalPointIdxs(); camera_params_const.insert(camera_params_const.end(), principal_point_idxs.begin(), principal_point_idxs.end()); if (!options.refine_focal_length) { const std::vector& focal_length_idxs = camera->FocalLengthIdxs(); camera_params_const.insert(camera_params_const.end(), focal_length_idxs.begin(), focal_length_idxs.end()); } if (!options.refine_extra_params) { const std::vector& extra_params_idxs = camera->ExtraParamsIdxs(); camera_params_const.insert(camera_params_const.end(), extra_params_idxs.begin(), extra_params_idxs.end()); } if (camera_params_const.size() == camera->NumParams()) { problem.SetParameterBlockConstant(camera->ParamsData()); } else { SetSubsetManifold(static_cast(camera->NumParams()), camera_params_const, &problem, camera->ParamsData()); } } } ceres::Solver::Options solver_options; solver_options.gradient_tolerance = options.gradient_tolerance; solver_options.max_num_iterations = options.max_num_iterations; solver_options.linear_solver_type = ceres::DENSE_QR; // The overhead of creating threads is too large. solver_options.num_threads = 1; #if CERES_VERSION_MAJOR < 2 solver_options.num_linear_solver_threads = 1; #endif // CERES_VERSION_MAJOR ceres::Solver::Summary summary; ceres::Solve(solver_options, &problem, &summary); if (solver_options.minimizer_progress_to_stdout) { std::cout << std::endl; } if (options.print_summary) { PrintHeading2("Pose refinement report"); PrintSolverSummary(summary); } if (problem.NumResiduals() > 0 && rot_tvec_covariance != nullptr) { ceres::Covariance::Options options; ceres::Covariance covariance(options); std::vector parameter_blocks = {qvec_data, tvec_data}; if (!covariance.Compute(parameter_blocks, &problem)) { return false; } // The rotation covariance is estimated in the tangent space of the // quaternion, which corresponds to the 3-DoF axis-angle local // parameterization. covariance.GetCovarianceMatrixInTangentSpace(parameter_blocks, rot_tvec_covariance->data()); } return summary.IsSolutionUsable(); } bool RefineRelativePose(const ceres::Solver::Options& options, const std::vector& points1, const std::vector& points2, Eigen::Vector4d* qvec, Eigen::Vector3d* tvec) { CHECK_EQ(points1.size(), points2.size()); // CostFunction assumes unit quaternions. *qvec = NormalizeQuaternion(*qvec); const double kMaxL2Error = 1.0; ceres::LossFunction* loss_function = new ceres::CauchyLoss(kMaxL2Error); ceres::Problem problem; for (size_t i = 0; i < points1.size(); ++i) { ceres::CostFunction* cost_function = RelativePoseCostFunction::Create(points1[i], points2[i]); problem.AddResidualBlock(cost_function, loss_function, qvec->data(), tvec->data()); } SetQuaternionManifold(&problem, qvec->data()); SetSphereManifold<3>(&problem, tvec->data()); ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); return summary.IsSolutionUsable(); } bool RefineGeneralizedAbsolutePose( const AbsolutePoseRefinementOptions& options, const std::vector& inlier_mask, const std::vector& points2D, const std::vector& points3D, const std::vector& camera_idxs, const std::vector& rig_qvecs, const std::vector& rig_tvecs, Eigen::Vector4d* qvec, Eigen::Vector3d* tvec, std::vector* cameras, Eigen::Matrix6d* rot_tvec_covariance) { CHECK_EQ(points2D.size(), inlier_mask.size()); CHECK_EQ(points2D.size(), points3D.size()); CHECK_EQ(points2D.size(), camera_idxs.size()); CHECK_EQ(rig_qvecs.size(), rig_tvecs.size()); CHECK_EQ(rig_qvecs.size(), cameras->size()); CHECK_GE(*std::min_element(camera_idxs.begin(), camera_idxs.end()), 0); CHECK_LT(*std::max_element(camera_idxs.begin(), camera_idxs.end()), cameras->size()); options.Check(); ceres::LossFunction* loss_function = new ceres::CauchyLoss(options.loss_function_scale); std::vector cameras_params_data; for (size_t i = 0; i < cameras->size(); i++) { cameras_params_data.push_back(cameras->at(i).ParamsData()); } std::vector camera_counts(cameras->size(), 0); double* qvec_data = qvec->data(); double* tvec_data = tvec->data(); std::vector points3D_copy = points3D; std::vector rig_qvecs_copy = rig_qvecs; std::vector rig_tvecs_copy = rig_tvecs; ceres::Problem problem; for (size_t i = 0; i < points2D.size(); ++i) { // Skip outlier observations if (!inlier_mask[i]) { continue; } const size_t camera_idx = camera_idxs[i]; camera_counts[camera_idx] += 1; ceres::CostFunction* cost_function = nullptr; switch (cameras->at(camera_idx).ModelId()) { #define CAMERA_MODEL_CASE(CameraModel) \ case CameraModel::kModelId: \ cost_function = \ RigBundleAdjustmentCostFunction::Create(points2D[i]); \ break; CAMERA_MODEL_SWITCH_CASES #undef CAMERA_MODEL_CASE } problem.AddResidualBlock( cost_function, loss_function, qvec_data, tvec_data, rig_qvecs_copy[camera_idx].data(), rig_tvecs_copy[camera_idx].data(), points3D_copy[i].data(), cameras_params_data[camera_idx]); problem.SetParameterBlockConstant(points3D_copy[i].data()); } if (problem.NumResiduals() > 0) { SetQuaternionManifold(&problem, qvec_data); // Camera parameterization. for (size_t i = 0; i < cameras->size(); i++) { if (camera_counts[i] == 0) continue; Camera& camera = cameras->at(i); // We don't optimize the rig parameters (it's likely under-constrained) problem.SetParameterBlockConstant(rig_qvecs_copy[i].data()); problem.SetParameterBlockConstant(rig_tvecs_copy[i].data()); if (!options.refine_focal_length && !options.refine_extra_params) { problem.SetParameterBlockConstant(camera.ParamsData()); } else { // Always set the principal point as fixed. std::vector camera_params_const; const std::vector& principal_point_idxs = camera.PrincipalPointIdxs(); camera_params_const.insert(camera_params_const.end(), principal_point_idxs.begin(), principal_point_idxs.end()); if (!options.refine_focal_length) { const std::vector& focal_length_idxs = camera.FocalLengthIdxs(); camera_params_const.insert(camera_params_const.end(), focal_length_idxs.begin(), focal_length_idxs.end()); } if (!options.refine_extra_params) { const std::vector& extra_params_idxs = camera.ExtraParamsIdxs(); camera_params_const.insert(camera_params_const.end(), extra_params_idxs.begin(), extra_params_idxs.end()); } if (camera_params_const.size() == camera.NumParams()) { problem.SetParameterBlockConstant(camera.ParamsData()); } else { SetSubsetManifold(static_cast(camera.NumParams()), camera_params_const, &problem, camera.ParamsData()); } } } } ceres::Solver::Options solver_options; solver_options.gradient_tolerance = options.gradient_tolerance; solver_options.max_num_iterations = options.max_num_iterations; solver_options.linear_solver_type = ceres::DENSE_QR; // The overhead of creating threads is too large. solver_options.num_threads = 1; #if CERES_VERSION_MAJOR < 2 solver_options.num_linear_solver_threads = 1; #endif // CERES_VERSION_MAJOR ceres::Solver::Summary summary; ceres::Solve(solver_options, &problem, &summary); if (solver_options.minimizer_progress_to_stdout) { std::cout << std::endl; } if (options.print_summary) { PrintHeading2("Pose refinement report"); PrintSolverSummary(summary); } if (problem.NumResiduals() > 0 && rot_tvec_covariance != nullptr) { ceres::Covariance::Options options; ceres::Covariance covariance(options); std::vector parameter_blocks = {qvec_data, tvec_data}; if (!covariance.Compute(parameter_blocks, &problem)) { return false; } covariance.GetCovarianceMatrixInTangentSpace(parameter_blocks, rot_tvec_covariance->data()); } return summary.IsSolutionUsable(); } } // namespace colmap