141 lines
7.4 KiB
ReStructuredText
141 lines
7.4 KiB
ReStructuredText
|
.. Copyright 2016 The Cartographer Authors
|
||
|
|
||
|
.. Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
.. http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
.. Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
|
||
|
.. cartographer SHA: ea7c39b6f078c693b92fed06d86ca501021147d9
|
||
|
.. cartographer_ros SHA: 44459e18102305745c56f92549b87d8e91f434fe
|
||
|
.. TODO(hrapp): mention insert_free_space somewhere
|
||
|
|
||
|
Tuning
|
||
|
======
|
||
|
|
||
|
Tuning Cartographer is unfortunately really difficult.
|
||
|
The system has many parameters many of which affect each other.
|
||
|
This tuning guide tries to explain a principled approach on concrete examples.
|
||
|
|
||
|
Two systems
|
||
|
-----------
|
||
|
|
||
|
Cartographer can be seen as two separate, but related systems.
|
||
|
The first one is local SLAM (sometimes also called frontend).
|
||
|
Its job is build a locally consistent set of submaps and tie them together, but it will drift over time.
|
||
|
Most of its options can be found in `trajectory_builder_2d.lua`_ for 2D and `trajectory_builder_3d.lua`_ for 3D.
|
||
|
|
||
|
.. _trajectory_builder_2d.lua: https://github.com/googlecartographer/cartographer/blob/ea7c39b6f078c693b92fed06d86ca501021147d9/configuration_files/trajectory_builder_2d.lua
|
||
|
.. _trajectory_builder_3d.lua: https://github.com/googlecartographer/cartographer/blob/ea7c39b6f078c693b92fed06d86ca501021147d9/configuration_files/trajectory_builder_3d.lua
|
||
|
|
||
|
The other system is global SLAM (sometimes called the backend).
|
||
|
It runs in background threads and its main job is to find loop closure constraints.
|
||
|
It does that by scan-matching scans against submaps.
|
||
|
It also incorporates other sensor data to get a higher level view and identify the most consistent global solution.
|
||
|
In 3D, it also tries to find the direction of gravity.
|
||
|
Most of its options can be found in `sparse_pose_graph.lua`_
|
||
|
|
||
|
.. _sparse_pose_graph.lua: https://github.com/googlecartographer/cartographer/blob/ea7c39b6f078c693b92fed06d86ca501021147d9/configuration_files/sparse_pose_graph.lua
|
||
|
|
||
|
On a higher abstraction, the job of local SLAM is to generate good submaps and the job of global SLAM is to tie them most consistently together.
|
||
|
|
||
|
Tuning local SLAM
|
||
|
-----------------
|
||
|
|
||
|
For this example we'll start at ``cartographer`` commit `ea7c39b`_ and ``cartographer_ros`` commit `44459e1`_ and look at the bag ``b2-2016-04-27-12-31-41.bag`` from our test data set.
|
||
|
|
||
|
At our starting configuration, we see some slipping pretty early in the bag.
|
||
|
The backpack passed over a ramp in the Deutsches Museum which violates the 2D assumption of a flat floor.
|
||
|
It is visible in the laser scan data that contradicting information is passed to the SLAM.
|
||
|
But the slipping also indicates that we trust the point cloud matching too much and disregard the other sensors quite strongly.
|
||
|
Our aim is to improve the situation through tuning.
|
||
|
|
||
|
.. _ea7c39b: https://github.com/googlecartographer/cartographer/commit/ea7c39b6f078c693b92fed06d86ca501021147d9
|
||
|
.. _44459e1: https://github.com/googlecartographer/cartographer_ros/commit/44459e18102305745c56f92549b87d8e91f434fe
|
||
|
|
||
|
If we only look at this particular submap, that the error is fully contained in one submap.
|
||
|
We also see that over time, global SLAM figures out that something weird happened and partially corrects for it.
|
||
|
The broken submap is broken forever though.
|
||
|
|
||
|
.. TODO(hrapp): VIDEO
|
||
|
|
||
|
Since the problem here is slippage inside a submap, it is a local SLAM issue.
|
||
|
So let's turn off global SLAM to not mess with our tuning.
|
||
|
|
||
|
.. code-block:: lua
|
||
|
|
||
|
SPARSE_POSE_GRAPH.optimize_every_n_scans = 0
|
||
|
|
||
|
Correct size of submaps
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
Local SLAM drifts over time, only loop closure can fix this drift.
|
||
|
Submaps must be small enough so that the drift inside them is below the resolution, so that they are locally correct.
|
||
|
On the other hand, they should be large enough to be being distinct for loop closure to work properly.
|
||
|
The size of submaps is configured through ``TRAJECTORY_BUILDER_2D.submaps.num_range_data``.
|
||
|
Looking at the individual submaps for this example they already fit the two constraints rather well, so we assume this parameter is well tuned.
|
||
|
|
||
|
The choice of scan matchers
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
The idea behind local SLAM is to use sensor data of other sensors besides the range finder to predict where the next scan should be inserted into the submap.
|
||
|
Then, the ``CeresScanMatcher`` takes this as prior and finds the best spot where the scan match fits the submap.
|
||
|
It does this by interpolating the submap and sub-pixel aligning the scan.
|
||
|
This is fast, but cannot fix errors that are significantly larger than the resolution of the submaps.
|
||
|
If your sensor setup and timing is reasonable, using only the ``CeresScanMatcher`` is usually the best choice to make.
|
||
|
|
||
|
If you do not have other sensors or you do not trust them, Cartographer also provides a ``RealTimeCorrelativeScanMatcher``.
|
||
|
It uses an approach similar to how scans are matched against submaps in loop closure, but instead it matches against the current submap.
|
||
|
The best match is then used as prior for the ``CeresScanMatcher``.
|
||
|
This scan matcher is very expensive and will essentially override any signal from other sensors but the range finder, but it is robust in feature rich environments.
|
||
|
|
||
|
Tuning the correlative scan matcher
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
TODO
|
||
|
|
||
|
Tuning the ``CeresScanMatcher``
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
||
|
In our case, the scan matcher can freely move the match forward and backwards without impacting the score.
|
||
|
We'd like to penalize this situation by making the scan matcher pay more for deviating from the prior that it got.
|
||
|
The two parameters controlling this are ``TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight`` and ``rotation_weight``.
|
||
|
The higher, the more expensive it is to move the result away from the prior, or in other words: scan matching has to generate a higher score in another position to be accepted.
|
||
|
|
||
|
For instructional purposes, let's make deviating from the prior really expensive:
|
||
|
|
||
|
.. code-block:: lua
|
||
|
|
||
|
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight = 1e3
|
||
|
|
||
|
.. TODO(hrapp): video
|
||
|
|
||
|
This allows the optimizer to pretty liberally overwrite the scan matcher results.
|
||
|
This results in poses close to the prior, but inconsistent with the depth sensor and clearly broken.
|
||
|
Experimenting with this value yields a better result at ``2e2``.
|
||
|
|
||
|
.. TODO(hrapp): VIDEO with translation_weight = 2e2
|
||
|
|
||
|
Here, the scan matcher used rotation to still slightly mess up the result though.
|
||
|
Setting the ``rotation_weight`` to ``4e2`` leaves us with a reasonable result.
|
||
|
|
||
|
|
||
|
Verification
|
||
|
------------
|
||
|
|
||
|
To make sure that we did not overtune for this particular issue, we need to run the configuration against other collected data.
|
||
|
In this case, the new parameters did reveal slipping, for example at the beginning of ``b2-2016-04-05-14-44-52.bag``, so we had to lower the ``translation_weight`` to ``1e2``.
|
||
|
This setting is worse for the case we wanted to fix, but no longer slips.
|
||
|
Before checking them in, we normalize all weights, since they only have relative meaning.
|
||
|
The result of this tuning was `PR 428`_.
|
||
|
In general, always try to tune for a platform, not a particular bag.
|
||
|
|
||
|
.. _PR 428: https://github.com/googlecartographer/cartographer/pull/428
|
||
|
|