This commit is contained in:
Ivan
2022-04-05 11:42:28 +03:00
commit 6dc0eb0fcf
5565 changed files with 1200500 additions and 0 deletions

View File

@@ -0,0 +1,303 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of unified camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::sqrt;
/// @brief Camera model used in the paper "Bundle Adjustment in the Large".
///
/// See https://grail.cs.washington.edu/projects/bal/ for details.
/// This model has N=3 parameters \f$ \mathbf{i} = \left[f, k_1, k_2
/// \right]^T \f$.
///
/// Unlike the original formulation we assume that the POSITIVE z-axis
/// points in camera direction and thus don't include the "minus" in the
/// perspective projection. You need to consider this when loading BAL data.
///
/// Specifically, for the camera frame we assume the positive z axis pointing
/// forward in view direction and in the image, y is poiting down, x to the
/// right. In the original BAL formulation, the camera points in negative z
/// axis, y is up in the image. Thus when loading the data, we invert the y and
/// z camera axes (y also in the image) in the perspective projection, we don't
/// have the "minus" like in the original Snavely model.
///
/// A 3D point P in camera coordinates is mapped to pixel coordinates p':
/// p = [P / P.z]_xy (perspective division)
/// p' = f * r(p) * p (conversion to pixel coordinates)
/// r(p) = 1.0 + k1 * ||p||^2 + k2 * ||p||^4.
///
/// See \ref project and \ref unproject functions for more details.
template <typename Scalar_ = double>
class BalCamera {
public:
using Scalar = Scalar_;
static constexpr int N = 3; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat2 = Eigen::Matrix<Scalar, 2, 2>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
/// @brief Default constructor with zero intrinsics
BalCamera() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [f, k1, k2]
explicit BalCamera(const VecN& p) { param_ = p; }
/// @brief Cast to different scalar type
template <class Scalar2>
BalCamera<Scalar2> cast() const {
return BalCamera<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Camera model name
///
/// @return "bal"
static std::string getName() { return "bal"; }
/// @brief Project the point and optionally compute Jacobians
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
checkProjectionDerivedTypes<DerivedPoint3D, DerivedPoint2D, DerivedJ3D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& f = param_[0];
const Scalar& k1 = param_[1];
const Scalar& k2 = param_[2];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
const Scalar mx = x / z;
const Scalar my = y / z;
const Scalar mx2 = mx * mx;
const Scalar my2 = my * my;
const Scalar r2 = mx2 + my2;
const Scalar r4 = r2 * r2;
const Scalar rp = Scalar(1) + k1 * r2 + k2 * r4;
proj = Vec2(f * mx * rp, f * my * rp);
const bool is_valid = z >= Sophus::Constants<Scalar>::epsilonSqrt();
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
d_proj_d_p3d->setZero();
const Scalar tmp = k1 + k2 * Scalar(2) * r2;
(*d_proj_d_p3d)(0, 0) = f * (rp + Scalar(2) * mx2 * tmp) / z;
(*d_proj_d_p3d)(1, 1) = f * (rp + Scalar(2) * my2 * tmp) / z;
(*d_proj_d_p3d)(1, 0) = (*d_proj_d_p3d)(0, 1) =
f * my * mx * Scalar(2) * tmp / z;
(*d_proj_d_p3d)(0, 2) = -f * mx * (rp + Scalar(2) * tmp * r2) / z;
(*d_proj_d_p3d)(1, 2) = -f * my * (rp + Scalar(2) * tmp * r2) / z;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
(*d_proj_d_param).setZero();
(*d_proj_d_param)(0, 0) = mx * rp;
(*d_proj_d_param)(1, 0) = my * rp;
(*d_proj_d_param)(0, 1) = f * mx * r2;
(*d_proj_d_param)(1, 1) = f * my * r2;
(*d_proj_d_param)(0, 2) = f * mx * r4;
(*d_proj_d_param)(1, 2) = f * my * r4;
} else {
UNUSED(d_proj_d_param);
}
return is_valid;
}
/// @brief Unproject the point and optionally compute Jacobians
///
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& f = param_[0];
const Scalar& k1 = param_[1];
const Scalar& k2 = param_[2];
const Scalar& u = proj_eval[0];
const Scalar& v = proj_eval[1];
const Vec2 pp(u / f, v / f);
Vec2 p = pp;
for (int i = 0; i < 3; i++) {
const Scalar r2 = p.squaredNorm();
const Scalar rp = (Scalar(1) + k1 * r2 + k2 * r2 * r2);
const Vec2 pp_computed = p * rp;
const Scalar tmp = k1 + k2 * Scalar(2) * r2;
Mat2 J_p;
J_p(0, 0) = (rp + Scalar(2) * p[0] * p[0] * tmp);
J_p(1, 1) = (rp + Scalar(2) * p[1] * p[1] * tmp);
J_p(1, 0) = J_p(0, 1) = p[0] * p[1] * Scalar(2) * tmp;
const Vec2 dp = (J_p.transpose() * J_p).inverse() * J_p.transpose() *
(pp_computed - pp);
p -= dp;
}
p3d.setZero();
p3d[0] = p[0];
p3d[1] = p[1];
p3d[2] = 1;
p3d.normalize();
BASALT_ASSERT_STREAM(d_p3d_d_proj == nullptr && d_p3d_d_param == nullptr,
"Jacobians for unprojection are not implemented");
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
return true;
}
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, 0, 0 \right]^T \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = 0;
param_[2] = 0;
}
/// @brief Increment intrinsic parameters by inc and clamp the values to the
/// valid range
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) { param_ += inc; }
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f, k1, k2 \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<BalCamera> getTestProjections() {
Eigen::aligned_vector<BalCamera> res;
VecN vec1;
vec1 << 399.752, -3.78048e-05, 5.37738e-07;
res.emplace_back(vec1);
return res;
}
/// @brief Resolutions used for unit-tests
static Eigen::aligned_vector<Eigen::Vector2i> getTestResolutions() {
Eigen::aligned_vector<Eigen::Vector2i> res;
res.emplace_back(640, 480);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt

View File

@@ -0,0 +1,126 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Static asserts for checking projection and unprojection types
*/
#pragma once
#include <Eigen/Dense>
#include <string_view>
namespace basalt {
/// @brief Helper struct to evaluate lazy Eigen expressions or const reference
/// them if they are Eigen::Matrix or Eigen::Map types
template <class Derived>
struct EvalOrReference {
using Type = typename Eigen::internal::eval<Derived>::type;
};
/// @brief Helper struct to evaluate lazy Eigen expressions or const reference
/// them if they are Eigen::Matrix or Eigen::Map types
template <typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows,
int _MaxCols, int MapOptions, typename StrideType>
struct EvalOrReference<Eigen::Map<
Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>,
MapOptions, StrideType>> {
using Type = const Eigen::MatrixBase<Eigen::Map<
Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>,
MapOptions, StrideType>> &;
};
/// @brief Helper function to check that 3D points are 3 or 4 dimensional and
/// the Jacobians have the appropriate shape in the project function
template <class DerivedPoint3D, class DerivedPoint2D, class DerivedJ3DPtr,
class DerivedJparamPtr, int N>
constexpr inline void checkProjectionDerivedTypes() {
EIGEN_STATIC_ASSERT(DerivedPoint3D::IsVectorAtCompileTime &&
(DerivedPoint3D::SizeAtCompileTime == 3 ||
DerivedPoint3D::SizeAtCompileTime == 4),
THIS_METHOD_IS_ONLY_FOR_VECTORS_OF_A_SPECIFIC_SIZE)
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedPoint2D, 2);
if constexpr (!std::is_same_v<DerivedJ3DPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJ3DPtr>);
using DerivedJ3D = typename std::remove_pointer<DerivedJ3DPtr>::type;
EIGEN_STATIC_ASSERT(DerivedJ3D::RowsAtCompileTime == 2 &&
int(DerivedJ3D::ColsAtCompileTime) ==
int(DerivedPoint3D::SizeAtCompileTime),
THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE)
}
if constexpr (!std::is_same_v<DerivedJparamPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJparamPtr>);
using DerivedJparam = typename std::remove_pointer<DerivedJparamPtr>::type;
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedJparam, 2, N);
}
}
/// @brief Helper function to check that 3D points are 3 or 4 dimensional and
/// the Jacobians have the appropriate shape in the unproject function
template <class DerivedPoint2D, class DerivedPoint3D, class DerivedJ2DPtr,
class DerivedJparamPtr, int N>
constexpr inline void checkUnprojectionDerivedTypes() {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedPoint2D, 2);
EIGEN_STATIC_ASSERT(DerivedPoint3D::IsVectorAtCompileTime &&
(DerivedPoint3D::SizeAtCompileTime == 3 ||
DerivedPoint3D::SizeAtCompileTime == 4),
THIS_METHOD_IS_ONLY_FOR_VECTORS_OF_A_SPECIFIC_SIZE)
if constexpr (!std::is_same_v<DerivedJ2DPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJ2DPtr>);
using DerivedJ2D = typename std::remove_pointer<DerivedJ2DPtr>::type;
EIGEN_STATIC_ASSERT(DerivedJ2D::ColsAtCompileTime == 2 &&
int(DerivedJ2D::RowsAtCompileTime) ==
int(DerivedPoint3D::SizeAtCompileTime),
THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE)
}
if constexpr (!std::is_same_v<DerivedJparamPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJparamPtr>);
using DerivedJparam = typename std::remove_pointer<DerivedJparamPtr>::type;
EIGEN_STATIC_ASSERT(DerivedJparam::ColsAtCompileTime == N &&
int(DerivedJparam::RowsAtCompileTime) ==
int(DerivedPoint3D::SizeAtCompileTime),
THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE)
}
}
} // namespace basalt

View File

