ggarciabaez/Graphical

An attempt at Graph SLAM using a minimal F1Tenth platform, from scratch.

★ 0Forks 0PythonGitHub ↗Compare

README

good luck twin.

TODO:

  • Move to using SE(2) for pose and to add the scan matching correction (no compose_se2)
  • The entirety of graph SLAM (and loop closure!).
  • Speed up critical components (further) (looking at you c++ bindings)

Moving from a simple "naive addition" loop to Graph SLAM is a significant architectural shift. You are moving from a Filter mindset (forgetting the past) to an Optimization mindset (revising the past when new info arrives).

Here is your roadmap for integrating GTSAM and transitioning to iSAM.


1. GTSAM Graph SLAM Checklist

In Graph SLAM, your "pose" is no longer a single variable; it is a series of nodes () connected by edges (constraints).

Phase A: The Infrastructure

  • [ ] Define Your Noise Models: GTSAM requires "Uncertainty" for every edge.

  • Odom Noise: Usually constant (e.g., gtsam.noiseModel.Diagonal.Sigmas([0.05, 0.05, 0.01])).

  • ICP Noise: Should ideally scale with the ICP fitness score.

  • [ ] Initialize the Factor Graph: * Create a gtsam.NonlinearFactorGraph().

  • Add a PriorFactor at to "anchor" the map to the origin .

Phase B: The Main Loop Integration

  • [ ] Create the Nodes: Instead of naive_add *= delta, you will now increment a key: X(i).
  • [ ] Add Odometry Factors: Between and , add a BetweenFactor. Use your odom_delta as the measurement.
  • [ ] Add Scan Match Factors: Add a second BetweenFactor (or fuse them) between and using your sm_delta.
  • [ ] Store Initial Estimates: You must provide GTSAM with a "guess" for where the robot is. Your current naive_add logic serves as the perfect initial estimate for each new node.

Phase C: Optimization

  • [ ] Batch Optimization: Every frames (or at the end), run gtsam.LevenbergMarquardtOptimizer(graph, initial_estimates).optimize().
  • [ ] Map Correction: When the optimizer finishes, it returns the corrected values for all previous poses. You must re-render your Occupancy Grid or Rerun trajectory using these corrected values.

2. Transition Plan: Moving to iSAM2

Batch optimization (Levenberg-Marquardt) gets slower as the map grows. iSAM2 (Incremental Smoothing and Mapping) uses a Bayes Tree to only update the parts of the graph that actually changed.

Step 1: Replace the Optimizer

Switch from LevenbergMarquardtOptimizer to gtsam.ISAM2.

parameters = gtsam.ISAM2Params()
parameters.setRelinearizeThreshold(0.1)
isam = gtsam.ISAM2(parameters)

Step 2: The "Incremental" Loop

Unlike batch optimization, you don't keep the whole graph in memory. You push "new" factors and "new" estimates to iSAM2 every frame:

  1. Add new factors to a temporary graph.
  2. Add new initial estimate to a temporary Values object.
  3. Call isam.update(new_factors, new_estimates).
  4. Retrieve the current corrected pose: isam.calculateEstimate(X(i)).

Step 3: Implement Loop Closure (The "Holy Grail")

The primary reason to use iSAM2 is loop closure.

  • The Logic: If your current pose is near an old pose , run an ICP match between the current scan and the old scan.
  • The Factor: If the match is good, add a BetweenFactor connecting and .
  • The Result: iSAM2 will instantly "snap" the entire trajectory to eliminate the drift accumulated over time.

3. Post-Implementation Checklist

  • [ ] Global vs. Local Mapping: In your update_scan for the occupancy grid, use the corrected pose from isam.calculateEstimate.
  • [ ] Consistency Check: Monitor the "Chi-squared" error of the graph. If it spikes, your ICP probably gave a "false positive" match (a bad loop closure).
  • [ ] Fixed-Lag Smoothing: If memory becomes an issue, tell iSAM2 to "forget" factors older than seconds to keep the graph size constant.

To implement Graph SLAM with GTSAM and eventually migrate to iSAM2, you need to move away from treating your robot as a "moving point" and start treating it as a collection of constraints.

Here is the documentation of the specific GTSAM tools and concepts you will need to integrate into your current loop.


1. The Core Infrastructure

gtsam.Symbol

In a graph, every pose needs a unique ID. Using symbols allows you to distinguish between different types of variables (e.g., Robot Poses vs. Landmarks).

  • Usage: Use gtsam.symbol('x', i) to create keys for your poses (). This prevents index collisions if you later add landmarks ('l').

gtsam.NonlinearFactorGraph

This is the "container" for your SLAM world. It doesn't store the positions; it only stores the relationships (factors) between them.

  • Role: You will .add() factors here every time you get an odometry update or a scan match.

gtsam.Values

This is a dictionary that stores the actual coordinates (the "Initial Estimates").

  • Role: Every time you create a new Pose node in the graph, you must give it a "guess" of where it is. You will use your current naive_add result as the value for the latest key.

2. Factor Types (The "Edges")

gtsam.PriorFactorPose2

A graph with only relative movements can float away in space. You need at least one "anchor."

  • Documentation: This fixes a specific variable (usually ) to a specific coordinate (usually ) with a very high confidence (low noise).

gtsam.BetweenFactorPose2

This is the workhorse for your project. It represents a relative transformation between two poses.

  • For Odometry: Connects to using your odom_delta.
  • For Scan Matching: Connects to using your sm_delta.
  • For Loop Closure: Connects to when you revisit a location.

3. The Noise Models

GTSAM is probabilistic. You cannot add a measurement without telling the graph how much you trust it.

gtsam.noiseModel.Diagonal.Sigmas

  • Usage: Define a 3-element vector: .
  • Logic: If your steering is noisy but your RPM is precise, you would set a higher sigma for and a lower one for .

gtsam.noiseModel.Robust (Optional but Recommended)

  • Usage: Wraps a standard noise model in a "Kernel" (like Cauchy or Huber).
  • Role: Since you are already using a Cauchy loss in your ICP, wrapping your Scan Match factors in a Robust kernel prevents one "bad match" (outlier) from pulling your entire graph apart.

4. The Optimizers (The "Solvers")

gtsam.LevenbergMarquardtOptimizer

The "Batch" solver. It takes the entire graph and all values, looks at the total error, and shifts every pose simultaneously to minimize it.

  • Best for: Post-processing a run or "Global" re-alignment.

gtsam.ISAM2

The "Incremental" solver. It uses a data structure called a Bayes Tree to perform partial updates.

  • Key Tool: ISAM2Params: You’ll need to tune relinearizeThreshold. This determines how much the robot has to move before iSAM2 recalculates the nonlinear parts of the graph.
  • Key Tool: ISAM2Result: Provides metadata on the update, such as how many variables were recalculated.

5. Transition: Data Flow Comparison

Feature Your Current Loop GTSAM Graph SLAM iSAM2 (Incremental)
Pose Storage One SE2 variable gtsam.Values (All history) gtsam.Values (Recent/Relevant)
Update Step Matrix multiplication Add Factor Batch Optimize Add Factor isam.update()
Drift Accumulates forever Distributed across graph Corrected via Loop Closure
Complexity (Grows with time) (Near constant)

Would you like me to explain how to calculate the Covariance matrix from your ICP results so you can feed a mathematically "honest" NoiseModel into GTSAM?

Contributors

ggarciabaez

Issues