Main Content

Build Map from 2-D Lidar Scans Using SLAM

This example shows how to implement the SLAM algorithm on a series of 2-D lidar scans using scan processing and pose graph optimization (PGO). The goal of this example is to estimate the trajectory of the robot and build a map of the environment.

SLAM stands for simultaneous localization and mapping.

  • Localization — Estimating the pose of a robot in a known environment.

  • Mapping — Building the map of an unknown environment from a known robot pose and sensor data.

In the SLAM process, a robot creates a map of an environment while localizing itself. SLAM has a wide range of applications in robotics, self-driving cars, and UAVs.

In offline SLAM, a robot steers through an environment and records the sensor data. The SLAM algorithm processes this data to compute a map of the environment. The map is stored and used for localization, path-planning during the actual robot operation.

This example uses a 2-D offline SLAM algorithm. The algorithm incrementally processes recorded lidar scans and builds a pose graph to create a map of the environment. To overcome the drift accumulated in the estimated robot trajectory, the example uses scan matching to recognize previously visited places and then uses this loop closure information to optimize poses and update the map of the environment. To optimize the pose graph, this example uses a 2-D pose graph optimization function from Navigation Toolbox™.

In this example, you learn how to:

  • Estimate robot trajectory from a series of scans using scan registration algorithms.

  • Optimize the drift in the estimated robot trajectory by identifying previously visited places (loop closures).

  • Visualize the map of the environment using scans and their absolute poses.

Load Laser Scans

This example uses data collected in an indoor environment using a Jackal™ robot from Clearpath Robotics™. The robot is equipped with a SICK™ TiM-511 laser scanner with a maximum range of 10 meters. Load the wareHouse.mat file containing the laser scans into the workspace.

data = load("wareHouse.mat");
scans = data.wareHouseScans;

Estimate Robot Trajectory

Create a lidarscanmap object. Using this object, you can:

  • Store and add lidar scans incrementally.

  • Detect, add, and delete loop closures.

  • Find and update the absolute poses of the scans.

  • Generate and visualize a pose graph.

Specify the maximum lidar range and grid resolution values. You can modify these values to fine-tune the map of the environment. Use these values to create the lidar scan map.

maxLidarRange = 8;
gridResolution = 20;
mapObj = lidarscanmap(gridResolution,maxLidarRange);

Incrementally add scans from the input data to the lidar scan map object by using the addScan function. This function rejects scans if they are too close to consecutive scans.

for i = 1:numel(scans)
    isScanAccepted = addScan(mapObj,scans{i});
     if ~isScanAccepted
        continue;
     end
end

Reconstruct the scene by plotting the scans and poses tracked by the lidar scan map.

hFigMap = figure;
axMap = axes(Parent=hFigMap);
show(mapObj,Parent=axMap);
title(axMap,"Map of the Environment and Robot Trajectory")

Figure contains an axes object. The axes object with title Map of the Environment and Robot Trajectory contains 72 objects of type line.

Notice that the estimated robot trajectory drifts over time. The drift can be due to any of these reasons:

  • Noisy scans from the sensor, without sufficient overlap.

  • Absence of significant features in the environment.

  • An inaccurate initial transformation, especially when rotation is significant

Drift in the estimated trajectory results in an inaccurate map of the environment.

Drift Correction

Correct the drift in trajectory by accurately detecting the loops, which are the places the robot returns to, after previously visiting. Add the loop closure edges to the lidarscanmap object to correct the drift in trajectory during pose graph optimization.

Loop Closure Detection

Loop closure detection determines whether, for a given scan, the robot has previously visited the current location. The search consists of matching the current scan against the previous scans around the current robot location, within the specified radius loopClosureSearchRadius. Accept a scan as a match if the match score is greater than the specified threshold loopClosureThreshold.

You can detect loop closures by using the detectLoopClosure function of the lidarscanmap object and the add them to the map object by using the addLoopClosure function.