@@ -0,0 +1,448 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of double sphere camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::sqrt;
/// @brief Double Sphere camera model
///
/// \image html ds.png
/// This model has N=6 parameters \f$ \mathbf{i} = \left[f_x, f_y, c_x, c_y,
/// \xi, \alpha \right]^T \f$ with \f$ \xi \in [-1,1], \alpha \in [0,1] \f$. See
/// \ref project and \ref unproject functions for more details.
template <typename Scalar_ = double>
class DoubleSphereCamera {
public:
using Scalar = Scalar_;
static constexpr int N = 6; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
/// @brief Default constructor with zero intrinsics
DoubleSphereCamera() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [fx, fy, cx, cy, xi, alpha]
explicit DoubleSphereCamera(const VecN& p) { param_ = p; }
/// @brief Cast to different scalar type
template <class Scalar2>
DoubleSphereCamera<Scalar2> cast() const {
return DoubleSphereCamera<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Camera model name
///
/// @return "ds"
static std::string getName() { return "ds"; }
/// @brief Project the point and optionally compute Jacobians
///
/// Projection function is defined as follows:
/// \f{align}{
/// \pi(\mathbf{x}, \mathbf{i}) &=
/// \begin{bmatrix}
/// f_x{\frac{x}{\alpha d_2 + (1-\alpha) (\xi d_1 + z)}}
/// \\ f_y{\frac{y}{\alpha d_2 + (1-\alpha) (\xi d_1 + z)}}
/// \\ \end{bmatrix}
/// +
/// \begin{bmatrix}
/// c_x
/// \\ c_y
/// \\ \end{bmatrix},
/// \\ d_1 &= \sqrt{x^2 + y^2 + z^2},
/// \\ d_2 &= \sqrt{x^2 + y^2 + (\xi d_1 + z)^2}.
/// \f}
/// A set of 3D points that results in valid projection is expressed as
/// follows: \f{align}{
/// \Omega &= \{\mathbf{x} \in \mathbb{R}^3 ~|~ z > -w_2 d_1 \}
/// \\ w_2 &= \frac{w_1+\xi}{\sqrt{2w_1\xi + \xi^2 + 1}}
/// \\ w_1 &= \begin{cases} \frac{\alpha}{1-\alpha}, & \mbox{if } \alpha
/// \le 0.5 \\ \frac{1-\alpha}{\alpha} & \mbox{if } \alpha > 0.5
/// \end{cases}
/// \f}
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
checkProjectionDerivedTypes<DerivedPoint3D, DerivedPoint2D, DerivedJ3D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& xi = param_[4];
const Scalar& alpha = param_[5];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
const Scalar xx = x * x;
const Scalar yy = y * y;
const Scalar zz = z * z;
const Scalar r2 = xx + yy;
const Scalar d1_2 = r2 + zz;
const Scalar d1 = sqrt(d1_2);
const Scalar w1 = alpha > Scalar(0.5) ? (Scalar(1) - alpha) / alpha
: alpha / (Scalar(1) - alpha);
const Scalar w2 =
(w1 + xi) / sqrt(Scalar(2) * w1 * xi + xi * xi + Scalar(1));
const bool is_valid = (z > -w2 * d1);
const Scalar k = xi * d1 + z;
const Scalar kk = k * k;
const Scalar d2_2 = r2 + kk;
const Scalar d2 = sqrt(d2_2);
const Scalar norm = alpha * d2 + (Scalar(1) - alpha) * k;
const Scalar mx = x / norm;
const Scalar my = y / norm;
proj[0] = fx * mx + cx;
proj[1] = fy * my + cy;
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
const Scalar norm2 = norm * norm;
const Scalar xy = x * y;
const Scalar tt2 = xi * z / d1 + Scalar(1);
const Scalar d_norm_d_r2 = (xi * (Scalar(1) - alpha) / d1 +
alpha * (xi * k / d1 + Scalar(1)) / d2) /
norm2;
const Scalar tmp2 =
((Scalar(1) - alpha) * tt2 + alpha * k * tt2 / d2) / norm2;
d_proj_d_p3d->setZero();
(*d_proj_d_p3d)(0, 0) = fx * (Scalar(1) / norm - xx * d_norm_d_r2);
(*d_proj_d_p3d)(1, 0) = -fy * xy * d_norm_d_r2;
(*d_proj_d_p3d)(0, 1) = -fx * xy * d_norm_d_r2;
(*d_proj_d_p3d)(1, 1) = fy * (Scalar(1) / norm - yy * d_norm_d_r2);
(*d_proj_d_p3d)(0, 2) = -fx * x * tmp2;
(*d_proj_d_p3d)(1, 2) = -fy * y * tmp2;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
const Scalar norm2 = norm * norm;
(*d_proj_d_param).setZero();
(*d_proj_d_param)(0, 0) = mx;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = my;
(*d_proj_d_param)(1, 3) = Scalar(1);
const Scalar tmp4 = (alpha - Scalar(1) - alpha * k / d2) * d1 / norm2;
const Scalar tmp5 = (k - d2) / norm2;
(*d_proj_d_param)(0, 4) = fx * x * tmp4;
(*d_proj_d_param)(1, 4) = fy * y * tmp4;
(*d_proj_d_param)(0, 5) = fx * x * tmp5;
(*d_proj_d_param)(1, 5) = fy * y * tmp5;
} else {
UNUSED(d_proj_d_param);
}
return is_valid;
}
/// @brief Unproject the point and optionally compute Jacobians
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi^{-1}(\mathbf{u}, \mathbf{i}) &=
/// \frac{m_z \xi + \sqrt{m_z^2 + (1 - \xi^2) r^2}}{m_z^2 + r^2}
/// \begin{bmatrix}
/// m_x \\ m_y \\m_z
/// \\ \end{bmatrix}-\begin{bmatrix}
/// 0 \\ 0 \\ \xi
/// \\ \end{bmatrix},
/// \\ m_x &= \frac{u - c_x}{f_x},
/// \\ m_y &= \frac{v - c_y}{f_y},
/// \\ r^2 &= m_x^2 + m_y^2,
/// \\ m_z &= \frac{1 - \alpha^2 r^2}{\alpha \sqrt{1 - (2 \alpha - 1)
/// r^2}
/// + 1 - \alpha},
/// \f}
///
/// The valid range of unprojections is \f{align}{
/// \Theta &= \begin{cases}
/// \mathbb{R}^2 & \mbox{if } \alpha \le 0.5
/// \\ \{ \mathbf{u} \in \mathbb{R}^2 ~|~ r^2 \le \frac{1}{2\alpha-1} \} &
/// \mbox{if} \alpha > 0.5 \end{cases}
/// \f}
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& xi = param_[4];
const Scalar& alpha = param_[5];
const Scalar mx = (proj_eval[0] - cx) / fx;
const Scalar my = (proj_eval[1] - cy) / fy;
const Scalar r2 = mx * mx + my * my;
const bool is_valid =
!static_cast<bool>(alpha > Scalar(0.5) &&
(r2 >= Scalar(1) / (Scalar(2) * alpha - Scalar(1))));
const Scalar xi2_2 = alpha * alpha;
const Scalar xi1_2 = xi * xi;
const Scalar sqrt2 = sqrt(Scalar(1) - (Scalar(2) * alpha - Scalar(1)) * r2);
const Scalar norm2 = alpha * sqrt2 + Scalar(1) - alpha;
const Scalar mz = (Scalar(1) - xi2_2 * r2) / norm2;
const Scalar mz2 = mz * mz;
const Scalar norm1 = mz2 + r2;
const Scalar sqrt1 = sqrt(mz2 + (Scalar(1) - xi1_2) * r2);
const Scalar k = (mz * xi + sqrt1) / norm1;
p3d.setZero();
p3d[0] = k * mx;
p3d[1] = k * my;
p3d[2] = k * mz - xi;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t> ||
!std::is_same_v<DerivedJparam, std::nullptr_t>) {
const Scalar norm2_2 = norm2 * norm2;
const Scalar norm1_2 = norm1 * norm1;
const Scalar d_mz_d_r2 = (Scalar(0.5) * alpha - xi2_2) *
(r2 * xi2_2 - Scalar(1)) /
(sqrt2 * norm2_2) -
xi2_2 / norm2;
const Scalar d_mz_d_mx = 2 * mx * d_mz_d_r2;
const Scalar d_mz_d_my = 2 * my * d_mz_d_r2;
const Scalar d_k_d_mz =
(norm1 * (xi * sqrt1 + mz) - 2 * mz * (mz * xi + sqrt1) * sqrt1) /
(norm1_2 * sqrt1);
const Scalar d_k_d_r2 =
(xi * d_mz_d_r2 +
Scalar(0.5) / sqrt1 *
(Scalar(2) * mz * d_mz_d_r2 + Scalar(1) - xi1_2)) /
norm1 -
(mz * xi + sqrt1) * (Scalar(2) * mz * d_mz_d_r2 + Scalar(1)) /
norm1_2;
const Scalar d_k_d_mx = d_k_d_r2 * 2 * mx;
const Scalar d_k_d_my = d_k_d_r2 * 2 * my;
constexpr int SIZE_3D = DerivedPoint3D::SizeAtCompileTime;
Eigen::Matrix<Scalar, SIZE_3D, 1> c0, c1;
c0.setZero();
c0[0] = (mx * d_k_d_mx + k);
c0[1] = my * d_k_d_mx;
c0[2] = (mz * d_k_d_mx + k * d_mz_d_mx);
c0 /= fx;
c1.setZero();
c1[0] = mx * d_k_d_my;
c1[1] = (my * d_k_d_my + k);
c1[2] = (mz * d_k_d_my + k * d_mz_d_my);
c1 /= fy;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_proj);
d_p3d_d_proj->col(0) = c0;
d_p3d_d_proj->col(1) = c1;
} else {
UNUSED(d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_param);
const Scalar d_k_d_xi1 = (mz * sqrt1 - xi * r2) / (sqrt1 * norm1);
const Scalar d_mz_d_xi2 =
((Scalar(1) - r2 * xi2_2) *
(r2 * alpha / sqrt2 - sqrt2 + Scalar(1)) / norm2 -
Scalar(2) * r2 * alpha) /
norm2;
const Scalar d_k_d_xi2 = d_k_d_mz * d_mz_d_xi2;
d_p3d_d_param->setZero();
(*d_p3d_d_param).col(0) = -c0 * mx;
(*d_p3d_d_param).col(1) = -c1 * my;
(*d_p3d_d_param).col(2) = -c0;
(*d_p3d_d_param).col(3) = -c1;
(*d_p3d_d_param)(0, 4) = mx * d_k_d_xi1;
(*d_p3d_d_param)(1, 4) = my * d_k_d_xi1;
(*d_p3d_d_param)(2, 4) = mz * d_k_d_xi1 - 1;
(*d_p3d_d_param)(0, 5) = mx * d_k_d_xi2;
(*d_p3d_d_param)(1, 5) = my * d_k_d_xi2;
(*d_p3d_d_param)(2, 5) = mz * d_k_d_xi2 + k * d_mz_d_xi2;
} else {
UNUSED(d_p3d_d_param);
UNUSED(d_k_d_mz);
}
} else {
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
}
return is_valid;
}
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, f_y, c_x, c_y, 0, 0.5
/// \right]^T \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = init[1];
param_[2] = init[2];
param_[3] = init[3];
param_[4] = 0;
param_[5] = 0.5;
}
/// @brief Increment intrinsic parameters by inc and clamp the values to the
/// valid range
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) {
param_ += inc;
param_[4] = std::clamp(param_[4], Scalar(-1), Scalar(1));
param_[5] = std::clamp(param_[5], Scalar(0), Scalar(1));
}
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f_x, f_y, c_x, c_y, \xi, \alpha
/// \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<DoubleSphereCamera> getTestProjections() {
Eigen::aligned_vector<DoubleSphereCamera> res;
VecN vec1;
vec1 << 0.5 * 805, 0.5 * 800, 505, 509, 0.5 * -0.150694, 0.5 * 1.48785;
res.emplace_back(vec1);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt

View File

