Backend Global Graph Optimization: Pose Parameterization and Jacobian Solution Scheme
Date:
Pose Parameterization and Jacobian Solution for Backend Global Graph Optimization
Implementation Based on Quaternion Rotation and Translation Vector Representation with Automatic Differentiation
Assuming for time
where EdgePoseError
, used in the Ceres CostFunction
class for computing the residual vector and its corresponding Jacobian.
template <typename T>
bool operator()(const T* const position_i, const T* const orientation_i,
const T* const position_j, const T* const orientation_j,
T* residual_ptr) const
{
Eigen::Map<const Eigen::Matrix<T,3,1>> p_i(position_i);
Eigen::Map<const Eigen::Quaternion<T>> q_i(orientation_i);
Eigen::Map<const Eigen::Matrix<T,3,1>> p_j(position_j);
Eigen::Map<const Eigen::Quaternion<T>> q_j(orientation_j);
Eigen::Quaternion<T> q_i_inv=q_i.conjugate();
Eigen::Quaternion<T> q_ij=q_i_inv*q_j;
Eigen::Matrix<T,3,1> p_ij=q_i_inv*(p_j-p_i);
Eigen::Quaternion<T> q_ij_meas(pose_ij_meas_.linear().template cast<T>());
Eigen::Quaternion<T> delta_q=q_ij_meas*q_ij.conjugate();
Eigen::Map<Eigen::Matrix<T,6,1>> residual(residual_ptr);
Eigen::Map<Eigen::Matrix<T,3,1>> residual_trs(residual_ptr);
Eigen::Map<Eigen::Matrix<T,3,1>> residual_rot(residual_ptr+3);
residual_trs=p_ij-pose_ij_meas_.translation().template cast<T>();
residual_rot=T(2.0)*delta_q.vec();
residual.applyOnTheLeft(sqrt_information_.template cast<T>());
return true;
}
The Jacobian solving utilizes the automatic differentiation method provided by Ceres.
static ceres::CostFunction* Create(const Eigen::Isometry3d& pose_ij,const Matrix6d& sqrt_information)
{
return new ceres::AutoDiffCostFunction<EdgePoseError,6,3,4,3,4>
(new EdgePoseError(pose_ij,sqrt_information));
}
Implementation Based on Pose Lie Algebra Parameterization and Analytical Differentiation
In this approach, the vehicle pose is represented in a minimal parameter form, i.e.,
The relative pose transformation between two nodes can be represented as
The residual of the corresponding edge is
The Jacobian matrix is calculated by
In the implementation approach, define the cost function class EdgePoseSE3CostFunction
, with the corresponding code for residual and Jacobian matrix calculation as follows.
virtual bool Evaluate(double const* const* parameters,
double *residuals, double **jacobians) const
{
Sophus::SE3 pose_i = Sophus::SE3::exp(Vector6d(parameters[0]));
Sophus::SE3 pose_j = Sophus::SE3::exp(Vector6d(parameters[1]));
Sophus::SE3 estimate = pose_i.inverse() * pose_j;
Eigen::Map<Vector6d> residual(residuals);
residual = sqrt_information_ * ((measurment_.inverse() * estimate).log());
if(jacobians)
{
if(jacobians[0]) {
Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_i(jacobians[0]);
Matrix6d J = JRInv(Sophus::SE3::exp(residual));
jacobian_i = (-J) * pose_j.inverse().Adj();
jacobian_i = sqrt_information_*(-J) * pose_j.inverse().Adj();
}
if(jacobians[1]){
Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_j(jacobians[1]);
Matrix6d J = JRInv(Sophus::SE3::exp(residual));
jacobian_j = J * pose_j.inverse().Adj();
jacobian_j = sqrt_information_*J * pose_j.inverse().Adj();
}
}
return true;
}
Automatic Differentiation Algorithm Based on Dual Numbers and Jets
Dual Numbers and Jets
Dual numbers extend real numbers by introducing a differential unit
where it consists of two parts: the real part
Based on the definition of dual numbers, it is possible to compute exact differentials of functions without explicitly providing the differential form. Specifically, for any continuously differentiable function
Next, extend real numbers to
where it includes a real part
The Taylor expansion of
For a multivariable function
Setting
Based on this, the Jacobian at a given value can be obtained according to the coefficients corresponding to each differential component
Ceres Implementation
The Ceres nonlinear optimization library provides an exact derivative solution based on dual numbers and Jets, known as Automatic Differentiation. First, the Jet structure needs to be defined.
template<int N> struct Jet {
double a;
Eigen::Matrix<double, 1, N> v;
};
template<int N> Jet<N> operator+(const Jet<N>& f, const Jet<N>& g) {
return Jet<N>(f.a + g.a, f.v + g.v);
}
template<int N> Jet<N> operator-(const Jet<N>& f, const Jet<N>& g) {
return Jet<N>(f.a - g.a, f.v - g.v);
}
template<int N> Jet<N> operator*(const Jet<N>& f, const Jet<N>& g) {
return Jet<N>(f.a * g.a, f.a * g.v + f.v * g.a);
}
template<int N> Jet<N> operator/(const Jet<N>& f, const Jet<N>& g) {
return Jet<N>(f.a / g.a, f.v / g.a - f.a * g.v / (g.a * g.a));
}
template <int N> Jet<N> exp(const Jet<N>& f) {
return Jet<T, N>(exp(f.a), exp(f.a) * f.v);
}
// This is a simple implementation for illustration purposes, the
// actual implementation of pow requires careful handling of a number
// of corner cases.
template <int N> Jet<N> pow(const Jet<N>& f, const Jet<N>& g) {
return Jet<N>(pow(f.a, g.a),
g.a * pow(f.a, g.a - 1.0) * f.v +
pow(f.a, g.a) * log(f.a); * g.v);
}
Next, taking the Rat43 problem as an example, define the functor class as follows.
struct Rat43CostFunctor {
Rat43CostFunctor(const double x, const double y) : x_(x), y_(y) {}
template <typename T>
bool operator()(const T* parameters, T* residuals) const {
const T b1 = parameters[0];
const T b2 = parameters[1];
const T b3 = parameters[2];
const T b4 = parameters[3];
residuals[0] = b1 * pow(1.0 + exp(b2 - b3 * x_), -1.0 / b4) - y_;
return true;
}
private:
const double x_;
const double y_;
};
CostFunction* cost_function =
new AutoDiffCostFunction<Rat43CostFunctor, 1, 4>(
new Rat43CostFunctor(x, y));
Unlike the implementation of numerical differentiation algorithms, the operator()
definition here uses a template form.
template <typename T> bool operator()(const T* parameters, T* residuals) const;
With this definition, the Jet type can be passed into the Rat43CostFunctor
function to compute the Jacobian matrix of the error function with respect to the variables.
Performance Comparison
The Ceres nonlinear optimization library provides three methods for Jacobian computation: Analytic Derivatives, Numeric Derivatives, and Automatic Derivatives.
Taking the Rat43 problem as an example, the performance comparison of these three differentiation methods is shown in Table 3.1. Here, Rat43AutomaticDiff represents the Automatic Differentiation solution, Rat43Analytic and Rat43AnalyticOptimized are the Analytic Derivatives methods, whose implementation details can be found in Ceres - Analytic Derivatives. Rat43NumericDiffForward, Rat43NumericDiffCentral, and Rat43NumericDiffRidders represent the Numeric Derivatives methods, with their implementation details available at Ceres - Numeric Derivatives.
CostFunction | Time (ns) |
---|---|
Rat43Analytic | 255 |
Rat43AnalyticOptimized | 92 |
Rat43NumericDiffForward | 262 |
Rat43NumericDiffCentral | 517 |
Rat43NumericDiffRidders | 3760 |
Rat43AutomaticDiff | 129 |
Some Tips
Since this is a large sparse problem (well large for DENSE_QR
anyways), one way to solve this problem is to set Solver::Options::linear_solver_type
to SPARSE_NORMAL_CHOLESKY
and call Solve()
. And while this is a reasonable thing to do, bundle adjustment problems have a special sparsity structure that can be exploited to solve them much more efficiently. Ceres provides three specialized solvers (collectively known as Schur-based solvers) for this task. The example code uses the simplest of them DENSE_SCHUR
.
Problem
by default takes ownership of the cost_function
, loss_function
and local_parameterization
pointers. These objects remain live for the life of the Problem
. If the user wishes to keep control over the destruction of these objects, then they can do this by setting the corresponding enums in the Problem::Options
struct.
Covariance Calculation and Propagation
Covariance Estimation Method Based on Hessian Matrix
For the linear least squares problem, assuming the objective function is
The optimal estimate of variable
The above equation provides an unbiased estimate of
Taking the second derivative of the above equation yields the Hessian matrix:
Substituting the covariance estimation result, we obtain
For the point cloud ICP scan matching method, its optimization objective is
The first derivative of solving
where
Based on this, solving the second derivative of
Implementation
Eigen::Matrix<float,6,6> ICPRegistration::GetCovariance()
{
// CloudData::CLOUD_PTR cloud;
// cloud=icp_ptr_->getInputSource();
Eigen::Matrix<float,6,6> hessian;
hessian.setZero();
for(int i=0;i<input_source_->size();i++)
{
Eigen::Vector3f p(input_source_->at(i).x,input_source_->at(i).y,input_source_->at(i).z);
Eigen::Matrix3f p_skew=Converter::toSkewSym(p);
hessian.block<3,3>(0,0)+=Eigen::Matrix3f::Identity();
hessian.block<3,3>(0,3)+=-p_skew;
hessian.block<3,3>(3,0)+=p_skew;
hessian.block<3,3>(3,3)+=-p_skew*p_skew;
}
return hessian.inverse() * GetFitnessScore() / (input_source_->size()-6);
}
Related Links
code: