commit
						6a23c476a1
					
				|  | @ -0,0 +1,129 @@ | |||
| /* ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
|  * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||||
|  * Atlanta, Georgia 30332-0415 | ||||
|  * All Rights Reserved | ||||
|  * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||||
| 
 | ||||
|  * See LICENSE for the license information | ||||
| 
 | ||||
|  * -------------------------------------------------------------------------- */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @file    FisheyeExample.cpp | ||||
|  * @brief   A visualSLAM example for the structure-from-motion problem on a | ||||
|  * simulated dataset. This version uses a fisheye camera model and a  GaussNewton | ||||
|  * solver to solve the graph in one batch | ||||
|  * @author  ghaggin | ||||
|  * @Date    Apr 9,2020 | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * A structure-from-motion example with landmarks | ||||
|  *  - The landmarks form a 10 meter cube | ||||
|  *  - The robot rotates around the landmarks, always facing towards the cube | ||||
|  */ | ||||
| 
 | ||||
| // For loading the data
 | ||||
| #include "SFMdata.h" | ||||
| 
 | ||||
| // Camera observations of landmarks will be stored as Point2 (x, y).
 | ||||
| #include <gtsam/geometry/Point2.h> | ||||
| 
 | ||||
| // Each variable in the system (poses and landmarks) must be identified with a
 | ||||
| // unique key. We can either use simple integer keys (1, 2, 3, ...) or symbols
 | ||||
| // (X1, X2, L1). Here we will use Symbols
 | ||||
| #include <gtsam/inference/Symbol.h> | ||||
| 
 | ||||
| // Use GaussNewtonOptimizer to solve graph
 | ||||
| #include <gtsam/nonlinear/GaussNewtonOptimizer.h> | ||||
| #include <gtsam/nonlinear/NonlinearFactorGraph.h> | ||||
| #include <gtsam/nonlinear/Values.h> | ||||
| 
 | ||||
| // In GTSAM, measurement functions are represented as 'factors'. Several common
 | ||||
| // factors have been provided with the library for solving robotics/SLAM/Bundle
 | ||||
| // Adjustment problems. Here we will use Projection factors to model the
 | ||||
| // camera's landmark observations. Also, we will initialize the robot at some
 | ||||
| // location using a Prior factor.
 | ||||
| #include <gtsam/geometry/Cal3Fisheye.h> | ||||
| #include <gtsam/slam/PriorFactor.h> | ||||
| #include <gtsam/slam/ProjectionFactor.h> | ||||
| 
 | ||||
| #include <fstream> | ||||
| #include <vector> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace gtsam; | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
|     // Define the camera calibration parameters
 | ||||
|     // Cal3_S2::shared_ptr K(new Cal3_S2(50.0, 50.0, 0.0, 50.0, 50.0));
 | ||||
|     boost::shared_ptr<Cal3Fisheye> K(new Cal3Fisheye( | ||||
|         278.66, 278.48, 0.0, 319.75, 241.96, -0.013721808247486035, 0.020727425669427896, -0.012786476702685545, 0.0025242267320687625)); | ||||
| 
 | ||||
|     // Define the camera observation noise model, 1 pixel stddev
 | ||||
|     auto measurementNoise = noiseModel::Isotropic::Sigma(2, 1.0); | ||||
| 
 | ||||
|     // Create the set of ground-truth landmarks
 | ||||
|     vector<Point3> points = createPoints(); | ||||
| 
 | ||||
|     // Create the set of ground-truth poses
 | ||||
|     vector<Pose3> poses = createPoses(); | ||||
| 
 | ||||
|     // Create a Factor Graph and Values to hold the new data
 | ||||
|     NonlinearFactorGraph graph; | ||||
|     Values initialEstimate; | ||||
| 
 | ||||
|     // Add a prior on pose x0, 30cm std on x,y,z and 0.1 rad on roll,pitch,yaw
 | ||||
|     static auto kPosePrior = noiseModel::Diagonal::Sigmas( | ||||
|         (Vector(6) << Vector3::Constant(0.1), Vector3::Constant(0.3)).finished()); | ||||
|     graph.emplace_shared<PriorFactor<Pose3>>(Symbol('x', 0), poses[0], kPosePrior); | ||||
| 
 | ||||
|     // Add a prior on landmark l0
 | ||||
|     static auto kPointPrior = noiseModel::Isotropic::Sigma(3, 0.1); | ||||
|     graph.emplace_shared<PriorFactor<Point3>>(Symbol('l', 0), points[0], kPointPrior); | ||||
| 
 | ||||
|     // Add initial guesses to all observed landmarks
 | ||||
