g2o is an open-source C++ framework for optimizing graph-based nonlinear error functions. g2o has been designed to be easily extensible to a wide range of problems and a new problem typically can be specified in a few lines of code. The current implementation provides solutions to several variants of SLAM and BA.
一个简单的例子
现将通过一个直线拟合的例子理解G2O的工作流程。
假设方程\(y = kx + b\), 给定n组观测数据\((x, y)\)求解系数\(X = [k, b]^T\).
令\(f(X) = y - kx -b\)N组数据可以组成一个大的非线性方程组
\[
\begin{bmatrix} y_1 - kx_1 -b \\ \vdots \\ y_n - kx_n -b \\ \end{bmatrix}
\]
可以构建一个最小二乘问题:
\[
x = arg \, \min\limits_{x} \frac{1}{2} || f(x) ||^2
\]
要求解这个问题,根据推导部分可知,需要求解雅克比。
\[
J(X) = \begin{bmatrix} -k_1 & -1 \\ \cdots & \cdots \\ -k_n & -1 \\ \end{bmatrix}
\]
/**
******************************************************************************
* @file
* @author maky <chengwei920412@outlook.com>
* @version
* @date 2018-12-12 20:22:58
* @brief
******************************************************************************
* @attention
*
*
******************************************************************************
*/
#include <iostream>
#include <random>
#include <vector>
#include <memory>
#include <algorithm>
#include <Eigen/Eigen>
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/core/robust_kernel_impl.h>
#include <g2o/solvers/cholmod/linear_solver_cholmod.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_binary_edge.h>
#include <g2o/core/base_unary_edge.h>
#include <opencv2/opencv.hpp>
class LineFittingVertex : public g2o::BaseVertex<2, Eigen::Vector2d>
{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW
virtual void setToOriginImpl()
{
_estimate << 0, 0;
}
virtual void oplusImpl(const double* update)
{
_estimate += Eigen::Vector2d(update);
}
virtual bool read(std::istream& in) { return true; }
virtual bool write(std::ostream& out) const { return true; }
};
class LineFittingEdge : public g2o::BaseUnaryEdge<1, Eigen::Vector2d, LineFittingVertex>
{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW
LineFittingEdge()
: BaseUnaryEdge()
{}
virtual void computeError()
{
const LineFittingVertex* vertex = static_cast<const LineFittingVertex*> (_vertices[0]);
const Eigen::Vector2d &estimate = vertex->estimate();
// error = y - (kx + b)
_error(0, 0) = _measurement(1, 0) - (estimate(0, 0) * _measurement(0, 0) + estimate(1, 0));
}
virtual void linearizeOplus() {
const auto vertex = static_cast<const LineFittingVertex*> (_vertices[0]);
const Eigen::Vector2d &estimate = vertex->estimate();
/*
* $$\begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_2}{\partial x_2} \end{bmatrix} = \begin{bmatrix} -k & -1 \end{bmatrix}$$
*/
_jacobianOplusXi(0, 0) = -_measurement(0, 0);
_jacobianOplusXi(0, 1) = -1;
}
virtual bool read(std::istream& in) { return true; }
virtual bool write(std::ostream& out) const { return true; }
};
int main(int argc, const char **argv)try
{
std::srand((unsigned int)std::time(NULL));
cv::RNG random;
double x_range = 100.;
std::uint64_t inlier_total = 1000;
double sigma = 1.;
double k = (double)(std::rand() % 20000 - 10000) / (10000. / 5.);
double b = (double)(std::rand() % 20000 - 10000) / (10000. / 3.);
std::vector<Eigen::Vector2d, Eigen::aligned_allocator<Eigen::Vector2d>> points_obs;
for (auto pos = 0; pos < inlier_total; pos++) {
Eigen::Vector2d point;
point.x() = (double)(std::rand() % 20000 - 10000) / (10000. / x_range);
point.y() = k * point.x() + b /*+ random.gaussian(sigma)*/;
points_obs.push_back(point);
}
// fitting
typedef g2o::BlockSolver<g2o::BlockSolverTraits<Eigen::Dynamic, Eigen::Dynamic>> BlockSolverType;
g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(
g2o::make_unique<BlockSolverType>(g2o::make_unique<g2o::LinearSolverDense<BlockSolverType::PoseMatrixType>>()));
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm(algorithm);
optimizer.setVerbose(true);
// vertex
{
LineFittingVertex* vertex = new LineFittingVertex();
vertex->setEstimate(Eigen::Vector2d(0., 0.));
vertex->setId(0);
optimizer.addVertex(vertex);
}
// edge
for (auto pos = 0; pos < points_obs.size(); pos++)
{
auto vertex = optimizer.vertices().find(0);
if (vertex == optimizer.vertices().end()) {
std::cout << "can not find vertext: " << 0 << std::endl;
continue;
}
LineFittingEdge* edge = new LineFittingEdge();
edge->setId(pos);
edge->setVertex(0, vertex->second);
edge->setMeasurement(points_obs.at(pos));
edge->setInformation(Eigen::Matrix<double, 1, 1>::Identity()/* * 1 / (sigma*sigma)*/);
optimizer.addEdge(edge);
}
optimizer.initializeOptimization();
optimizer.optimize(100);
auto vertex = optimizer.vertices().find(0);
if (vertex == optimizer.vertices().end()) {
std::cout << "can not find vertext: " << 0 << std::endl;
return -1;
}
Eigen::Vector2d estimate = ((LineFittingVertex *)(vertex->second))->estimate();
std::cout << "true: " << k << " " << b << " " << "estimated: " << estimate.transpose() << std::endl;
return 0;
}
catch (const std::exception &exp) {
std::printf("exception: %s.", exp.what());
return -1;
}
catch (...) {
std::printf("exception: unknown exception.");
return -1;
}
Reference
https://github.com/RainerKuemmerle/g2o
https://openslam-org.github.io/g2o.html
https://github.com/rosskidson/g2o_tutorial
Rainer & Grisetti g2o: A General Framework for Graph Optimization
Grisetti g2o:a general framework for(hyper) graph optimization