Skip to content

Suboptimal result for range-only SLAM #864

@tipf

Description

@tipf

Hi guys,
I implemented a small toy example for range-only SLAM. It is based on a 2D Manhattan-world trajectory surrounded by a polygon of 9 ranging beacons.
The graph consists of relative poses (odometry) and range measurements. All landmark positions are treated as unknown.

Unfortunately, the solver fails to find the optimal solution. As you can see in the attached plots, some of the landmarks (1, 2, and 3) are not at their actual positions, and the trajectory is distorted.
At the beginning (Pose 10), the landmark's uncertainty is high, and the trajectory looks correct - but later, the uncertainty shrinks while the position of the landmarks is still wrong.
I used an incremental estimation scheme but got a similar result by estimating the whole trajectory at once.
Plots for incremental and batch solutions (after timestep 1/10/100) are attached.
Result_0001.pdf
Result_0010.pdf
ResultFinal_Incremental.pdf
ResultFinal_Batch.pdf

To check my data, I created another scenario where a prior is added to each landmark based on its true position. This case converges successfully. I attached these plots also.
Result_GT_0001.pdf
Result_GT_0010.pdf
ResultFinal_GT.pdf

Finally, I threw the data (without the landmark priors!) into a parametric solver (ceres) and was astonished that it converges to the correct solution. A comparison plot is attached.
Result_Parametric.pdf

Maybe I was too optimistic about the capabilities of a nonparametric solver, but is this behavior expected? Shouldn't it be able to solve such a toy example?
And are there any specific settings or methods that might improve the result?

I furthermore attached my source code as well as the dataset file. You can switch between incremental and batch estimation by changing the "IncrementalEstimation" parameter.
W100_Input.txt

# configure script
const MaxPose = 100
const IncrementalEstimation = true
const InputPath = "./W100_Input.txt"
const ImagePath = "./Result"

# umbrella package
using RoME

# library used for graph visualization
using GraphPlot, Cairo, RoMEPlotting
Gadfly.set_default_plot_size(15cm,15cm)

# load data
using CSV
using DataFrames
Data = CSV.read(InputPath, DataFrame, header = 0, ignoreemptyrows = true, delim=' ', ignorerepeated=true, maxwarnings=1)
OdomData = Data[Data[!,1] .== "odom2",:]
RangeData = Data[Data[!,1] .== "range_lm2",:]

# get timestamps
Time = unique(RangeData[:,2])
const NumOdom = size(OdomData,1)
const NumPoses = min(size(Time,1),MaxPose)

# symbols
createSymbolPose(n) = Symbol("x",n)
createSymbolLM(n) = Symbol("l",n)

# functions
function addPriorFactor!(fg, SymbolPose)
    # add first pose with prior
    addVariable!(fg, SymbolPose, Pose2)

    PriorDensity = MvNormal([0, 0, pi], diagm([0.1,0.1,0.01].^2))
    addFactor!(fg, [SymbolPose], PriorPose2(PriorDensity)) 
end

function addOdomFactor!(fg, Symbol1, Symbol2, Odom, OldTime)
    DeltaTime = Odom[2] - OldTime;
    # add new pose
    addVariable!(fg, Symbol2, Pose2)

    # add between pose factor
    OdomGauss = MvNormal([Odom[3],Odom[4],Odom[5]] * DeltaTime,
                         diagm([Odom[6],Odom[7],Odom[8]] * DeltaTime^2))
    addFactor!(fg, [Symbol1, Symbol2], Pose2Pose2(OdomGauss))
end

function addRangeFactor!(fg, SymbolPose, Range)

    # create Symbol
    SymbolLM = createSymbolLM(Range[5])

    # add landmark variable if not exist
    if ~any(ls(fg) .== SymbolLM)
        addVariable!(fg, SymbolLM, Point2)
    end

    # add range factor
    RangeGauss = Normal(Range[3], Range[4])
    addFactor!(fg, [SymbolPose, SymbolLM], Pose2Point2Range(RangeGauss))
end

# Start with an empty factor graph
fg = initfg()

# loop over data
for n in 1:(NumPoses)

    if n == 1
        # add a simple prior
        addPriorFactor!(fg, createSymbolPose(n))
        # create a new Bayes tree
        global Tree = buildTreeReset!(fg)
    else
        # add a single odometry constraint
        addOdomFactor!(fg, createSymbolPose(n-1), createSymbolPose(n), OdomData[n-1,:], Time[n-1])
    end

    # add multiple range measurements
    CurrentRanges = RangeData[RangeData[!,2] .== Time[n],:]
    for nRange = 1:size(CurrentRanges,1)
        addRangeFactor!(fg, createSymbolPose(n), CurrentRanges[nRange,:])
    end

    if IncrementalEstimation == true
        # solve
        Tree = solveTree!(fg, Tree, multithread=true)

        # plot
        pl = plotSLAM2D(fg, drawContour=false, drawEllipse=true, drawPoints=false,
                        xmin=-20, xmax=20, ymin=-20, ymax=20)
        NumStr = lpad(n, 4, "0")
        pl |> PDF(ImagePath*"_$NumStr.pdf", 15cm, 15cm)
    end
end

# final batch solution
if IncrementalEstimation == false
    solveTree!(fg, multithread=true)
end

# final trajectory plot
pl = plotSLAM2D(fg, drawContour=false, drawEllipse=true, drawPoints=true,
                xmin=-20, xmax=20, ymin=-20, ymax=20)
pl |> PDF(ImagePath*"Final.pdf", 15cm, 15cm)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions