IsometricPatternMatcher/CameraModels.h (326 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #pragma once #include <ceres/ceres.h> #include <Eigen/Core> #include <cmath> #include <sophus/se3.hpp> #include <sophus/so3.hpp> namespace surreal_opensource { // Helper Newton functions for camera unprojection namespace { constexpr float kFloatTolerance = 1e-5; constexpr float kDoubleTolerance = 1e-7; template <typename T> inline double IgnoreJetInfinitesimal(const T& j) { static_assert(std::is_same<decltype(j.a), double>::value || std::is_same<decltype(j.a), float>::value, "T should be a ceres jet"); return j.a; } inline double IgnoreJetInfinitesimal(double j) { return j; } inline float IgnoreJetInfinitesimal(float j) { return j; } template <typename T> constexpr float getConvergenceTolerance() { if (std::is_same<T, ceres::Jet<float, T::DIMENSION>>::value) { return kFloatTolerance; } if (std::is_same<T, ceres::Jet<double, T::DIMENSION>>::value) { // largest number that passes project / unproject test to within 1e-8 pixels // for all models. return kDoubleTolerance; } } template <> constexpr float getConvergenceTolerance<float>() { return kFloatTolerance; } template <> constexpr float getConvergenceTolerance<double>() { return kDoubleTolerance; } template <typename T> inline bool hasConverged(const T& step) { using std::abs; return abs(IgnoreJetInfinitesimal(step)) < getConvergenceTolerance<T>(); } template <typename T> inline T initTheta(const T& r) { using std::sqrt; return sqrt(r); } // Dehomogenize / project input template <typename Derived> EIGEN_STRONG_INLINE static Eigen::Matrix<typename Derived::Scalar, Derived::RowsAtCompileTime - 1, 1> Project(const Eigen::MatrixBase<Derived>& v) { return v.template head<Derived::RowsAtCompileTime - 1>() / v[Derived::RowsAtCompileTime - 1]; } } // namespace // Pinhole Projection Model // // parameters = fx, fy, cx, cy class PinholeProjection { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW static constexpr int kNumParams = 4; static constexpr char kName[] = "Pinhole"; static constexpr char kDescription[] = "fx, fy, cx, cy"; static constexpr int kNumDistortionParams = 0; static constexpr int kFocalXIdx = 0; static constexpr int kFocalYIdx = 1; static constexpr int kPrincipalPointColIdx = 2; static constexpr int kPrincipalPointRowIdx = 3; static constexpr bool kIsFisheye = false; static constexpr bool kHasAnalyticalProjection = true; // Takes in 3-point ``pointOptical`` in the local reference frame of the // camera and projects it onto the image plan. // // Precondition: pointOptical.z() != 0. // // Return 2-point in the image plane. // template <class D, class DP, class DJ1 = Eigen::Matrix<typename D::Scalar, 2, 3>, class DJ2 = Eigen::Matrix<typename D::Scalar, 2, kNumParams>> static Eigen::Matrix<typename D::Scalar, 2, 1> project( const Eigen::MatrixBase<D>& pointOptical, const Eigen::MatrixBase<DP>& params, Eigen::MatrixBase<DJ1>* d_point = nullptr, Eigen::MatrixBase<DJ2>* d_params = nullptr) { using T = typename D::Scalar; static_assert(D::RowsAtCompileTime == 3 && D::ColsAtCompileTime == 1, "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); static_assert( DP::ColsAtCompileTime == 1 && (DP::RowsAtCompileTime == kNumParams || DP::RowsAtCompileTime == Eigen::Dynamic), "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); static_assert((DJ1::ColsAtCompileTime == 3 || DJ1::ColsAtCompileTime == Eigen::Dynamic) && (DJ1::RowsAtCompileTime == 2 || DJ1::RowsAtCompileTime == Eigen::Dynamic), "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); static_assert((DJ2::ColsAtCompileTime == kNumParams || DJ2::ColsAtCompileTime == Eigen::Dynamic) && (DJ2::RowsAtCompileTime == 2 || DJ2::RowsAtCompileTime == Eigen::Dynamic), "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); SOPHUS_ENSURE(pointOptical.z() != T(0), "z(%) must not be zero.", pointOptical.z()); // Focal length and principal point const Eigen::Matrix<T, 2, 1> ff = params.template head<2>(); const Eigen::Matrix<T, 2, 1> pp = params.template segment<2>(2); const Eigen::Matrix<T, 2, 1> px = (Project(pointOptical).array() * ff.array()).matrix() + pp; if (d_point) { const T oneOverZ = T(1) / pointOptical(2); (*d_point)(0, 0) = ff(0) * oneOverZ; (*d_point)(0, 1) = 0.0; (*d_point)(0, 2) = -(*d_point)(0, 0) * pointOptical(0) * oneOverZ; (*d_point)(1, 0) = 0.0; (*d_point)(1, 1) = ff(1) * oneOverZ; (*d_point)(1, 2) = -(*d_point)(1, 1) * pointOptical(1) * oneOverZ; } if (d_params) { (*d_params)(0, 0) = pointOptical(0) / pointOptical(2); (*d_params)(0, 1) = T(0.0); (*d_params)(0, 2) = T(1.0); (*d_params)(0, 3) = T(0.0); (*d_params)(1, 0) = T(0.0); (*d_params)(1, 1) = pointOptical(1) / pointOptical(2); (*d_params)(1, 2) = T(0.0); (*d_params)(1, 3) = T(1.0); } return px; } // Takes in 2-point ``uv`` in the image plane of the camera and unprojects it // into the reference frame of the camera. // // This function is the inverse of ``project``. In particular it holds that // // X = unproject(project(X)) [for X=(x,y,z) in R^3, z>0] // // and // // x = project(unproject(s*x)) [for s!=0 and x=(u,v) in R^2] // // Return 3-point in the camera frame with z = 1. // template <typename D, typename DP> static Eigen::Matrix<typename D::Scalar, 3, 1> unproject( const Eigen::MatrixBase<D>& uvPixel, const Eigen::MatrixBase<DP>& params) { EIGEN_STATIC_ASSERT(D::RowsAtCompileTime == 2 && D::ColsAtCompileTime == 1, THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE); EIGEN_STATIC_ASSERT( DP::ColsAtCompileTime == 1 && (DP::RowsAtCompileTime == kNumParams || DP::RowsAtCompileTime == Eigen::Dynamic), THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE); using T = typename D::Scalar; // Unprojection const T fu = params[0]; const T fv = params[1]; const T u0 = params[2]; const T v0 = params[3]; const T un = (uvPixel(0) - u0) / fu; const T vn = (uvPixel(1) - v0) / fv; return Eigen::Matrix<T, 3, 1>(un, vn, T(1.0)); } }; // Kannala and Brandt Like 'Generic' Projection Model // http://cs.iupui.edu/~tuceryan/pdf-repository/Kannala2006.pdf // https://april.eecs.umich.edu/wiki/Camera_suite // NOTE, our implementation presents some important differences wrt the original // paper: // - k1 in eq(6) in the paper is fixed to 1.0, so k0 here is k2 in the paper // (for the same reason we have only x4 k parameters instead of x5 in the // paper, for order theta^9) // // In this projection model the points behind the camera are projected in a way // that the optimization cost function is continuous, therefore the optimization // problem can be nicely solved. This option should be used during calibration. // // parameters = fx, fy, cx, cy, kb0, kb1, kb2, kb3 class KannalaBrandtK3Projection { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW static constexpr int kNumParams = 8; static constexpr char kName[] = "KannalaBrandtK3"; static constexpr char kDescription[] = "fx, fy, cx, cy, kb0, kb1, kb2, kb3"; static constexpr int kNumDistortionParams = 4; static constexpr int kFocalXIdx = 0; static constexpr int kFocalYIdx = 1; static constexpr int kPrincipalPointColIdx = 2; static constexpr int kPrincipalPointRowIdx = 3; static constexpr bool kIsFisheye = true; static constexpr bool kHasAnalyticalProjection = true; // Takes in 3-point ``pointOptical`` in the local reference frame of the // camera and projects it onto the image plan. // // Precondition: pointOptical.z() != 0. // // Return 2-point in the image plane. // template <class D, class DP, class DJ1 = Eigen::Matrix<typename D::Scalar, 2, 3>, class DJ2 = Eigen::Matrix<typename D::Scalar, 2, kNumParams>> static Eigen::Matrix<typename D::Scalar, 2, 1> project( const Eigen::MatrixBase<D>& pointOptical, const Eigen::MatrixBase<DP>& params, Eigen::MatrixBase<DJ1>* d_point = nullptr, Eigen::MatrixBase<DJ2>* d_params = nullptr) { using T = typename D::Scalar; static_assert(D::RowsAtCompileTime == 3 && D::ColsAtCompileTime == 1, "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); static_assert( DP::ColsAtCompileTime == 1 && (DP::RowsAtCompileTime == kNumParams || DP::RowsAtCompileTime == Eigen::Dynamic), "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); static_assert((DJ1::ColsAtCompileTime == 3 || DJ1::ColsAtCompileTime == Eigen::Dynamic) && (DJ1::RowsAtCompileTime == 2 || DJ1::RowsAtCompileTime == Eigen::Dynamic), "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); static_assert((DJ2::ColsAtCompileTime == kNumParams || DJ2::ColsAtCompileTime == Eigen::Dynamic) && (DJ2::RowsAtCompileTime == 2 || DJ2::RowsAtCompileTime == Eigen::Dynamic), "THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE"); SOPHUS_ENSURE(pointOptical.z() != T(0), "z(%) must not be zero.", pointOptical.z()); // Focal length and principal point const Eigen::Matrix<T, 2, 1> ff = params.template head<2>(); const Eigen::Matrix<T, 2, 1> pp = params.template segment<2>(2); const T k0 = params[4]; const T k1 = params[5]; const T k2 = params[6]; const T k3 = params[7]; const T radiusSquared = pointOptical(0) * pointOptical(0) + pointOptical(1) * pointOptical(1); using std::atan2; using std::sqrt; if (radiusSquared > Sophus::Constants<T>::epsilon()) { const T radius = sqrt(radiusSquared); const T radiusInverse = T(1.0) / radius; const T theta = atan2(radius, pointOptical(2)); const T theta2 = theta * theta; const T theta4 = theta2 * theta2; const T theta6 = theta4 * theta2; const T theta8 = theta4 * theta4; const T rDistorted = theta * (T(1.0) + k0 * theta2 + k1 * theta4 + k2 * theta6 + k3 * theta8); const T scaling = rDistorted * radiusInverse; if (d_point) { const T xSquared = pointOptical(0) * pointOptical(0); const T ySquared = pointOptical(1) * pointOptical(1); const T normSquared = pointOptical(2) * pointOptical(2) + radiusSquared; const T rDistortedDerivative = T(1.0) + T(3.0) * k0 * theta2 + T(5.0) * k1 * theta4 + T(7.0) * k2 * theta6 + T(9.0) * k3 * theta8; const T x13 = pointOptical(2) * rDistortedDerivative / normSquared - scaling; const T rDistortedDerivativeNormalized = rDistortedDerivative / normSquared; const T x20 = pointOptical(2) * rDistortedDerivative / (normSquared)-radiusInverse * rDistorted; (*d_point)(0, 0) = xSquared / radiusSquared * x20 + scaling; (*d_point)(0, 1) = pointOptical(1) * x13 * pointOptical(0) / radiusSquared; (*d_point)(0, 2) = -pointOptical(0) * rDistortedDerivativeNormalized; (*d_point)(1, 0) = (*d_point)(0, 1); (*d_point)(1, 1) = ySquared / radiusSquared * x20 + scaling; (*d_point)(1, 2) = -pointOptical(1) * rDistortedDerivativeNormalized; // toDenseMatrix() is needed for CUDA to explicitly know the matrix // dimensions (*d_point) = ff.asDiagonal().toDenseMatrix() * (*d_point); } using std::pow; if (d_params) { const T xScaled = pointOptical[0] * params[0] * radiusInverse; const T yScaled = pointOptical[1] * params[1] * radiusInverse; const T theta3 = theta * theta2; const T theta5 = theta3 * theta2; const T theta7 = theta5 * theta2; const T theta9 = theta7 * theta2; (*d_params)(0, 0) = pointOptical[0] * scaling; (*d_params)(0, 1) = T(0.0); (*d_params)(0, 2) = T(1.0); (*d_params)(0, 3) = T(0.0); (*d_params)(0, 4) = xScaled * theta3; (*d_params)(0, 5) = xScaled * theta5; (*d_params)(0, 6) = xScaled * theta7; (*d_params)(0, 7) = xScaled * theta9; (*d_params)(1, 0) = T(0.0); (*d_params)(1, 1) = pointOptical[1] * scaling; (*d_params)(1, 2) = T(0.0); (*d_params)(1, 3) = T(1.0); (*d_params)(1, 4) = yScaled * theta3; (*d_params)(1, 5) = yScaled * theta5; (*d_params)(1, 6) = yScaled * theta7; (*d_params)(1, 7) = yScaled * theta9; } const Eigen::Matrix<T, 2, 1> px = scaling * ff.cwiseProduct(pointOptical.template head<2>()) + pp; return px; } else { // linearize r around radius=0 if (d_point) { const T z2 = pointOptical(2) * pointOptical(2); // clang-format off (*d_point) << ff.x() / pointOptical(2), T(0.0), -ff.x() * pointOptical(0) / z2, T(0.0), ff.y() / pointOptical(2), -ff.y() * pointOptical(1) / z2; // clang-format on } if (d_params) { (*d_params)(0, 0) = pointOptical(0) / pointOptical(2); (*d_params)(0, 1) = T(0.0); (*d_params)(0, 2) = T(1.0); (*d_params)(0, 3) = T(0.0); (*d_params)(1, 0) = T(0.0); (*d_params)(1, 1) = pointOptical(1) / pointOptical(2); (*d_params)(1, 2) = T(0.0); (*d_params)(1, 3) = T(1.0); (*d_params).template rightCols<4>().setZero(); } const Eigen::Matrix<T, 2, 1> px = ff.cwiseProduct(pointOptical.template head<2>()) / pointOptical(2) + pp; return px; } } // Takes in 2-point ``uv`` in the image plane of the camera and unprojects it // into the reference frame of the camera. // // This function is the inverse of ``project``. In particular it holds that // // X = unproject(project(X)) [for X=(x,y,z) in R^3, z>0] // // and // // x = project(unproject(s*x)) [for s!=0 and x=(u,v) in R^2] // // Return 3-point in the camera frame with z = 1. // template <typename D, typename DP> static Eigen::Matrix<typename D::Scalar, 3, 1> unproject( const Eigen::MatrixBase<D>& uvPixel, const Eigen::MatrixBase<DP>& params) { EIGEN_STATIC_ASSERT(D::RowsAtCompileTime == 2 && D::ColsAtCompileTime == 1, THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE); EIGEN_STATIC_ASSERT( DP::ColsAtCompileTime == 1 && (DP::RowsAtCompileTime == kNumParams || DP::RowsAtCompileTime == Eigen::Dynamic), THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE); using T = typename D::Scalar; // Unprojection const T fu = params[0]; const T fv = params[1]; const T u0 = params[2]; const T v0 = params[3]; const T k0 = params[4]; const T k1 = params[5]; const T k2 = params[6]; const T k3 = params[7]; const T un = (uvPixel(0) - u0) / fu; const T vn = (uvPixel(1) - v0) / fv; const T rth2 = un * un + vn * vn; if (rth2 < Sophus::Constants<T>::epsilon() * Sophus::Constants<T>::epsilon()) { return Eigen::Matrix<T, 3, 1>(un, vn, T(1.0)); } const T rth = sqrt(rth2); // Use Newtons method to solve for theta, 50 iterations max T th = initTheta(rth); for (int i = 0; i < 50; ++i) { const T th2 = th * th; const T th4 = th2 * th2; const T th6 = th4 * th2; const T th8 = th4 * th4; const T thd = th * (T(1.0) + k0 * th2 + k1 * th4 + k2 * th6 + k3 * th8); const T d_thd_wtr_th = T(1.0) + T(3.0) * k0 * th2 + T(5.0) * k1 * th4 + T(7.0) * k2 * th6 + T(9.0) * k3 * th8; const T step = (thd - rth) / d_thd_wtr_th; th -= step; if (hasConverged(step)) { break; } } using std::tan; T radiusUndistorted = tan(th); if (radiusUndistorted < T(0.0)) { return Eigen::Matrix<T, 3, 1>(-radiusUndistorted * un / rth, -radiusUndistorted * vn / rth, T(-1.0)); } return Eigen::Matrix<T, 3, 1>(radiusUndistorted * un / rth, radiusUndistorted * vn / rth, T(1.0)); } }; } // namespace surreal_opensource