|     // Intentionally initialize the variables off from the ground truth
 | ||||
|     static Point3 kDeltaPoint(-0.25, 0.20, 0.15); | ||||
|     for (size_t j = 0; j < points.size(); ++j) | ||||
|         initialEstimate.insert<Point3>(Symbol('l', j), points[j] + kDeltaPoint); | ||||
| 
 | ||||
|     // Loop over the poses, adding the observations to the graph
 | ||||
|     for (size_t i = 0; i < poses.size(); ++i) | ||||
|     { | ||||
|         // Add factors for each landmark observation
 | ||||
|         for (size_t j = 0; j < points.size(); ++j) | ||||
|         { | ||||
|             PinholeCamera<Cal3Fisheye> camera(poses[i], *K); | ||||
|             Point2 measurement = camera.project(points[j]); | ||||
|             graph.emplace_shared<GenericProjectionFactor<Pose3, Point3, Cal3Fisheye>>( | ||||
|                 measurement, measurementNoise, Symbol('x', i), Symbol('l', j), K); | ||||
|         } | ||||
| 
 | ||||
|         // Add an initial guess for the current pose
 | ||||
|         // Intentionally initialize the variables off from the ground truth
 | ||||
|         static Pose3 kDeltaPose(Rot3::Rodrigues(-0.1, 0.2, 0.25), Point3(0.05, -0.10, 0.20)); | ||||
|         initialEstimate.insert(Symbol('x', i), poses[i] * kDeltaPose); | ||||
|     } | ||||
| 
 | ||||
|     GaussNewtonParams params; | ||||
|     params.setVerbosity("TERMINATION"); | ||||
|     params.maxIterations = 10000; | ||||
| 
 | ||||
|     std::cout << "Optimizing the factor graph" << std::endl; | ||||
|     GaussNewtonOptimizer optimizer(graph, initialEstimate, params); | ||||
|     Values result = optimizer.optimize(); | ||||
|     std::cout << "Optimization complete" << std::endl; | ||||
| 
 | ||||
|     std::cout << "initial error=" << graph.error(initialEstimate) << std::endl; | ||||
|     std::cout << "final error=" << graph.error(result) << std::endl; | ||||
| 
 | ||||
|     std::ofstream os("examples/vio_batch.dot"); | ||||
|     graph.saveGraph(os, result); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| /* ************************************************************************* */ | ||||
|  | @ -0,0 +1,217 @@ | |||
| /* ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
|  * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||||
|  * Atlanta, Georgia 30332-0415 | ||||
|  * All Rights Reserved | ||||
|  * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||||
| 
 | ||||
|  * See LICENSE for the license information | ||||
| 
 | ||||
|  * -------------------------------------------------------------------------- */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @file Cal3Fisheye.cpp | ||||
|  * @date Apr 8, 2020 | ||||
|  * @author ghaggin | ||||
|  */ | ||||
| 
 | ||||
| #include <gtsam/base/Matrix.h> | ||||
| #include <gtsam/base/Vector.h> | ||||
| #include <gtsam/geometry/Cal3Fisheye.h> | ||||
| #include <gtsam/geometry/Point2.h> | ||||
| #include <gtsam/geometry/Point3.h> | ||||
| 
 | ||||
