Files
PythonRobotics/docs/modules/slam/graphSLAM_SE2_example.rst
Atsushi Sakai d34f3ca7fa clean up SLAM docs (#572)
* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs

* clean up SLAM docs
2021-11-21 22:39:18 +09:00

210 lines
5.8 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Graph SLAM for a real-world SE(2) dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: ipython3
from graphslam.graph import Graph
from graphslam.load import load_g2o_se2
Introduction
^^^^^^^^^^^^
For a complete derivation of the Graph SLAM algorithm, please see
`Graph SLAM Formulation`_.
This notebook illustrates the iterative optimization of a real-world
:math:`SE(2)` dataset. The code can be found in the ``graphslam``
folder. For simplicity, numerical differentiation is used in lieu of
analytic Jacobians. This code originated from the
`python-graphslam <https://github.com/JeffLIrion/python-graphslam>`__
repo, which is a full-featured Graph SLAM solver. The dataset in this
example is used with permission from Luca Carlone and was downloaded
from his `website <https://lucacarlone.mit.edu/datasets/>`__.
The Dataset
^^^^^^^^^^^^
.. code:: ipython3
g = load_g2o_se2("data/input_INTEL.g2o")
print("Number of edges: {}".format(len(g._edges)))
print("Number of vertices: {}".format(len(g._vertices)))
.. parsed-literal::
Number of edges: 1483
Number of vertices: 1228
.. code:: ipython3
g.plot(title=r"Original ($\chi^2 = {:.0f}$)".format(g.calc_chi2()))
.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_4_0.png
Each edge in this dataset is a constraint that compares the measured
:math:`SE(2)` transformation between two poses to the expected
:math:`SE(2)` transformation between them, as computed using the current
pose estimates. These edges can be classified into two categories:
1. Odometry edges constrain two consecutive vertices, and the
measurement for the :math:`SE(2)` transformation comes directly from
odometry data.
2. Scan-matching edges constrain two non-consecutive vertices. These
scan matches can be computed using, for example, 2-D LiDAR data or
landmarks; the details of how these constraints are determined is
beyond the scope of this example. This is often referred to as *loop
closure* in the Graph SLAM literature.
We can easily parse out the two different types of edges present in this
dataset and plot them.
.. code:: ipython3
def parse_edges(g):
"""Split the graph `g` into two graphs, one with only odometry edges and the other with only scan-matching edges.
Parameters
----------
g : graphslam.graph.Graph
The input graph
Returns
-------
g_odom : graphslam.graph.Graph
A graph consisting of the vertices and odometry edges from `g`
g_scan : graphslam.graph.Graph
A graph consisting of the vertices and scan-matching edges from `g`
"""
edges_odom = []
edges_scan = []
for e in g._edges:
if abs(e.vertex_ids[1] - e.vertex_ids[0]) == 1:
edges_odom.append(e)
else:
edges_scan.append(e)
g_odom = Graph(edges_odom, g._vertices)
g_scan = Graph(edges_scan, g._vertices)
return g_odom, g_scan
.. code:: ipython3
g_odom, g_scan = parse_edges(g)
print("Number of odometry edges: {:4d}".format(len(g_odom._edges)))
print("Number of scan-matching edges: {:4d}".format(len(g_scan._edges)))
print("\nχ^2 error from odometry edges: {:11.3f}".format(g_odom.calc_chi2()))
print("χ^2 error from scan-matching edges: {:11.3f}".format(g_scan.calc_chi2()))
.. parsed-literal::
Number of odometry edges: 1227
Number of scan-matching edges: 256
χ^2 error from odometry edges: 0.232
χ^2 error from scan-matching edges: 7191686.151
.. code:: ipython3
g_odom.plot(title="Odometry edges")
.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_8_0.png
.. code:: ipython3
g_scan.plot(title="Scan-matching edges")
.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_9_0.png
Optimization
^^^^^^^^^^^^
Initially, the pose estimates are consistent with the collected odometry
measurements, and the odometry edges contribute almost zero towards the
:math:`\chi^2` error. However, there are large discrepancies between the
scan-matching constraints and the initial pose estimates. This is not
surprising, since small errors in odometry readings that are propagated
over time can lead to large errors in the robots trajectory. What makes
Graph SLAM effective is that it allows incorporation of multiple
different data sources into a single optimization problem.
.. code:: ipython3
g.optimize()
.. parsed-literal::
Iteration chi^2 rel. change
--------- ----- -----------
0 7191686.3825
1 320031728.8624 43.500234
2 125083004.3299 -0.609154
3 338155.9074 -0.997297
4 735.1344 -0.997826
5 215.8405 -0.706393
6 215.8405 -0.000000
.. figure:: graphSLAM_SE2_example_files/Graph_SLAM_optimization.gif
:alt: Graph_SLAM_optimization.gif
.. code:: ipython3
g.plot(title="Optimized")
.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_13_0.png
.. code:: ipython3
print("\nχ^2 error from odometry edges: {:7.3f}".format(g_odom.calc_chi2()))
print("χ^2 error from scan-matching edges: {:7.3f}".format(g_scan.calc_chi2()))
.. parsed-literal::
χ^2 error from odometry edges: 142.189
χ^2 error from scan-matching edges: 73.652
.. code:: ipython3
g_odom.plot(title="Odometry edges")
.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_15_0.png
.. code:: ipython3
g_scan.plot(title="Scan-matching edges")
.. image:: graphSLAM_SE2_example_files/graphSLAM_SE2_example_16_0.png