You can increase the loopClosureThreshold value to avoid false positives in loop closure detection, but the function can still return bad matches in environments with similar or repeated features. To address this, increase the loopClosureSearchRadius value to search a larger radius around the current scan for loop closures, though this increases computation time.

You can also specify the number of loop closure matches loopClosureNumMatches. All of these parameters help in fine-tuning the loop closure detections.

loopClosureThreshold = 110;
loopClosureSearchRadius = 2;
loopClosureNumMatches = 1;
mapObjLoop = lidarscanmap(gridResolution,maxLidarRange);
for i = 1:numel(scans)
    isScanAccepted = addScan(mapObjLoop,scans{i});
    % Detect loop closure if scan is accepted
     if isScanAccepted
         [relPose,matchScanId] = detectLoopClosure(mapObjLoop, ...
             MatchThreshold=loopClosureThreshold, ...
             SearchRadius=loopClosureSearchRadius, ...
             NumMatches=loopClosureNumMatches);
         % Add loop closure to map object if relPose is estimated
         if ~isempty(relPose)
            addLoopClosure(mapObjLoop,matchScanId,i,relPose);
         end
     end
end

Optimize Trajectory

Create a pose graph object from the drift-corrected lidar scan map by using the poseGraph function. Use the optimizePoseGraph (Navigation Toolbox) function to optimize the pose graph.

pGraph = poseGraph(mapObjLoop);
updatedPGraph = optimizePoseGraph(pGraph);

Extract the optimized absolute poses from the pose graph by using the nodeEstimates (Navigation Toolbox) function, and update the trajectory to build an accurate map of the environment.

optimizedScanPoses = nodeEstimates(updatedPGraph);
updateScanPoses(mapObjLoop,optimizedScanPoses);

Visualize Results

Visualize the change in robot trajectory before and after pose graph optimization. The red lines represent loop closure edges.

hFigTraj = figure(Position=[0 0 900 450]);

% Visualize robot trajectory before optimization
axPGraph = subplot(1,2,1,Parent=hFigTraj);
axPGraph.Position = [0.04 0.1 0.45 0.8];
show(pGraph,IDs="off",Parent=axPGraph);
title(axPGraph,"Before PGO")

% Visualize robot trajectory after optimization
axUpdatedPGraph = subplot(1,2,2,Parent=hFigTraj);
axUpdatedPGraph.Position = [0.54 0.1 0.45 0.8];
show(updatedPGraph,IDs="off",Parent=axUpdatedPGraph);
title(axUpdatedPGraph,"After PGO")
axis([axPGraph axUpdatedPGraph],[-6 10 -7 3])
sgtitle("Robot Trajectory",FontWeight="bold")

Figure contains 2 axes objects and another object of type subplottext. Axes object 1 with title Before PGO contains 3 objects of type line. Axes object 2 with title After PGO contains 3 objects of type line.

Visualize the map of the environment and robot trajectory before and after pose graph optimization.

hFigMapTraj = figure(Position=[0 0 900 450]);

% Visualize map and robot trajectory before optimization
axOldMap = subplot(1,2,1,Parent=hFigMapTraj);
axOldMap.Position = [0.05 0.1 0.44 0.8];
show(mapObj,Parent=axOldMap);
title(axOldMap,"Before PGO")

% Visualize map and robot trajectory after optimization
axUpdatedMap = subplot(1,2,2,Parent=hFigMapTraj);
axUpdatedMap.Position = [0.56 0.1 0.44 0.8];
show(mapObjLoop,Parent=axUpdatedMap);
title(axUpdatedMap,"After PGO")
axis([axOldMap axUpdatedMap],[-9 18 -10 9])
sgtitle("Map of the Environment and Robot Trajectory",FontWeight="bold")

Figure contains 2 axes objects and another object of type subplottext. Axes object 1 with title Before PGO contains 72 objects of type line. Axes object 2 with title After PGO contains 72 objects of type line.

See Also

Functions

Related Topics