| // Ceres Solver - A fast non-linear least squares minimizer | |
| // Copyright 2026 Google Inc. All rights reserved. | |
| // Copyright (c) 2016, ETH Zurich and UNC Chapel Hill. | |
| // Copyright (c) 2016-2026, The COLMAP Contributors. | |
| // Copyright (c) 2011-2013, libmv authors. | |
| // http://ceres-solver.org/ | |
| // | |
| // 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 Google Inc., ETH Zurich, 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 OWNER 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. | |
| // | |
| // SPDX-License-Identifier: BSD-3-Clause AND MIT | |
| // | |
| // Authors: darius.rueckert@fau.de (Darius Rueckert) | |
| // nikolaus@nikolaus-demmel.de (Nikolaus Demmel) | |
| // sameeragarwal@google.com (Sameer Agarwal) | |
| #ifndef CERES_INTERNAL_BENCHMARK_COST_FUNCTIONS_H_ | |
| #define CERES_INTERNAL_BENCHMARK_COST_FUNCTIONS_H_ | |
| #include <algorithm> | |
| #include <array> | |
| #include <cmath> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <numeric> | |
| #include <random> | |
| #include <tuple> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #include "Eigen/Core" | |
| #include "Eigen/Dense" | |
| #include "Eigen/Geometry" | |
| #include "ceres/ceres.h" | |
| #include "ceres/constants.h" | |
| #include "ceres/cubic_interpolation.h" | |
| #include "ceres/rotation.h" | |
| namespace ceres::internal { | |
| // Owns fixed-size parameter block arrays for a benchmark cost functor and | |
| // exposes an array of pointers suitable for CostFunction::Evaluate. | |
| template <int... Ns> | |
| class ParameterBlockStorage { | |
| public: | |
| static constexpr int kNumBlocks = sizeof...(Ns); | |
| explicit ParameterBlockStorage(const std::array<double, Ns>&... blocks) | |
| : blocks_(blocks...) {} | |
| std::array<const double*, kNumBlocks> Pointers() const { | |
| return PointersImpl(std::make_index_sequence<kNumBlocks>()); | |
| } | |
| private: | |
| template <size_t... Indices> | |
| std::array<const double*, kNumBlocks> PointersImpl( | |
| std::index_sequence<Indices...>) const { | |
| return {std::get<Indices>(blocks_).data()...}; | |
| } | |
| std::tuple<std::array<double, Ns>...> blocks_; | |
| }; | |
| // Adapts a fixed-arity cost functor `CostFunctor(p0, p1, ..., residuals)` to | |
| // the dynamic-arity signature `operator()(const T* const* parameters, T* | |
| // residuals)` expected by DynamicAutoDiffCostFunction and | |
| // DynamicNumericDiffCostFunction. | |
| template <typename CostFunctor, int kNumParameterBlocks> | |
| class DynamicCostFunctorAdapter { | |
| public: | |
| template <typename... Args> | |
| explicit DynamicCostFunctorAdapter(Args&&... args) | |
| : functor_(std::forward<Args>(args)...) {} | |
| template <typename T> | |
| EIGEN_STRONG_INLINE bool operator()(const T* const* parameters, | |
| T* residuals) const { | |
| return Apply( | |
| parameters, residuals, std::make_index_sequence<kNumParameterBlocks>()); | |
| } | |
| private: | |
| template <typename T, size_t... Indices> | |
| EIGEN_STRONG_INLINE bool Apply(const T* const* parameters, | |
| T* residuals, | |
| std::index_sequence<Indices...>) const { | |
| return functor_(parameters[Indices]..., residuals); | |
| } | |
| CostFunctor functor_; | |
| }; | |
| // Base class declaring the residual count and parameter block dimensions of a | |
| // benchmark cost functor, along with CostFunction, Manifold, and LossFunction | |
| // factories. | |
| template <int kResiduals, int... Ns> | |
| struct BenchmarkCostFunctor { | |
| static constexpr int kNumResiduals = kResiduals; | |
| static constexpr int kNumParameterBlocks = sizeof...(Ns); | |
| static constexpr int kTotalParameters = (Ns + ...); | |
| static constexpr std::array<int, kNumParameterBlocks> kBlockSizes = {Ns...}; | |
| using ParameterBlocks = ParameterBlockStorage<Ns...>; | |
| template <typename CostFunctor, typename... Args> | |
| static std::unique_ptr<CostFunction> CreateAutoDiff(Args&&... args) { | |
| return std::make_unique< | |
| AutoDiffCostFunction<CostFunctor, kNumResiduals, Ns...>>( | |
| std::make_unique<CostFunctor>(std::forward<Args>(args)...)); | |
| } | |
| template <typename CostFunctor, typename... Args> | |
| static std::unique_ptr<CostFunction> CreateAutoDiffDynamicResiduals( | |
| Args&&... args) { | |
| return std::make_unique<AutoDiffCostFunction<CostFunctor, DYNAMIC, Ns...>>( | |
| std::make_unique<CostFunctor>(std::forward<Args>(args)...), | |
| kNumResiduals); | |
| } | |
| template <typename CostFunctor, typename... Args> | |
| static std::unique_ptr<CostFunction> CreateDynamicAutoDiff(Args&&... args) { | |
| using Adapter = DynamicCostFunctorAdapter<CostFunctor, kNumParameterBlocks>; | |
| auto cost_function = std::make_unique<DynamicAutoDiffCostFunction<Adapter>>( | |
| std::make_unique<Adapter>(std::forward<Args>(args)...)); | |
| (cost_function->AddParameterBlock(Ns), ...); | |
| cost_function->SetNumResiduals(kNumResiduals); | |
| return cost_function; | |
| } | |
| template <typename CostFunctor, | |
| NumericDiffMethodType kMethod, | |
| typename... Args> | |
| static std::unique_ptr<CostFunction> CreateNumericDiff(Args&&... args) { | |
| return std::make_unique< | |
| NumericDiffCostFunction<CostFunctor, kMethod, kNumResiduals, Ns...>>( | |
| std::make_unique<CostFunctor>(std::forward<Args>(args)...)); | |
| } | |
| template <typename CostFunctor, | |
| NumericDiffMethodType kMethod, | |
| typename... Args> | |
| static std::unique_ptr<CostFunction> CreateDynamicNumericDiff( | |
| Args&&... args) { | |
| using Adapter = DynamicCostFunctorAdapter<CostFunctor, kNumParameterBlocks>; | |
| auto cost_function = | |
| std::make_unique<DynamicNumericDiffCostFunction<Adapter, kMethod>>( | |
| std::make_unique<const Adapter>(std::forward<Args>(args)...)); | |
| (cost_function->AddParameterBlock(Ns), ...); | |
| cost_function->SetNumResiduals(kNumResiduals); | |
| return cost_function; | |
| } | |
| static std::unique_ptr<Manifold> MakeManifold(int /*block_index*/) { | |
| return nullptr; | |
| } | |
| static std::unique_ptr<LossFunction> MakeLossFunction() { return nullptr; } | |
| }; | |
| inline std::array<double, 3> NormalizedVector3(double x, double y, double z) { | |
| std::array v{x, y, z}; | |
| Eigen::Map<Eigen::Vector3d>(v.data()).normalize(); | |
| return v; | |
| } | |
| inline std::array<double, 4> NormalizedQuaternion(double qx, | |
| double qy, | |
| double qz, | |
| double qw) { | |
| std::array q{qx, qy, qz, qw}; | |
| Eigen::Map<Eigen::Quaterniond>(q.data()).normalize(); | |
| return q; | |
| } | |
| inline std::array<double, 7> NormalizedPose(double qx, | |
| double qy, | |
| double qz, | |
| double qw, | |
| double tx, | |
| double ty, | |
| double tz) { | |
| std::array pose{qx, qy, qz, qw, tx, ty, tz}; | |
| Eigen::Map<Eigen::Quaterniond>(pose.data()).normalize(); | |
| return pose; | |
| } | |
| // Converts the angle-axis rotation and translation prefix of a 9-parameter BAL | |
| // camera `[rx, ry, rz, tx, ty, tz, ...]` into a 7-parameter pose `[qx, qy, qz, | |
| // qw, tx, ty, tz]` with an Eigen-convention quaternion. | |
| inline std::array<double, 7> PoseFromBALCamera(const double* bal_camera) { | |
| double q_wxyz[4]; | |
| AngleAxisToQuaternion(bal_camera, q_wxyz); | |
| return {q_wxyz[1], | |
| q_wxyz[2], | |
| q_wxyz[3], | |
| q_wxyz[0], | |
| bal_camera[3], | |
| bal_camera[4], | |
| bal_camera[5]}; | |
| } | |
| template <int kParameterBlockSize> | |
| struct ConstantCostFunction : BenchmarkCostFunctor<1, kParameterBlockSize> { | |
| using | |
| typename BenchmarkCostFunctor<1, kParameterBlockSize>::ParameterBlocks; | |
| static ParameterBlocks MakeParameters() { | |
| std::array<double, kParameterBlockSize> x{}; | |
| std::iota(x.begin(), x.end(), 0.0); | |
| return ParameterBlocks(x); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const x, T* residuals) const { | |
| residuals[0] = T(5); | |
| return true; | |
| } | |
| }; | |
| template <int kDimension> | |
| struct LinearCostFunction : BenchmarkCostFunctor<kDimension, kDimension> { | |
| using typename BenchmarkCostFunctor<kDimension, kDimension>::ParameterBlocks; | |
| static ParameterBlocks MakeParameters() { | |
| std::array<double, kDimension> x{}; | |
| std::iota(x.begin(), x.end(), 1.0); | |
| return ParameterBlocks(x); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const x, T* residuals) const { | |
| for (int i = 0; i < kDimension; ++i) { | |
| residuals[i] = x[i] + T(i); | |
| } | |
| return true; | |
| } | |
| }; | |
| // From the NIST problem collection. | |
| struct Rat43CostFunctor : BenchmarkCostFunctor<1, 4> { | |
| explicit Rat43CostFunctor(const double x = 0.2, const double y = 0.3) | |
| : x_(x), y_(y) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks({1., 2., 3., 4.}); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* parameters, T* residuals) const { | |
| const T& b1 = parameters[0]; | |
| const T& b2 = parameters[1]; | |
| const T& b3 = parameters[2]; | |
| const T& b4 = parameters[3]; | |
| residuals[0] = b1 * pow(1.0 + exp(b2 - b3 * x_), -1.0 / b4) - y_; | |
| return true; | |
| } | |
| private: | |
| const double x_; | |
| const double y_; | |
| }; | |
| struct SnavelyReprojectionError : BenchmarkCostFunctor<2, 9, 3> { | |
| static constexpr int kPointBlockIndex = 1; | |
| explicit SnavelyReprojectionError(double observed_x = 0.2, | |
| double observed_y = 0.3) | |
| : observed_x(observed_x), observed_y(observed_y) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks({1., 2., 3., 4., 5., 6., 7., 8., 9.}, {1., 2., 3.}); | |
| } | |
| static ParameterBlocks MakeParametersFromBAL(const double* bal_camera, | |
| const double* bal_point) { | |
| std::array<double, 9> camera{}; | |
| std::array<double, 3> point{}; | |
| std::copy_n(bal_camera, 9, camera.data()); | |
| std::copy_n(bal_point, 3, point.data()); | |
| return ParameterBlocks(camera, point); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const camera, | |
| const T* const point, | |
| T* residuals) const { | |
| T ox = T(observed_x); | |
| T oy = T(observed_y); | |
| // camera[0,1,2] are the angle-axis rotation. | |
| T p[3]; | |
| ceres::AngleAxisRotatePoint(camera, point, p); | |
| // camera[3,4,5] are the translation. | |
| p[0] += camera[3]; | |
| p[1] += camera[4]; | |
| p[2] += camera[5]; | |
| // Compute the center of distortion. The sign change comes from | |
| // the camera model that Noah Snavely's Bundler assumes, whereby | |
| // the camera coordinate system has a negative z axis. | |
| const T xp = -p[0] / p[2]; | |
| const T yp = -p[1] / p[2]; | |
| // Apply second and fourth order radial distortion. | |
| const T& l1 = camera[7]; | |
| const T& l2 = camera[8]; | |
| const T r2 = xp * xp + yp * yp; | |
| const T distortion = T(1.0) + r2 * (l1 + l2 * r2); | |
| // Compute final projected point position. | |
| const T& focal = camera[6]; | |
| const T predicted_x = focal * distortion * xp; | |
| const T predicted_y = focal * distortion * yp; | |
| // The error is the difference between the predicted and observed position. | |
| residuals[0] = predicted_x - ox; | |
| residuals[1] = predicted_y - oy; | |
| return true; | |
| } | |
| double observed_x; | |
| double observed_y; | |
| }; | |
| // Photometric residual that computes the intensity difference for a patch | |
| // between host and target frame. The point is parameterized with inverse | |
| // distance relative to the host frame. The relative pose between host and | |
| // target frame is computed from their respective absolute poses. | |
| // | |
| // The residual is similar to the one defined by Engel et al. [1]. Differences | |
| // include: | |
| // | |
| // 1. Use of a camera model based on spherical projection, namely the enhanced | |
| // unified camera model [2][3]. This is intended to bring some variability to | |
| // the benchmark compared to the SnavelyReprojection that uses a | |
| // polynomial-based distortion model. | |
| // | |
| // 2. To match the camera model, inverse distance parameterization is used for | |
| // points instead of inverse depth [4]. | |
| // | |
| // 3. For simplicity, camera intrinsics are assumed constant, and thus host | |
| // frame points are passed as (unprojected) bearing vectors, which avoids the | |
| // need for an 'unproject' function. | |
| // | |
| // 4. Some details of the residual in [1] are omitted for simplicity: The | |
| // brightness transform parameters [a,b], the constant pre-weight w, and the | |
| // per-pixel robust norm. | |
| // | |
| // [1] J. Engel, V. Koltun and D. Cremers, "Direct Sparse Odometry," in IEEE | |
| // Transactions on Pattern Analysis and Machine Intelligence, vol. 40, no. 3, | |
| // pp. 611-625, 1 March 2018. | |
| // | |
| // [2] B. Khomutenko, G. Garcia and P. Martinet, "An Enhanced Unified Camera | |
| // Model," in IEEE Robotics and Automation Letters, vol. 1, no. 1, pp. 137-144, | |
| // Jan. 2016. | |
| // | |
| // [3] V. Usenko, N. Demmel and D. Cremers, "The Double Sphere Camera Model," | |
| // 2018 International Conference on 3D Vision (3DV), Verona, 2018, pp. 552-560. | |
| // | |
| // [4] H. Matsuki, L. von Stumberg, V. Usenko, J. Stückler and D. Cremers, | |
| // "Omnidirectional DSO: Direct Sparse Odometry With Fisheye Cameras," in IEEE | |
| // Robotics and Automation Letters, vol. 3, no. 4, pp. 3693-3700, Oct. 2018. | |
| template <int PATCH_SIZE_ = 8> | |
| struct PhotometricError : BenchmarkCostFunctor<PATCH_SIZE_, 7, 7, 1> { | |
| static constexpr int PATCH_SIZE = PATCH_SIZE_; | |
| static constexpr int POSE_SIZE = 7; | |
| static constexpr int POINT_SIZE = 1; | |
| using typename BenchmarkCostFunctor<PATCH_SIZE_, 7, 7, 1>::ParameterBlocks; | |
| using ImageType = Eigen::Matrix<uint8_t, 128, 128, Eigen::RowMajor>; | |
| using Grid = Grid2D<uint8_t, 1>; | |
| using Interpolator = BiCubicInterpolator<Grid>; | |
| using Intrinsics = Eigen::Array<double, 6, 1>; | |
| template <typename T> | |
| using Patch = Eigen::Array<T, PATCH_SIZE, 1>; | |
| template <typename T> | |
| using PatchVectors = Eigen::Matrix<T, 3, PATCH_SIZE>; | |
| PhotometricError() | |
| : grid_(image_.data(), 0, image_.rows(), 0, image_.cols()), | |
| image_target_(grid_) { | |
| std::mt19937 gen(42); | |
| std::uniform_real_distribution<double> uniform01(0.0, 1.0); | |
| std::uniform_int_distribution<unsigned int> uniform0255(0, 255); | |
| intensities_host_ = | |
| Patch<double>::NullaryExpr([&]() { return uniform0255(gen); }); | |
| bearings_host_ = | |
| PatchVectors<double>::NullaryExpr([&]() { return uniform01(gen); }); | |
| bearings_host_.row(2).array() = 1.0; | |
| bearings_host_.colwise().normalize(); | |
| image_ = ImageType::NullaryExpr( | |
| [&]() { return static_cast<uint8_t>(uniform0255(gen)); }); | |
| intrinsics_ << 128, 128, 1, -1, 0.5, 0.5; | |
| } | |
| PhotometricError(const PhotometricError&) = delete; | |
| PhotometricError& operator=(const PhotometricError&) = delete; | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| NormalizedPose(1., 2., 3., 4., 5., 6., 7.), | |
| NormalizedPose(1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1), | |
| {1.0}); | |
| } | |
| template <typename T> | |
| inline bool Project(Eigen::Matrix<T, 2, 1>& proj, | |
| const Eigen::Matrix<T, 3, 1>& p) const { | |
| const double& fx = intrinsics_[0]; | |
| const double& fy = intrinsics_[1]; | |
| const double& cx = intrinsics_[2]; | |
| const double& cy = intrinsics_[3]; | |
| const double& alpha = intrinsics_[4]; | |
| const double& beta = intrinsics_[5]; | |
| const T rho2 = beta * (p.x() * p.x() + p.y() * p.y()) + p.z() * p.z(); | |
| const T rho = sqrt(rho2); | |
| // Check if 3D point is in domain of projection function. | |
| // See (8) and (17) in [3]. | |
| constexpr double NUMERIC_EPSILON = 1e-10; | |
| const double w = | |
| alpha > 0.5 ? (1.0 - alpha) / alpha : alpha / (1.0 - alpha); | |
| if (p.z() <= -w * rho + NUMERIC_EPSILON) { | |
| return false; | |
| } | |
| const T norm = alpha * rho + (1.0 - alpha) * p.z(); | |
| const T norm_inv = 1.0 / norm; | |
| const T mx = p.x() * norm_inv; | |
| const T my = p.y() * norm_inv; | |
| proj[0] = fx * mx + cx; | |
| proj[1] = fy * my + cy; | |
| return true; | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const pose_host_ptr, | |
| const T* const pose_target_ptr, | |
| const T* const idist_ptr, | |
| T* residuals_ptr) const { | |
| Eigen::Map<const Eigen::Quaternion<T>> q_w_h(pose_host_ptr); | |
| Eigen::Map<const Eigen::Matrix<T, 3, 1>> t_w_h(pose_host_ptr + 4); | |
| Eigen::Map<const Eigen::Quaternion<T>> q_w_t(pose_target_ptr); | |
| Eigen::Map<const Eigen::Matrix<T, 3, 1>> t_w_t(pose_target_ptr + 4); | |
| const T& idist = *idist_ptr; | |
| Eigen::Map<Patch<T>> residuals(residuals_ptr); | |
| // Compute relative pose from host to target frame. | |
| const Eigen::Quaternion<T> q_t_h = q_w_t.conjugate() * q_w_h; | |
| const Eigen::Matrix<T, 3, 3> R_t_h = q_t_h.toRotationMatrix(); | |
| const Eigen::Matrix<T, 3, 1> t_t_h = q_w_t.conjugate() * (t_w_h - t_w_t); | |
| // Transform points from host to target frame. 3D point in target frame is | |
| // scaled by idist for numerical stability when idist is close to 0 | |
| // (projection is invariant to scaling). | |
| PatchVectors<T> p_target_scaled = | |
| (R_t_h * bearings_host_).colwise() + idist * t_t_h; | |
| // Project points and interpolate image. | |
| Patch<T> intensities_target; | |
| for (int i = 0; i < p_target_scaled.cols(); ++i) { | |
| Eigen::Matrix<T, 2, 1> uv; | |
| if (!Project(uv, Eigen::Matrix<T, 3, 1>(p_target_scaled.col(i)))) { | |
| // If any point of the patch is outside the domain of the projection | |
| // function, the residual cannot be evaluated. For the benchmark we want | |
| // to avoid this case and thus return false; | |
| return false; | |
| } | |
| // Mind the order of u and v: Evaluate takes (row, column), but u is | |
| // left-to-right and v top-to-bottom image axis. | |
| image_target_.Evaluate(uv[1], uv[0], &intensities_target[i]); | |
| } | |
| // Residual is intensity difference between host and target frame. | |
| residuals = intensities_target - intensities_host_; | |
| return true; | |
| } | |
| private: | |
| ImageType image_; | |
| Grid grid_; | |
| Interpolator image_target_; | |
| Patch<double> intensities_host_; | |
| PatchVectors<double> bearings_host_; | |
| Intrinsics intrinsics_; | |
| }; | |
| // Relative pose error as one might use in SE(3) pose graph optimization. | |
| // The measurement is a relative pose T_i_j, and the parameters are absolute | |
| // poses T_w_i and T_w_j. For the residual we use the log of the residual | |
| // pose, in split representation SO(3) x R^3. | |
| struct RelativePoseError : BenchmarkCostFunctor<6, 7, 7> { | |
| explicit RelativePoseError( | |
| Eigen::Quaterniond q_i_j = Eigen::Quaterniond(1, 2, 3, 4).normalized(), | |
| Eigen::Vector3d t_i_j = Eigen::Vector3d(1, 2, 3)) | |
| : meas_q_i_j_(std::move(q_i_j)), meas_t_i_j_(std::move(t_i_j)) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| NormalizedPose(1., 2., 3., 4., 5., 6., 7.), | |
| NormalizedPose(1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1)); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const pose_i_ptr, | |
| const T* const pose_j_ptr, | |
| T* residuals_ptr) const { | |
| Eigen::Map<const Eigen::Quaternion<T>> q_w_i(pose_i_ptr); | |
| Eigen::Map<const Eigen::Matrix<T, 3, 1>> t_w_i(pose_i_ptr + 4); | |
| Eigen::Map<const Eigen::Quaternion<T>> q_w_j(pose_j_ptr); | |
| Eigen::Map<const Eigen::Matrix<T, 3, 1>> t_w_j(pose_j_ptr + 4); | |
| Eigen::Map<Eigen::Matrix<T, 6, 1>> residuals(residuals_ptr); | |
| // Compute estimate of relative pose from i to j. | |
| const Eigen::Quaternion<T> est_q_j_i = q_w_j.conjugate() * q_w_i; | |
| const Eigen::Matrix<T, 3, 1> est_t_j_i = | |
| q_w_j.conjugate() * (t_w_i - t_w_j); | |
| // Compute residual pose. | |
| const Eigen::Quaternion<T> res_q = meas_q_i_j_.cast<T>() * est_q_j_i; | |
| const Eigen::Matrix<T, 3, 1> res_t = | |
| meas_q_i_j_.cast<T>() * est_t_j_i + meas_t_i_j_; | |
| // Convert quaternion to ceres convention (Eigen stores xyzw, Ceres wxyz). | |
| Eigen::Matrix<T, 4, 1> res_q_ceres; | |
| res_q_ceres << res_q.w(), res_q.vec(); | |
| // Residual is log of pose. Use split representation SO(3) x R^3. | |
| QuaternionToAngleAxis(res_q_ceres.data(), residuals.data()); | |
| residuals.template bottomRows<3>() = res_t; | |
| return true; | |
| } | |
| private: | |
| // Measurement of relative pose from j to i. | |
| Eigen::Quaterniond meas_q_i_j_; | |
| Eigen::Vector3d meas_t_i_j_; | |
| }; | |
| // The brdf is based on: | |
| // Burley, Brent, and Walt Disney Animation Studios. "Physically-based shading | |
| // at disney." ACM SIGGRAPH. Vol. 2012. 2012. | |
| // | |
| // The implementation is based on: | |
| // https://github.com/wdas/brdf/blob/master/src/brdfs/disney.brdf | |
| struct Brdf : BenchmarkCostFunctor<3, 10, 3, 3, 3, 3, 3, 3> { | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}, | |
| {0.1, 0.2, 0.3}, | |
| NormalizedVector3(-0.1, 0.5, 0.2), | |
| NormalizedVector3(0.5, -0.2, 0.9), | |
| NormalizedVector3(-0.3, 0.4, -0.3), | |
| NormalizedVector3(0.5, 0.7, -0.1), | |
| NormalizedVector3(0.2, -0.2, -0.2)); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const material, | |
| const T* const c_ptr, | |
| const T* const n_ptr, | |
| const T* const v_ptr, | |
| const T* const l_ptr, | |
| const T* const x_ptr, | |
| const T* const y_ptr, | |
| T* residual) const { | |
| using Vec3 = Eigen::Matrix<T, 3, 1>; | |
| T metallic = material[0]; | |
| T subsurface = material[1]; | |
| T specular = material[2]; | |
| T roughness = material[3]; | |
| T specular_tint = material[4]; | |
| T anisotropic = material[5]; | |
| T sheen = material[6]; | |
| T sheen_tint = material[7]; | |
| T clearcoat = material[8]; | |
| T clearcoat_gloss = material[9]; | |
| Eigen::Map<const Vec3> c(c_ptr); | |
| Eigen::Map<const Vec3> n(n_ptr); | |
| Eigen::Map<const Vec3> v(v_ptr); | |
| Eigen::Map<const Vec3> l(l_ptr); | |
| Eigen::Map<const Vec3> x(x_ptr); | |
| Eigen::Map<const Vec3> y(y_ptr); | |
| const T n_dot_l = n.dot(l); | |
| const T n_dot_v = n.dot(v); | |
| const Vec3 l_p_v = l + v; | |
| const Vec3 h = l_p_v / l_p_v.norm(); | |
| const T n_dot_h = n.dot(h); | |
| const T l_dot_h = l.dot(h); | |
| const T h_dot_x = h.dot(x); | |
| const T h_dot_y = h.dot(y); | |
| const T c_dlum = T(0.3) * c[0] + T(0.6) * c[1] + T(0.1) * c[2]; | |
| const Vec3 c_tint = c / c_dlum; | |
| const Vec3 c_spec0 = | |
| Lerp(specular * T(0.08) * | |
| Lerp(Vec3(T(1), T(1), T(1)), c_tint, specular_tint), | |
| c, | |
| metallic); | |
| const Vec3 c_sheen = Lerp(Vec3(T(1), T(1), T(1)), c_tint, sheen_tint); | |
| // Diffuse fresnel - go from 1 at normal incidence to .5 at grazing | |
| // and mix in diffuse retro-reflection based on roughness | |
| const T fl = SchlickFresnel(n_dot_l); | |
| const T fv = SchlickFresnel(n_dot_v); | |
| const T fd_90 = T(0.5) + T(2) * l_dot_h * l_dot_h * roughness; | |
| const T fd = Lerp(T(1), fd_90, fl) * Lerp(T(1), fd_90, fv); | |
| // Based on Hanrahan-Krueger brdf approximation of isotropic bssrdf | |
| // 1.25 scale is used to (roughly) preserve albedo | |
| // Fss90 used to "flatten" retroreflection based on roughness | |
| const T fss_90 = l_dot_h * l_dot_h * roughness; | |
| const T fss = Lerp(T(1), fss_90, fl) * Lerp(T(1), fss_90, fv); | |
| const T ss = | |
| T(1.25) * (fss * (T(1) / (n_dot_l + n_dot_v) - T(0.5)) + T(0.5)); | |
| // specular | |
| const T eps = T(0.001); | |
| const T aspct = Aspect(anisotropic); | |
| const T ax_temp = Square(roughness) / aspct; | |
| const T ay_temp = Square(roughness) * aspct; | |
| const T ax = (ax_temp < eps ? eps : ax_temp); | |
| const T ay = (ay_temp < eps ? eps : ay_temp); | |
| const T ds = GTR2Aniso(n_dot_h, h_dot_x, h_dot_y, ax, ay); | |
| const T fh = SchlickFresnel(l_dot_h); | |
| const Vec3 fs = Lerp(c_spec0, Vec3(T(1), T(1), T(1)), fh); | |
| const T roughg = Square(roughness * T(0.5) + T(0.5)); | |
| const T ggxn_dot_l = SmithG_GGX(n_dot_l, roughg); | |
| const T ggxn_dot_v = SmithG_GGX(n_dot_v, roughg); | |
| const T gs = ggxn_dot_l * ggxn_dot_v; | |
| // sheen | |
| const Vec3 f_sheen = fh * sheen * c_sheen; | |
| // clearcoat (ior = 1.5 -> F0 = 0.04) | |
| const T a = Lerp(T(0.1), T(0.001), clearcoat_gloss); | |
| const T dr = GTR1(n_dot_h, a); | |
| const T fr = Lerp(T(0.04), T(1), fh); | |
| const T cggxn_dot_l = SmithG_GGX(n_dot_l, T(0.25)); | |
| const T cggxn_dot_v = SmithG_GGX(n_dot_v, T(0.25)); | |
| const T gr = cggxn_dot_l * cggxn_dot_v; | |
| const Vec3 result_no_cosine = | |
| (T(1.0 / constants::pi) * Lerp(fd, ss, subsurface) * c + f_sheen) * | |
| (T(1) - metallic) + | |
| gs * fs * ds + | |
| Vec3(T(0.25), T(0.25), T(0.25)) * clearcoat * gr * fr * dr; | |
| const Vec3 result = n_dot_l * result_no_cosine; | |
| residual[0] = result(0); | |
| residual[1] = result(1); | |
| residual[2] = result(2); | |
| return true; | |
| } | |
| template <typename T> | |
| inline T SchlickFresnel(const T& u) const { | |
| T m = T(1) - u; | |
| const T m2 = m * m; | |
| return m2 * m2 * m; // (1-u)^5 | |
| } | |
| template <typename T> | |
| inline T Aspect(const T& anisotropic) const { | |
| return T(sqrt(T(1) - anisotropic * T(0.9))); | |
| } | |
| template <typename T> | |
| inline T SmithG_GGX(const T& n_dot_v, const T& alpha_g) const { | |
| const T a = alpha_g * alpha_g; | |
| const T b = n_dot_v * n_dot_v; | |
| return T(1) / (n_dot_v + T(sqrt(a + b - a * b))); | |
| } | |
| // Generalized-Trowbridge-Reitz (GTR) Microfacet Distribution | |
| // See paper, Appendix B | |
| template <typename T> | |
| inline T GTR1(const T& n_dot_h, const T& a) const { | |
| T result = T(0); | |
| if (a >= T(1)) { | |
| result = T(1 / constants::pi); | |
| } else { | |
| const T a2 = a * a; | |
| const T t = T(1) + (a2 - T(1)) * n_dot_h * n_dot_h; | |
| result = (a2 - T(1)) / (T(constants::pi) * T(log(a2) * t)); | |
| } | |
| return result; | |
| } | |
| template <typename T> | |
| inline T GTR2Aniso(const T& n_dot_h, | |
| const T& h_dot_x, | |
| const T& h_dot_y, | |
| const T& ax, | |
| const T& ay) const { | |
| return T(1) / (T(constants::pi) * ax * ay * | |
| Square(Square(h_dot_x / ax) + Square(h_dot_y / ay) + | |
| n_dot_h * n_dot_h)); | |
| } | |
| template <typename T> | |
| inline T Lerp(const T& a, const T& b, const T& u) const { | |
| return a + u * (b - a); | |
| } | |
| template <typename Derived1, typename Derived2> | |
| inline typename Derived1::PlainObject Lerp( | |
| const Eigen::MatrixBase<Derived1>& a, | |
| const Eigen::MatrixBase<Derived2>& b, | |
| typename Derived1::Scalar alpha) const { | |
| return (typename Derived1::Scalar(1) - alpha) * a + alpha * b; | |
| } | |
| template <typename T> | |
| inline T Square(const T& x) const { | |
| return x * x; | |
| } | |
| }; | |
| // Functors for benchmarking CostFunctionToFunctor (static sizes) and | |
| // DynamicCostFunctionToFunctor (dynamic sizes). | |
| // | |
| // CostFunctionToFunctor and DynamicCostFunctionToFunctor allow an existing | |
| // CostFunction object (which only accepts `double` inputs and computes `double` | |
| // residuals and Jacobians via CostFunction::Evaluate) to be used as a templated | |
| // functor inside another cost functor. When called with `Jet<double, N>` | |
| // arguments during automatic differentiation of the outer functor, the adapter: | |
| // 1. Extracts the scalar (`double`) parts of the input Jets. | |
| // 2. Calls the inner CostFunction::Evaluate to compute the inner residuals | |
| // and Jacobians with respect to the inner inputs. | |
| // 3. Applies the chain rule to multiply the inner Jacobians by the derivative | |
| // (`.v`) parts of the input Jets to form the output residual Jets. | |
| // | |
| // To benchmark this composition realistically, we split a standard pinhole | |
| // camera projection with radial distortion into two stages: | |
| // | |
| // - Stage 1 (`InnerProjectionFunctor`): Projects a 3D point already in the | |
| // camera coordinate frame, `p_cam = [X, Y, Z]`, into 2D pixel coordinates | |
| // using camera `intrinsics = [focal, k1, k2]`: | |
| // xp = X / Z, yp = Y / Z, r^2 = xp^2 + yp^2 | |
| // distortion = 1 + k1 * r^2 + k2 * r^4 | |
| // residuals = [focal * distortion * xp, focal * distortion * yp] | |
| // This inner functor is wrapped in an `AutoDiffCostFunction<..., 2, 3, 3>`, | |
| // representing a pre-existing CostFunction object. | |
| // | |
| // - Stage 2 (`OuterProjectionFunctor`): Takes four 3D parameter blocks | |
| // (`rotation` angle-axis, `translation`, `intrinsics`, and a world-frame | |
| // `point`), transforms the world point into the camera frame: | |
| // p_cam = AngleAxisRotatePoint(rotation, point) + translation | |
| // and then invokes the inner CostFunction via either: | |
| // * `CostFunctionToFunctor<2, 3, 3>` (when `!kUseDynamicAdapter`), or | |
| // * `DynamicCostFunctionToFunctor` (when `kUseDynamicAdapter`). | |
| struct InnerProjectionFunctor { | |
| template <typename T> | |
| bool operator()(const T* const intrinsics, | |
| const T* const point, | |
| T* residuals) const { | |
| const T xp = point[0] / point[2]; | |
| const T yp = point[1] / point[2]; | |
| const T r2 = xp * xp + yp * yp; | |
| const T distortion = T(1.0) + r2 * (intrinsics[1] + intrinsics[2] * r2); | |
| residuals[0] = intrinsics[0] * distortion * xp; | |
| residuals[1] = intrinsics[0] * distortion * yp; | |
| return true; | |
| } | |
| }; | |
| template <bool kUseDynamicAdapter> | |
| struct OuterProjectionFunctor : BenchmarkCostFunctor<2, 3, 3, 3, 3> { | |
| using typename BenchmarkCostFunctor<2, 3, 3, 3, 3>::ParameterBlocks; | |
| OuterProjectionFunctor() | |
| : inner_(std::make_unique< | |
| AutoDiffCostFunction<InnerProjectionFunctor, 2, 3, 3>>( | |
| std::make_unique<InnerProjectionFunctor>())) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks({0.1, -0.2, 0.05}, | |
| {0.5, -0.1, 2.0}, | |
| {500.0, -0.01, 0.001}, | |
| {0.3, -0.4, 5.0}); | |
| } | |
| template <typename T> | |
| bool operator()(const T* const rotation, | |
| const T* const translation, | |
| const T* const intrinsics, | |
| const T* const point, | |
| T* residuals) const { | |
| // Transform the 3D point from the world frame into the camera frame. | |
| T p_cam[3]; | |
| AngleAxisRotatePoint(rotation, point, p_cam); | |
| p_cam[0] += translation[0]; | |
| p_cam[1] += translation[1]; | |
| p_cam[2] += translation[2]; | |
| // Project the camera-frame point into 2D pixel coordinates by invoking the | |
| // wrapped inner CostFunction through CostFunctionToFunctor (static) or | |
| // DynamicCostFunctionToFunctor (dynamic). | |
| if constexpr (!kUseDynamicAdapter) { | |
| return inner_(intrinsics, p_cam, residuals); | |
| } else { | |
| const T* params[2] = {intrinsics, p_cam}; | |
| return inner_(params, residuals); | |
| } | |
| } | |
| std::conditional_t<!kUseDynamicAdapter, | |
| CostFunctionToFunctor<2, 3, 3>, | |
| DynamicCostFunctionToFunctor> | |
| inner_; | |
| }; | |
| // 1. COLMAP ReprojErrorCostFunctor<OpenCVCameraModel>: <2, 3, 7, 8> | |
| // point3D_in_world (3), cam_from_world [qx, qy, qz, qw, tx, ty, tz] (7), | |
| // camera_params [fx, fy, cx, cy, k1, k2, p1, p2] (8). | |
| struct ColmapOpenCVReprojectionError : BenchmarkCostFunctor<2, 3, 7, 8> { | |
| static constexpr int kPointBlockIndex = 0; | |
| explicit ColmapOpenCVReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {0.5, -0.3, 4.2}, | |
| NormalizedPose(0.01, 0.02, -0.01, 0.9997, 0.1, -0.2, 0.5), | |
| {1000.0, 1000.0, 640.0, 360.0, -0.05, 0.01, 1e-4, -1e-4}); | |
| } | |
| static ParameterBlocks MakeParametersFromBAL(const double* bal_camera, | |
| const double* bal_point) { | |
| // BAL cameras look down -Z with focal length bal_camera[6] and radial | |
| // distortion (bal_camera[7], bal_camera[8]); negate focal length so | |
| // projection matches COLMAP's +Z camera convention. | |
| std::array<double, 8> intrinsics = {-bal_camera[6], | |
| -bal_camera[6], | |
| 0.0, | |
| 0.0, | |
| bal_camera[7], | |
| bal_camera[8], | |
| 1e-4, | |
| 1e-4}; | |
| return ParameterBlocks({bal_point[0], bal_point[1], bal_point[2]}, | |
| PoseFromBALCamera(bal_camera), | |
| intrinsics); | |
| } | |
| static std::unique_ptr<Manifold> MakeManifold(int block_index) { | |
| if (block_index == 1) { | |
| return std::make_unique< | |
| ProductManifold<EigenQuaternionManifold, EuclideanManifold<3>>>(); | |
| } | |
| return nullptr; | |
| } | |
| static std::unique_ptr<LossFunction> MakeLossFunction() { | |
| return std::make_unique<HuberLoss>(1.0); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const point3D_in_world, | |
| const T* const cam_from_world, | |
| const T* const camera_params, | |
| T* residuals) const { | |
| const Eigen::Map<const Eigen::Quaternion<T>> q(cam_from_world); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> t(cam_from_world + 4); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_w(point3D_in_world); | |
| const Eigen::Matrix<T, 3, 1> p_c = q * p_w + t; | |
| const T f1 = camera_params[0]; | |
| const T f2 = camera_params[1]; | |
| const T c1 = camera_params[2]; | |
| const T c2 = camera_params[3]; | |
| const T k1 = camera_params[4]; | |
| const T k2 = camera_params[5]; | |
| const T p1 = camera_params[6]; | |
| const T p2 = camera_params[7]; | |
| const T u = p_c[0] / p_c[2]; | |
| const T v = p_c[1] / p_c[2]; | |
| const T u2 = u * u; | |
| const T uv = u * v; | |
| const T v2 = v * v; | |
| const T r2 = u2 + v2; | |
| const T radial = k1 * r2 + k2 * r2 * r2; | |
| const T du = u * radial + T(2) * p1 * uv + p2 * (r2 + T(2) * u2); | |
| const T dv = v * radial + T(2) * p2 * uv + p1 * (r2 + T(2) * v2); | |
| residuals[0] = f1 * (u + du) + c1 - T(obs_x); | |
| residuals[1] = f2 * (v + dv) + c2 - T(obs_y); | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 2. COLMAP ReprojErrorCostFunctor<FullOpenCVCameraModel>: <2, 3, 7, 12> | |
| // Rational radial + tangential distortion: | |
| // camera_params [fx, fy, cx, cy, k1, k2, p1, p2, k3, k4, k5, k6] (12). | |
| struct ColmapFullOpenCVReprojectionError : BenchmarkCostFunctor<2, 3, 7, 12> { | |
| static constexpr int kPointBlockIndex = 0; | |
| explicit ColmapFullOpenCVReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {0.5, -0.3, 4.2}, | |
| NormalizedPose(0.01, 0.02, -0.01, 0.9997, 0.1, -0.2, 0.5), | |
| {1000.0, | |
| 1000.0, | |
| 640.0, | |
| 360.0, | |
| -0.05, | |
| 0.01, | |
| 1e-4, | |
| -1e-4, | |
| 0.002, | |
| 0.01, | |
| -0.005, | |
| 0.001}); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const point3D_in_world, | |
| const T* const cam_from_world, | |
| const T* const camera_params, | |
| T* residuals) const { | |
| const Eigen::Map<const Eigen::Quaternion<T>> q(cam_from_world); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> t(cam_from_world + 4); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_w(point3D_in_world); | |
| const Eigen::Matrix<T, 3, 1> p_c = q * p_w + t; | |
| const T f1 = camera_params[0]; | |
| const T f2 = camera_params[1]; | |
| const T c1 = camera_params[2]; | |
| const T c2 = camera_params[3]; | |
| const T k1 = camera_params[4]; | |
| const T k2 = camera_params[5]; | |
| const T p1 = camera_params[6]; | |
| const T p2 = camera_params[7]; | |
| const T k3 = camera_params[8]; | |
| const T k4 = camera_params[9]; | |
| const T k5 = camera_params[10]; | |
| const T k6 = camera_params[11]; | |
| const T u = p_c[0] / p_c[2]; | |
| const T v = p_c[1] / p_c[2]; | |
| const T u2 = u * u; | |
| const T uv = u * v; | |
| const T v2 = v * v; | |
| const T r2 = u2 + v2; | |
| const T r4 = r2 * r2; | |
| const T r6 = r4 * r2; | |
| const T radial = (T(1) + k1 * r2 + k2 * r4 + k3 * r6) / | |
| (T(1) + k4 * r2 + k5 * r4 + k6 * r6); | |
| const T x = u * radial + T(2) * p1 * uv + p2 * (r2 + T(2) * u2); | |
| const T y = v * radial + T(2) * p2 * uv + p1 * (r2 + T(2) * v2); | |
| residuals[0] = f1 * x + c1 - T(obs_x); | |
| residuals[1] = f2 * y + c2 - T(obs_y); | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 3. COLMAP RigReprojErrorCostFunctor<OpenCVCameraModel>: <2, 3, 7, 7, 8> | |
| // point3D_in_world (3), cam_from_rig (7), rig_from_world (7), camera_params | |
| // (8). | |
| struct ColmapRigReprojectionError : BenchmarkCostFunctor<2, 3, 7, 7, 8> { | |
| static constexpr int kPointBlockIndex = 0; | |
| explicit ColmapRigReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {0.5, -0.3, 4.2}, | |
| NormalizedPose(0.01, -0.01, 0.02, 0.9997, 0.05, 0.0, 0.0), | |
| NormalizedPose(0.02, 0.01, -0.02, 0.9995, 0.1, -0.2, 0.5), | |
| {1000.0, 1000.0, 640.0, 360.0, -0.05, 0.01, 1e-4, -1e-4}); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const point3D_in_world, | |
| const T* const cam_from_rig, | |
| const T* const rig_from_world, | |
| const T* const camera_params, | |
| T* residuals) const { | |
| const Eigen::Map<const Eigen::Quaternion<T>> q_cr(cam_from_rig); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> t_cr(cam_from_rig + 4); | |
| const Eigen::Map<const Eigen::Quaternion<T>> q_rw(rig_from_world); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> t_rw(rig_from_world + 4); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_w(point3D_in_world); | |
| const Eigen::Matrix<T, 3, 1> p_c = q_cr * (q_rw * p_w + t_rw) + t_cr; | |
| const T f1 = camera_params[0]; | |
| const T f2 = camera_params[1]; | |
| const T c1 = camera_params[2]; | |
| const T c2 = camera_params[3]; | |
| const T k1 = camera_params[4]; | |
| const T k2 = camera_params[5]; | |
| const T p1 = camera_params[6]; | |
| const T p2 = camera_params[7]; | |
| const T u = p_c[0] / p_c[2]; | |
| const T v = p_c[1] / p_c[2]; | |
| const T u2 = u * u; | |
| const T uv = u * v; | |
| const T v2 = v * v; | |
| const T r2 = u2 + v2; | |
| const T radial = k1 * r2 + k2 * r2 * r2; | |
| const T du = u * radial + T(2) * p1 * uv + p2 * (r2 + T(2) * u2); | |
| const T dv = v * radial + T(2) * p2 * uv + p1 * (r2 + T(2) * v2); | |
| residuals[0] = f1 * (u + du) + c1 - T(obs_x); | |
| residuals[1] = f2 * (v + dv) + c2 - T(obs_y); | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 4. COLMAP SampsonErrorCostFunctor: <1, 7> | |
| // Essential matrix E = [t]_x R(q) and signed Sampson epipolar error. | |
| struct ColmapSampsonError : BenchmarkCostFunctor<1, 7> { | |
| explicit ColmapSampsonError( | |
| const Eigen::Vector2d& p1 = Eigen::Vector2d(0.1, -0.2), | |
| const Eigen::Vector2d& p2 = Eigen::Vector2d(0.15, -0.18)) | |
| : point1_(p1), point2_(p2) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| NormalizedPose(0.01, 0.02, -0.01, 0.9997, 0.8, 0.4, 0.4472)); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const cam2_from_cam1, T* residuals) const { | |
| const Eigen::Matrix<T, 3, 3> R = | |
| Eigen::Map<const Eigen::Quaternion<T>>(cam2_from_cam1) | |
| .toRotationMatrix(); | |
| Eigen::Matrix<T, 3, 3> t_x; | |
| t_x << T(0), -cam2_from_cam1[6], cam2_from_cam1[5], cam2_from_cam1[6], T(0), | |
| -cam2_from_cam1[4], -cam2_from_cam1[5], cam2_from_cam1[4], T(0); | |
| const Eigen::Matrix<T, 3, 3> E = t_x * R; | |
| const Eigen::Matrix<T, 3, 1> p1(T(point1_.x()), T(point1_.y()), T(1)); | |
| const Eigen::Matrix<T, 3, 1> p2(T(point2_.x()), T(point2_.y()), T(1)); | |
| const Eigen::Matrix<T, 3, 1> epipolar_line1 = E * p1; | |
| const T num = p2.dot(epipolar_line1); | |
| const Eigen::Matrix<T, 4, 1> denom(p2.dot(E.col(0)), | |
| p2.dot(E.col(1)), | |
| epipolar_line1.x(), | |
| epipolar_line1.y()); | |
| residuals[0] = num / denom.norm(); | |
| return true; | |
| } | |
| Eigen::Vector2d point1_; | |
| Eigen::Vector2d point2_; | |
| }; | |
| // 5. COLMAP ReprojErrorCostFunctor<OpenCVFisheyeCameraModel>: <2, 3, 7, 8> | |
| // Kannala-Brandt equidistant fisheye camera model: | |
| // point3D_in_world (3), cam_from_world (7), | |
| // camera_params [fx, fy, cx, cy, k1, k2, k3, k4] (8). | |
| struct ColmapFisheyeReprojectionError : BenchmarkCostFunctor<2, 3, 7, 8> { | |
| static constexpr int kPointBlockIndex = 0; | |
| explicit ColmapFisheyeReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {0.5, -0.3, 4.2}, | |
| NormalizedPose(0.01, 0.02, -0.01, 0.9997, 0.1, -0.2, 0.5), | |
| {800.0, 800.0, 640.0, 360.0, -0.02, 0.005, -0.001, 0.0002}); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const point3D_in_world, | |
| const T* const cam_from_world, | |
| const T* const camera_params, | |
| T* residuals) const { | |
| const Eigen::Map<const Eigen::Quaternion<T>> q(cam_from_world); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> t(cam_from_world + 4); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_w(point3D_in_world); | |
| const Eigen::Matrix<T, 3, 1> p_c = q * p_w + t; | |
| const T f1 = camera_params[0]; | |
| const T f2 = camera_params[1]; | |
| const T c1 = camera_params[2]; | |
| const T c2 = camera_params[3]; | |
| const T k1 = camera_params[4]; | |
| const T k2 = camera_params[5]; | |
| const T k3 = camera_params[6]; | |
| const T k4 = camera_params[7]; | |
| const T u = p_c[0] / p_c[2]; | |
| const T v = p_c[1] / p_c[2]; | |
| const T r = hypot(u, v); | |
| T x = u; | |
| T y = v; | |
| if (r > T(1e-8)) { | |
| const T theta = atan(r); | |
| const T theta2 = theta * theta; | |
| const T theta4 = theta2 * theta2; | |
| const T theta6 = theta4 * theta2; | |
| const T theta8 = theta4 * theta4; | |
| const T thetad = theta * (T(1) + k1 * theta2 + k2 * theta4 + k3 * theta6 + | |
| k4 * theta8); | |
| const T scale = thetad / r; | |
| x *= scale; | |
| y *= scale; | |
| } | |
| residuals[0] = f1 * x + c1 - T(obs_x); | |
| residuals[1] = f2 * y + c2 - T(obs_y); | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 6. COLMAP ReprojErrorCostFunctor<EquirectangularCameraModel>: <2, 3, 7> | |
| // 360x180 spherical equirectangular projection using atan2 and asin. | |
| struct ColmapSphericalReprojectionError : BenchmarkCostFunctor<2, 3, 7> { | |
| static constexpr int kPointBlockIndex = 0; | |
| explicit ColmapSphericalReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {0.5, -0.3, 4.2}, | |
| NormalizedPose(0.01, 0.02, -0.01, 0.9997, 0.1, -0.2, 0.5)); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const point3D_in_world, | |
| const T* const cam_from_world, | |
| T* residuals) const { | |
| const Eigen::Map<const Eigen::Quaternion<T>> q(cam_from_world); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> t(cam_from_world + 4); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_w(point3D_in_world); | |
| const Eigen::Matrix<T, 3, 1> p_c = q * p_w + t; | |
| const T norm = p_c.norm(); | |
| const T lon = atan2(p_c[0], p_c[2]); | |
| const T lat = asin(p_c[1] / norm); | |
| constexpr double kWidth = 1920.0; | |
| constexpr double kHeight = 960.0; | |
| constexpr double kInvTwoPi = 1.0 / (2.0 * constants::pi); | |
| constexpr double kInvPi = 1.0 / constants::pi; | |
| residuals[0] = T(kWidth) * (T(0.5) + lon * T(kInvTwoPi)) - T(obs_x); | |
| residuals[1] = T(kHeight) * (T(0.5) + lat * T(kInvPi)) - T(obs_y); | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 7. Blender libmv Brown-Conrady ReprojectionErrorApplyIntrinsics: <2, 9, 6, 3> | |
| // intrinsics [f, px, py, k1, k2, k3, k4, p1, p2] (9), R_t [rx, ry, rz, tx, ty, | |
| // tz] (6), X (3). | |
| struct LibmvBrownReprojectionError : BenchmarkCostFunctor<2, 9, 6, 3> { | |
| static constexpr int kPointBlockIndex = 2; | |
| explicit LibmvBrownReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {1000.0, 640.0, 360.0, -0.05, 0.01, -0.002, 0.0005, 1e-4, -1e-4}, | |
| {0.02, -0.03, 0.01, 0.1, -0.2, 0.5}, | |
| {0.5, -0.3, 4.2}); | |
| } | |
| static ParameterBlocks MakeParametersFromBAL(const double* bal_camera, | |
| const double* bal_point) { | |
| std::array<double, 9> intrinsics = {-bal_camera[6], | |
| 0.0, | |
| 0.0, | |
| bal_camera[7], | |
| bal_camera[8], | |
| 0.0, | |
| 0.0, | |
| 1e-4, | |
| 1e-4}; | |
| std::array<double, 6> pose{}; | |
| std::copy_n(bal_camera, 6, pose.data()); | |
| return ParameterBlocks( | |
| intrinsics, pose, {bal_point[0], bal_point[1], bal_point[2]}); | |
| } | |
| static std::unique_ptr<Manifold> MakeManifold(int block_index) { | |
| if (block_index == 0) { | |
| return std::make_unique<SubsetManifold>(9, std::vector<int>{1, 2, 5, 6}); | |
| } | |
| return nullptr; | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const intrinsics, | |
| const T* const R_t, | |
| const T* const X, | |
| T* residuals) const { | |
| T p[3]; | |
| ceres::AngleAxisRotatePoint(R_t, X, p); | |
| p[0] += R_t[3]; | |
| p[1] += R_t[4]; | |
| p[2] += R_t[5]; | |
| const T x = p[0] / p[2]; | |
| const T y = p[1] / p[2]; | |
| const T focal = intrinsics[0]; | |
| const T px = intrinsics[1]; | |
| const T py = intrinsics[2]; | |
| const T k1 = intrinsics[3]; | |
| const T k2 = intrinsics[4]; | |
| const T k3 = intrinsics[5]; | |
| const T k4 = intrinsics[6]; | |
| const T p1 = intrinsics[7]; | |
| const T p2 = intrinsics[8]; | |
| const T x2 = x * x; | |
| const T y2 = y * y; | |
| const T xy2 = T(2) * x * y; | |
| const T r2 = x2 + y2; | |
| const T r_coeff = T(1) + (((k4 * r2 + k3) * r2 + k2) * r2 + k1) * r2; | |
| const T tx = p1 * (r2 + T(2) * x2) + p2 * xy2; | |
| const T ty = p2 * (r2 + T(2) * y2) + p1 * xy2; | |
| residuals[0] = focal * (x * r_coeff + tx) + px - T(obs_x); | |
| residuals[1] = focal * (y * r_coeff + ty) + py - T(obs_y); | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 8. Blender libmv Nuke ReprojectionErrorInvertIntrinsics: <2, 8, 6, 3> | |
| // intrinsics [f, px, py, k1, k2, p1, p2, aspect] (8), R_t (6), X (3). | |
| struct LibmvNukeInvertReprojectionError : BenchmarkCostFunctor<2, 8, 6, 3> { | |
| static constexpr int kPointBlockIndex = 2; | |
| explicit LibmvNukeInvertReprojectionError(double u = 650.0, double v = 350.0) | |
| : obs_x(u), obs_y(v) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {1000.0, 640.0, 360.0, -0.05, 0.01, 1e-4, -1e-4, 1.0}, | |
| {0.02, -0.03, 0.01, 0.1, -0.2, 0.5}, | |
| {0.5, -0.3, 4.2}); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const intrinsics, | |
| const T* const R_t, | |
| const T* const X, | |
| T* residuals) const { | |
| const T focal = intrinsics[0]; | |
| const T px = intrinsics[1]; | |
| const T py = intrinsics[2]; | |
| const T k1 = intrinsics[3]; | |
| const T k2 = intrinsics[4]; | |
| const T p1 = intrinsics[5]; | |
| const T p2 = intrinsics[6]; | |
| T p[3]; | |
| ceres::AngleAxisRotatePoint(R_t, X, p); | |
| p[0] += R_t[3]; | |
| p[1] += R_t[4]; | |
| p[2] += R_t[5]; | |
| const T xn = p[0] / p[2]; | |
| const T yn = p[1] / p[2]; | |
| const T predicted_x = focal * xn + px; | |
| const T predicted_y = focal * yn + py; | |
| constexpr double kHalfMaxImageSize = 960.0; | |
| const T xd = (T(obs_x) - px) / T(kHalfMaxImageSize); | |
| const T yd = (T(obs_y) - py) / T(kHalfMaxImageSize); | |
| const T xd2 = xd * xd; | |
| const T yd2 = yd * yd; | |
| const T rd2 = xd2 + yd2; | |
| const T rd4 = rd2 * rd2; | |
| const T xu = xd / (T(1) + k1 * rd2 + k2 * rd4 + p1 * yd2); | |
| const T yu = yd / (T(1) + k1 * rd2 + k2 * rd4 + p2 * xd2); | |
| const T obs_undistorted_x = xu * T(kHalfMaxImageSize) + px; | |
| const T obs_undistorted_y = yu * T(kHalfMaxImageSize) + py; | |
| residuals[0] = predicted_x - obs_undistorted_x; | |
| residuals[1] = predicted_y - obs_undistorted_y; | |
| return true; | |
| } | |
| double obs_x; | |
| double obs_y; | |
| }; | |
| // 9. 3D Pose Graph SLAM Error Term (PoseGraph3dErrorTerm): <6, 3, 4, 3, 4> | |
| struct PoseGraph3dError : BenchmarkCostFunctor<6, 3, 4, 3, 4> { | |
| explicit PoseGraph3dError( | |
| const Eigen::Vector3d& p_ab = Eigen::Vector3d(1.0, 1.0, 1.0), | |
| const Eigen::Quaterniond& q_ab = Eigen::Quaterniond::Identity(), | |
| const Eigen::Matrix<double, 6, 6>& sqrt_info = | |
| Eigen::Matrix<double, 6, 6>::Identity()) | |
| : p_ab_measured_(p_ab), | |
| q_ab_measured_(q_ab), | |
| sqrt_information_(sqrt_info) {} | |
| static ParameterBlocks MakeParameters() { | |
| return ParameterBlocks( | |
| {1.0, 2.0, 3.0}, | |
| NormalizedQuaternion(0.01, 0.02, -0.01, 0.9997), | |
| {2.1, 3.2, 4.3}, | |
| NormalizedQuaternion(0.03, -0.01, 0.02, 0.9993)); | |
| } | |
| template <typename T> | |
| inline bool operator()(const T* const p_a_ptr, | |
| const T* const q_a_ptr, | |
| const T* const p_b_ptr, | |
| const T* const q_b_ptr, | |
| T* residuals_ptr) const { | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_a(p_a_ptr); | |
| const Eigen::Map<const Eigen::Quaternion<T>> q_a(q_a_ptr); | |
| const Eigen::Map<const Eigen::Matrix<T, 3, 1>> p_b(p_b_ptr); | |
| const Eigen::Map<const Eigen::Quaternion<T>> q_b(q_b_ptr); | |
| const Eigen::Quaternion<T> q_a_inverse = q_a.conjugate(); | |
| const Eigen::Quaternion<T> q_ab_estimated = q_a_inverse * q_b; | |
| const Eigen::Matrix<T, 3, 1> p_ab_estimated = q_a_inverse * (p_b - p_a); | |
| const Eigen::Quaternion<T> delta_q = | |
| q_ab_measured_.template cast<T>() * q_ab_estimated.conjugate(); | |
| Eigen::Map<Eigen::Matrix<T, 6, 1>> residuals(residuals_ptr); | |
| residuals.template block<3, 1>(0, 0) = | |
| p_ab_estimated - p_ab_measured_.template cast<T>(); | |
| residuals.template block<3, 1>(3, 0) = T(2.0) * delta_q.vec(); | |
| residuals.applyOnTheLeft(sqrt_information_.template cast<T>()); | |
| return true; | |
| } | |
| Eigen::Vector3d p_ab_measured_; | |
| Eigen::Quaterniond q_ab_measured_; | |
| Eigen::Matrix<double, 6, 6> sqrt_information_; | |
| }; | |
| } // namespace ceres::internal | |
| #endif // CERES_INTERNAL_BENCHMARK_COST_FUNCTIONS_H_ |