@@ -0,0 +1,430 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of extended unified camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::sqrt;
/// @brief Extended unified camera model
///
/// \image html eucm.png
/// This model has N=6 parameters \f$ \mathbf{i} = \left[f_x, f_y, c_x, c_y,
/// \alpha, \beta \right]^T \f$ with \f$ \alpha \in [0,1], \beta > 0 \f$.
/// See \ref project and \ref unproject functions for more details.
template <typename Scalar_>
class ExtendedUnifiedCamera {
public:
using Scalar = Scalar_;
static constexpr int N = 6; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
/// @brief Default constructor with zero intrinsics
ExtendedUnifiedCamera() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [fx, fy, cx, cy, alpha, beta]
explicit ExtendedUnifiedCamera(const VecN& p) { param_ = p; }
/// @brief Cast to different scalar type
template <class Scalar2>
ExtendedUnifiedCamera<Scalar2> cast() const {
return ExtendedUnifiedCamera<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Camera model name
///
/// @return "eucm"
static std::string getName() { return "eucm"; }
/// @brief Project the point and optionally compute Jacobians
///
/// Projection function is defined as follows:
/// \f{align}{
/// \pi(\mathbf{x}, \mathbf{i}) &=
/// \begin{bmatrix}
/// f_x{\frac{x}{\alpha d + (1-\alpha) z}}
/// \\ f_y{\frac{y}{\alpha d + (1-\alpha) z}}
/// \\ \end{bmatrix}
/// +
/// \begin{bmatrix}
/// c_x
/// \\ c_y
/// \\ \end{bmatrix},
/// \\ d &= \sqrt{\beta(x^2 + y^2) + z^2}.
/// \f}
/// A set of 3D points that results in valid projection is expressed as
/// follows: \f{align}{
/// \Omega &= \{\mathbf{x} \in \mathbb{R}^3 ~|~ z > -wd \},
/// \\ w &= \begin{cases} \frac{\alpha}{1-\alpha}, & \mbox{if } \alpha \le
/// 0.5,
/// \\ \frac{1-\alpha}{\alpha} & \mbox{if } \alpha > 0.5, \end{cases} \f}
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
checkProjectionDerivedTypes<DerivedPoint3D, DerivedPoint2D, DerivedJ3D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& alpha = param_[4];
const Scalar& beta = param_[5];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
const Scalar r2 = x * x + y * y;
const Scalar rho2 = beta * r2 + z * z;
const Scalar rho = sqrt(rho2);
const Scalar norm = alpha * rho + (Scalar(1) - alpha) * z;
const Scalar mx = x / norm;
const Scalar my = y / norm;
proj = Vec2(fx * mx + cx, fy * my + cy);
// Check if valid
const Scalar w = alpha > Scalar(0.5) ? (Scalar(1) - alpha) / alpha
: alpha / (Scalar(1) - alpha);
const bool is_valid = (z > -w * rho);
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
const Scalar denom = norm * norm * rho;
const Scalar mid = -(alpha * beta * x * y);
const Scalar add = norm * rho;
const Scalar addz = (alpha * z + (Scalar(1) - alpha) * rho);
(*d_proj_d_p3d).setZero();
(*d_proj_d_p3d)(0, 0) = fx * (add - x * x * alpha * beta);
(*d_proj_d_p3d)(1, 0) = fy * mid;
(*d_proj_d_p3d)(0, 1) = fx * mid;
(*d_proj_d_p3d)(1, 1) = fy * (add - y * y * alpha * beta);
(*d_proj_d_p3d)(0, 2) = -fx * x * addz;
(*d_proj_d_p3d)(1, 2) = -fy * y * addz;
(*d_proj_d_p3d) /= denom;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
const Scalar norm2 = norm * norm;
(*d_proj_d_param).setZero();
(*d_proj_d_param)(0, 0) = mx;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = my;
(*d_proj_d_param)(1, 3) = Scalar(1);
const Scalar tmp_x = -fx * x / norm2;
const Scalar tmp_y = -fy * y / norm2;
const Scalar tmp4 = (rho - z);
(*d_proj_d_param)(0, 4) = tmp_x * tmp4;
(*d_proj_d_param)(1, 4) = tmp_y * tmp4;
const Scalar tmp5 = Scalar(0.5) * alpha * r2 / rho;
(*d_proj_d_param)(0, 5) = tmp_x * tmp5;
(*d_proj_d_param)(1, 5) = tmp_y * tmp5;
} else {
UNUSED(d_proj_d_param);
}
return is_valid;
}
/// @brief Unproject the point and optionally compute Jacobians
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi ^ { -1 }(\mathbf{u}, \mathbf{i}) &=
/// \frac{1} {
/// \sqrt { m_x ^ 2 + m_y ^ 2 + m_z ^ 2 }
/// }
/// \begin{bmatrix} m_x \\ m_y \\ m_z \\ \end{bmatrix},
/// \\ m_x &= \frac{u - c_x}{f_x},
/// \\ m_y &= \frac{v - c_y}{f_y},
/// \\ r^2 &= m_x^2 + m_y^2,
/// \\ m_z &= \frac{1 - \beta \alpha^2 r^2}{\alpha \sqrt{1 - (2\alpha - 1)
/// \beta r^2} + (1 - \alpha)}. \f}
///
/// The valid range of unprojections is \f{align}{
/// \Theta &= \begin{cases}
/// \mathbb{R}^2 & \mbox{if } \alpha \le 0.5
/// \\ \{ \mathbf{u} \in \mathbb{R}^2 ~|~ r^2 \le \frac{1}{\beta(2\alpha-1)}
/// \} & \mbox{if } \alpha > 0.5 \end{cases}
/// \f}
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& alpha = param_[4];
const Scalar& beta = param_[5];
const Scalar mx = (proj_eval[0] - cx) / fx;
const Scalar my = (proj_eval[1] - cy) / fy;
const Scalar r2 = mx * mx + my * my;
const Scalar gamma = Scalar(1) - alpha;
// Check if valid
const bool is_valid = !static_cast<bool>(
alpha > Scalar(0.5) && (r2 >= Scalar(1) / ((alpha - gamma) * beta)));
const Scalar tmp1 = (Scalar(1) - alpha * alpha * beta * r2);
const Scalar tmp_sqrt = sqrt(Scalar(1) - (alpha - gamma) * beta * r2);
const Scalar tmp2 = (alpha * tmp_sqrt + gamma);
const Scalar k = tmp1 / tmp2;
const Scalar norm = sqrt(r2 + k * k);
p3d.setZero();
p3d(0) = mx / norm;
p3d(1) = my / norm;
p3d(2) = k / norm;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t> ||
!std::is_same_v<DerivedJparam, std::nullptr_t>) {
const Scalar norm2 = norm * norm;
const Scalar tmp2_2 = tmp2 * tmp2;
const Scalar d_k_d_r2 =
Scalar(0.5) * alpha * beta *
(-Scalar(2) * alpha * tmp2 + tmp1 * (alpha - gamma) / tmp_sqrt) /
tmp2_2;
const Scalar d_norm_inv_d_r2 =
-Scalar(0.5) * (Scalar(1) + Scalar(2) * k * d_k_d_r2) / norm2;
constexpr int SIZE_3D = DerivedPoint3D::SizeAtCompileTime;
Eigen::Matrix<Scalar, SIZE_3D, 1> c0, c1;
c0.setZero();
c0[0] = (1 + 2 * mx * mx * d_norm_inv_d_r2);
c0[1] = (2 * my * mx * d_norm_inv_d_r2);
c0[2] = 2 * mx * (k * d_norm_inv_d_r2 + d_k_d_r2);
c0 /= fx * norm;
c1.setZero();
c1[0] = (2 * my * mx * d_norm_inv_d_r2);
c1[1] = (1 + 2 * my * my * d_norm_inv_d_r2);
c1[2] = 2 * my * (k * d_norm_inv_d_r2 + d_k_d_r2);
c1 /= fy * norm;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_proj);
d_p3d_d_proj->col(0) = c0;
d_p3d_d_proj->col(1) = c1;
} else {
UNUSED(d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_param);
d_p3d_d_param->setZero();
(*d_p3d_d_param).col(0) = -c0 * mx;
(*d_p3d_d_param).col(1) = -c1 * my;
(*d_p3d_d_param).col(2) = -c0;
(*d_p3d_d_param).col(3) = -c1;
const Scalar d_k_d_alpha =
(-Scalar(2) * alpha * beta * r2 * tmp2 -
(tmp_sqrt - alpha * beta * r2 / tmp_sqrt - Scalar(1)) * tmp1) /
tmp2_2;
const Scalar d_k_d_beta =
alpha * r2 *
(Scalar(0.5) * tmp1 * (alpha - gamma) / tmp_sqrt - alpha * tmp2) /
tmp2_2;
const Scalar d_norm_inv_d_k = -k / norm2;
(*d_p3d_d_param)(0, 4) = mx * d_norm_inv_d_k * d_k_d_alpha;
(*d_p3d_d_param)(1, 4) = my * d_norm_inv_d_k * d_k_d_alpha;
(*d_p3d_d_param)(2, 4) = (k * d_norm_inv_d_k + 1) * d_k_d_alpha;
d_p3d_d_param->col(4) /= norm;
(*d_p3d_d_param)(0, 5) = mx * d_norm_inv_d_k * d_k_d_beta;
(*d_p3d_d_param)(1, 5) = my * d_norm_inv_d_k * d_k_d_beta;
(*d_p3d_d_param)(2, 5) = (k * d_norm_inv_d_k + 1) * d_k_d_beta;
d_p3d_d_param->col(5) /= norm;
} else {
UNUSED(d_p3d_d_param);
}
} else {
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
}
return is_valid;
}
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, f_y, c_x, c_y, 0.5, 1
/// \right]^T \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = init[1];
param_[2] = init[2];
param_[3] = init[3];
param_[4] = 0.5;
param_[5] = 1;
}
/// @brief Increment intrinsic parameters by inc and clamp the values to the
/// valid range
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) {
param_ += inc;
// alpha in [0, 1], beta > 0
param_[4] = std::clamp(param_[4], Scalar(0), Scalar(1));
if (param_[5] < Sophus::Constants<Scalar>::epsilonSqrt()) {
param_[5] = Sophus::Constants<Scalar>::epsilonSqrt();
}
}
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f_x, f_y, c_x, c_y, \alpha, \beta
/// \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<ExtendedUnifiedCamera> getTestProjections() {
Eigen::aligned_vector<ExtendedUnifiedCamera> res;
VecN vec1;
// Euroc
vec1 << 460.76484651566468, 459.4051018049483, 365.8937161309615,
249.33499869752445, 0.5903365915227143, 1.127468196965374;
res.emplace_back(vec1);
// TUM VI 512
vec1 << 191.14799816648748, 191.13150946585135, 254.95857715233118,
256.8815466235898, 0.6291060871161842, 1.0418067403139693;
res.emplace_back(vec1);
return res;
}
/// @brief Resolutions used for unit-tests
static Eigen::aligned_vector<Eigen::Vector2i> getTestResolutions() {
Eigen::aligned_vector<Eigen::Vector2i> res;
res.emplace_back(752, 480);
res.emplace_back(512, 512);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt

View File

@@ -0,0 +1,435 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of field-of-view camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::sqrt;
/// @brief Field-of-View camera model
///
/// \image html fov.png
/// This model has N=5 parameters \f$ \mathbf{i} = \left[f_x, f_y, c_x, c_y,
/// w \right]^T \f$. See \ref project and \ref unproject
/// functions for more details.
template <typename Scalar_ = double>
class FovCamera {
public:
using Scalar = Scalar_;
static constexpr int N = 5; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
using Mat44 = Eigen::Matrix<Scalar, 4, 4>;
/// @brief Default constructor with zero intrinsics
FovCamera() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [fx, fy, cx, cy, w]
explicit FovCamera(const VecN& p) { param_ = p; }
/// @brief Cast to different scalar type
template <class Scalar2>
FovCamera<Scalar2> cast() const {
return FovCamera<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Camera model name
///
/// @return "fov"
static std::string getName() { return "fov"; }
/// @brief Project the point and optionally compute Jacobians
///
/// Projection function is defined as follows:
/// \f{align}{
/// \DeclareMathOperator{\atantwo}{atan2}
/// \pi(\mathbf{x}, \mathbf{i}) &=
/// \begin{bmatrix} f_x r_d \frac{x} { r_u }
/// \\ f_y r_d \frac{y} { r_u }
/// \\ \end{bmatrix}
/// +
/// \begin{bmatrix}
/// c_x
/// \\ c_y
/// \\ \end{bmatrix},
/// \\ r_u &= \sqrt{x^2 + y^2},
/// \\ r_d &= \frac{\atantwo(2 r_u \tan{\frac{w}{2}}, z)}{w}.
/// \f}
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
checkProjectionDerivedTypes<DerivedPoint3D, DerivedPoint2D, DerivedJ3D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& w = param_[4];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
Scalar r2 = x * x + y * y;
Scalar r = sqrt(r2);
Scalar z2 = z * z;
const Scalar tanwhalf = std::tan(w / 2);
const Scalar atan_wrd = std::atan2(2 * tanwhalf * r, z);
Scalar rd = Scalar(1);
Scalar d_rd_d_w = Scalar(0);
Scalar d_rd_d_x = Scalar(0);
Scalar d_rd_d_y = Scalar(0);
Scalar d_rd_d_z = Scalar(0);
Scalar tmp1 = Scalar(1) / std::cos(w / 2);
Scalar d_tanwhalf_d_w = Scalar(0.5) * tmp1 * tmp1;
Scalar tmp = (z2 + Scalar(4) * tanwhalf * tanwhalf * r2);
Scalar d_atan_wrd_d_w = Scalar(2) * r * d_tanwhalf_d_w * z / tmp;
bool is_valid = true;
if (w > Sophus::Constants<Scalar>::epsilonSqrt()) {
if (r2 < Sophus::Constants<Scalar>::epsilonSqrt()) {
if (z < Sophus::Constants<Scalar>::epsilonSqrt()) {
is_valid = false;
}
rd = Scalar(2) * tanwhalf / w;
d_rd_d_w = Scalar(2) * (d_tanwhalf_d_w * w - tanwhalf) / (w * w);
} else {
rd = atan_wrd / (r * w);
d_rd_d_w = (d_atan_wrd_d_w * w - atan_wrd) / (r * w * w);
const Scalar d_r_d_x = x / r;
const Scalar d_r_d_y = y / r;
const Scalar d_atan_wrd_d_x = Scalar(2) * tanwhalf * d_r_d_x * z / tmp;
const Scalar d_atan_wrd_d_y = Scalar(2) * tanwhalf * d_r_d_y * z / tmp;
const Scalar d_atan_wrd_d_z = -Scalar(2) * tanwhalf * r / tmp;
d_rd_d_x = (d_atan_wrd_d_x * r - d_r_d_x * atan_wrd) / (r * r * w);
d_rd_d_y = (d_atan_wrd_d_y * r - d_r_d_y * atan_wrd) / (r * r * w);
d_rd_d_z = d_atan_wrd_d_z / (r * w);
}
}
const Scalar mx = x * rd;
const Scalar my = y * rd;
proj[0] = fx * mx + cx;
proj[1] = fy * my + cy;
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
d_proj_d_p3d->setZero();
(*d_proj_d_p3d)(0, 0) = fx * (d_rd_d_x * x + rd);
(*d_proj_d_p3d)(0, 1) = fx * d_rd_d_y * x;
(*d_proj_d_p3d)(0, 2) = fx * d_rd_d_z * x;
(*d_proj_d_p3d)(1, 0) = fy * d_rd_d_x * y;
(*d_proj_d_p3d)(1, 1) = fy * (d_rd_d_y * y + rd);
(*d_proj_d_p3d)(1, 2) = fy * d_rd_d_z * y;
} else {
UNUSED(d_proj_d_p3d);
UNUSED(d_rd_d_x);
UNUSED(d_rd_d_y);
UNUSED(d_rd_d_z);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
d_proj_d_param->setZero();
(*d_proj_d_param)(0, 0) = mx;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = my;
(*d_proj_d_param)(1, 3) = Scalar(1);
(*d_proj_d_param)(0, 4) = fx * x * d_rd_d_w;
(*d_proj_d_param)(1, 4) = fy * y * d_rd_d_w;
} else {
UNUSED(d_proj_d_param);
UNUSED(d_rd_d_w);
}
return is_valid;
}
/// @brief Unproject the point and optionally compute Jacobians
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi^{-1}(\mathbf{u}, \mathbf{i}) &=
/// \begin{bmatrix}
/// m_x \frac{\sin(r_d w)}{ 2 r_d \tan{\frac{w}{2}}}
/// \\ m_y \frac{\sin(r_d w)}{ 2 r_d \tan{\frac{w}{2}}}
/// \\ \cos(r_d w)
/// \\ \end{bmatrix},
/// \\ m_x &= \frac{u - c_x}{f_x},
/// \\ m_y &= \frac{v - c_y}{f_y},
/// \\ r_d &= \sqrt{m_x^2 + m_y^2}.
/// \f}
///
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& w = param_[4];
const Scalar tan_w_2 = std::tan(w / Scalar(2));
const Scalar mul2tanwby2 = tan_w_2 * Scalar(2);
const Scalar mx = (proj_eval[0] - cx) / fx;
const Scalar my = (proj_eval[1] - cy) / fy;
const Scalar rd = sqrt(mx * mx + my * my);
Scalar ru = Scalar(1);
Scalar sin_rd_w = Scalar(0);
Scalar cos_rd_w = Scalar(1);
Scalar d_ru_d_rd = Scalar(0);
Scalar rd_inv = Scalar(1);
if (mul2tanwby2 > Sophus::Constants<Scalar>::epsilonSqrt() &&
rd > Sophus::Constants<Scalar>::epsilonSqrt()) {
sin_rd_w = std::sin(rd * w);
cos_rd_w = std::cos(rd * w);
ru = sin_rd_w / (rd * mul2tanwby2);
rd_inv = Scalar(1) / rd;
d_ru_d_rd =
(w * cos_rd_w * rd - sin_rd_w) * rd_inv * rd_inv / mul2tanwby2;
}
p3d.setZero();
p3d[0] = mx * ru;
p3d[1] = my * ru;
p3d[2] = cos_rd_w;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t> ||
!std::is_same_v<DerivedJparam, std::nullptr_t>) {
constexpr int SIZE_3D = DerivedPoint3D::SizeAtCompileTime;
Eigen::Matrix<Scalar, SIZE_3D, 1> c0, c1;
c0.setZero();
c0(0) = (ru + mx * d_ru_d_rd * mx * rd_inv) / fx;
c0(1) = my * d_ru_d_rd * mx * rd_inv / fx;
c0(2) = -sin_rd_w * w * mx * rd_inv / fx;
c1.setZero();
c1(0) = my * d_ru_d_rd * mx * rd_inv / fy;
c1(1) = (ru + my * d_ru_d_rd * my * rd_inv) / fy;
c1(2) = -sin_rd_w * w * my * rd_inv / fy;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_proj);
d_p3d_d_proj->col(0) = c0;
d_p3d_d_proj->col(1) = c1;
} else {
UNUSED(d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_param);
d_p3d_d_param->setZero();
d_p3d_d_param->col(2) = -c0;
d_p3d_d_param->col(3) = -c1;
d_p3d_d_param->col(0) = -c0 * mx;
d_p3d_d_param->col(1) = -c1 * my;
Scalar tmp = (cos_rd_w - (tan_w_2 * tan_w_2 + Scalar(1)) * sin_rd_w *
rd_inv / (2 * tan_w_2)) /
mul2tanwby2;
(*d_p3d_d_param)(0, 4) = mx * tmp;
(*d_p3d_d_param)(1, 4) = my * tmp;
(*d_p3d_d_param)(2, 4) = -sin_rd_w * rd;
} else {
UNUSED(d_p3d_d_param);
UNUSED(d_ru_d_rd);
}
Scalar norm = p3d.norm();
Scalar norm_inv = Scalar(1) / norm;
Scalar norm_inv2 = norm_inv * norm_inv;
Scalar norm_inv3 = norm_inv2 * norm_inv;
Eigen::Matrix<Scalar, SIZE_3D, SIZE_3D> d_p_norm_d_p;
d_p_norm_d_p.setZero();
d_p_norm_d_p(0, 0) = norm_inv * (Scalar(1) - p3d[0] * p3d[0] * norm_inv2);
d_p_norm_d_p(1, 0) = -p3d[1] * p3d[0] * norm_inv3;
d_p_norm_d_p(2, 0) = -p3d[2] * p3d[0] * norm_inv3;
d_p_norm_d_p(0, 1) = -p3d[1] * p3d[0] * norm_inv3;
d_p_norm_d_p(1, 1) = norm_inv * (Scalar(1) - p3d[1] * p3d[1] * norm_inv2);
d_p_norm_d_p(2, 1) = -p3d[1] * p3d[2] * norm_inv3;
d_p_norm_d_p(0, 2) = -p3d[2] * p3d[0] * norm_inv3;
d_p_norm_d_p(1, 2) = -p3d[2] * p3d[1] * norm_inv3;
d_p_norm_d_p(2, 2) = norm_inv * (Scalar(1) - p3d[2] * p3d[2] * norm_inv2);
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
(*d_p3d_d_proj) = d_p_norm_d_p * (*d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
(*d_p3d_d_param) = d_p_norm_d_p * (*d_p3d_d_param);
}
} else {
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
UNUSED(d_ru_d_rd);
}
p3d /= p3d.norm();
return true;
}
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, f_y, c_x, c_y, 1
/// \right]^T \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = init[1];
param_[2] = init[2];
param_[3] = init[3];
param_[4] = 1;
}
/// @brief Increment intrinsic parameters by inc
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) { param_ += inc; }
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f_x, f_y, c_x, c_y, k1, k2, k3, k4
/// \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<FovCamera> getTestProjections() {
Eigen::aligned_vector<FovCamera> res;
VecN vec1;
// Euroc
vec1 << 379.045, 379.008, 505.512, 509.969, 0.9259487501905697;
res.emplace_back(vec1);
return res;
}
/// @brief Resolutions used for unit-tests
static Eigen::aligned_vector<Eigen::Vector2i> getTestResolutions() {
Eigen::aligned_vector<Eigen::Vector2i> res;
res.emplace_back(752, 480);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt

View File

@@ -0,0 +1,364 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of generic camera model
*/
#pragma once
#include <basalt/camera/bal_camera.hpp>
#include <basalt/camera/double_sphere_camera.hpp>
#include <basalt/camera/extended_camera.hpp>
#include <basalt/camera/fov_camera.hpp>
#include <basalt/camera/kannala_brandt_camera4.hpp>
#include <basalt/camera/pinhole_camera.hpp>
#include <basalt/camera/unified_camera.hpp>
#include <variant>
namespace basalt {
using std::sqrt;
/// @brief Generic camera model that can store different camera models
///
/// Particular class of camera model is stored as \ref variant and can be casted
/// to specific type using std::visit.
template <typename Scalar_>
class GenericCamera {
using Scalar = Scalar_;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec3 = Eigen::Matrix<Scalar, 3, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4 = Eigen::Matrix<Scalar, 4, 4>;
using VariantT =
std::variant<ExtendedUnifiedCamera<Scalar>, DoubleSphereCamera<Scalar>,
KannalaBrandtCamera4<Scalar>, UnifiedCamera<Scalar>,
PinholeCamera<Scalar>>; ///< Possible variants of camera
///< models.
public:
/// @brief Cast to different scalar type
template <typename Scalar2>
inline GenericCamera<Scalar2> cast() const {
GenericCamera<Scalar2> res;
std::visit([&](const auto& v) { res.variant = v.template cast<Scalar2>(); },
variant);
return res;
}
/// @brief Number of intrinsic parameters
inline int getN() const {
int res;
std::visit([&](const auto& v) { res = v.N; }, variant);
return res;
}
/// @brief Camera model name
inline std::string getName() const {
std::string res;
std::visit([&](const auto& v) { res = v.getName(); }, variant);
return res;
}
/// @brief Set parameters from initialization
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
std::visit([&](auto& v) { return v.setFromInit(init); }, variant);
}
/// @brief Increment intrinsic parameters by inc and if necessary clamp the
/// values to the valid range
inline void applyInc(const VecX& inc) {
std::visit([&](auto& v) { return v += inc; }, variant);
}
/// @brief Returns a vector of intrinsic parameters
///
/// The order of parameters depends on the stored model.
/// @return vector of intrinsic parameters vector
inline VecX getParam() const {
VecX res;
std::visit([&](const auto& cam) { res = cam.getParam(); }, variant);
return res;
}
/// @brief Project a single point and optionally compute Jacobian
///
/// **SLOW** function, as it requires vtable lookup for every projection.
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @return if projection is valid
template <typename DerivedJ3DPtr = std::nullptr_t>
inline bool project(const Vec4& p3d, Vec2& proj,
DerivedJ3DPtr d_proj_d_p3d = nullptr) const {
if constexpr (!std::is_same_v<DerivedJ3DPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJ3DPtr>);
using DerivedJ3D = typename std::remove_pointer<DerivedJ3DPtr>::type;
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedJ3D, 2, 4);
}
bool res;
std::visit(
[&](const auto& cam) { res = cam.project(p3d, proj, d_proj_d_p3d); },
variant);
return res;
}
/// @brief Unproject a single point and optionally compute Jacobian
///
/// **SLOW** function, as it requires vtable lookup for every unprojection.
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @return if unprojection is valid
template <typename DerivedJ2DPtr = std::nullptr_t>
inline bool unproject(const Vec2& proj, Vec4& p3d,
DerivedJ2DPtr d_p3d_d_proj = nullptr) const {
if constexpr (!std::is_same_v<DerivedJ2DPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJ2DPtr>);
using DerivedJ2D = typename std::remove_pointer<DerivedJ2DPtr>::type;
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedJ2D, 4, 2);
}
bool res;
std::visit(
[&](const auto& cam) { res = cam.unproject(proj, p3d, d_p3d_d_proj); },
variant);
return res;
}
/// @brief Project a single point and optionally compute Jacobian
///
/// **SLOW** function, as it requires vtable lookup for every projection.
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @return if projection is valid
template <typename DerivedJ3DPtr = std::nullptr_t>
inline bool project(const Vec3& p3d, Vec2& proj,
DerivedJ3DPtr d_proj_d_p3d = nullptr) const {
if constexpr (!std::is_same_v<DerivedJ3DPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJ3DPtr>);
using DerivedJ3D = typename std::remove_pointer<DerivedJ3DPtr>::type;
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedJ3D, 2, 3);
}
bool res;
std::visit(
[&](const auto& cam) { res = cam.project(p3d, proj, d_proj_d_p3d); },
variant);
return res;
}
/// @brief Unproject a single point and optionally compute Jacobian
///
/// **SLOW** function, as it requires vtable lookup for every unprojection.
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @return if unprojection is valid
template <typename DerivedJ2DPtr = std::nullptr_t>
inline bool unproject(const Vec2& proj, Vec3& p3d,
DerivedJ2DPtr d_p3d_d_proj = nullptr) const {
if constexpr (!std::is_same_v<DerivedJ2DPtr, std::nullptr_t>) {
static_assert(std::is_pointer_v<DerivedJ2DPtr>);
using DerivedJ2D = typename std::remove_pointer<DerivedJ2DPtr>::type;
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedJ2D, 3, 2);
}
bool res;
std::visit(
[&](const auto& cam) { res = cam.unproject(proj, p3d, d_p3d_d_proj); },
variant);
return res;
}
/// @brief Project a vector of points
///
/// @param[in] p3d points to project
/// @param[in] T_c_w transformation from world to camera frame that should be
/// applied to points before projection
/// @param[out] proj results of projection
/// @param[out] proj_success if projection is valid
inline void project(const Eigen::aligned_vector<Vec3>& p3d, const Mat4& T_c_w,
Eigen::aligned_vector<Vec2>& proj,
std::vector<bool>& proj_success) const {
std::visit(
[&](const auto& cam) {
proj.resize(p3d.size());
proj_success.resize(p3d.size());
for (size_t i = 0; i < p3d.size(); i++) {
proj_success[i] =
cam.project(T_c_w * p3d[i].homogeneous(), proj[i]);
}
},
variant);
}
/// @brief Project a vector of points
///
/// @param[in] p3d points to project
/// @param[in] T_c_w transformation from world to camera frame that should be
/// applied to points before projection
/// @param[out] proj results of projection
/// @param[out] proj_success if projection is valid
inline void project(const Eigen::aligned_vector<Vec4>& p3d, const Mat4& T_c_w,
Eigen::aligned_vector<Vec2>& proj,
std::vector<bool>& proj_success) const {
std::visit(
[&](const auto& cam) {
proj.resize(p3d.size());
proj_success.resize(p3d.size());
for (size_t i = 0; i < p3d.size(); i++) {
proj_success[i] = cam.project(T_c_w * p3d[i], proj[i]);
}
},
variant);
}
/// @brief Project a vector of points
///
/// @param[in] p3d points to project
/// @param[out] proj results of projection
/// @param[out] proj_success if projection is valid
inline void project(const Eigen::aligned_vector<Vec4>& p3d,
Eigen::aligned_vector<Vec2>& proj,
std::vector<bool>& proj_success) const {
std::visit(
[&](const auto& cam) {
proj.resize(p3d.size());
proj_success.resize(p3d.size());
for (size_t i = 0; i < p3d.size(); i++) {
proj_success[i] = cam.project(p3d[i], proj[i]);
}
},
variant);
}
/// @brief Project a vector of points, compute polar and azimuthal angles
///
/// @param[in] p3d points to project
/// @param[in] T_c_w transformation from world to camera frame that should be
/// applied to points before projection
/// @param[out] proj results of projection
/// @param[out] proj_success if projection is valid
inline void project(
const Eigen::aligned_vector<Vec4>& p3d, const Mat4& T_c_w,
Eigen::aligned_vector<Vec2>& proj, std::vector<bool>& proj_success,
Eigen::aligned_vector<Vec2>& polar_azimuthal_angle) const {
std::visit(
[&](const auto& cam) {
proj.resize(p3d.size());
proj_success.resize(p3d.size());
polar_azimuthal_angle.resize(p3d.size());
for (size_t i = 0; i < p3d.size(); i++) {
Vec4 p3dt = T_c_w * p3d[i];
proj_success[i] = cam.project(p3dt, proj[i]);
Scalar r2 = p3dt[0] * p3dt[0] + p3dt[1] * p3dt[1];
Scalar r = std::sqrt(r2);
polar_azimuthal_angle[i][0] = std::atan2(r, p3dt[2]);
polar_azimuthal_angle[i][1] = std::atan2(p3dt[0], p3dt[1]);
}
},
variant);
}
/// @brief Unproject a vector of points
///
/// @param[in] proj points to unproject
/// @param[out] p3d results of unprojection
/// @param[out] unproj_success if unprojection is valid
inline void unproject(const Eigen::aligned_vector<Vec2>& proj,
Eigen::aligned_vector<Vec4>& p3d,
std::vector<bool>& unproj_success) const {
std::visit(
[&](const auto& cam) {
p3d.resize(proj.size());
unproj_success.resize(proj.size());
for (size_t i = 0; i < p3d.size(); i++) {
unproj_success[i] = cam.unproject(proj[i], p3d[i]);
}
},
variant);
}
/// @brief Construct a particular type of camera model from name
static GenericCamera<Scalar> fromString(const std::string& name) {
GenericCamera<Scalar> res;
constexpr size_t VARIANT_SIZE = std::variant_size<VariantT>::value;
visitAllTypes<VARIANT_SIZE - 1>(res, name);
return res;
}
VariantT variant;
private:
/// @brief Iterate over all possible types of the variant and construct that
/// type that has a matching name
template <int I>
static void visitAllTypes(GenericCamera<Scalar>& res,
const std::string& name) {
if constexpr (I >= 0) {
using cam_t = typename std::variant_alternative<I, VariantT>::type;
if (cam_t::getName() == name) {
cam_t val;
res.variant = val;
}
visitAllTypes<I - 1>(res, name);
}
}
};
} // namespace basalt

