1\documentclass[a4paper]{article} 2\usepackage{graphicx} 3\usepackage{fullpage} 4\usepackage{graphicx} % for pdf, bitmapped graphics files 5\usepackage{amsmath} % assumes amsmath package installed 6\usepackage{amssymb} % assumes amsmath package installed\ref 7\usepackage{psfrag} 8\usepackage{algorithmic} 9\usepackage{algorithm} 10\usepackage{color} 11\usepackage{xcolor} 12\usepackage{listings} 13\begin{document} 14 15\lstset{ 16language=C++, 17captionpos=b, 18tabsize=2, 19keywordstyle=\color{blue}, 20commentstyle=\color{green}, 21stringstyle=\color{red}, 22breaklines=true, 23showstringspaces=false, 24frame=shadowbox, 25rulesepcolor=\color{gray}, 26basicstyle=\footnotesize% 27} 28 29 30\newcommand{\gopt}{g\ensuremath{^2}o} 31\newcommand{\defeq}{\stackrel{\text{def.}}{=}} 32\newcommand{\gcomment}[1]{\textcolor{red}{\textbf{Giorgio:}~\emph{#1}}} 33\newcommand{\rcomment}[1]{\textcolor{red}{\textbf{Rainer:}~\emph{#1}}} 34 35\def\secref#1{Section~\ref{#1}} 36\def\figref#1{Figure~\ref{#1}} 37\def\tabref#1{Table~\ref{#1}} 38\def\eqref#1{Eq.~\ref{#1}} 39 40\newcommand{\Dom}{\mathtt{Dom}} 41\newcommand{\bZero}{\mathbf{0}} 42\newcommand{\bb}{\mathbf{b}} 43\newcommand{\bc}{\mathbf{c}} 44\newcommand{\bh}{\mathbf{h}} 45\newcommand{\bH}{\mathbf{H}} 46\newcommand{\bA}{\mathbf{A}} 47\newcommand{\bM}{\mathbf{M}} 48\newcommand{\bF}{\mathbf{F}} 49\newcommand{\bG}{\mathbf{G}} 50\newcommand{\bI}{\mathbf{I}} 51\newcommand{\bB}{\mathbf{B}} 52\newcommand{\bR}{\mathbf{R}} 53\newcommand{\bp}{\mathbf{p}} 54\newcommand{\bq}{\mathbf{q}} 55\newcommand{\bqTilde}{\mathbf{\tilde q}} 56\newcommand{\ec}{\mathbf{e}} 57\newcommand{\be}{\mathbf{e}} 58\newcommand{\br}{\mathbf{r}} 59\newcommand{\bx}{\mathbf{x}} 60\newcommand\bbx{\breve{\bx}} 61\newcommand{\bu}{\mathbf{u}} 62\newcommand{\bz}{\mathbf{z}} 63\newcommand{\bt}{\mathbf{t}} 64\newcommand{\bDeltax}{\mathbf{\Delta x}} 65\newcommand{\bDeltaAlpha}{\mathbf{\Delta \alpha}} 66\newcommand{\bDeltaAlphaTilde}{\mathbf{\Delta\tilde\alpha}} 67\newcommand{\bTDeltax}{\mathbf{\Delta \tilde x}} 68\newcommand{\bDelta}{\mathbf{\Delta}} 69\newcommand{\balpha}{\mathbf{\alpha}} 70\newcommand{\bOmega}{\mathbf{\Omega}} 71\newcommand{\bSigma}{\mathbf{\Sigma}} 72\newcommand{\bJ}{\mathbf{J}} 73\newcommand{\diff}{\partial} 74\def\argmax{\mathop{\rm argmax}} 75\def\argmin{\mathop{\rm argmin}} 76 77\newcommand{\Rstar}{{\cal R} } 78\newcommand{\Rx}{R} 79\newcommand{\tx}{t} 80 81\newcommand{\angleOf}{\mathbf{angleOf}} 82\newcommand{\axisOf}{\mathbf{axisOf}} 83\newcommand{\slerp}{\mathbf{slerp}} 84 85\title{\gopt: A general Framework for (Hyper) Graph Optimization} 86\author{Giorgio Grisetti, Rainer K\"ummerle, Hauke Strasdat, Kurt Konolige\\ 87 email: \texttt{\{grisetti,kuemmerl\}@informatik.uni-freiburg.de}\\ 88 \texttt{strasdat@gmail.com konolige@willowgarage.com} 89} 90\maketitle 91 92In this document we describe a C++ framework for performing the 93optimization of nonlinear least squares problems that can be embedded 94as a graph or in a hyper-graph. A hyper-graph is an extension of a 95graph where an edge can connect multiple nodes and not only two. 96Several problems in robotics and in computer vision require to find 97the optimum of an error function with respect of a set of parameters. 98Examples include, popular applications like SLAM and Bundle 99adjustment. 100 101In the literature, many approaches have been proposed to address this 102class of problems. The naive implementation of standard methods, like 103Levenberg-Marquardt or Gauss-Newton can lead to acceptable results for 104most applications, when the correct parameterization is 105chosen. However, to achieve the maximum performances substantial 106efforts might be required. 107 108\gopt\ stands for General (Hyper) Graph Optimization. The purposes of 109this framework are the following: 110\begin{itemize} 111\item To provide an easy-to-extend and easy-to-use general library for 112 graph optimization that can be easily applied to different problems, 113\item To provide people who want to understand SLAM or BA with an 114 easy-to-read implementation that focuses on the relevant details of 115 the problem specification. 116\item Achieve state-of-the-art performances, 117 while being as general as possible. 118\end{itemize} 119 120In the remainder of this document we will first characterize the 121(hyper) graph-embeddable problems, and we will give an introduction to 122their solution via the popular Levenberg-Marquardt or Gauss-Newton 123algorithms implemented in this library. Subsequently, we will 124describe the high-level behavior of the library, and the basic 125structures. Finally, we will introduce how to implement 2D SLAM as a 126simple example.\\ 127\vspace{.5cm}\\ 128\textbf{This document is not a replacement for the 129 in-line documentation. Instead, it is a digest to help the 130 user/reader to read/browse and extend the code.} 131 132 \vspace{.5cm} 133\noindent Please cite this when using \gopt:\\ 134R. K\"ummerle, G. Grisetti, H. Strasdat, K. Konolige, and W. Burgard. 135g2o: A General Framework for Graph Optimization. 136In Proc. of the IEEE Int. Conf. on Robotics and Automation (ICRA). Shanghai, China, May 2011. 137 138\section{(Hyper)Graph-Embeddable Optimization Problems} 139A least squares minimization problem can be described by the following equation: 140\begin{eqnarray} 141\bF(\bx)&=& \sum_{k \in\mathcal{C}} 142\underbrace{\be_k(\bx_k, \bz_{k})^T \bOmega_{k} \be_k(\bx_k, \bz_{k})}_{\bF_{k}} 143\label{eq:sumOfFactors}\\ 144\bx^*&=&\argmin_{\bx} \bF(\bx). 145\label{eq:toMinimize} 146\end{eqnarray} 147Here 148\begin{itemize} 149 \item $\bx=(\bx_1^T,\;\ldots\;,\bx_n^T)^T$ is a vector of 150 parameters, where each $\bx_i$ represents a generic parameter block. 151 \item $\bx_k=(\bx_{k_1}^T,\;\ldots\;,\bx_{k_q}^T)^T \subset 152 (\bx_1^T,\;\ldots\;,\bx_n^T)^T$ is the subset of the parameters 153 involved in the $k^\mathrm{th}$ constraint. 154 \item $\bz_{k}$ and $\bOmega_{k}$ represent 155 respectively the mean and the information matrix of a constraint 156 relating the parameters in $\bx_k$. 157 \item $\be_k(\bx_k \bz_{k})$ is a vector error function 158 that measures how well the parameter blocks in $\bx_k$ satisfy the 159 constraint $\bz_{k}$. It is $\bZero$ when $\bx_k$ and $\bx_j$ 160 perfectly match the constraint. As an example, if one has a 161 measurement function $\hat \bz_k = \bh_k(\bx_k)$ that generates a 162 synthetic measurement $\hat \bz_k$ given an actual configuration 163 of the nodes in $\bx_k$. A straightforward error function would 164 then be $\be(\bx_k, \bz_{k}) = \bh_k(\bx_k) - \bz_k$. 165\end{itemize} 166For simplicity of notation, in the rest of this paper we will encode 167the measurement in the indices of the error function: 168\begin{equation} 169\be_k(\bx_k, \bz_{k}) \; \defeq \; \be_{k}(\bx_k) \; \defeq \; \be_{k}(\bx). 170\end{equation} 171Note that each parameter block and each error 172function can span over a different space. A problem in this form can 173be effectively represented by a directed hyper-graph. A node $i$ of 174the graph represents the parameter block $\bx_i \in \bx_k$ and an 175hyper-edge among the nodes $\bx_{i} \in \bx_k $ represents a 176constraint involving all nodes in $\bx_k$. In case the hyper edges 177have size 2, the hyper-graph becomes an ordinary graph. 178Figure~\ref{fig:graph-example} shows an 179example of mapping between a hyper-graph and an objective function. 180 181\begin{figure} 182\centering 183\psfrag{p0}{$\bp_0$} 184\psfrag{p1}{$\bp_1$} 185\psfrag{p2}{$\bp_2$} 186\psfrag{pi}{$\bp_i$} 187\psfrag{pj}{$\bp_j$} 188\psfrag{pim1}{$\bp_{i-1}$} 189\psfrag{z2i}{$\bz_{2i}$} 190\psfrag{K}{$\mathbf{K}$} 191\psfrag{zij}{$\bz_{ij}$} 192\psfrag{u0}{$\bu_{0}$} 193\psfrag{u1}{$\bu_{1}$} 194\psfrag{ui1}{$\bu_{i-1}$} 195\includegraphics{pics/hgraph.eps} 196\caption{This example illustrates how to represent an objective 197 function by a hyper-graph. Here we illustrate a portion of a small 198 SLAM problem~\cite{konolige10iros}. In this example we assume that 199 where the measurement functions are governed by some unknown 200 calibration parameters $\mathbf{K}$. The robot poses are represented 201 by the variables $\bp_{1:n}$. These variables are connected by 202 constraints $\bz_{ij}$ depicted by the square boxes. The constraints 203 arise, for instance, by matching nearby laser scans \emph{in the 204 laser reference frame}. The relation between a laser match and a 205 robot pose, however, depends on the position of the sensor on the 206 robot, which is modeled by the calibration parameters 207 $\mathbf{K}$. Conversely, subsequent robot poses are connected by 208 binary constraints $\bu_{k}$ arising from odometry 209 measurements. These measurements are made in the frame of the robot 210 mobile base.} 211\label{fig:graph-example} 212\end{figure} 213 214\section{Least Squares Optimization} 215If a good initial guess $\breve{\bx}$ of the parameters is known, a 216numerical solution of \eqref{eq:toMinimize} can be obtained by using 217the popular Gauss-Newton or Levenberg-Marquardt 218algorithms~\cite[\S15.5]{Press92Book}. The idea is to approximate the 219error function by its first order Taylor expansion around the current 220initial guess $\breve{\bx}$ 221\begin{eqnarray} 222\be_{k}(\breve{\bx}_k + \bDeltax_k) &=& \be_{k}(\breve{\bx} + \bDeltax)\\ 223&\simeq& \ec_{k} + \bJ_{k} \bDeltax. 224\label{eq:taylor} 225\end{eqnarray} 226Here $\bJ_{k}$ is the Jacobian of $\be_{k}(\bx)$ computed in 227$\breve{\bx}$ and $\ec_{k} \defeq \be_{k}(\breve{\bx})$. 228Substituting \eqref{eq:taylor} in the error terms $\bF_{k}$ of 229\eqref{eq:sumOfFactors}, we obtain 230 231\vspace{-3.5mm} 232{\small 233\begin{eqnarray} 234\lefteqn{\bF_{k}(\breve{\bx} + \bDeltax) }\\ 235&=& \be_{k}(\breve{\bx} + \bDeltax)^T \bOmega_{k} \be_{k}(\breve{\bx} + \bDeltax) \\ 236\label{eq:errorQuad1} 237 &\simeq& \left(\ec_{k} + \bJ_{k} \bDeltax \right)^T \bOmega_{k} \left(\ec_{k} + \bJ_{k} \bDeltax \right) \\ 238\label{eq:errorQuad2} 239 &=& \underbrace{\ec_{k}^T \bOmega_{k}\ec_{k}}_{\mathrm{c}_{k}} + 2 \underbrace{\ec_{k}^T \bOmega_{k} \bJ_{k}}_{\bb_{k}} \bDeltax + 240 \bDeltax^T \underbrace{\bJ_{k}^T \bOmega_{k}\bJ_{k}}_{\bH_{k}} \bDeltax \\ 241 &=&\mathrm{c}_{k} + 2 \bb_{k} \bDeltax + \bDeltax^T \bH_{k} \bDeltax 242\label{eq:errorQuad} 243\end{eqnarray} 244} 245With this local approximation, we can rewrite the function 246$\bF(\bx)$ given in \eqref{eq:sumOfFactors} as 247 248\vspace{-3.5mm} 249{\small 250\begin{eqnarray} 251\bF(\breve{\bx} + \bDeltax) &=& \sum_{k \in\mathcal{C}} \bF_{k}(\breve{\bx} + \bDeltax) 252\label{eq:optNetwork0}\\ 253 &\simeq& \sum_{k \in\mathcal{C}} \mathrm{c}_{k} + 2 \bb_{k} \bDeltax + \bDeltax^T \bH_{k} \bDeltax 254\label{eq:optNetwork1}\\ 255 &=&\mathrm{c} + 2 \bb^T \bDeltax + \bDeltax^T \bH \bDeltax. 256\label{eq:optNetwork2} 257\end{eqnarray} 258} The quadratic form in \eqref{eq:optNetwork2} is obtained from 259\eqref{eq:optNetwork1} by setting $\mathrm{c}=\sum \mathrm{c}_{k}$, 260$\bb=\sum \bb_{k}$ and $\bH=\sum \bH_{k}$. It can be minimized in 261$\bDeltax$ by solving the linear system 262\begin{eqnarray} 263 \bH\,\bDeltax^* &=& - \bb. 264\label{eq:oneLinearIteration} 265\end{eqnarray} 266The matrix $\bH$ is the information matrix of the system and is sparse 267by construction, having non-zeros only between blocks connected by a 268constraint. Its number of non-zero blocks is twice the number of 269constrains plus the number of nodes. This allows to solve 270\eqref{eq:oneLinearIteration} with efficient approaches like sparse 271Cholesky factorization or Preconditioned Conjugate Gradients (PCG). An 272highly efficient implementation of sparse Cholesky factorization can 273be found in publicly available packages like CSparse~\cite{csparse} or 274CHOLMOD~\cite{cholmod}. The linearized solution is then obtained by 275adding to the initial guess the computed increments 276\begin{eqnarray} 277 \bx^*&=&\breve{\bx}+\bDeltax^*. 278\label{eq:linearPropagation} 279\end{eqnarray} 280The popular Gauss-Newton algorithm iterates the linearization in 281\eqref{eq:optNetwork2}, the solution in \eqref{eq:oneLinearIteration} 282and the update step in \eqref{eq:linearPropagation}. In every 283iteration, the previous solution is used as linearization point and as 284initial guess. 285 286The Levenberg-Marquardt (LM) algorithm is a nonlinear variant to Gauss-Newton that introduces 287a damping factor and backup actions to control the convergence. 288Instead of solving directly Eq.~\ref{eq:oneLinearIteration} 289LM solves a damped version of it 290\begin{eqnarray} 291 (\bH +\lambda \bI)\,\bDeltax^* &=& - \bb. 292\label{eq:oneLinearIterationLevenberg} 293\end{eqnarray} 294Here $\lambda$ is a damping factor: the larger $\lambda$ is the 295smaller are the $\bDeltax$. This is useful to control the step size in 296case of non-linear surfaces. The idea behind the LM algorithm is to 297dynamically control the damping factor. At each iteration the error 298of the new configuration is monitored. If the new error is lower than 299the previous one, lambda is decreased for the next iteration. 300Otherwise, the solution is reverted and lambda is increased. 301For a more detailed explanation of the LM algorithm implemented in our package 302we refer to~\cite{lourakis2009toms}. 303 304The procedures described above are a general approach to multivariate 305function minimization. The general approach, however, assumes that the 306space of parameters $\bx$ is Euclidean, which is not valid for several 307problems like SLAM or bundle adjustment. This may lead to sub-optimal 308solutions. In the remainder of this section we discuss first the 309general solution when the space of the parameters is Euclidean, and 310subsequently we extend this solution to more general non-Euclidean 311spaces. 312 313\section{Considerations about the Structure of the Linearized System} 314According to \eqref{eq:optNetwork2}, the matrix $\bH$ and the vector 315$\bb$ are obtained by summing up a set of matrices and vectors, one 316for every constraint. If we set $\bb_{k}=\bJ_{k}^T \bOmega_{k} 317\ec_{k}$ and $\bH_{k}= \bJ_{k}^T \bOmega_{k}\bJ_{k}$ we can 318rewrite $\bH$ and $\bb$ as 319\begin{eqnarray} 320 \bb&=&\sum_{k \in\mathcal{C}} \bb_{ij}\\ 321 \bH&=&\sum_{k \in\mathcal{C}} \bH_{ij}. \label{eq:addendTerm} 322\end{eqnarray} 323Every constraint will contribute to the system with an addend 324term. The \emph{structure} of this addend depends on the Jacobian of 325the error function. Since the error function of a constraint depends 326only on the values of the nodes $\bx_i \in \bx_k$, the Jacobian in 327\eqref{eq:taylor} has the following form: 328\begin{eqnarray} 329\bJ_{k} &=& \left(\bZero \cdots \bZero \; \bJ_{k_1} \; \cdots \; \bJ_{k_i} \;\cdots \bZero\; \cdots\; \bJ_{k_q} \bZero \cdots \bZero \right). 330\label{eq:jacExpanded} 331\end{eqnarray} 332Here $\bJ_{k_i}= \frac{\partial \be(\bx_k)}{\partial \bx_{k_i}}$ are the 333derivatives of the error function with respect to the nodes connected 334by the $k^\mathrm{th}$ hyper-edge, with respect to the parameter block 335$\bx_{k_i} \in \bx_k$. 336 337From \eqref{eq:errorQuad2} we obtain the following structure for the block matrix $\bH_{ij}$: 338\begin{eqnarray} 339\bH_{k}&=&\left( 340\begin{array}{cccccccc} 341\ddots & & & & & & &\\ 342& \bJ_{k_1}^T \bOmega_{k} \bJ_{k_1} & \cdots & \bJ_{k_1}^T \bOmega_{k} \bJ_{k_i} & \cdots & \bJ_{k_1}^T \bOmega_{k} \bJ_{k_q} &\\ 343& \vdots & & \vdots & & \vdots & \\ 344& \bJ_{k_i}^T \bOmega_{k} \bJ_{k_1} & \cdots & \bJ_{k_i}^T \bOmega_{k} \bB_{k_i} & \cdots & \bJ_{k_i}^T \bOmega_{k} \bJ_{k_q} & \\ 345& \vdots & & \vdots & & \vdots & \\ 346& \bJ_{k_q}^T \bOmega_{k} \bJ_{k_1} & \cdots & \bJ_{k_q}^T \bOmega_{k} \bB_{k_i} & \cdots & \bJ_{k_q}^T \bOmega_{k} \bJ_{k_q} & \\ 347& &&&&&& \ddots \\ 348\end{array} 349\right) \\ 350\bb_{k}&=&\left( 351\begin{array}{c} 352\vdots\\ 353\bJ_{k_1} \bOmega_{k} \ec_{k}\\ 354\vdots\\ 355\bJ_{k_i}^T \bOmega_{k} \ec_{k}\\ 356\vdots\\ 357\bJ_{k_q}^T \bOmega_{k} \ec_{k}\\ 358\vdots\\ 359\end{array} 360\right) 361\end{eqnarray} 362For simplicity of notation we omitted the zero blocks. The reader 363might notice that the block structure of the matrix $\bH$ is the 364adjacency matrix of the hyper graph. Additionally the Hessian $\bH$ 365is a symmetric matrix, since all the $\bH_k$ are symmetric. A single 366hyper-edge connecting $q$ vertices will introduce $q^2$ non zero 367blocks in the Hessian, in correspondence of each pair $\left< 368\bx_{k_i}, \bx_{k_j} \right>$, of nodes connected. 369 370 371\section{Least Squares on Manifold} 372\label{sec:manifold} 373 374To deal with parameter blocks that span over a non-Euclidean spaces, 375it is common to apply the error minimization on a manifold. A 376manifold is a mathematical space that is not necessarily Euclidean on 377a global scale, but can be seen as Euclidean on a local 378scale~\cite{Lee2003SmoothManifolds}. 379 380For example, in the context of SLAM problem, each parameter block 381$\bx_i$ consists of a translation vector $\bt_i$ and a rotational 382component $\balpha_i$. The translation~$\bt_i$ clearly forms a Euclidean 383space. In contrast to that, the rotational components~$\balpha_i$ span 384over the non-Euclidean 2D or 3D rotation group $SO(2)$ or $SO(3)$. To 385avoid singularities, these spaces are usually described in an 386over-parameterized way, e.g., by rotation matrices or quaternions. 387Directly applying \eqref{eq:linearPropagation} to these 388over-parameterized representations breaks the constraints induced by the 389over-parameterization. The over-parameterization results in additional 390degrees of freedom and thus introduces errors in the solution. To 391overcome this problem, one can use a minimal representation for the 392rotation (like Euler angles in 3D). This, however, is then subject to 393singularities. 394 395An alternative idea is to consider the underlying space as a manifold 396and to define an operator $\boxplus$ that maps a local variation 397$\bDeltax$ in the Euclidean space to a variation on the manifold, 398$\bDeltax\mapsto\bx\boxplus\bDeltax$. We refer the reader to 399\cite[\S1.3]{hertzberg08diplom} for more mathematical details. With 400this operator, a new error function can be defined as 401\begin{eqnarray} 402\breve \be_{k}(\bTDeltax_k) &\defeq& 403 \be_{k}(\bbx_k \boxplus \bTDeltax_k) \\ 404 &=& \be_{k}(\bbx \boxplus \bTDeltax) \label{eq:manifoldTaylor0} 405 \simeq \breve \be_{k} + \tilde \bJ_{k} \bTDeltax, 406\label{eq:manifoldTaylor} 407\end{eqnarray} 408where $\bbx$ spans over the original over-parameterized space, for 409instance quaternions. The term $\bTDeltax$ is a small increment around 410the original position $\bbx$ and is expressed in a minimal 411representation. A common choice for $SO(3)$ is to use the vector part 412of the unit quaternion. 413% 414In more detail, one can represent the increments $\bTDeltax$ as 6D vectors 415$\bTDeltax^T = ( {\bDelta\tilde\bt}^T \, {\bqTilde}^T)$, 416where $\bDelta \tilde \bt$ denotes the translation and $\bqTilde^T 417= ( \Delta q_x \, \Delta q_y \, \Delta q_z)^T$ is the 418vector part of the unit quaternion representing the 3D rotation. Conversely, $\bbx^T=( 419\breve \bt^T \, \breve \bq^T)$ uses a quaternion $\breve \bq$ to 420encode the rotational part. Thus, the operator $\boxplus$ can be 421expressed by first converting $\bDelta \tilde \bq$ to a full quaternion $\bDelta 422\bq$ and then applying the transformation $\bDelta \bx ^T = ( \bDelta 423\bt^T \, \bDelta \bq^T)$ to $\bbx$. In the equations 424describing the error minimization, these operations can nicely be 425encapsulated by the $\boxplus$ operator. The Jacobian $\tilde 426\bJ_{k}$ can be expressed by 427\begin{eqnarray} 428\tilde \bJ_{k} &=& \left. \frac{\partial \be_{k}(\breve{\bx} \boxplus \bTDeltax)} {\partial \bTDeltax} \right|_{\bTDeltax=\bZero}. 429\label{eq:manifoldJacobian} 430\end{eqnarray} 431Since in the previous equation $\breve{\be}$ depends only on $\bTDeltax_{k_i} \in \bTDeltax_{k}$ we can further expand it as follows: 432\begin{eqnarray} 433\tilde \bJ_{k} &=& 434\left. 435\frac{\partial \be_{k}(\breve{\bx} \boxplus \bTDeltax)} {\partial \bTDeltax} \right|_{\bTDeltax=\bZero}\\ 436&=& \left(\bZero \cdots \bZero \; \tilde \bJ_{k_1} \; \cdots \; \tilde \bJ_{k_i} \;\cdots \bZero\; \cdots\; \tilde \bJ_{k_q} \bZero \cdots \bZero \right). 437\label{eq:manifoldJacobianSparse} 438\end{eqnarray} 439With a straightforward extension of notation, we set 440\begin{equation} 441\tilde \bJ_{k_i} = \left. \frac{ \partial \be_{k}(\breve{\bx} \boxplus \bTDeltax)}{\partial \bTDeltax_{k_i}} \right|_{\bTDeltax=\bZero} 442\end{equation} 443 444%% Using the rule on the partial derivatives and exploiting the fact 445%% that the Jacobian is evaluated in $\bTDeltax=\bZero$, the non-zero 446%% blocks become: 447%% \begin{eqnarray} 448%% \frac{\partial \be_{k}(\breve{\bx_k} \boxplus \bTDeltax_k)} {\partial \bTDeltax_i} &=& 449%% \underbrace{\frac{\partial \be_{k}(\breve{\bx})} {\partial \breve{\bx}_i}}_{\bJ_{k_i}} 450%% \cdot 451%% \left. 452%% \underbrace{ 453%% \frac{\breve{\bx}_{k_i} \boxplus \bTDeltax_{k_i}} {\partial \bTDeltax_{k_i}} 454%% }_{\bM_{k_i}} 455%% \right|_{\bTDeltax=\bZero} 456%% \label{eq:manifoldJacobianBlocks} 457%% \end{eqnarray} 458%% Accordingly, one can easily derive from the a Jacobian \emph{not} defined on a manifold 459%% of Eq.~\ref{eq:jacExpanded} a Jacobian on a manifold just by multiplying its non-zero blocks 460%% by the derivative of the $\boxplus$ operator computed in $\breve{\bx_i}$ and $\breve{\bx_j}$. 461%% Let the Jacobians of $\boxplus$ be denoted by $\bM_{k_i}$. By using the notation in 462%% Eq.~\ref{eq:jacExpanded} we can rewrite Eq.~\ref{eq:manifoldJacobian} as 463%% \begin{eqnarray} 464%% \tilde \bJ_{ij} &=& 465%% \left(\bZero \cdots \bZero \; \bJ_{k_1} \bM_{k_1} \; \cdots \; \bJ_{k_i} \bM_{k_i} \;\cdots \bZero\; \cdots\; \bJ_{k_q}\bM_{k_q} \bZero \cdots \bZero \right). 466%% \label{eq:jacManExpanded} 467%% \end{eqnarray} 468 469With a straightforward extension of the notation, we can insert 470\eqref{eq:manifoldTaylor} in \eqref{eq:errorQuad1} and 471\eqref{eq:optNetwork0}. This leads to the following increments: 472\begin{eqnarray} 473 \tilde \bH \, \bTDeltax^* &=& - \tilde \bb . 474\label{eq:oneLinearManifoldIteration} 475\end{eqnarray} 476Since the increments $\bTDeltax^*$ are computed in the local Euclidean 477surroundings of the initial guess $\breve{\bx}$, they need to be 478re-mapped into the original redundant space by the $\boxplus$ 479operator. Accordingly, the update rule of \eqref{eq:linearPropagation} 480becomes 481\begin{eqnarray} 482 \bx^*& =& \breve{\bx} \boxplus \bTDeltax^*. 483\label{eq:manifoldPropagation} 484\end{eqnarray} 485In summary, formalizing the minimization problem on a manifold consists 486of first computing a set of increments in a local Euclidean 487approximation around the initial guess by 488\eqref{eq:oneLinearManifoldIteration}, and second accumulating the 489increments in the global non-Euclidean space by 490\eqref{eq:manifoldPropagation}. Note that the linear system computed on 491a manifold representation has the same structure like the linear system 492computed on an Euclidean space. One can easily derive a manifold 493version of a graph minimization from a non-manifold version, only by 494defining an $\boxplus$ operator and its Jacobian $\tilde \bJ_{k_{i}}$ 495w.r.t. the corresponding parameter block. In \gopt{} we provide tools 496for numerically computing the Jacobians on the manifold space. This 497requires the user to implement the error function and the $\boxplus$ 498operator only. As a design choice, we do not address the non-manifold 499case since it is already contained in the manifold one. However, to 500achieve the maximum performances and accuracy we recommend the user to 501implement analytic Jacobians, once the system is functioning with the 502numeric ones. 503 504\section{Robust Least Squares\label{sec:robust_kernel}} 505Optionally, the least squares optimization can be robustified. 506Note, that the error terms in Eq.~\ref{eq:sumOfFactors} have the following form: 507\begin{eqnarray} 508\bF_k = \be_k^T\Omega_k \be_k = \rho_2\left(\sqrt{\be_k^T\Omega_k \be_k}\right) \quad \text{with} \quad \rho_2(x):=x^2. 509\end{eqnarray} 510Thus, the error vector $\be_k$ has quadratic influence on $\bF$, 511so that a single potential outlier would have major negative impact. 512In order be more outlier robust, the quadratic error function $\rho_2$ 513can be replaced by a more robust cost function which weighs large errors less. 514In \gopt, the Huber cost function $\rho_H$ can be used 515\begin{eqnarray} 516\rho_H(x) := \begin{cases} 517 x^2 & \text{if } |x|<b\\ 518 2b |x| - b^2 & \text{else}, 519 \end{cases} 520\end{eqnarray} 521which is quadratic for small $|x|$ but linear for large $|x|$. Compared to other, 522even more robust cost functions, the Huber kernel has to advantage that it is 523still convex and thus does not introduce new local minima in $\bF$~\cite[pp.616]{Hartley:Zisserman:Book2004}. 524In practice, we do not need to modify Eq.~$\ref{eq:sumOfFactors}$. Instead, the following scheme 525is applied. First the error $\be_k$ is computed as usual. Then, $\be_k$ is replaced by 526a weighted version $w_k\be_k$ such that 527\begin{eqnarray} 528(w_k\be_k)^T\Omega_k (w_k\be_k) = \rho_H\left(\sqrt{\be_k^T\Omega_k \be_k}\right). 529\end{eqnarray} 530Here, the weights $w_k$ are calculated as follows 531\begin{eqnarray} 532w_k = \frac{\sqrt{\rho_H\left(||\be_k||_\Omega \right)}}{||\be_k||_\Omega } \quad \text{with} \quad ||\be_k||_\Omega := \sqrt{\be_k^T\Omega_k \be_k}. 533\end{eqnarray} 534In \gopt, the user has fine-grained control and can enable/disable the robust cost function for each edge individually (see Section~\ref{sec:error}). 535 536 537 538 539\section{Library Overview} 540From the above sections it should be clear that a graph-optimization problem is entirely defined by: 541\begin{itemize} 542\item The types of the vertices in the graph (that are the parameters blocks $\{\bx_i\}$. 543 For each of those one has to specify: 544 \begin{itemize} 545 \item the domain $\Dom(\bx_i)$ of the internal parameterization, 546 \item the domain $\Dom(\bDeltax_i)$ of the increments $\bDeltax_i$, 547 \item $\boxplus: \Dom(\bx_i) \times \Dom(\bDeltax_i) \rightarrow \Dom(\bx_i)$ that 548 applies the increment $\bDeltax_i$ to the previous solution $\bx_i$. 549 \end{itemize} 550\item the error function for every type of hyper-edge 551 $\be_{k}:\Dom(\bDeltax_{k_1}) \times \Dom(\bDeltax_{k_2}) \times \dots \times 552 \Dom(\bDeltax_{k_q}) \rightarrow \Dom(\bz_{k})$ that should be zero when 553 the perturbated estimate $\bx_{k} \boxplus \bDeltax_{k}$ perfectly satisfies the constraint $\bz_{k}$. 554\end{itemize} 555By default the Jacobians are computed numerically by our 556framework. However to achieve the maximum performances in a specific 557implementation one can specify the Jacobian of the error functions and 558of the manifold operators. 559 560In the reminder we will shortly discuss some basic concepts to use and 561extend \gopt. This documentation is by no means complete, but it is 562intended to help you browsing the automatically generated 563documentation. To better visualize the interplay of the components of 564\gopt{} we refer to the class diagram of Figure~\ref{fig:classes}. 565\begin{figure} 566\centering 567\includegraphics[width=0.7\columnwidth]{pics/classes.eps} 568\caption{Class diagram of \gopt{}.} 569%% TODO update by adding OptimizationAlgorithm 570\label{fig:classes} 571\end{figure} 572 573\subsection {Representation of an Optimization Problem} 574\label{sec:representation} 575All in all our system utilizes a generic hyper-graph structure to 576represent a problem instance (defined in \verb+hyper_graph.h+). This 577generic hyper graph is specialized to represent an optimization 578problem by the class \verb+OptimizableGraph+, defined in 579\verb+optimizable_graph.h+. Within the \verb+OptimizableGraph+ the 580inner classes \verb+OptimizableGraph::Vertex+ and 581\verb+OptimizableGraph::Edge+ are used to represent generic hyper 582edges and hyper vertices. Whereas the specific implementation might 583be done by directly extending these classes, we provided a template 584specialization that implements automatically most of the methods that 585are mandatory for the system to work. 586 587These classes are \verb+BaseVertex+ and \verb+BaseUnaryEdge+, 588\verb+BaseBinaryEdge+ and \verb+BaseMultiEdge+. 589\begin{description} 590\item \verb+BaseVertex+ templatizes the dimension of a parameter block 591 $\bx_i$ and of the corresponding manifold $\bDeltax_i$, thus it can 592 use blocks of memory whose layout is known at compile-time (means 593 efficiency). Furthermore, it implements some mapping operators to 594 store the Hessian and the parameter blocks of the linearized 595 problem, and a stack of previous values that can be used to 596 save/restore parts of the graph. The method \verb+oplusImpl(double* v)+ 597 that applies the perturbation $\bDeltax_i$ represented by \verb+v+, 598 to the member variable \verb+_estimate+ should be implemented. This 599 is the $\boxplus$ operator. Additionally, 600 \verb+setToOriginImpl()+ that should set the internal state of the vertex 601 to $\bZero$ has to specified. 602 603\item \verb+BaseUnaryEdge+ is a template class to model a unary 604 hyper-edge, which can be used to represent a prior. It offers for 605 free the calculation of the Jacobians, via an implementation of the 606 \verb+linearizeOplus+ method. It requires to specify the types of 607 the (single) vertex $\bx_i$, and type and dimension of the error 608 $\be(\bx_k)$ as template parameters. The function 609 \verb+computeError+ that stores the result of the error $\be(\bx_k)$ in the 610 member \verb+Eigen::Matrix _error+ should be implemented. 611 612\item \verb+BaseBinaryEdge+ is a template class that models a binary 613 constraint, namely an error function in the form $\be_k(\bx_{k_1}, 614 \bx_{k_2})$. It offers the same facilities of \verb+BaseUnaryEdge+, and it 615 requires to specify the following template parameters: the type of the nodes 616 $\bx_{k_1}$ and $\bx_{k_2}$ and the type and the dimension of the measurement. 617 Again, it implements the numeric Jacobians via 618 a default implementation of the \verb+linearizeOplus+ method. 619 Again, the \verb+computeError+ should be implemented in a derived class. 620 621\item \verb+BaseMultiEdge+ is a template class that models a 622 multi-vertex constraint in the form of $\be_k(\bx_{k_1}, \bx_{k_2}, 623 \ldots, \bx_{k_q})$. It offers the same facilities of the types 624 above, and it requires to specify only the type and dimension of the 625 measurement as template parameters. The specialized class should 626 take care of resizing the connected vertices to the correct size 627 $q$. This class relies on a dynamic memory, since too many 628 parameters are unknown, and if you need of an efficient 629 implementation for a specific problem you can program it yourself. 630 Numeric Jacobian comes for free, but you should implement the 631 \verb+computeError+ in a derived class, as usual 632\end{description} 633 634In short, all you need to do to define a new problem instance is to 635derive a set of classes from those above listed, one for each type of 636parameter block and one for each type of (hyper)edge. Always try to 637derive from the class which does the most work for you. If you want to 638have a look at a simple example look at \verb+vertex_se2+ and 639\verb+edge_se2+. Those two types define a simple 2D graph SLAM 640problem, like the one described in many SLAM papers. 641 642\begin{lstlisting}[float,label=lst:inittypes,caption=Registering types 643 by a constructor from a library] 644#include "g2o/core/factory.h" 645 646namespace g2o { 647 G2O_REGISTER_TYPE_GROUP(slam2d); 648 649 G2O_REGISTER_TYPE(VERTEX_SE2, VertexSE2); 650 G2O_REGISTER_TYPE(VERTEX_XY, VertexPointXY); 651 652 // ... 653} 654\end{lstlisting} 655 656Of course, for every type you construct you should define also the 657\verb+read+ and \verb+write+ functions to read and write your data to a 658stream. Finally, once you define a new type, to enable the loading and 659the saving of the new type you should ``register'' it to a factory. 660This is easily done by assigning a string tag to a new type, via the 661\verb+registerType+ function. This should be called once before 662all files are loaded. 663 664To this end, \gopt{} provides an easy macro to carry out the registration of the 665class to the factory. See Listing~\ref{lst:inittypes} for an example, 666the full example can be found in \verb+types_slam2d.cpp+. The first 667parameter given to the macro \verb+G2O_REGISTER_TYPE+ specifies the tag 668under which a certain vertex / edge is known. \gopt\ will use this 669information while loading files and for saving the current graph into a 670file. In the example given in Listing~\ref{lst:inittypes} we register 671the tags \verb+VERTEX_SE2+ and \verb+EDGE_SE2+ with the classes 672\verb+VertexSE2+ and \verb+EdgeSE2+, respectively. 673 674Furthermore, the macro \verb+G2O_REGISTER_TYPE_GROUP+ allows to declare 675a type group. This is necessary if we use the factory to construct the 676types and we have to enforce that our code is linked to a specific type 677group. Otherwise the linker may drop our library, since we do not 678explicitly use any symbol provided by the library containing our type. 679Declaring the usage of a specific type library and hence enforcing the 680linking is done by the macro called \verb+G2O_USE_TYPE_GROUP+. 681 682\subsection{Construction and Representation of the Linearized Problem} 683The construction and the solution can be separated into individual 684steps which are iterated. 685\begin{itemize} 686 \item Initialization of the optimization (only before the first 687 iteration). 688 \item Computing the error vector for each constraint. 689 \item Linearize each constraint. 690 \item Build the linear system. 691 \item Updating the Levenberg-Marquardt damping factor. 692\end{itemize} 693 694Within the following sections we will describe the steps. 695 696\subsubsection{Initialization} 697The class \verb+SparseOptimizer+ offers several methods to initialize 698the underlying data structure. The methods 699\verb+initializeOptimization()+ either takes a subset of vertices or a 700subset of edges which will be considered for the next optimization runs. 701Additionally, all vertices and edges can be considered for optimization. 702We refer to the vertices and edges currently considered as \emph{active} 703vertices and edges, respectively. 704 705Within the initialization procedure, the optimizer assigns a temporary 706index to each active vertex. This temporary index corresponds to the 707block column / row of the vertex in the Hessian. Some of the vertices 708might need to be kept fixed during the optimization, to resolve 709arbitrary degrees of freedom (gauge freedom). This can be done by 710setting the \verb+_fixed+ attribute of a vertex. 711 712\subsubsection{Compute error\label{sec:error}} 713The \verb+computeActiveErrors()+ function takes the current estimate of 714the active vertices and for each active edge calls 715\verb+computeError()+ for computing the current error vector. Using 716the base edge classes described in Section~\ref{sec:representation} 717the error should be cached in the member variable \verb+_error+. 718 719 If \verb+robustKernel()+ is set to true for a particular active edge, 720\verb+robustifyError()+ is called and \verb+_error+ is robustified 721as described in Section~\ref{sec:robust_kernel}. 722 723\subsubsection{Linearizing the system} 724Each active edge is linearized by calling its 725\verb+linearizeOplus()+ function. Again the Jacobians can be cached by 726member variables provided by the templatized base classes described in 727Section~\ref{sec:representation}. If the \verb+linearizeOplus()+ 728function is not re-implemented the Jacobian will be computed 729numerically as follows: 730\begin{eqnarray} 731 \tilde \bJ_k^{\bullet l} = \frac{1}{2\delta} \left( 732 \be_k (\bx_k \boxplus \delta\mathbf{1}_l) 733 - 734 \be_k (\bx_k \boxplus -\delta\mathbf{1}_l) 735 \right), 736 \label{eqn:jacobiannumeric} 737\end{eqnarray} 738where $\delta > 0$ is a small constant ($10^{-9}$ in our 739implementation) and $\mathbf{1}_l$ is the unit vector along dimension 740$l$. Note that we only store and calculate the non-zero entries of 741$\tilde \bJ_k$ that have not been fixed during the initialization. 742 743\subsubsection{Building the system} 744For each active edge the addend term for Eq.~\ref{eq:addendTerm} is 745computed by multiplying the corresponding blocks of the Jacobians and 746the information matrix of the edge. The addend term is calculated in 747each edge by calling \verb+constructQuadraticForm()+. 748 749\subsubsection{Updating Levenberg-Marquardt} 750As illustrated in \eqref{eq:oneLinearIterationLevenberg} the 751Levenberg-Marquardt algorithm requires updates to the linear system. 752However, only the elements along the main diagonal need to be modified. 753To this end, the methods \verb+updateLevenbergSystem(double lambda)+ and 754\verb+recoverSystem(double lambda)+ of the \verb+Solver+ class apply the 755modifications by respectively adding or subtracting $\lambda$ along the 756main diagonal of $\bH$. 757 758\subsection{Solvers} 759A central component of these least-squares approaches is the solution 760of the linear system $\tilde \bH \, \bTDeltax^* = - \tilde \bb$. To 761this end there are several approaches available, some of them exploit 762the known structure of certain problems and perform intermediate 763reductions of the system, like by applying the Schur complement to a 764subset of variables. In \gopt{} we do not select any particular solver, 765but we rely on external libraries. To this end, we decouple these 766\emph{structural} operations (like the Schur complement) 767from the solution of the linear system. 768 769The construction of the linear problem from the Jacobian matrices and the error vectors 770in the hyper-graph elements are controlled by a so-called \verb+Solver+ class. 771To use a specific factorization of the system, the user has to extend the 772\verb+Solver+ class, and to implement the virtual functions. 773Namely a solver should be able to extract from an 774hyper-graph the linear system, and to return a solution. This is 775done in several steps: at the beginning of the optimization the 776function \verb+initializeStructure+ is called, to allocate the 777necessary memory that will be overwritten in the subsequent 778operations. This is possible since the structure of the system does 779not change between iterations. Then the user should provide means to 780access to the increment vector $\bTDeltax$ and $\tilde \bb$, via the 781functions \verb+b()+ and \verb+x()+. To support Levenberg-Marquardt 782one should also implement a function to perturb the Hessian with the 783$\lambda \bI$ term. This function is called 784\verb+setLambda(double lambda)+ and needs to be implemented by the 785specific solver. 786 787We provide a templatized implementation of the solver class, the 788\verb+BlockSolver<>+ that stores the linear system in a 789\verb+SparseBlockMatrix<>+ class. The \verb+BlockSolver<>+ implements 790also the Schur complement, and relies on another abstract class, the 791\verb+LinearSolver+ to solve the system. 792An implementation of the linear solver does the actual work 793of solving the (reduced) linear system, and has to implement a few methods. 794In this release of \gopt{} we provide linear solvers that use respectively 795preconditioned gradient descent, CSparse, and CHOLMOD. 796 797 798\subsection{Actions} 799To the extent of \gopt{}, the entities stored in a hyper-graph have a 800pure mathematical meaning. They either represent variables to be 801optimized (vertices), or they encode optimization constraints. 802However, in general these variables are usually related to more 803``concrete'' objects, like laser scans, robot poses, camera parameters 804and so on. Some variable type may support only a subset of feasible 805operations. For instance it is possible to ``draw'' a robot pose, but 806it is not possible to ``draw'' the calibration parameters. More in 807general we cannot know a priori the kind of operations that will be 808supported by the user types of \gopt. However, we want to design a set 809of tools and of functions that rely on certain operations. These include, 810for instance viewers, or functions to save/load the graph in a specific format. 811 812A possibility to do this would be to ``overload'' the base classes of 813the hyper-graph elements (vertices and edges) with many virtual 814functions, one for each of the functionality we want to support. This 815is of course not elegant, because we would need to patch the base 816classes with the new function every time something new is added. 817Another possibility would be to make use of the multiple inheritance 818of C++, and to define an abstract ``drawable'' object, on which the 819viewer operates. This solution is a bit better, however we cannot 820have more than one ``drawing'' function for each object. 821 822The solution used in \gopt{} consists in creating a library of 823function objects that operate on the elements (vertices or edges) of 824the graph. One of these function objects is identified by a function 825name and by a type on which it operates. These function objects can 826be registered into an action library. Once these objects are loaded 827in the action library it is possible to call them on a graph. These 828functionalities are defined in \verb+hyper_graph_action.h+. It is 829common to register and create the actions when defining the types for 830the edges and the vertices. You can see many examples in 831\verb+types_*/*.h+. 832 833\section{\gopt\ Tools} 834\gopt\ comes with two tools which allow to process data stored in 835files. The data can be loaded from a file and stored again after 836processing. In the following we will give a brief introduction to these 837tools, namely a command line interface and a graphical user interface. 838 839\subsection{\gopt\ Command Line Interface} 840 841\verb+g2o+ is the command line interface included in \gopt. It 842allows to optimize graphs stored in files and save the result back to a 843file. This allows a fast prototyping of optimization problems, as it is 844only required to implement the new types or solvers. The \gopt\ 845distribution includes a data folder which comprises some data files on 846which \verb+g2o+ can be applied. 847 848\subsection{\gopt\ Viewer} 849 850\begin{figure} 851 \centering 852 \includegraphics[width=0.7\columnwidth]{pics/viewer} 853 \caption{Graphical interface to \gopt. The GUI allows to select 854 different suitable optimizers and perform the optimization.} 855 \label{fig:viewer} 856\end{figure} 857 858The Graphical User Interface depicted in Figure~\ref{fig:viewer} allows 859to visualize the optimization problem. Additionally, the various 860parameters of the algorithms can be controlled. 861 862\subsection{\gopt{} incremental} 863 864\gopt{} includes an experimental binary for performing optimization in 865an incremental fashion, i.e., optimizing after inserting one or several 866nodes along with their measurements. In this case, \gopt{} performs 867rank updates on the Hessian matrix to update the linear system. Please 868see the \verb+README+ in the \verb+g2o_incremental+ sub-folder for 869additional information. 870 871Example for the Manhattan3500 dataset: 872\begin{verbatim} 873g2o_incremental -i manhattanOlson3500.g2o 874\end{verbatim} 875 876\subsection{Plug-in Architecture} 877 878\begin{lstlisting}[float,label=lst:initsolvers,caption=Registering 879 solvers by a constructor from a library] 880class PCGSolverCreator : public AbstractOptimizationAlgorithmCreator 881{ 882 public: 883 PCGSolverCreator(const OptimizationAlgorithmProperty& p) : AbstractOptimizationAlgorithmCreator(p) {} 884 virtual OptimizationAlgorithm* construct() 885 { 886 // create the optimization algorithm 887 // see g2o/solver_pcg/solver_pcg.cpp for the details 888 } 889}; 890 891G2O_REGISTER_OPTIMIZATION_LIBRARY(pcg); 892 893G2O_REGISTER_OPTIMIZATION_ALGORITHM(gn_pcg, new PCGSolverCreator(OptimizationAlgorithmProperty("gn_pcg", "Gauss-Newton: PCG solver using block-Jacobi pre-conditioner (variable blocksize)", "PCG", false, Eigen::Dynamic, Eigen::Dynamic))); 894 895G2O_REGISTER_OPTIMIZATION_ALGORITHM(gn_pcg3_2, new PCGSolverCreator(OptimizationAlgorithmProperty("gn_pcg3_2", "Gauss-Newton: PCG solver using block-Jacobi pre-conditioner (fixed blocksize)", "PCG", true, 3, 2))); 896 897//... 898\end{lstlisting} 899 900Both tools support the loading of types and optimization algorithms at 901run-time from dynamic libraries. This is realized as follows. The tools 902load from the libs folder all libraries matching ``*\_types\_*'' and 903``*\_solver\_*'' to register types and optimization algorithms, 904respectively. We assume that by loading the libraries the types and the 905algorithms register via their respective constructors to the system. 906Listing~\ref{lst:inittypes} shows how to register types to the system 907and Listing~\ref{lst:initsolvers} is an example, which shows how to 908register an optimization algorithm via the plug-in architecture. 909 910For loading dynamic library containing types or optimization algorithms, we support two 911different methods: 912\begin{itemize} 913 \item The tools recognize the command line switch \verb+-typeslib+ 914 and \verb+-solverlib+ to load a specific library. 915 \item You may specify the environment variables \verb+G2O_TYPES_DIR+ 916 and \verb+G2O_SOLVER_DIR+ which are scanned at start and libraries 917 matching ``*\_types\_*'' and ``*\_solver\_*'' are automatically 918 loaded. 919\end{itemize} 920 921\section{2D SLAM: An Example} 922SLAM is a well known problem in robotics and this acronym stands for 923``Simultaneous Localization And Mapping''. The problem can be stated 924as follows: given a moving robot equipped with some sensors, we want 925to estimate both the map and the pose of the robot in the environment 926from the sensor measurements. Usually, the sensors can be classified 927in exteroceptive or proprioceptive. An exteroceptive sensor is a 928device that measures quantities relative to the environment where the 929robot moves. Examples of these sensors can be cameras that acquire an 930image of the world at a particular location, laser scanners that 931measure a set of distances around the robot or accelerometers in 932presence of gravity that measure the gravity vector or GPS that derive 933a pose estimate by observing the constellation of known satellites. 934In contrast, proprioceptive sensors measure the change of the robot's 935state (the position), relative to the previous robot position. Example 936include odometers, that measure the relative movement of the robot 937between two time steps or gyroscopes. In traditional approaches to 938SLAM, like EKF these two sensors play a substantially different role 939in the system. The proprioceptive measurements are used to evolve a 940set of state variables, while the exteroceptive measurements are used 941to correct these estimates, by feeding back the measurement errors. 942This is not the case of smoothing methods (like the ones that can be 943implemented with \gopt{}), where all measurements are treated in a 944substantially similar manner. 945 946A complete solution to SLAM is typically rather complex and involves 947processing raw sensor data and determining correspondences between 948previously seen parts of the environment and actual measurements (data 949association). Describing a complete solution to the problem is out of 950the scope of this document. However, in the reminder we will present a 951simplified but meaningful version of the problem that contains all the relevant elements 952and that is well suited to be implemented with \gopt. 953 954The scenario is a robot moving on a plane. The robot is equipped 955with an odometry sensor that is able to measure the relative movement 956of the robot between two time frames and of a ``landmark'' sensor 957that is able to measure the position of some environment landmarks 958nearby the robot \emph{in the robot reference frame}. One could 959implement this landmark detector, for instance, by extracting corners 960from a laser scan or by detecting the position of relevant features 961from a stereo image pair. A simplification that we make in this 962section is that the landmarks are uniquely identifiable. In other 963words whenever the robot sees a landmark, it can tell if it is a new 964one or if it has already seen it and when. 965 966Clearly both odometers and landmark sensors are affected by noise. In 967principle, if the odometry would not be affected by noise one could 968reconstruct the trajectory of the robot simply by chaining the 969odometry measurements. However, this is not the case and integrating 970the odometry leads to an increasing positioning error that becomes 971evident when the robot reenters a known region. In a similar way, 972if the robot would have unlimited perception range, it could acquire 973all the map in one shot and the position could be retrieved by simple 974geometric constructions. Again this is not the case and the robot 975perceives the position of the landmarks that are located within a 976maximum range. These measurements are affected by a noise, that 977usually increases with the distance of a landmark from the robot. 978 979In the remainder of this section we will walk through all essential 980steps that are required to characterize a problem within \gopt{}. 981These are: 982\begin{itemize} 983\item identification of the state variables $\bx_i$ and of their domain, 984\item characterization of the constraints and identification of the graph structure, 985\item choice of the parameterization for the increments $\bTDeltax_i$, and definition of the 986$\boxplus$ operator. 987\item construction of the error functions $e_k(\bx_k)$. 988\end{itemize} 989 990\subsection{Identification of the State Variables} 991Figure~\ref{fig:slam} illustrates a fragment of a SLAM graph. The 992robot positions are denoted by the nodes $\bx^\mathrm{s}_t$, while the 993landmarks are denoted by the nodes $\bx^\mathrm{l}_i$. We assume that 994our landmark sensor is able to detect only the 2D pose of a landmark, 995but not its orientation. In other words the landmarks ``live'' in 996$\Re^2$. Conversely, the robot poses are parameterized by the robot 997location $(x,y)$ on the plane and its orientation $\theta$, thus they 998belong to the group of 2D transformations $SE(2)$. More 999formally, the nodes of a 2D SLAM graph are of two types 1000\begin{itemize} 1001\item Robot positions $\bx^\mathrm{s}_t = ( x^\mathrm{s}_t \; y^\mathrm{s}_t \; \theta^\mathrm{s}_t)^T \in SE(2)$ 1002\item Landmark positions $\bx^\mathrm{l}_i = ( x^\mathrm{l}_i \; y^\mathrm{l}_i )^T \in \Re^2$ 1003\end{itemize} 1004 1005\begin{figure} 1006\psfrag{p1}{$\bx^\mathrm{s}_1$} 1007\psfrag{p2}{$\bx^\mathrm{s}_2$} 1008\psfrag{pt1}{$\bx^\mathrm{s}_{t-1}$} 1009\psfrag{pt}{$\bx^\mathrm{s}_{t}$} 1010\psfrag{L1}{$\bx^\mathrm{l}_1$} 1011\psfrag{L2}{$\bx^\mathrm{l}_2$} 1012\psfrag{Li1}{$\bx^\mathrm{l}_{i-1}$} 1013\psfrag{Li}{$\bx^\mathrm{l}_{i}$} 1014\psfrag{u12}{$\bz^\mathrm{s}_{1,2}$} 1015\psfrag{utt1}{$\bz^\mathrm{s}_{t-1,t}$} 1016\psfrag{l11}{$\bz^\mathrm{l}_{1,1}$} 1017\psfrag{l12}{$\bz^\mathrm{l}_{1,2}$} 1018\psfrag{lit}{$\bz^\mathrm{l}_{t,i}$} 1019\psfrag{l1i}{$\bz^\mathrm{l}_{1,i}$} 1020\psfrag{lti1}{$\bz^\mathrm{l}_{t,i-1}$} 1021\psfrag{lt1i}{$\bz^\mathrm{l}_{t-1,i-1}$} 1022\centering 1023\includegraphics[width=0.8\columnwidth]{pics/slam.eps} 1024\caption{Graphical representation of a SLAM process. The vertices of 1025 the graph, depicted with circular nodes, denote either robot poses 1026 $\bx^\mathrm{s}_*$ or landmarks $\bx^\mathrm{l}_*$. The measurement 1027 of a landmark from a robot pose is captured by a constraints 1028 $\bz^{\mathrm{l}}_*$ and odometry measurements connecting subsequent 1029robot poses are modeled by the constraints $\bz^\mathrm{s}_*$.} 1030\label{fig:slam} 1031\end{figure} 1032 1033\subsection{Modeling of the Constraints} 1034Two subsequent robot positions $\bx^\mathrm{s}_t$ and 1035$\bx^\mathrm{s}_{t+1}$ are related by an odometry measurement, that 1036represent the relative motion that brings the robot from 1037$\bx^\mathrm{s}_t$ to $\bx^\mathrm{s}_{t+1}$ \emph{measured} by the 1038odometry. This measurement will be typically slightly different from 1039the \emph{real} transformation between the two pose because of the 1040noise affecting the sensors. Being an odometry measurement, an 1041Euclidean transformation, it is also a member of $SE(2)$ group. 1042Assuming the noise affecting the measurement being white and Gaussian, 1043it can be modeled by an $3 \times 3$ symmetric positive definite 1044information matrix. In real applications the entries of this matrix 1045depend on the motion of the robot. i.e., the bigger the movement is 1046the larger the uncertainty will be. 1047Thus an odometry edge between the nodes $\bx^\mathrm{s}_t$ and 1048$\bx^\mathrm{s}_{t+1}$ consists of these two entities: 1049\begin{itemize} 1050\item $\bz^\mathrm{s}_{t,t+1} \in SE(2)$ that represents the motion 1051 between the nodes and 1052\item $\bOmega^\mathrm{s}_{t,t+1} \in \Re^{3 \times 3}$ that represents the 1053 inverse covariance of the measurement, and thus is symmetric and 1054 positive definite. 1055\end{itemize} 1056 1057If the robot senses a landmark $\bx^\mathrm{l}_i$ from the location 1058$\bx^\mathrm{s}_t$, the corresponding measurement will be modeled by 1059an edge going from the robot pose to the landmark. A measurement about 1060the landmark consists in a point in the $x$-$y$ plane, perceived in the 1061robot frame. Thus a landmark measurement lives in $\Re^2$ as the 1062landmarks do. Again, under white Gaussian noise assumption, the 1063noise can be modeled by its inverse covariance. Accordingly, an edge between 1064a robot pose and a landmark is parametrized in this way: 1065\begin{itemize} 1066\item $\bz^\mathrm{l}_{t,i} \in \Re^2$ that represents position of the landmark 1067 in the frame expressed by $\bx^\mathrm{s}_t$ and 1068\item $\bOmega^\mathrm{l}_{t,i} \in \Re^{2 \times 2}$ that represents the inverse 1069 covariance of the measurement and is SPD. 1070\end{itemize} 1071 1072\subsection{Choice of the Parameterization for the Increments} 1073So far, we defined most of the elements necessary to implement a 2D 1074SLAM algorithm with \gopt{}. Namely we characterized the domains of 1075the variables and the domains of the measurements. What remains to do 1076is to define the error functions for the two kinds of edges in our 1077system and to determine a (possibly smart) parameterization for the 1078increments. 1079 1080 1081The landmark positions are parameterized in $\Re^2$, which is already 1082an Euclidean space. Thus the increments $\bTDeltax^\mathrm{l}_i$ can live in the same space and the 1083 $\boxplus$ operator can be safely chosen as the vector sum: 1084\begin{eqnarray} 1085 \bx^\mathrm{l}_i \boxplus \bTDeltax^\mathrm{l}_i & \doteq & \bx^\mathrm{l}_i + \bTDeltax^\mathrm{l}_i 1086\end{eqnarray} 1087 1088The poses, conversely, live in the non-Euclidean space $SE(2)$. 1089This space admits many parameterizations. Examples include: 1090rotation matrix $\bR(\theta)$ and translation vector $(x \; y)^T$ or 1091angle $\theta$ and translation vector $(x \; y)^T$. 1092 1093As a parameterization for the increments, we choose a minimal one, 1094that is translation vector and angle. Having chosen this 1095parameterization, we need to define the $\boxplus$ operator between a 1096pose and a pose increment. One possible choice would be to treat the 1097three scalar parameters $x$, $y$ and $\theta$ of a pose as if they 1098were a vector, and define the $\boxplus$ as the vector sum. There are 1099many reasons why this is a poor choice. One of them is that the angles 1100are not Euclidean, and one would need to re-normalize them after every 1101addition. 1102 1103A better choice is to define the $\boxplus$ between a pose and a pose 1104increment as the motion composition operator. Namely, given a robot 1105pose $\bx^{s}_t = (x\; y\; \theta)^T$ and an increment $\bTDeltax^{s}_t 1106= (\Delta x\; \Delta y\; \Delta \theta)^T$, where $\Delta x$ is the 1107longitudinal displacement (i.e. in direction of the heading of the robot), 1108$\Delta y$ the lateral displacement and $\Delta \theta$ the rotational 1109change, the operator can be defined as follows: 1110\begin{eqnarray} 1111 \bx^\mathrm{s}_t \boxplus \bTDeltax^\mathrm{s}_t & \doteq & 1112 \left( 1113 \begin{array}{c} 1114 x + \Delta x \cos \theta - \Delta y \sin \theta \\ 1115 y + \Delta x \sin \theta + \Delta y \cos \theta \\ 1116 \mathrm{normAngle}(\theta + \Delta \theta) 1117 \end{array} 1118 \right)\\ 1119 = \bx^\mathrm{s}_t \oplus \bTDeltax^\mathrm{s}_t. 1120\end{eqnarray} 1121 1122In the previous equation we introduced the motion composition operator $\oplus$ 1123Similarly to $\oplus$ there is the $\ominus$ operator that performs the opposite operation 1124and is defined as follows: 1125\begin{eqnarray} 1126 \bx^\mathrm{s}_a \ominus \bx^\mathrm{s}_b & \doteq & 1127 \left( 1128 \begin{array}{c} 1129 (x_a - x_b) \cos \theta_b + (y_a - y_b) \sin \theta_b \\ 1130 - (x_a - x_b) \sin \theta_b + (y_a - y_b) \cos \theta_b \\ 1131 \mathrm{normAngle}(\theta_b - \theta_a) 1132 \end{array} 1133 \right) 1134\end{eqnarray} 1135 1136\subsection{Design of the Error Functions} 1137The last step in formalizing the problem is to design error functions 1138$\be(\bx_k)$ that are ``reasonable''. A common way to do this to define a 1139so-called \emph{measurement} function $\bh_k(\bx_k)$ that ``predicts'' 1140a measurement $\hat \bz_k$, given the knowledge of the vertices in the 1141set $\bx_k$. Defining this function is usually rather easy, and can be 1142done by directly implementing the error model. Subsequently, the error 1143vector can be computed as the vector difference between the prediction 1144$\hat \bz_k$ and the real measurement. This is a general approach to 1145construct error functions and it works when the space of the errors is 1146locally Euclidean around the origin of the measurement. If this is not 1147the case one might want to replace the vector difference with some 1148other operator which is more ``regular''. 1149 1150We will now construct the error functions for the edges connecting a 1151robot pose $\bx^\mathrm{s}_t$ and a landmark $\bx^\mathrm{l}_i$. The 1152first step is to construct a measurement prediction 1153$\bh^\mathrm{l}_{t,i}(\bx^\mathrm{s}_t, \bx^\mathrm{l}_i)$ that computes 1154a ``virtual measurement''. This virtual measurement is the position of 1155the landmark $\bx^\mathrm{l}_i$, seen from the robot position 1156$\bx^\mathrm{s}_t$. The equation for $\bh^\mathrm{l}_{t,i}(\cdot)$ is the 1157following: 1158\begin{eqnarray} 1159 \bh^\mathrm{l}_{t,i}(\bx^\mathrm{s}_t, \bx^\mathrm{l}_i) & \doteq & 1160 \left( 1161 \begin{array}{c} 1162 (x^\mathrm{s}_t - x_i) \cos \theta^\mathrm{s}_t + (y^\mathrm{s}_t - y_i) \sin \theta^\mathrm{s}_t \\ 1163 - (x^\mathrm{s}_t - x_i) \sin \theta^\mathrm{s}_t + (y^\mathrm{s}_t - y_i) \cos \theta^\mathrm{s}_t 1164 \end{array} 1165 \right) 1166\end{eqnarray} 1167which converts the position of the landmark into the coordinate system of 1168the robot. 1169 1170Since the landmarks live in an Euclidean space, it is reasonable to 1171compute the error function as the normal vector difference. This 1172leads to the following definition for the error functions of the 1173landmarks. 1174\begin{eqnarray} 1175 \be^\mathrm{l}_{t,i}(\bx^\mathrm{s}_t, \bx^\mathrm{l}_i) & \doteq & \bz_{t,i} - \bh^\mathrm{l}_{t,i}(\bx^\mathrm{s}_t, \bx^\mathrm{l}_i). 1176 \label{eq:landmarkError} 1177\end{eqnarray} 1178 1179In a similar way, we can define the error functions of an odometry 1180edge connecting two robot poses $\bx^\mathrm{s}_t$ and 1181$\bx^\mathrm{s}_{t+1}$. As stated before, an odometry measurement lives in $SE(2)$. 1182By using the $\oplus$ operator we can write a synthetic measurement function: 1183\begin{eqnarray} 1184 \bh^\mathrm{s}_{t,t+1}(\bx^\mathrm{s}_t, \bx^\mathrm{s}_{t+1}) & \doteq & \bx^\mathrm{s}_{t+1} \ominus \bx^\mathrm{s}_{t}. 1185\end{eqnarray} 1186In short this function returns the motion that brings the robot from 1187$\bx^\mathrm{s}_t$ to $\bx^\mathrm{s}_{t+1}$, that is the ``ideal'' 1188odometry. Once again the error can be obtained as a difference 1189between the measurement and the prediction. However, since our measurements 1190do not live in an Euclidean space we can use $\ominus$ instead of the vector difference. 1191 1192\begin{eqnarray} 1193 \be^\mathrm{s}_{t,t+1}(\bx^\mathrm{s}_t, \bx^\mathrm{s}_{t+1}) & \doteq & \bz_{t,t+1} \ominus \bh^\mathrm{s}_{t,t+1}(\bx^\mathrm{s}_t, \bx^\mathrm{s}_{t+1}). 1194 \label{eq:odometryError} 1195\end{eqnarray} 1196 1197\subsection{Putting things together} 1198Here we summarize the relevant parts of the previous problem definition, 1199and we get ready for the implementation.\\[.3em] 1200{\small 1201\begin{tabular}{|c|c|c|c|c|c|} 1202\hline 1203\textbf{Variable} & \textbf{Symbol} & \textbf{Domain} & \textbf{Dimension} & \textbf{Parameterization of }$\bDeltax$ & $\boxplus$ \textbf{operator} \\ 1204\hline 1205Robot pose & $\bx^\mathrm{s}_t$ & $SE(2)$ & 3 & $(\Delta x \; \Delta y \; \Delta\theta)$ & $\bx^\mathrm{s}_t \oplus \bDeltax^\mathrm{s}_t$ \\ 1206\hline 1207Landmark pose & $\bx^\mathrm{l}_i$ & $\Re^2$ & 2 & $(\Delta x \; \Delta y )$ & $\bx^\mathrm{l}_i + \bDeltax^\mathrm{l}_i$\\ 1208\hline 1209\end{tabular}\\[.3em] 1210\begin{tabular}{|c|c|c|c|c|c|} 1211\hline 1212\textbf{Measurement} & \textbf{Symbol} & \textbf{Domain} & \textbf{Dimension} & \textbf{Set $\bx_k$ of variables involved} & \textbf{error function}\\ 1213\hline 1214Odometry & = $\bz^\mathrm{s}_{t,t+1}$ & $SE(2)$ & 3 & $\left\{ \bx^\mathrm{s}_t, \bx^\mathrm{s}_{t+1} \right\}$ & Eq.~\ref{eq:odometryError} \\ 1215\hline 1216Landmark & = $\bz^\mathrm{l}_{t,i}$ & $\Re^2$ & 2 & $\left\{ \bx^\mathrm{s}_t, \bx^\mathrm{l}_{i} \right\}$ & Eq.~\ref{eq:landmarkError} \\ 1217\hline 1218\end{tabular}}\\[.3em] 1219 1220The first thing we are going to do is to implement a class that 1221represents elements of the $SE(2)$ group. We represent these elements 1222internally by using the rotation matrix and the translation vector 1223representation, via the types defined in \verb+Eigen::Geometry+. Thus 1224we define an \verb+operator*(...)+ that implements the motion composition 1225operator $\oplus$, and an \verb+inverse()+ function that returns the inverse of 1226a transformation. For convenience we also implement an 1227\verb+operator*+ that transforms 2D points. To convert the elements 1228from and to a minimal representation that utilizes an \verb+Eigen::Vector3d+ 1229we define the methods \verb+fromVector(...)+ and \verb+toVector(...)+. 1230The constructor initializes this class as a point in the origin 1231oriented at 0 degrees. Note that having a separate class for a group 1232is not mandatory in \gopt, but makes the code much more readable and 1233reusable. The corresponding C++ class is reported in 1234Listing~\ref{lst:se2}. 1235\begin{lstlisting}[float,label=lst:se2,caption=\text{Helper class to represent $SE(2)$}.] 1236class SE2 { 1237 public: 1238 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 1239 SE2():_R(0),_t(0,0){} 1240 1241 SE2(double x, double y, double theta):_R(theta),_t(x,y){} 1242 1243 SE2 operator * (const SE2& tr2) const{ 1244 SE2 result(*this); 1245 result._t += _R*tr2._t; 1246 result._R.angle()+= tr2._R.angle(); 1247 result._R.angle()=normalize_theta(result._R.angle()); 1248 return result; 1249 } 1250 1251 Vector2d operator * (const Vector2d& v2) const{ 1252 Vector2d result(*this); 1253 result._t = _t + _R*tr2._t; 1254 return result; 1255 } 1256 1257 SE2 inverse() const{ 1258 SE2 ret; 1259 ret._R=_R.inverse(); 1260 ret._R.angle()=normalize_theta(ret._R.angle()); 1261 ret._t=ret._R*(_t*-1.); 1262 return ret; 1263 } 1264 1265 void fromVector (const Vector3d& v){ 1266 *this=SE2(v[0], v[1], v[2]); 1267 } 1268 1269 Vector3d toVector() const { 1270 Vector3d ret; 1271 for (int i=0; i<3; i++){ 1272 ret(i)=(*this)[i]; 1273 } 1274 return ret; 1275 } 1276 1277 protected: 1278 Rotation2Dd _R; 1279 Vector2d _t; 1280}; 1281\end{lstlisting} 1282 1283Once we defined our nice $SE(2)$ group we are ready to implement the 1284vertices. To this end we extend the \verb+BaseVertex<>+ class, and we 1285derive the classes \verb+VertexSE2+ to represent a robot pose and 1286\verb+VertexPointXY+ to represent a point landmark in the plane. 1287The class definition for robot pose vertices is reported in 1288Listing~\ref{lst:vertexse2}. 1289The pose-vertex extends a template specialization of 1290\verb+BaseVertex<>+. We should say to \gopt{} that the internal type 1291has dimension 3 and that the estimate is of type \verb+SE2+. This 1292means that the member \verb+_estimate+ of a \verb+VertexSE2+ is of 1293type \verb+SE2+. Then all we need to do is to redefine the methods 1294\verb+setToOriginImpl()+ that resets the estimate to a known 1295configuration, and the method \verb+\oplusImpl(double*)+. The method 1296should apply an increment, expressed in the increment parameterization 1297(that is a vector $(\Delta x \; \Delta y \; \Delta \theta )^t$ to the 1298current estimate. To do this, we first convert the vector passed as 1299argument into an \verb+SE(2)+, then we multiply this increment at the 1300right of the previous estimate. After that we should implement the 1301read and write functions to a stream, but this is straight-forward and 1302you can look it up yourself in the code. 1303 1304\begin{lstlisting}[float,label=lst:vertexse2,caption=Vertex representing 1305 a 2D robot pose] 1306class VertexSE2 : public BaseVertex<3, SE2> 1307{ 1308 public: 1309 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 1310 VertexSE2(); 1311 1312 virtual void setToOriginImpl() { 1313 _estimate=SE2(); 1314 } 1315 1316 virtual void oplusImpl(double* update) 1317 { 1318 SE2 up(update[0], update[1], update[2]); 1319 _estimate = _estimate * up; 1320 } 1321 1322 virtual bool read(std::istream& is); 1323 virtual bool write(std::ostream& os) const; 1324 1325}; 1326\end{lstlisting} 1327The next step is to implement a vertex to describe a landmark 1328position. Since the landmarks are parameterized in $\Re^2$, we do not 1329need to define any group for that, and we use directly the vector 1330classes defined in Eigen. This class is reported in Listing~\ref{lst:vertexxy}. 1331 1332\begin{lstlisting}[float,label=lst:vertexxy,caption=Vertex representing 1333 a 2D landmark] 1334class VertexPointXY : public BaseVertex<2, Eigen::Vector2d> 1335{ 1336 public: 1337 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 1338 VertexPointXY(); 1339 1340 virtual void setToOriginImpl() { 1341 _estimate.setZero(); 1342 } 1343 1344 virtual void oplusImpl(double* update) 1345 { 1346 _estimate[0] += update[0]; 1347 _estimate[1] += update[1]; 1348 } 1349 1350 virtual bool read(std::istream& is); 1351 virtual bool write(std::ostream& os) const; 1352}; 1353\end{lstlisting} 1354 1355Now we are done with the vertices. We should go for the edges. Since 1356both edges are binary edges we, extend the class 1357\verb+BaseBinaryEdge<>+. 1358To represent an odometry edge (see Listing~\ref{lst:edgese2}) that 1359connects two \verb+VertexSE2+, we need to extend 1360\verb+BaseBinaryEdge<>+, specialized with the types of the 1361connected vertices (the order matters), where the measurement itself 1362is represented by an \verb+SE2+ that has dimension 3. The second 1363template parameter is the one used for the member variable 1364\verb+_measurement+. The second step is to construct an error 1365function, by redefining the \verb+computeError()+ method. The 1366\verb+computeError()+ should put the error vector in a member variable 1367\verb+error+ that has type \verb+Eigen::Vector<double,3>+. Here the 3 1368comes from the template parameter that specifies the dimension. 1369Again, the read and write functions can be looked up in the code. 1370Now we are done with this edge. The Jacobians are computed numerically 1371by \gopt{}. However, if you want to speed up the execution of your 1372code after everything works, you are warmly invited to redefine the 1373\verb+linearizeOplus+ method. 1374 1375\begin{lstlisting}[float,label=lst:edgese2,caption=\text{Edge connecting two 1376 robot poses, for example, the odometry of the robot.}] 1377class EdgeSE2 : public BaseBinaryEdge<3, SE2, VertexSE2, VertexSE2> 1378{ 1379 public: 1380 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 1381 EdgeSE2(); 1382 1383 void computeError() 1384 { 1385 const VertexSE2* v1 = static_cast<const VertexSE2*>(_vertices[0]); 1386 const VertexSE2* v2 = static_cast<const VertexSE2*>(_vertices[1]); 1387 SE2 delta = _measurement.inverse() * (v1->estimate().inverse()*v2->estimate()); 1388 _error = delta.toVector(); 1389 } 1390 virtual bool read(std::istream& is); 1391 virtual bool write(std::ostream& os) const; 1392}; 1393\end{lstlisting} 1394 1395The last thing that remains to do is to define a class to represent a 1396landmark measurement. This is shown in Listing~\ref{lst:edgese2xy}. 1397Again, we extend a specialization of \verb+BaseBinaryEdge<>+, and we 1398tell the system that it connects a \verb+VertexSE2+ with a 1399\verb+VertexPointXY+, that the measurement is represented by an 1400\verb+Eigen::Vector2d+ that has dimension 2. 1401 1402\begin{lstlisting}[float,label=lst:edgese2xy,caption=Edge connecting a 1403 robot poses and a landmark.] 1404class EdgeSE2PointXY : public BaseBinaryEdge<2, Eigen::Vector2d, VertexSE2, VertexPointXY> 1405{ 1406 public: 1407 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 1408 EdgeSE2PointXY(); 1409 1410 void computeError() 1411 { 1412 const VertexSE2* v1 = static_cast<const VertexSE2*>(_vertices[0]); 1413 const VertexPointXY* l2 = static_cast<const VertexPointXY*>(_vertices[1]); 1414 _error = (v1->estimate().inverse() * l2->estimate()) - _measurement; 1415 } 1416 1417 virtual bool read(std::istream& is); 1418 virtual bool write(std::ostream& os) const; 1419 1420}; 1421\end{lstlisting} 1422The final step we should do to make our system operational, is to 1423register the types to let \gopt{} know that there are new types ready. 1424However, if you intend to manually construct your graph without doing 1425any i/o operation on disk, this step is not even necessary. Have fun! 1426 1427You may find the full code of this 2D SLAM example in the folder 1428\texttt{examples/tutorials\_slam2d}. 1429 1430{\small 1431\bibliographystyle{unsrt} 1432\bibliography{robots}} 1433 1434\end{document} 1435