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