View File

@@ -0,0 +1,531 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of Kannala-Brandt camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::cos;
using std::sin;
using std::sqrt;
/// @brief Kannala-Brandt camera model
///
/// \image html kb.png
/// The displacement of the projection from the optical center is proportional
/// to \f$d(\theta)\f$, which is a polynomial function of the angle between the
/// point and optical axis \f$\theta\f$.
/// This model has N=8 parameters \f$ \mathbf{i} = \left[f_x, f_y, c_x, c_y,
/// k_1, k_2, k_3, k_4 \right]^T \f$. See \ref project and \ref unproject
/// functions for more details. This model corresponds to fisheye camera model
/// in OpenCV.
template <typename Scalar_ = double>
class KannalaBrandtCamera4 {
public:
using Scalar = Scalar_;
static constexpr int N = 8; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
/// @brief Default constructor with zero intrinsics
KannalaBrandtCamera4() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [fx, fy, cx, cy, k1, k2, k3,
/// k4]
explicit KannalaBrandtCamera4(const VecN& p) { param_ = p; }
/// @brief Camera model name
///
/// @return "kb4"
static std::string getName() { return "kb4"; }
/// @brief Cast to different scalar type
template <class Scalar2>
KannalaBrandtCamera4<Scalar2> cast() const {
return KannalaBrandtCamera4<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Project the point and optionally compute Jacobians
///
/// Projection function is defined as follows:
/// \f{align}{
/// \DeclareMathOperator{\atantwo}{atan2}
/// \pi(\mathbf{x}, \mathbf{i}) &=
/// \begin{bmatrix}
/// f_x ~ d(\theta) ~ \frac{x}{r}
/// \\ f_y ~ d(\theta) ~ \frac{y}{r}
/// \\ \end{bmatrix}
/// +
/// \begin{bmatrix}
/// c_x
/// \\ c_y
/// \\ \end{bmatrix},
/// \\ r &= \sqrt{x^2 + y^2},
/// \\ \theta &= \atantwo(r, z),
/// \\ d(\theta) &= \theta + k_1 \theta^3 + k_2 \theta^5 + k_3 \theta^7 + k_4
/// \theta^9.
/// \f}
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
checkProjectionDerivedTypes<DerivedPoint3D, DerivedPoint2D, DerivedJ3D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& k1 = param_[4];
const Scalar& k2 = param_[5];
const Scalar& k3 = param_[6];
const Scalar& k4 = param_[7];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
const Scalar r2 = x * x + y * y;
const Scalar r = sqrt(r2);
bool is_valid = true;
if (r > Sophus::Constants<Scalar>::epsilonSqrt()) {
const Scalar theta = atan2(r, z);
const Scalar theta2 = theta * theta;
Scalar r_theta = k4 * theta2;
r_theta += k3;
r_theta *= theta2;
r_theta += k2;
r_theta *= theta2;
r_theta += k1;
r_theta *= theta2;
r_theta += 1;
r_theta *= theta;
const Scalar mx = x * r_theta / r;
const Scalar my = y * r_theta / r;
proj[0] = fx * mx + cx;
proj[1] = fy * my + cy;
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
const Scalar d_r_d_x = x / r;
const Scalar d_r_d_y = y / r;
const Scalar tmp = (z * z + r2);
const Scalar d_theta_d_x = d_r_d_x * z / tmp;
const Scalar d_theta_d_y = d_r_d_y * z / tmp;
const Scalar d_theta_d_z = -r / tmp;
Scalar d_r_theta_d_theta = Scalar(9) * k4 * theta2;
d_r_theta_d_theta += Scalar(7) * k3;
d_r_theta_d_theta *= theta2;
d_r_theta_d_theta += Scalar(5) * k2;
d_r_theta_d_theta *= theta2;
d_r_theta_d_theta += Scalar(3) * k1;
d_r_theta_d_theta *= theta2;
d_r_theta_d_theta += Scalar(1);
d_proj_d_p3d->setZero();
(*d_proj_d_p3d)(0, 0) =
fx *
(r_theta * r + x * r * d_r_theta_d_theta * d_theta_d_x -
x * x * r_theta / r) /
r2;
(*d_proj_d_p3d)(1, 0) =
fy * y * (d_r_theta_d_theta * d_theta_d_x * r - x * r_theta / r) /
r2;
(*d_proj_d_p3d)(0, 1) =
fx * x * (d_r_theta_d_theta * d_theta_d_y * r - y * r_theta / r) /
r2;
(*d_proj_d_p3d)(1, 1) =
fy *
(r_theta * r + y * r * d_r_theta_d_theta * d_theta_d_y -
y * y * r_theta / r) /
r2;
(*d_proj_d_p3d)(0, 2) = fx * x * d_r_theta_d_theta * d_theta_d_z / r;
(*d_proj_d_p3d)(1, 2) = fy * y * d_r_theta_d_theta * d_theta_d_z / r;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
(*d_proj_d_param).setZero();
(*d_proj_d_param)(0, 0) = mx;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = my;
(*d_proj_d_param)(1, 3) = Scalar(1);
(*d_proj_d_param)(0, 4) = fx * x * theta * theta2 / r;
(*d_proj_d_param)(1, 4) = fy * y * theta * theta2 / r;
d_proj_d_param->col(5) = d_proj_d_param->col(4) * theta2;
d_proj_d_param->col(6) = d_proj_d_param->col(5) * theta2;
d_proj_d_param->col(7) = d_proj_d_param->col(6) * theta2;
} else {
UNUSED(d_proj_d_param);
}
} else {
// Check that the point is not cloze to zero norm
if (z < Sophus::Constants<Scalar>::epsilonSqrt()) {
is_valid = false;
}
proj[0] = fx * x / z + cx;
proj[1] = fy * y / z + cy;
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
d_proj_d_p3d->setZero();
const Scalar z2 = z * z;
(*d_proj_d_p3d)(0, 0) = fx / z;
(*d_proj_d_p3d)(0, 2) = -fx * x / z2;
(*d_proj_d_p3d)(1, 1) = fy / z;
(*d_proj_d_p3d)(1, 2) = -fy * y / z2;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
d_proj_d_param->setZero();
(*d_proj_d_param)(0, 0) = x / z;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = y / z;
(*d_proj_d_param)(1, 3) = Scalar(1);
} else {
UNUSED(d_proj_d_param);
}
}
return is_valid;
}
/// @brief solve for theta using Newton's method.
///
/// Find root of the polynomisl \f$ d(\theta) - r_{\theta} = 0 \f$ using
/// Newton's method (https://en.wikipedia.org/wiki/Newton%27s_method). Used in
/// \ref unproject function.
///
/// @param ITER number of iterations
/// @param[in] r_theta number of iterations
/// @param[out] d_func_d_theta derivative of the function with respect to
/// theta at the solution
/// @returns theta - root of the polynomial
template <int ITER>
inline Scalar solveTheta(const Scalar& r_theta,
Scalar& d_func_d_theta) const {
const Scalar& k1 = param_[4];
const Scalar& k2 = param_[5];
const Scalar& k3 = param_[6];
const Scalar& k4 = param_[7];
Scalar theta = r_theta;
for (int i = ITER; i > 0; i--) {
Scalar theta2 = theta * theta;
Scalar func = k4 * theta2;
func += k3;
func *= theta2;
func += k2;
func *= theta2;
func += k1;
func *= theta2;
func += Scalar(1);
func *= theta;
d_func_d_theta = Scalar(9) * k4 * theta2;
d_func_d_theta += Scalar(7) * k3;
d_func_d_theta *= theta2;
d_func_d_theta += Scalar(5) * k2;
d_func_d_theta *= theta2;
d_func_d_theta += Scalar(3) * k1;
d_func_d_theta *= theta2;
d_func_d_theta += Scalar(1);
// Iteration of Newton method
theta += (r_theta - func) / d_func_d_theta;
}
return theta;
}
/// @brief Unproject the point and optionally compute Jacobians
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi^{-1}(\mathbf{u}, \mathbf{i}) &=
/// \begin{bmatrix}
/// \sin(\theta^{*}) ~ \frac{m_x}{r_{\theta}}
/// \\ \sin(\theta^{*}) ~ \frac{m_y}{r_{\theta}}
/// \\ \cos(\theta^{*})
/// \\ \end{bmatrix},
/// \\ m_x &= \frac{u - c_x}{f_x},
/// \\ m_y &= \frac{v - c_y}{f_y},
/// \\ r_{\theta} &= \sqrt{m_x^2 + m_y^2},
/// \\ \theta^{*} &= d^{-1}(r_{\theta}).
/// \f}
///
/// \f$\theta^{*}\f$ is the solution of \f$d(\theta)=r_{\theta}\f$. See \ref
/// solve_theta for details.
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar mx = (proj_eval[0] - cx) / fx;
const Scalar my = (proj_eval[1] - cy) / fy;
Scalar theta(0);
Scalar sin_theta(0);
Scalar cos_theta(1);
Scalar thetad = sqrt(mx * mx + my * my);
Scalar scaling(1);
Scalar d_func_d_theta(0);
if (thetad > Sophus::Constants<Scalar>::epsilonSqrt()) {
theta = solveTheta<3>(thetad, d_func_d_theta);
sin_theta = sin(theta);
cos_theta = cos(theta);
scaling = sin_theta / thetad;
}
p3d.setZero();
p3d[0] = mx * scaling;
p3d[1] = my * scaling;
p3d[2] = cos_theta;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t> ||
!std::is_same_v<DerivedJparam, std::nullptr_t>) {
Scalar d_thetad_d_mx = Scalar(0);
Scalar d_thetad_d_my = Scalar(0);
Scalar d_scaling_d_thetad = Scalar(0);
Scalar d_cos_d_thetad = Scalar(0);
Scalar d_scaling_d_k1 = Scalar(0);
Scalar d_cos_d_k1 = Scalar(0);
Scalar theta2 = Scalar(0);
if (thetad > Sophus::Constants<Scalar>::epsilonSqrt()) {
d_thetad_d_mx = mx / thetad;
d_thetad_d_my = my / thetad;
theta2 = theta * theta;
d_scaling_d_thetad = (thetad * cos_theta / d_func_d_theta - sin_theta) /
(thetad * thetad);
d_cos_d_thetad = sin_theta / d_func_d_theta;
d_scaling_d_k1 =
-cos_theta * theta * theta2 / (d_func_d_theta * thetad);
d_cos_d_k1 = d_cos_d_thetad * theta * theta2;
}
const Scalar d_res0_d_mx =
scaling + mx * d_scaling_d_thetad * d_thetad_d_mx;
const Scalar d_res0_d_my = mx * d_scaling_d_thetad * d_thetad_d_my;
const Scalar d_res1_d_mx = my * d_scaling_d_thetad * d_thetad_d_mx;
const Scalar d_res1_d_my =
scaling + my * d_scaling_d_thetad * d_thetad_d_my;
const Scalar d_res2_d_mx = -d_cos_d_thetad * d_thetad_d_mx;
const Scalar d_res2_d_my = -d_cos_d_thetad * d_thetad_d_my;
constexpr int SIZE_3D = DerivedPoint3D::SizeAtCompileTime;
Eigen::Matrix<Scalar, SIZE_3D, 1> c0, c1;
c0.setZero();
c0(0) = d_res0_d_mx / fx;
c0(1) = d_res1_d_mx / fx;
c0(2) = d_res2_d_mx / fx;
c1.setZero();
c1(0) = d_res0_d_my / fy;
c1(1) = d_res1_d_my / fy;
c1(2) = d_res2_d_my / fy;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_proj);
d_p3d_d_proj->col(0) = c0;
d_p3d_d_proj->col(1) = c1;
} else {
UNUSED(d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_param);
d_p3d_d_param->setZero();
d_p3d_d_param->col(2) = -c0;
d_p3d_d_param->col(3) = -c1;
d_p3d_d_param->col(0) = -c0 * mx;
d_p3d_d_param->col(1) = -c1 * my;
(*d_p3d_d_param)(0, 4) = mx * d_scaling_d_k1;
(*d_p3d_d_param)(1, 4) = my * d_scaling_d_k1;
(*d_p3d_d_param)(2, 4) = d_cos_d_k1;
d_p3d_d_param->col(5) = d_p3d_d_param->col(4) * theta2;
d_p3d_d_param->col(6) = d_p3d_d_param->col(5) * theta2;
d_p3d_d_param->col(7) = d_p3d_d_param->col(6) * theta2;
} else {
UNUSED(d_p3d_d_param);
UNUSED(d_scaling_d_k1);
UNUSED(d_cos_d_k1);
}
} else {
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
}
return true;
}
/// @brief Increment intrinsic parameters by inc
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) { param_ += inc; }
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f_x, f_y, c_x, c_y, k_1, k_2, k_3, k_4
/// \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, f_y, c_x, c_y, 0, 0, 0, 0
/// \right]^T \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = init[1];
param_[2] = init[2];
param_[3] = init[3];
param_[4] = 0;
param_[5] = 0;
param_[6] = 0;
param_[7] = 0;
}
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<KannalaBrandtCamera4> getTestProjections() {
Eigen::aligned_vector<KannalaBrandtCamera4> res;
VecN vec1;
vec1 << 379.045, 379.008, 505.512, 509.969, 0.00693023, -0.0013828,
-0.000272596, -0.000452646;
res.emplace_back(vec1);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt

View File

@@ -0,0 +1,324 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of pinhole camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::sqrt;
/// @brief Pinhole camera model
///
/// This model has N=4 parameters \f$ \mathbf{i} = \left[f_x, f_y, c_x, c_y
/// \right]^T \f$ with. See \ref
/// project and \ref unproject functions for more details.
template <typename Scalar_ = double>
class PinholeCamera {
public:
using Scalar = Scalar_;
static constexpr int N = 4; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
/// @brief Default constructor with zero intrinsics
PinholeCamera() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [fx, fy, cx, cy]
explicit PinholeCamera(const VecN& p) { param_ = p; }
/// @brief Cast to different scalar type
template <class Scalar2>
PinholeCamera<Scalar2> cast() const {
return PinholeCamera<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Camera model name
///
/// @return "pinhole"
static std::string getName() { return "pinhole"; }
/// @brief Project the point and optionally compute Jacobians
///
/// Projection function is defined as follows:
/// \f{align}{
/// \pi(\mathbf{x}, \mathbf{i}) &=
/// \begin{bmatrix}
/// f_x{\frac{x}{z}}
/// \\ f_y{\frac{y}{z}}
/// \\ \end{bmatrix}
/// +
/// \begin{bmatrix}
/// c_x
/// \\ c_y
/// \\ \end{bmatrix}.
/// \f}
/// A set of 3D points that results in valid projection is expressed as
/// follows: \f{align}{
/// \Omega &= \{\mathbf{x} \in \mathbb{R}^3 ~|~ z > 0 \}
/// \f}
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
checkProjectionDerivedTypes<DerivedPoint3D, DerivedPoint2D, DerivedJ3D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
proj[0] = fx * x / z + cx;
proj[1] = fy * y / z + cy;
const bool is_valid = z >= Sophus::Constants<Scalar>::epsilonSqrt();
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
d_proj_d_p3d->setZero();
const Scalar z2 = z * z;
(*d_proj_d_p3d)(0, 0) = fx / z;
(*d_proj_d_p3d)(0, 2) = -fx * x / z2;
(*d_proj_d_p3d)(1, 1) = fy / z;
(*d_proj_d_p3d)(1, 2) = -fy * y / z2;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
d_proj_d_param->setZero();
(*d_proj_d_param)(0, 0) = x / z;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = y / z;
(*d_proj_d_param)(1, 3) = Scalar(1);
} else {
UNUSED(d_proj_d_param);
}
return is_valid;
}
/// @brief Unproject the point and optionally compute Jacobians
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi^{-1}(\mathbf{u}, \mathbf{i}) &=
/// \frac{1}{m_x^2 + m_y^2 + 1}
/// \begin{bmatrix}
/// m_x \\ m_y \\ 1
/// \\ \end{bmatrix}
/// \\ m_x &= \frac{u - c_x}{f_x},
/// \\ m_y &= \frac{v - c_y}{f_y}.
/// \f}
///
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of
/// unprojection with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar mx = (proj_eval[0] - cx) / fx;
const Scalar my = (proj_eval[1] - cy) / fy;
const Scalar r2 = mx * mx + my * my;
const Scalar norm = sqrt(Scalar(1) + r2);
const Scalar norm_inv = Scalar(1) / norm;
p3d.setZero();
p3d[0] = mx * norm_inv;
p3d[1] = my * norm_inv;
p3d[2] = norm_inv;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t> ||
!std::is_same_v<DerivedJparam, std::nullptr_t>) {
const Scalar d_norm_inv_d_r2 =
-Scalar(0.5) * norm_inv * norm_inv * norm_inv;
constexpr int SIZE_3D = DerivedPoint3D::SizeAtCompileTime;
Eigen::Matrix<Scalar, SIZE_3D, 1> c0, c1;
c0.setZero();
c0(0) = (norm_inv + 2 * mx * mx * d_norm_inv_d_r2) / fx;
c0(1) = (2 * my * mx * d_norm_inv_d_r2) / fx;
c0(2) = 2 * mx * d_norm_inv_d_r2 / fx;
c1.setZero();
c1(0) = (2 * my * mx * d_norm_inv_d_r2) / fy;
c1(1) = (norm_inv + 2 * my * my * d_norm_inv_d_r2) / fy;
c1(2) = 2 * my * d_norm_inv_d_r2 / fy;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_proj);
d_p3d_d_proj->col(0) = c0;
d_p3d_d_proj->col(1) = c1;
} else {
UNUSED(d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_param);
d_p3d_d_param->col(2) = -c0;
d_p3d_d_param->col(3) = -c1;
d_p3d_d_param->col(0) = -c0 * mx;
d_p3d_d_param->col(1) = -c1 * my;
} else {
UNUSED(d_p3d_d_param);
}
} else {
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
}
return true;
}
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, f_y, c_x, c_y, \right]^T
/// \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = init[1];
param_[2] = init[2];
param_[3] = init[3];
}
/// @brief Increment intrinsic parameters by inc
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) { param_ += inc; }
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f_x, f_y, c_x, c_y, \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<PinholeCamera> getTestProjections() {
Eigen::aligned_vector<PinholeCamera> res;
VecN vec1;
// Euroc
vec1 << 460.76484651566468, 459.4051018049483, 365.8937161309615,
249.33499869752445;
res.emplace_back(vec1);
// TUM VI 512
vec1 << 191.14799816648748, 191.13150946585135, 254.95857715233118,
256.8815466235898;
res.emplace_back(vec1);
return res;
}
/// @brief Resolutions used for unit-tests
static Eigen::aligned_vector<Eigen::Vector2i> getTestResolutions() {
Eigen::aligned_vector<Eigen::Vector2i> res;
res.emplace_back(752, 480);
res.emplace_back(512, 512);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt

View File

@@ -0,0 +1,155 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of stereographic parametrization for unit vectors
*/
#pragma once
#include <Eigen/Dense>
namespace basalt {
/// @brief Stereographic projection for minimal representation of unit vectors
///
/// \image html stereographic.png
/// The projection is defined on the entire
/// sphere, except at one point: the projection point. Where it is defined, the
/// mapping is smooth and bijective. The illustrations shows 3 examples of unit
/// vectors parametrized with a point in the 2D plane. See
/// \ref project and \ref unproject functions for more details.
template <typename Scalar_ = double>
class StereographicParam {
public:
using Scalar = Scalar_;
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
/// @brief Project the point and optionally compute Jacobian
///
/// Projection function is defined as follows:
/// \f{align}{
/// \pi(\mathbf{x}) &=
/// \begin{bmatrix}
/// {\frac{x}{d + z}}
/// \\ {\frac{y}{d + z}}
/// \\ \end{bmatrix},
/// \\ d &= \sqrt{x^2 + y^2 + z^2}.
/// \f}
/// @param[in] p3d point to project [x,y,z]
/// @param[out] d_r_d_p if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @return 2D projection of the point which parametrizes the corresponding
/// direction vector
static inline Vec2 project(const Vec4& p3d, Mat24* d_r_d_p = nullptr) {
const Scalar sqrt = p3d.template head<3>().norm();
const Scalar norm = p3d[2] + sqrt;
const Scalar norm_inv = Scalar(1) / norm;
Vec2 res(p3d[0] * norm_inv, p3d[1] * norm_inv);
if (d_r_d_p) {
Scalar norm_inv2 = norm_inv * norm_inv;
Scalar tmp = -norm_inv2 / sqrt;
(*d_r_d_p).setZero();
(*d_r_d_p)(0, 0) = norm_inv + p3d[0] * p3d[0] * tmp;
(*d_r_d_p)(1, 0) = p3d[0] * p3d[1] * tmp;
(*d_r_d_p)(1, 1) = norm_inv + p3d[1] * p3d[1] * tmp;
(*d_r_d_p)(0, 1) = p3d[0] * p3d[1] * tmp;
(*d_r_d_p)(0, 2) = p3d[0] * norm * tmp;
(*d_r_d_p)(1, 2) = p3d[1] * norm * tmp;
(*d_r_d_p)(0, 3) = Scalar(0);
(*d_r_d_p)(1, 3) = Scalar(0);
}
return res;
}
/// @brief Unproject the point and optionally compute Jacobian
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi^{-1}(\mathbf{u}) &=
/// \frac{2}{1 + x^2 + y^2}
/// \begin{bmatrix}
/// u \\ v \\ 1
/// \\ \end{bmatrix}-\begin{bmatrix}
/// 0 \\ 0 \\ 1
/// \\ \end{bmatrix}.
/// \f}
///
/// @param[in] proj point to unproject [u,v]
/// @param[out] d_r_d_p if not nullptr computed Jacobian of unprojection
/// with respect to proj
/// @return unprojected 3D unit vector
static inline Vec4 unproject(const Vec2& proj, Mat42* d_r_d_p = nullptr) {
const Scalar x2 = proj[0] * proj[0];
const Scalar y2 = proj[1] * proj[1];
const Scalar r2 = x2 + y2;
const Scalar norm_inv = Scalar(2) / (Scalar(1) + r2);
Vec4 res(proj[0] * norm_inv, proj[1] * norm_inv, norm_inv - Scalar(1),
Scalar(0));
if (d_r_d_p) {
const Scalar norm_inv2 = norm_inv * norm_inv;
const Scalar xy = proj[0] * proj[1];
(*d_r_d_p)(0, 0) = (norm_inv - x2 * norm_inv2);
(*d_r_d_p)(0, 1) = -xy * norm_inv2;
(*d_r_d_p)(1, 0) = -xy * norm_inv2;
(*d_r_d_p)(1, 1) = (norm_inv - y2 * norm_inv2);
(*d_r_d_p)(2, 0) = -proj[0] * norm_inv2;
(*d_r_d_p)(2, 1) = -proj[1] * norm_inv2;
(*d_r_d_p)(3, 0) = Scalar(0);
(*d_r_d_p)(3, 1) = Scalar(0);
}
return res;
}
};
} // namespace basalt

View File

@@ -0,0 +1,408 @@
/**
BSD 3-Clause License
This file is part of the Basalt project.
https://gitlab.com/VladyslavUsenko/basalt-headers.git
Copyright (c) 2019, Vladyslav Usenko and Nikolaus Demmel.
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 the copyright holder 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 HOLDER 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.
@file
@brief Implementation of unified camera model
*/
#pragma once
#include <basalt/camera/camera_static_assert.hpp>
#include <basalt/utils/sophus_utils.hpp>
namespace basalt {
using std::sqrt;
/// @brief Unified camera model
///
/// \image html ucm.png
/// This model has N=5 parameters \f$ \mathbf{i} = \left[f_x, f_y, c_x, c_y,
/// \alpha \right]^T \f$ with \f$ \alpha \in [0,1] \f$.
/// See \ref project and \ref unproject functions for more details.
template <typename Scalar_ = double>
class UnifiedCamera {
public:
using Scalar = Scalar_;
static constexpr int N = 5; ///< Number of intrinsic parameters.
using Vec2 = Eigen::Matrix<Scalar, 2, 1>;
using Vec4 = Eigen::Matrix<Scalar, 4, 1>;
using VecN = Eigen::Matrix<Scalar, N, 1>;
using Mat24 = Eigen::Matrix<Scalar, 2, 4>;
using Mat2N = Eigen::Matrix<Scalar, 2, N>;
using Mat42 = Eigen::Matrix<Scalar, 4, 2>;
using Mat4N = Eigen::Matrix<Scalar, 4, N>;
/// @brief Default constructor with zero intrinsics
UnifiedCamera() { param_.setZero(); }
/// @brief Construct camera model with given vector of intrinsics
///
/// @param[in] p vector of intrinsic parameters [fx, fy, cx, cy, alpha]
explicit UnifiedCamera(const VecN& p) { param_ = p; }
/// @brief Cast to different scalar type
template <class Scalar2>
UnifiedCamera<Scalar2> cast() const {
return UnifiedCamera<Scalar2>(param_.template cast<Scalar2>());
}
/// @brief Camera model name
///
/// @return "ucm"
static std::string getName() { return "ucm"; }
/// @brief Project the point and optionally compute Jacobians
///
/// Projection function is defined as follows:
/// \f{align}{
/// \pi(\mathbf{x}, \mathbf{i}) &=
/// \begin{bmatrix}
/// f_x{\frac{x}{\alpha d + (1-\alpha) z}}
/// \\ f_y{\frac{y}{\alpha d + (1-\alpha) z}}
/// \\ \end{bmatrix}
/// +
/// \begin{bmatrix}
/// c_x
/// \\ c_y
/// \\ \end{bmatrix},
/// \\ d &= \sqrt{x^2 + y^2 + z^2}.
/// \f}
/// A set of 3D points that results in valid projection is expressed as
/// follows: \f{align}{
/// \Omega &= \{\mathbf{x} \in \mathbb{R}^3 ~|~ z > -wd \},
/// \\ w &= \begin{cases} \frac{\alpha}{1-\alpha}, & \mbox{if } \alpha \le
/// 0.5,
/// \\ \frac{1-\alpha}{\alpha} & \mbox{if } \alpha > 0.5, \end{cases} \f}
///
/// @param[in] p3d point to project
/// @param[out] proj result of projection
/// @param[out] d_proj_d_p3d if not nullptr computed Jacobian of projection
/// with respect to p3d
/// @param[out] d_proj_d_param point if not nullptr computed Jacobian of
/// projection with respect to intrinsic parameters
/// @return if projection is valid
template <class DerivedPoint3D, class DerivedPoint2D,
class DerivedJ3D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool project(const Eigen::MatrixBase<DerivedPoint3D>& p3d,
Eigen::MatrixBase<DerivedPoint2D>& proj,
DerivedJ3D d_proj_d_p3d = nullptr,
DerivedJparam d_proj_d_param = nullptr) const {
const typename EvalOrReference<DerivedPoint3D>::Type p3d_eval(p3d);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& alpha = param_[4];
const Scalar& x = p3d_eval[0];
const Scalar& y = p3d_eval[1];
const Scalar& z = p3d_eval[2];
const Scalar r2 = x * x + y * y;
const Scalar rho2 = r2 + z * z;
const Scalar rho = sqrt(rho2);
const Scalar norm = alpha * rho + (Scalar(1) - alpha) * z;
const Scalar mx = x / norm;
const Scalar my = y / norm;
proj = Vec2(fx * mx + cx, fy * my + cy);
// Check if valid
const Scalar w = alpha > Scalar(0.5) ? (Scalar(1) - alpha) / alpha
: alpha / (Scalar(1) - alpha);
const bool is_valid = (z > -w * rho);
if constexpr (!std::is_same_v<DerivedJ3D, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_p3d);
const Scalar denom = norm * norm * rho;
const Scalar mid = -(alpha * x * y);
const Scalar add = norm * rho;
const Scalar addz = (alpha * z + (Scalar(1) - alpha) * rho);
d_proj_d_p3d->setZero();
(*d_proj_d_p3d)(0, 0) = fx * (add - x * x * alpha);
(*d_proj_d_p3d)(1, 0) = fy * mid;
(*d_proj_d_p3d)(0, 1) = fx * mid;
(*d_proj_d_p3d)(1, 1) = fy * (add - y * y * alpha);
(*d_proj_d_p3d)(0, 2) = -fx * x * addz;
(*d_proj_d_p3d)(1, 2) = -fy * y * addz;
(*d_proj_d_p3d) /= denom;
} else {
UNUSED(d_proj_d_p3d);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_proj_d_param);
const Scalar norm2 = norm * norm;
(*d_proj_d_param).setZero();
(*d_proj_d_param)(0, 0) = mx;
(*d_proj_d_param)(0, 2) = Scalar(1);
(*d_proj_d_param)(1, 1) = my;
(*d_proj_d_param)(1, 3) = Scalar(1);
const Scalar tmp_x = -fx * x / norm2;
const Scalar tmp_y = -fy * y / norm2;
const Scalar tmp4 = (rho - z);
(*d_proj_d_param)(0, 4) = tmp_x * tmp4;
(*d_proj_d_param)(1, 4) = tmp_y * tmp4;
} else {
UNUSED(d_proj_d_param);
}
return is_valid;
}
/// @brief Unproject the point and optionally compute Jacobians
///
/// The unprojection function is computed as follows: \f{align}{
/// \pi ^ { -1 }(\mathbf{u}, \mathbf{i}) &=
/// \frac{\xi + \sqrt{1 + (1 - \xi ^ 2) r ^ 2}} {
/// 1 + r ^ 2
/// }
/// \begin{bmatrix} m_x \\ m_y \\ 1 \\ \end{bmatrix} -
/// \begin {bmatrix} 0 \\ 0 \\ \xi \\ \end{bmatrix},
/// \\ m_x &= \frac{u - c_x}{f_x}(1-\alpha),
/// \\ m_y &= \frac{v - c_y}{f_y}(1-\alpha),
/// \\ r^2 &= m_x^2 + m_y^2,
/// \\ \xi &= \frac{\alpha}{1-\alpha}.
/// \f}
///
/// The valid range of unprojections is \f{align}{
/// \Theta &=
/// \begin{cases}
/// \mathbb{R}^2 & \mbox{if } \alpha \le 0.5
/// \\ \{ \mathbf{u} \in \mathbb{R}^2 ~|~ r^2 \le
/// \frac{(1-\alpha)^2}{2\alpha
/// - 1} \} & \mbox{if } \alpha > 0.5 \end{cases}
/// \f}
///
/// @param[in] proj point to unproject
/// @param[out] p3d result of unprojection
/// @param[out] d_p3d_d_proj if not nullptr computed Jacobian of
/// unprojection with respect to proj
/// @param[out] d_p3d_d_param point if not nullptr computed Jacobian of
/// unprojection with respect to intrinsic parameters
/// @return if unprojection is valid
template <class DerivedPoint2D, class DerivedPoint3D,
class DerivedJ2D = std::nullptr_t,
class DerivedJparam = std::nullptr_t>
inline bool unproject(const Eigen::MatrixBase<DerivedPoint2D>& proj,
Eigen::MatrixBase<DerivedPoint3D>& p3d,
DerivedJ2D d_p3d_d_proj = nullptr,
DerivedJparam d_p3d_d_param = nullptr) const {
checkUnprojectionDerivedTypes<DerivedPoint2D, DerivedPoint3D, DerivedJ2D,
DerivedJparam, N>();
const typename EvalOrReference<DerivedPoint2D>::Type proj_eval(proj);
const Scalar& fx = param_[0];
const Scalar& fy = param_[1];
const Scalar& cx = param_[2];
const Scalar& cy = param_[3];
const Scalar& alpha = param_[4];
const Scalar& u = proj_eval[0];
const Scalar& v = proj_eval[1];
const Scalar xi = alpha / (Scalar(1) - alpha);
const Scalar mxx = (u - cx) / fx;
const Scalar myy = (v - cy) / fy;
const Scalar mx = (Scalar(1) - alpha) * mxx;
const Scalar my = (Scalar(1) - alpha) * myy;
const Scalar r2 = mx * mx + my * my;
// Check if valid
const bool is_valid = !static_cast<bool>(
(alpha > Scalar(0.5)) &&
(r2 >= Scalar(1) / ((Scalar(2) * alpha - Scalar(1)))));
const Scalar xi2 = xi * xi;
const Scalar n = sqrt(Scalar(1) + (Scalar(1) - xi2) * (r2));
const Scalar m = (Scalar(1) + r2);
const Scalar k = (xi + n) / m;
p3d.setZero();
p3d[0] = k * mx;
p3d[1] = k * my;
p3d[2] = k - xi;
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t> ||
!std::is_same_v<DerivedJparam, std::nullptr_t>) {
const Scalar dk_dmx = -Scalar(2) * mx * (n + xi) / (m * m) +
mx * (Scalar(1) - xi2) / (n * m);
const Scalar dk_dmy = -Scalar(2) * my * (n + xi) / (m * m) +
my * (Scalar(1) - xi2) / (n * m);
constexpr int SIZE_3D = DerivedPoint3D::SizeAtCompileTime;
Eigen::Matrix<Scalar, SIZE_3D, 1> c0, c1;
c0.setZero();
c0(0) = (dk_dmx * mx + k) / fx;
c0(1) = dk_dmx * my / fx;
c0(2) = dk_dmx / fx;
c1.setZero();
c1(0) = dk_dmy * mx / fy;
c1(1) = (dk_dmy * my + k) / fy;
c1(2) = dk_dmy / fy;
c0 *= (1 - alpha);
c1 *= (1 - alpha);
if constexpr (!std::is_same_v<DerivedJ2D, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_proj);
d_p3d_d_proj->col(0) = c0;
d_p3d_d_proj->col(1) = c1;
} else {
UNUSED(d_p3d_d_proj);
}
if constexpr (!std::is_same_v<DerivedJparam, std::nullptr_t>) {
BASALT_ASSERT(d_p3d_d_param);
const Scalar d_xi_d_alpha =
Scalar(1) / ((Scalar(1) - alpha) * (Scalar(1) - alpha));
const Scalar d_m_d_alpha =
-Scalar(2) * (Scalar(1) - alpha) * (mxx * mxx + myy * myy);
const Scalar d_n_d_alpha = -(mxx * mxx + myy * myy) / n;
const Scalar dk_d_alpha =
((d_xi_d_alpha + d_n_d_alpha) * m - d_m_d_alpha * (xi + n)) /
(m * m);
d_p3d_d_param->setZero();
d_p3d_d_param->col(0) = -mxx * c0;
d_p3d_d_param->col(1) = -myy * c1;
d_p3d_d_param->col(2) = -c0;
d_p3d_d_param->col(3) = -c1;
(*d_p3d_d_param)(0, 4) = dk_d_alpha * mx - k * mxx;
(*d_p3d_d_param)(1, 4) = dk_d_alpha * my - k * myy;
(*d_p3d_d_param)(2, 4) = dk_d_alpha - d_xi_d_alpha;
} else {
UNUSED(d_p3d_d_param);
}
} else {
UNUSED(d_p3d_d_proj);
UNUSED(d_p3d_d_param);
}
return is_valid;
}
/// @brief Set parameters from initialization
///
/// Initializes the camera model to \f$ \left[f_x, f_y, c_x, c_y, 0.5,
/// \right]^T \f$
///
/// @param[in] init vector [fx, fy, cx, cy]
inline void setFromInit(const Vec4& init) {
param_[0] = init[0];
param_[1] = init[1];
param_[2] = init[2];
param_[3] = init[3];
param_[4] = 0.5;
}
/// @brief Increment intrinsic parameters by inc and clamp the values to the
/// valid range
///
/// @param[in] inc increment vector
void operator+=(const VecN& inc) {
param_ += inc;
// alpha in [0, 1]
param_[4] = std::clamp(param_[4], Scalar(0), Scalar(1));
}
/// @brief Returns a const reference to the intrinsic parameters vector
///
/// The order is following: \f$ \left[f_x, f_y, c_x, c_y, \xi, \alpha
/// \right]^T \f$
/// @return const reference to the intrinsic parameters vector
const VecN& getParam() const { return param_; }
/// @brief Projections used for unit-tests
static Eigen::aligned_vector<UnifiedCamera> getTestProjections() {
Eigen::aligned_vector<UnifiedCamera> res;
VecN vec1;
// Euroc
vec1 << 460.76484651566468, 459.4051018049483, 365.8937161309615,
249.33499869752445, 0.5903365915227143;
res.emplace_back(vec1);
// TUM VI 512
vec1 << 191.14799816648748, 191.13150946585135, 254.95857715233118,
256.8815466235898, 0.6291060871161842;
res.emplace_back(vec1);
return res;
}
/// @brief Resolutions used for unit-tests
static Eigen::aligned_vector<Eigen::Vector2i> getTestResolutions() {
Eigen::aligned_vector<Eigen::Vector2i> res;
res.emplace_back(752, 480);
res.emplace_back(512, 512);
return res;
}
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
private:
VecN param_;
};
} // namespace basalt