Python example: mrpt_slam_example.py

ICP scan alignment and incremental ICP-SLAM with mrpt.slam.

Modules: mrpt.slam, mrpt.maps, mrpt.obs, mrpt.poses

 1#!/usr/bin/env python3
 2"""
 3ICP scan alignment and incremental ICP-SLAM with mrpt.slam.
 4
 5Demonstrates:
 6  - CICP: align two point-cloud maps, inspect TICPReturnInfo
 7  - CMetricMapBuilderICP: incremental ICP-SLAM from a sequence of laser scans
 8"""
 9
10import math, numpy as np
11from mrpt.slam import (
12    CICP, CICPOptions, TICPReturnInfo,
13    CMetricMapBuilderICP, CMetricMapBuilderICPOptions,
14    TICPAlgorithm, TICPCovarianceMethod,
15)
16from mrpt.maps import CSimplePointsMap
17from mrpt.obs import CObservation2DRangeScan, CActionCollection, CSensoryFrame
18from mrpt.poses import CPosePDFGaussian, CPose2D
19
20# ---------------------------------------------------------------------------
21# Build two synthetic point clouds offset by 1 m along X
22# ---------------------------------------------------------------------------
23def make_cloud(cx, cy, n_points=50, radius=3.0):
24    pts = CSimplePointsMap()
25    for i in range(n_points):
26        angle = 2 * math.pi * i / n_points
27        pts.insertPoint(cx + radius * math.cos(angle),
28                        cy + radius * math.sin(angle), 0.0)
29    return pts
30
31cloud1 = make_cloud(0.0, 0.0)
32cloud2 = make_cloud(1.0, 0.0)   # shifted 1 m in X
33
34# ---------------------------------------------------------------------------
35# CICP — align cloud2 onto cloud1
36# ---------------------------------------------------------------------------
37opts = CICPOptions()
38opts.maxIterations = 40
39opts.thresholdDist = 0.5
40opts.thresholdAng  = math.radians(5)
41
42icp = CICP(opts)
43
44init_est = CPosePDFGaussian()   # identity initial guess
45
46result_pdf, info = icp.AlignPDF(cloud1, cloud2, init_est)
47print(f"ICP result:")
48print(f"  nIterations = {info.nIterations}")
49print(f"  goodness    = {info.goodness:.4f}")
50print(f"  quality     = {info.quality:.4f}")
51print(f"  {info}")
52
53# ---------------------------------------------------------------------------
54# CMetricMapBuilderICP — simple ICP-SLAM pipeline
55# ---------------------------------------------------------------------------
56def make_scan(radius=5.0, n_rays=180, noise=0.01):
57    """Generate a synthetic 180° laser scan (arc at `radius` metres)."""
58    scan = CObservation2DRangeScan()
59    scan.aperture    = math.pi
60    scan.maxRange    = 20.0
61    scan.rightToLeft = True
62    scan.resizeScan(n_rays)
63    rng = np.random.default_rng(0)
64    for i in range(n_rays):
65        r = radius + rng.normal(0, noise)
66        scan.setScanRange(i, float(r))
67        scan.setScanRangeValidity(i, True)
68    return scan
69
70builder = CMetricMapBuilderICP()
71builder.ICP_options.insertionLinDistance = 0.3
72builder.ICP_options.insertionAngDistance = math.radians(10)
73builder.useSimplePointsMap()  # configure mapInitializers with a CSimplePointsMap (must be before initialize())
74builder.initialize()
75
76# Feed 5 scans; each call to processObservation builds the map
77for step in range(5):
78    scan = make_scan(radius=4.0 + step * 0.1)
79    scan.sensorLabel = "LASER"
80    builder.processObservation(scan)
81
82print(f"\nCMetricMapBuilderICP after 5 scans:")
83print(f"  map size = {builder.getCurrentlyBuiltMapSize()} keyframes")
84pose_pdf = builder.getCurrentPoseEstimation()
85print(f"  current pose PDF: {pose_pdf}")
86xs, ys = builder.getCurrentMapPoints()
87print(f"  point-map size: {len(xs)} points")