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 i, the vehicle pose is represented as xi=[piT,qiT]T, where pi denotes the three-dimensional position vector and qi is a unit quaternion representing the vehicle orientation. For two time instances i and j, the residual term corresponding to the relative pose between vehicles is defined as

rij=[R(qi)T(pjpi)p^ij2vec((qi1qj)q^ij1)]

where vec() denotes the vector part of a quaternion, specifically [qx,qy,qz]T, and R(q) represents the rotation matrix corresponding to quaternion q. The corresponding code implementation is in the Functor class 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., ξiR6. The corresponding transformation matrix for the three-dimensional vehicle pose is represented by TiSE(3).

ξise(3) Ti=exp(ξi)SE(3)

The relative pose transformation between two nodes can be represented as

δξij=log(δTij)=log(Ti1Tj)=log(exp(ξi)exp(ξj))

The residual of the corresponding edge is

rij=log(δTij1Ti1Tj)=log(exp(ξij)exp(ξi)exp(ξj))

The Jacobian matrix is calculated by

rijξi=J1(rij)Ad(Tj1)rijξj=J1(rij)Ad(Tj1)

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 ϵ with the property ϵ2=0. A dual number is defined as:

a+vϵ

where it consists of two parts: the real part a and the differential part v.

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 f(x), considering the Taylor expansion of f(x+ϵ) around x, we have:

f(x+ϵ)=f(x)+Df(x)ϵ+D2f(x)ϵ22!+D3f(x)ϵ33!+=f(x)+Df(x)ϵ

Next, extend real numbers to n differential units ϵi,i=1,,n, satisfying i,j:ϵiϵj=0. Define a Jet as

x=a+jvjϵj

where it includes a real part a and an n-dimensional differential part v, represented as

x=a+v

The Taylor expansion of f(a+v) yields

f(a+v)=f(a)+Df(a)v

For a multivariable function f:RnRm, the Taylor expansion at xi=ai+vi,i=1,,n is given by

f(x1,,xn)=f(a1,,an)+iDif(a1,,an)vi

Setting vi=ei, the basis vector for each dimension, we have

f(x1,,xn)=f(a1,,an)+iDif(a1,,an)ϵi

Based on this, the Jacobian at a given value can be obtained according to the coefficients corresponding to each differential component ϵi.

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.

CostFunctionTime (ns)
Rat43Analytic255
Rat43AnalyticOptimized92
Rat43NumericDiffForward262
Rat43NumericDiffCentral517
Rat43NumericDiffRidders3760
Rat43AutomaticDiff129

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

E(X^)=(YMX^)T(YMX^)

The optimal estimate of variable X^ and its covariance is given by

X^=(MTM)1MTYC(X^)=(MTM)1σ2

The above equation provides an unbiased estimate of σ2, where N is the number of observation data, and dim() denotes the dimensionality of the variables.

s2=Emin(X^)Ndim(X)

Taking the second derivative of the above equation yields the Hessian matrix:

H=dE2(X^)dX^2=2MTMMTM=12H

Substituting the covariance estimation result, we obtain

C(X^)=(12H)1σ2

For the point cloud ICP scan matching method, its optimization objective is

E(ξ)=i=1Nei2=i=1NRpi+tpi2

The first derivative of solving E(ξ) with respect to ξ gives

E(ξ)ξ=2i=1NeiξTei

where

eiξ=[Ipi]

Based on this, solving the second derivative of E(ξ) with respect to ξ yields the Hessian matrix

H=2Eξ2=2i=1N[IpiTpipipiT]

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);
}

code: