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