| namespace gtsam { | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Cal3Fisheye::Cal3Fisheye(const Vector& v) | ||||
|     : fx_(v[0]), | ||||
|       fy_(v[1]), | ||||
|       s_(v[2]), | ||||
|       u0_(v[3]), | ||||
|       v0_(v[4]), | ||||
|       k1_(v[5]), | ||||
|       k2_(v[6]), | ||||
|       k3_(v[7]), | ||||
|       k4_(v[8]) {} | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Vector9 Cal3Fisheye::vector() const { | ||||
|   Vector9 v; | ||||
|   v << fx_, fy_, s_, u0_, v0_, k1_, k2_, k3_, k4_; | ||||
|   return v; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Matrix3 Cal3Fisheye::K() const { | ||||
|   Matrix3 K; | ||||
|   K << fx_, s_, u0_, 0.0, fy_, v0_, 0.0, 0.0, 1.0; | ||||
|   return K; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| static Matrix29 D2dcalibration(const double xd, const double yd, | ||||
|                                const double xi, const double yi, | ||||
|                                const double t3, const double t5, | ||||
|                                const double t7, const double t9, const double r, | ||||
|                                Matrix2& DK) { | ||||
|   // order: fx, fy, s, u0, v0
 | ||||
|   Matrix25 DR1; | ||||
|   DR1 << xd, 0.0, yd, 1.0, 0.0, 0.0, yd, 0.0, 0.0, 1.0; | ||||
| 
 | ||||
|   // order: k1, k2, k3, k4
 | ||||
|   Matrix24 DR2; | ||||
|   DR2 << t3 * xi, t5 * xi, t7 * xi, t9 * xi, t3 * yi, t5 * yi, t7 * yi, t9 * yi; | ||||
|   DR2 /= r; | ||||
|   Matrix29 D; | ||||
|   D << DR1, DK * DR2; | ||||
|   return D; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| static Matrix2 D2dintrinsic(const double xi, const double yi, const double r, | ||||
|                             const double td, const double t, const double tt, | ||||
|                             const double t4, const double t6, const double t8, | ||||
|                             const double k1, const double k2, const double k3, | ||||
|                             const double k4, const Matrix2& DK) { | ||||
|   const double dr_dxi = xi / sqrt(xi * xi + yi * yi); | ||||
|   const double dr_dyi = yi / sqrt(xi * xi + yi * yi); | ||||
|   const double dt_dr = 1 / (1 + r * r); | ||||
|   const double dtd_dt = | ||||
|       1 + 3 * k1 * tt + 5 * k2 * t4 + 7 * k3 * t6 + 9 * k4 * t8; | ||||
|   const double dtd_dxi = dtd_dt * dt_dr * dr_dxi; | ||||
|   const double dtd_dyi = dtd_dt * dt_dr * dr_dyi; | ||||
| 
 | ||||
|   const double rinv = 1 / r; | ||||
|   const double rrinv = 1 / (r * r); | ||||
|   const double dxd_dxi = | ||||
|       dtd_dxi * xi * rinv + td * rinv + td * xi * (-rrinv) * dr_dxi; | ||||
|   const double dxd_dyi = dtd_dyi * xi * rinv - td * xi * rrinv * dr_dyi; | ||||
|   const double dyd_dxi = dtd_dxi * yi * rinv - td * yi * rrinv * dr_dxi; | ||||
|   const double dyd_dyi = | ||||
|       dtd_dyi * yi * rinv + td * rinv + td * yi * (-rrinv) * dr_dyi; | ||||
| 
 | ||||
|   Matrix2 DR; | ||||
|   DR << dxd_dxi, dxd_dyi, dyd_dxi, dyd_dyi; | ||||
| 
 | ||||
|   return DK * DR; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Point2 Cal3Fisheye::uncalibrate(const Point2& p, OptionalJacobian<2, 9> H1, | ||||
|                                 OptionalJacobian<2, 2> H2) const { | ||||
|   const double xi = p.x(), yi = p.y(); | ||||
|   const double r = sqrt(xi * xi + yi * yi); | ||||
|   const double t = atan(r); | ||||
|   const double tt = t * t, t4 = tt * tt, t6 = tt * t4, t8 = t4 * t4; | ||||
|   const double td = t * (1 + k1_ * tt + k2_ * t4 + k3_ * t6 + k4_ * t8); | ||||
|   const double td_o_r = r > 1e-8 ? td / r : 1; | ||||
|   const double xd = td_o_r * xi, yd = td_o_r * yi; | ||||
|   Point2 uv(fx_ * xd + s_ * yd + u0_, fy_ * yd + v0_); | ||||
| 
 | ||||
|   Matrix2 DK; | ||||
|   if (H1 || H2) DK << fx_, s_, 0.0, fy_; | ||||
| 
 | ||||
|   // Derivative for calibration parameters (2 by 9)
 | ||||
|   if (H1) | ||||
|     *H1 = D2dcalibration(xd, yd, xi, yi, t * tt, t * t4, t * t6, t * t8, r, DK); | ||||
| 
 | ||||
|   // Derivative for points in intrinsic coords (2 by 2)
 | ||||
|   if (H2) | ||||
|     *H2 = | ||||
|         D2dintrinsic(xi, yi, r, td, t, tt, t4, t6, t8, k1_, k2_, k3_, k4_, DK); | ||||
| 
 | ||||
|   return uv; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Point2 Cal3Fisheye::calibrate(const Point2& uv, const double tol) const { | ||||
|   // initial gues just inverts the pinhole model
 | ||||
|   const double u = uv.x(), v = uv.y(); | ||||
|   const double yd = (v - v0_) / fy_; | ||||
|   const double xd = (u - s_ * yd - u0_) / fx_; | ||||
|   Point2 pi(xd, yd); | ||||
| 
 | ||||
|   // Perform newtons method, break when solution converges past tol,
 | ||||
|   // throw exception if max iterations are reached
 | ||||
|   const int maxIterations = 10; | ||||
|   int iteration; | ||||
|   for (iteration = 0; iteration < maxIterations; ++iteration) { | ||||
|     Matrix2 jac; | ||||
| 
 | ||||
|     // Calculate the current estimate (uv_hat) and the jacobian
 | ||||
|     const Point2 uv_hat = uncalibrate(pi, boost::none, jac); | ||||
| 
 | ||||
|     // Test convergence
 | ||||
|     if ((uv_hat - uv).norm() < tol) break; | ||||
| 
 | ||||
|     // Newton's method update step
 | ||||
|     pi = pi - jac.inverse() * (uv_hat - uv); | ||||
|   } | ||||
| 
 | ||||
|   if (iteration >= maxIterations) | ||||
|     throw std::runtime_error( | ||||
|         "Cal3Fisheye::calibrate fails to converge. need a better " | ||||
|         "initialization"); | ||||
| 
 | ||||
|   return pi; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Matrix2 Cal3Fisheye::D2d_intrinsic(const Point2& p) const { | ||||
|   const double xi = p.x(), yi = p.y(); | ||||
|   const double r = sqrt(xi * xi + yi * yi); | ||||
|   const double t = atan(r); | ||||
|   const double tt = t * t, t4 = tt * tt, t6 = t4 * tt, t8 = t4 * t4; | ||||
|   const double td = t * (1 + k1_ * tt + k2_ * t4 + k3_ * t6 + k4_ * t8); | ||||
| 
 | ||||
|   Matrix2 DK; | ||||
|   DK << fx_, s_, 0.0, fy_; | ||||
| 
 | ||||
|   return D2dintrinsic(xi, yi, r, td, t, tt, t4, t6, t8, k1_, k2_, k3_, k4_, DK); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Matrix29 Cal3Fisheye::D2d_calibration(const Point2& p) const { | ||||
|   const double xi = p.x(), yi = p.y(); | ||||
|   const double r = sqrt(xi * xi + yi * yi); | ||||
|   const double t = atan(r); | ||||
|   const double tt = t * t, t4 = tt * tt, t6 = tt * t4, t8 = t4 * t4; | ||||
|   const double td = t * (1 + k1_ * tt + k2_ * t4 + k3_ * t6 + k4_ * t8); | ||||
|   const double xd = td / r * xi, yd = td / r * yi; | ||||
| 
 | ||||
|   Matrix2 DK; | ||||
|   DK << fx_, s_, 0.0, fy_; | ||||
|   return D2dcalibration(xd, yd, xi, yi, t * tt, t * t4, t * t6, t * t8, r, DK); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| void Cal3Fisheye::print(const std::string& s_) const { | ||||
|   gtsam::print((Matrix)K(), s_ + ".K"); | ||||
|   gtsam::print(Vector(k()), s_ + ".k"); | ||||
|   ; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| bool Cal3Fisheye::equals(const Cal3Fisheye& K, double tol) const { | ||||
|   if (std::abs(fx_ - K.fx_) > tol || std::abs(fy_ - K.fy_) > tol || | ||||
|       std::abs(s_ - K.s_) > tol || std::abs(u0_ - K.u0_) > tol || | ||||
|       std::abs(v0_ - K.v0_) > tol || std::abs(k1_ - K.k1_) > tol || | ||||
|       std::abs(k2_ - K.k2_) > tol || std::abs(k3_ - K.k3_) > tol || | ||||
|       std::abs(k4_ - K.k4_) > tol) | ||||
|     return false; | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Cal3Fisheye Cal3Fisheye::retract(const Vector& d) const { | ||||
|   return Cal3Fisheye(vector() + d); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| Vector Cal3Fisheye::localCoordinates(const Cal3Fisheye& T2) const { | ||||
|   return T2.vector() - vector(); | ||||
| } | ||||
| 
 | ||||
| }  // namespace gtsam
 | ||||
| /* ************************************************************************* */ | ||||
|  | @ -0,0 +1,211 @@ | |||
| /* ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
|  * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||||
|  * Atlanta, Georgia 30332-0415 | ||||
|  * All Rights Reserved | ||||
|  * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||||
| 
 | ||||
|  * See LICENSE for the license information | ||||
| 
 | ||||
|  * -------------------------------------------------------------------------- */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @file Cal3Fisheye.h | ||||
|  * @brief Calibration of a fisheye camera | ||||
|  * @date Apr 8, 2020 | ||||
|  * @author ghaggin | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gtsam/geometry/Point2.h> | ||||
| 
 | ||||
| namespace gtsam { | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Calibration of a fisheye camera | ||||
|  * @addtogroup geometry | ||||
|  * \nosubgrouping | ||||
|  * | ||||
|  * Uses same distortionmodel as OpenCV, with | ||||
|  * https://docs.opencv.org/master/db/d58/group__calib3d__fisheye.html
 | ||||
|  * 3D point in camera frame | ||||
|  *   p = (x, y, z) | ||||
|  * Intrinsic coordinates: | ||||
|  *   [x_i;y_i] = [x/z; y/z] | ||||
|  * Distorted coordinates: | ||||
|  *   r^2 = (x_i)^2 + (y_i)^2 | ||||
|  *   th = atan(r) | ||||
|  *   th_d = th(1 + k1*th^2 + k2*th^4 + k3*th^6 + k4*th^8) | ||||
|  *   [x_d; y_d] = (th_d / r)*[x_i; y_i] | ||||
|  * Pixel coordinates: | ||||
|  *   K = [fx s u0; 0 fy v0 ;0 0 1] | ||||
|  *   [u; v; 1] = K*[x_d; y_d; 1] | ||||
|  */ | ||||
| class GTSAM_EXPORT Cal3Fisheye { | ||||
|  protected: | ||||
|   double fx_, fy_, s_, u0_, v0_;  // focal length, skew and principal point
 | ||||
|   double k1_, k2_, k3_, k4_;      // fisheye distortion coefficients
 | ||||
| 
 | ||||
|  public: | ||||
|   enum { dimension = 9 }; | ||||
|   typedef boost::shared_ptr<Cal3Fisheye> | ||||
|       shared_ptr;  ///< shared pointer to fisheye calibration object
 | ||||
| 
 | ||||
|   /// @name Standard Constructors
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   /// Default Constructor with only unit focal length
 | ||||
|   Cal3Fisheye() | ||||
|       : fx_(1), fy_(1), s_(0), u0_(0), v0_(0), k1_(0), k2_(0), k3_(0), k4_(0) {} | ||||
| 
 | ||||
|   Cal3Fisheye(const double fx, const double fy, const double s, const double u0, | ||||
|               const double v0, const double k1, const double k2, | ||||
|               const double k3, const double k4) | ||||
|       : fx_(fx), | ||||
|         fy_(fy), | ||||
|         s_(s), | ||||
|         u0_(u0), | ||||
|         v0_(v0), | ||||
|         k1_(k1), | ||||
|         k2_(k2), | ||||
|         k3_(k3), | ||||
|         k4_(k4) {} | ||||
| 
 | ||||
|   virtual ~Cal3Fisheye() {} | ||||
| 
 | ||||
|   /// @}
 | ||||
|   /// @name Advanced Constructors
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   Cal3Fisheye(const Vector& v); | ||||
| 
 | ||||
|   /// @}
 | ||||
|   /// @name Standard Interface
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   /// focal length x
 | ||||
|   inline double fx() const { return fx_; } | ||||
| 
 | ||||
|   /// focal length x
 | ||||
|   inline double fy() const { return fy_; } | ||||
| 
 | ||||
|   /// skew
 | ||||
|   inline double skew() const { return s_; } | ||||
| 
 | ||||
|   /// image center in x
 | ||||
|   inline double px() const { return u0_; } | ||||
| 
 | ||||
|   /// image center in y
 | ||||
|   inline double py() const { return v0_; } | ||||
| 
 | ||||
|   /// First distortion coefficient
 | ||||
|   inline double k1() const { return k1_; } | ||||
| 
 | ||||
|   /// Second distortion coefficient
 | ||||
|   inline double k2() const { return k2_; } | ||||
| 
 | ||||
|   /// First tangential distortion coefficient
 | ||||
|   inline double k3() const { return k3_; } | ||||
| 
 | ||||
|   /// Second tangential distortion coefficient
 | ||||
|   inline double k4() const { return k4_; } | ||||
| 
 | ||||
|   /// return calibration matrix
 | ||||
|   Matrix3 K() const; | ||||
| 
 | ||||
|   /// return distortion parameter vector
 | ||||
|   Vector4 k() const { return Vector4(k1_, k2_, k3_, k4_); } | ||||
| 
 | ||||
|   /// Return all parameters as a vector
 | ||||
|   Vector9 vector() const; | ||||
| 
 | ||||
|   /**
 | ||||
|    * @brief convert intrinsic coordinates [x_i; y_i] to (distorted) image | ||||
|    * coordinates [u; v] | ||||
|    * @param p point in intrinsic coordinates | ||||
|    * @param Dcal optional 2*9 Jacobian wrpt intrinsic parameters (fx, fy, ..., | ||||
|    * k4) | ||||
|    * @param Dp optional 2*2 Jacobian wrpt intrinsic coordinates (xi, yi) | ||||
|    * @return point in (distorted) image coordinates | ||||
|    */ | ||||
|   Point2 uncalibrate(const Point2& p, OptionalJacobian<2, 9> Dcal = boost::none, | ||||
|                      OptionalJacobian<2, 2> Dp = boost::none) const; | ||||
| 
 | ||||
|   /// Convert (distorted) image coordinates [u;v] to intrinsic coordinates [x_i,
 | ||||
|   /// y_i]
 | ||||
|   Point2 calibrate(const Point2& p, const double tol = 1e-5) const; | ||||
| 
 | ||||
|   /// Derivative of uncalibrate wrpt intrinsic coordinates [x_i; y_i]
 | ||||
|   Matrix2 D2d_intrinsic(const Point2& p) const; | ||||
| 
 | ||||
|   /// Derivative of uncalibrate wrpt the calibration parameters
 | ||||
|   /// [fx, fy, s, u0, v0, k1, k2, k3, k4]
 | ||||
|   Matrix29 D2d_calibration(const Point2& p) const; | ||||
| 
 | ||||
|   /// @}
 | ||||
|   /// @name Testable
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   /// print with optional string
 | ||||
|   virtual void print(const std::string& s = "") const; | ||||
| 
 | ||||
|   /// assert equality up to a tolerance
 | ||||
|   bool equals(const Cal3Fisheye& K, double tol = 10e-9) const; | ||||
| 
 | ||||
|   /// @}
 | ||||
|   /// @name Manifold
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   /// Given delta vector, update calibration
 | ||||
|   Cal3Fisheye retract(const Vector& d) const; | ||||
| 
 | ||||
|   /// Given a different calibration, calculate update to obtain it
 | ||||
|   Vector localCoordinates(const Cal3Fisheye& T2) const; | ||||
| 
 | ||||
|   /// Return dimensions of calibration manifold object
 | ||||
|   virtual size_t dim() const { return 9; } | ||||
| 
 | ||||
|   /// Return dimensions of calibration manifold object
 | ||||
|   static size_t Dim() { return 9; } | ||||
| 
 | ||||
|   /// @}
 | ||||
|   /// @name Clone
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   /// @return a deep copy of this object
 | ||||
|   virtual boost::shared_ptr<Cal3Fisheye> clone() const { | ||||
|     return boost::shared_ptr<Cal3Fisheye>(new Cal3Fisheye(*this)); | ||||
|   } | ||||
| 
 | ||||
|   /// @}
 | ||||
| 
 | ||||
|  private: | ||||
|   /// @name Advanced Interface
 | ||||
|   /// @{
 | ||||
| 
 | ||||
|   /** Serialization function */ | ||||
|   friend class boost::serialization::access; | ||||
|   template <class Archive> | ||||
|   void serialize(Archive& ar, const unsigned int /*version*/) { | ||||
|     ar& BOOST_SERIALIZATION_NVP(fx_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(fy_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(s_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(u0_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(v0_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(k1_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(k2_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(k3_); | ||||
|     ar& BOOST_SERIALIZATION_NVP(k4_); | ||||
|   } | ||||
| 
 | ||||
|   /// @}
 | ||||
| }; | ||||
| 
 | ||||
| template <> | ||||
| struct traits<Cal3Fisheye> : public internal::Manifold<Cal3Fisheye> {}; | ||||
| 
 | ||||
| template <> | ||||
| struct traits<const Cal3Fisheye> : public internal::Manifold<Cal3Fisheye> {}; | ||||
| 
 | ||||
| }  // namespace gtsam
 | ||||
|  | @ -0,0 +1,206 @@ | |||
| /* ----------------------------------------------------------------------------
 | ||||
| 
 | ||||
|  * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||||
|  * Atlanta, Georgia 30332-0415 | ||||
|  * All Rights Reserved | ||||
|  * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||||
| 
 | ||||
|  * See LICENSE for the license information | ||||
| 
 | ||||
|  * -------------------------------------------------------------------------- */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @file  testCal3Fisheye.cpp | ||||
|  * @brief Unit tests for fisheye calibration class | ||||
|  * @author ghaggin | ||||
|  */ | ||||
| 
 | ||||
| #include <CppUnitLite/TestHarness.h> | ||||
| #include <gtsam/base/Testable.h> | ||||
| #include <gtsam/base/numericalDerivative.h> | ||||
| #include <gtsam/geometry/Cal3Fisheye.h> | ||||
| #include <gtsam/geometry/Point3.h> | ||||
| 
 | ||||
| using namespace gtsam; | ||||
| 
 | ||||
| GTSAM_CONCEPT_TESTABLE_INST(Cal3Fisheye) | ||||
| GTSAM_CONCEPT_MANIFOLD_INST(Cal3Fisheye) | ||||
| 
 | ||||
| static const double fx = 250, fy = 260, s = 0.1, u0 = 320, v0 = 240; | ||||
| static Cal3Fisheye K(fx, fy, s, u0, v0, -0.013721808247486035, | ||||
|                      0.020727425669427896, -0.012786476702685545, | ||||
|                      0.0025242267320687625); | ||||
| static Point2 p(2, 3); | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| TEST(Cal3Fisheye, uncalibrate1) { | ||||
|   // Calculate the solution
 | ||||
|   const double xi = p.x(), yi = p.y(); | ||||
|   const double r = sqrt(xi * xi + yi * yi); | ||||
|   const double t = atan(r); | ||||
|   const double tt = t * t, t4 = tt * tt, t6 = tt * t4, t8 = t4 * t4; | ||||
|   const double td = | ||||
|       t * (1 + K.k1() * tt + K.k2() * t4 + K.k3() * t6 + K.k4() * t8); | ||||
|   Vector3 pd(td / r * xi, td / r * yi, 1); | ||||
|   Vector3 v = K.K() * pd; | ||||
| 
 | ||||
|   Point2 uv_sol(v[0] / v[2], v[1] / v[2]); | ||||
| 
 | ||||
|   Point2 uv = K.uncalibrate(p); | ||||
|   CHECK(assert_equal(uv, uv_sol)); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  * Check that a point at (0,0) projects to the | ||||
|  * image center. | ||||
|  */ | ||||
| TEST(Cal3Fisheye, uncalibrate2) { | ||||
|   Point2 pz(0, 0); | ||||
|   auto uv = K.uncalibrate(pz); | ||||
|   CHECK(assert_equal(uv, Point2(u0, v0))); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  *  This test uses cv2::fisheye::projectPoints to test that uncalibrate | ||||
|  *  properly projects a point into the image plane.  One notable difference | ||||
|  *  between opencv and the Cal3Fisheye::uncalibrate function is the skew | ||||
|  *  parameter. The equivalence is alpha = s/fx. | ||||
|  * | ||||
|  * | ||||
|  * Python script to project points with fisheye model in OpenCv | ||||
|  * (script run with OpenCv version 4.2.0 and Numpy version 1.18.2) | ||||
|  */ | ||||
| // clang-format off
 | ||||
| /*
 | ||||
| =========================================================== | ||||
| 
 | ||||
| import numpy as np | ||||
| import cv2 | ||||
| 
 | ||||
| objpts = np.float64([[23,27,31]]).reshape(1,-1,3) | ||||
| 
 | ||||
| cameraMatrix = np.float64([ | ||||
|     [250, 0, 320], | ||||
|     [0, 260, 240], | ||||
|     [0,0,1] | ||||
| ]) | ||||
| alpha = 0.1/250 | ||||
| distCoeffs = np.float64([-0.013721808247486035, 0.020727425669427896,-0.012786476702685545, 0.0025242267320687625])  | ||||
| 
 | ||||
| rvec = np.float64([[0.,0.,0.]]) | ||||
| tvec = np.float64([[0.,0.,0.]]); | ||||
| imagePoints, jacobian = cv2.fisheye.projectPoints(objpts, rvec, tvec, cameraMatrix, distCoeffs, alpha=alpha)  | ||||
| np.set_printoptions(precision=14)  | ||||
| print(imagePoints) | ||||
| =========================================================== | ||||
|  * Script output: [[[457.82638130304935 408.18905848512986]]] | ||||
|  */ | ||||
| // clang-format on
 | ||||
| TEST(Cal3Fisheye, uncalibrate3) { | ||||
|   Point3 p3(23, 27, 31); | ||||
|   Point2 xi(p3.x() / p3.z(), p3.y() / p3.z()); | ||||
|   auto uv = K.uncalibrate(xi); | ||||
|   CHECK(assert_equal(uv, Point2(457.82638130304935, 408.18905848512986))); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| TEST(Cal3Fisheye, calibrate1) { | ||||
|   Point2 pi; | ||||
|   Point2 uv; | ||||
|   Point2 pi_hat; | ||||
| 
 | ||||
|   pi = Point2(0.5, 0.5);     // point in intrinsic coordinates
 | ||||
|   uv = K.uncalibrate(pi);    // map intrinsic coord to image plane (pi)
 | ||||
|   pi_hat = K.calibrate(uv);  // map image coords (pi) back to intrinsic coords
 | ||||
|   CHECK(traits<Point2>::Equals(pi, pi_hat, | ||||
|                                1e-5));  // check that the inv mapping works
 | ||||
| 
 | ||||
|   pi = Point2(-0.7, -1.2); | ||||
|   uv = K.uncalibrate(pi); | ||||
|   pi_hat = K.calibrate(uv); | ||||
|   CHECK(traits<Point2>::Equals(pi, pi_hat, 1e-5)); | ||||
| 
 | ||||
|   pi = Point2(-3, 5); | ||||
|   uv = K.uncalibrate(pi); | ||||
|   pi_hat = K.calibrate(uv); | ||||
|   CHECK(traits<Point2>::Equals(pi, pi_hat, 1e-5)); | ||||
| 
 | ||||
|   pi = Point2(7, -12); | ||||
|   uv = K.uncalibrate(pi); | ||||
|   pi_hat = K.calibrate(uv); | ||||
|   CHECK(traits<Point2>::Equals(pi, pi_hat, 1e-5)); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| /**
 | ||||
|  * Check that calibrate returns (0,0) for the image center | ||||
|  */ | ||||
| TEST(Cal3Fisheye, calibrate2) { | ||||
|   Point2 uv(u0, v0); | ||||
|   auto xi_hat = K.calibrate(uv); | ||||
|   CHECK(assert_equal(xi_hat, Point2(0, 0))) | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Run calibrate on OpenCv test from uncalibrate3 | ||||
|  *  (script shown above) | ||||
|  * 3d point: (23, 27, 31) | ||||
|  * 2d point in image plane: (457.82638130304935, 408.18905848512986) | ||||
|  */ | ||||
| TEST(Cal3Fisheye, calibrate3) { | ||||
|   Point3 p3(23, 27, 31); | ||||
|   Point2 xi(p3.x() / p3.z(), p3.y() / p3.z()); | ||||
|   Point2 uv(457.82638130304935, 408.18905848512986); | ||||
|   auto xi_hat = K.calibrate(uv); | ||||
|   CHECK(assert_equal(xi_hat, xi)); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| // For numerical derivatives
 | ||||
| Point2 uncalibrate_(const Cal3Fisheye& k, const Point2& pt) { | ||||
|   return k.uncalibrate(pt); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| TEST(Cal3Fisheye, Duncalibrate1) { | ||||
|   Matrix computed; | ||||
|   K.uncalibrate(p, computed, boost::none); | ||||
|   Matrix numerical = numericalDerivative21(uncalibrate_, K, p, 1e-7); | ||||
|   CHECK(assert_equal(numerical, computed, 1e-5)); | ||||
|   Matrix separate = K.D2d_calibration(p); | ||||
|   CHECK(assert_equal(numerical, separate, 1e-5)); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| TEST(Cal3Fisheye, Duncalibrate2) { | ||||
|   Matrix computed; | ||||
|   K.uncalibrate(p, boost::none, computed); | ||||
|   Matrix numerical = numericalDerivative22(uncalibrate_, K, p, 1e-7); | ||||
|   CHECK(assert_equal(numerical, computed, 1e-5)); | ||||
|   Matrix separate = K.D2d_intrinsic(p); | ||||
|   CHECK(assert_equal(numerical, separate, 1e-5)); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| TEST(Cal3Fisheye, assert_equal) { CHECK(assert_equal(K, K, 1e-5)); } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| TEST(Cal3Fisheye, retract) { | ||||
|   Cal3Fisheye expected(K.fx() + 1, K.fy() + 2, K.skew() + 3, K.px() + 4, | ||||
|                        K.py() + 5, K.k1() + 6, K.k2() + 7, K.k3() + 8, | ||||
|                        K.k4() + 9); | ||||
|   Vector d(9); | ||||
|   d << 1, 2, 3, 4, 5, 6, 7, 8, 9; | ||||
|   Cal3Fisheye actual = K.retract(d); | ||||
|   CHECK(assert_equal(expected, actual, 1e-7)); | ||||
|   CHECK(assert_equal(d, K.localCoordinates(actual), 1e-7)); | ||||
| } | ||||
| 
 | ||||
| /* ************************************************************************* */ | ||||
| int main() { | ||||
|   TestResult tr; | ||||
|   return TestRegistry::runAllTests(tr); | ||||
| } | ||||
| /* ************************************************************************* */ | ||||
		Loading…
	
		Reference in New Issue