diff --git a/README.md b/README.md
index 9949508..20449ce 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Main features are:
This work is released under the GNU General Public License version 3.
### Citation
-This library is the implementation of the following paper [1]. Further references will be added with additional content.
+This library is the implementation of [1] and [2]. Further references will be added with additional content.
[1] Tim Pfeifer and Peter Protzel, Expectation-Maximization for Adaptive Mixture Models in Graph Optimization, Proc. of Intl. Conf. on Robotics and Automation (ICRA), 2019
@@ -26,6 +26,17 @@ BibTeX:
year = {2019},
}
+[2] Tim Pfeifer and Peter Protzel, Incrementally learned Mixture Models for GNSS Localization, Proc. of Intelligent Vehicles Symposium (IV), 2019
+
+BibTeX:
+
+ @InProceedings{Pfeifer2019a,
+ author = {Tim Pfeifer and Peter Protzel},
+ title = {Incrementally learned Mixture Models for GNSS Localization},
+ booktitle = {Proc. of Intelligent Vehicles Symposium (IV)},
+ year = {2019},
+ }
+
## Installation
The libRSF is a CMake project that requires the installation of the following dependencies:
@@ -64,7 +75,7 @@ After building the library, some applications are provided. Usually they corresp
#### ICRA 2019
These two applications are made for the ICRA 2019 conference, the corresponding paper is [1].
-One can be used for GNSS datasets and calculated a 3D position in the ECEF frame, while the other one is for the 2D UWB ranging dataset.
+One can be used for GNSS datasets and calculates a 3D position in the ECEF frame, while the other one is for 2D ranging datasets.
To run them, the following syntax have to be used:
libRSF/build/applications/ICRA19_GNSS error:
@@ -99,3 +110,36 @@ To run them, the following syntax have to be used:
A full example could be:
libRSF/build/applications/ICRA19_GNSS libRSF/datasets/smartLoc/Data_Berlin_Potsdamer_Platz_Web.txt Result_Berlin_Potsdamer_Platz_Web.txt error: gauss
+
+#### IV 2019
+
+These application is made for the IV 2019 conference, the corresponding paper is [2].
+It can be used for GNSS datasets and calculates a 3D position in the ECEF frame.
+To run them, the following syntax have to be used:
+
+ libRSF/build/applications/IV19_GNSS error:
+
+- **\ ** is the dataset you want to process, the format is explained by readme files in the datasets folder.
+- **\** is the estimated Trajectory. The output file contains several columns that represent timestamps and estimated positions:
+
+ Column 1 - Timestamp [s]
+ Column 2 - X coordinate in the ECEF frame [m]
+ Column 3 - Y coordinate in the ECEF frame [m]
+ Column 4 - Z coordinate in the ECEF frame [m]
+ Column 5-13 - Covariance matrix of the estimated position in row-major format (Currently not used!)
+
+- **\** is one of the following error models:
+
+ gauss - A Gaussian distribution
+ dcs - Dynamic Covariance Scaling
+ cdce - Closed form Dynamic Covariance Estimation
+ mm - Max-Mixture (an approximation of a Gaussian mixture)
+ sm - Sum-Mixture (an exact Gaussian mixture)
+ stmm - Adaptive Max-Mixture using the EM Algorithm
+ stsm - Adaptive Sum-Mixture using the EM Algorithm
+ stmm_vbi - Incrementally learned Max-Mixture using the VBI Algorithm
+ stsm_vbi - Incrementally learned Sum-Mixture using the VIB Algorithm
+
+A full example could be:
+
+ libRSF/build/applications/IV19_GNSS libRSF/datasets/smartLoc/Data_Berlin_Potsdamer_Platz_Web.txt Result_Berlin_Potsdamer_Platz_Web.txt error: gauss
diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt
index 91b6afb..b05ee45 100644
--- a/applications/CMakeLists.txt
+++ b/applications/CMakeLists.txt
@@ -23,3 +23,6 @@ target_link_libraries(ICRA19_Ranging ${CERES_LIBRARIES} libRSF)
add_executable(ICRA19_GNSS ICRA19_GNSS.cpp)
target_link_libraries(ICRA19_GNSS ${CERES_LIBRARIES} libRSF)
+
+add_executable(IV19_GNSS IV19_GNSS.cpp)
+target_link_libraries(IV19_GNSS ${CERES_LIBRARIES} libRSF)
diff --git a/applications/ICRA19_GNSS.cpp b/applications/ICRA19_GNSS.cpp
index b1b28b4..b7df00f 100644
--- a/applications/ICRA19_GNSS.cpp
+++ b/applications/ICRA19_GNSS.cpp
@@ -205,7 +205,7 @@ void TuneErrorModel(libRSF::FactorGraph &Graph,
for(int nComponent = 0; nComponent < NumberOfComponents; ++nComponent)
{
- Component.setParamsStdDev((ceres::Vector(1) << 10).finished()*pow(10, nComponent),
+ Component.setParamsStdDev((ceres::Vector(1) << pow(10, nComponent+1)).finished(),
(ceres::Vector(1) << 0.0).finished(),
(ceres::Vector(1) << 1.0/NumberOfComponents).finished());
diff --git a/applications/ICRA19_Ranging.cpp b/applications/ICRA19_Ranging.cpp
index e4f8961..ca02bcb 100644
--- a/applications/ICRA19_Ranging.cpp
+++ b/applications/ICRA19_Ranging.cpp
@@ -31,7 +31,6 @@
#include "ICRA19_Ranging.h"
-
/** @brief Generates a delta time measurement object from two timestamps
*
* @param TimestampOld a double timestamp
@@ -310,8 +309,6 @@ int main(int argc, char** argv)
Graph.addState(ORIENTATION_STATE, libRSF::StateType::Angle, Timestamp);
/** add motion model or odometry */
- libRSF::SensorData DeltaTime = GenerateDeltaTime(TimestampOld, Timestamp);
-
libRSF::StateList MotionList;
MotionList.add(POSITION_STATE, TimestampOld);
MotionList.add(POSITION_STATE, Timestamp);
diff --git a/applications/IV19_GNSS.cpp b/applications/IV19_GNSS.cpp
new file mode 100644
index 0000000..75aa63b
--- /dev/null
+++ b/applications/IV19_GNSS.cpp
@@ -0,0 +1,469 @@
+/***************************************************************************
+ * libRSF - A Robust Sensor Fusion Library
+ *
+ * Copyright (C) 2018 Chair of Automation Technology / TU Chemnitz
+ * For more information see https://www.tu-chemnitz.de/etit/proaut/self-tuning
+ *
+ * libRSF is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libRSF is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with libRSF. If not, see .
+ *
+ * Author: Tim Pfeifer (tim.pfeifer@etit.tu-chemnitz.de)
+ ***************************************************************************/
+
+/**
+ * @file IV19_GNSS.cpp
+ * @author Tim Pfeifer
+ * @date 04 Jun 2019
+ * @brief File containing an application for 3D pose estimation based on pseudo range measurements. This special version is made for the Intelligent Vehicles Symposium 2019.
+ * @copyright GNU Public License.
+ *
+ */
+
+#include "IV19_GNSS.h"
+
+/** @brief Generates a delta time measurement object from two timestamps
+ *
+ * @param TimestampOld a double timestamp
+ * @param TimestampNew another double timestamp
+ * @return A SensorData Object, thats represents the time difference
+ *
+ */
+libRSF::SensorData GenerateDeltaTime(const double TimestampOld,
+ const double TimestampNew)
+{
+ libRSF::SensorData DeltaTime(libRSF::SensorType::DeltaTime, TimestampNew);
+ ceres::Vector DeltaTimeVector(1);
+ DeltaTimeVector[0] = TimestampNew - TimestampOld;
+ DeltaTime.setMean(DeltaTimeVector);
+ DeltaTime.setTimestamp(TimestampNew);
+
+ return DeltaTime;
+}
+
+/** @brief Build the factor Graph with initial values and a first set of measurements
+ *
+ * @param Graph reference to the factor graph object
+ * @param Measurements reference to the dataset that contains the pseudo range measurements
+ * @param Config reference to the factor graph config
+ * @param Options solver option to estimate initial values
+ * @param TimestampFirst first timestamp in the Dataset
+ * @return nothing
+ *
+ */
+void InitGraph(libRSF::FactorGraph &Graph,
+ libRSF::SensorDataSet &Measurements,
+ libRSF::FactorGraphConfig const &Config,
+ ceres::Solver::Options Options,
+ double TimestampFirst)
+{
+ /** build simple graph */
+ libRSF::FactorGraphConfig SimpleConfig = Config;
+ SimpleConfig.RangeErrorModel.Type = ROBUST_NONE;
+ libRSF::FactorGraph SimpleGraph;
+
+ SimpleGraph.addState(POSITION_STATE, libRSF::StateType::Pose3, TimestampFirst);
+ SimpleGraph.addState(CLOCK_ERROR_STATE, libRSF::StateType::Val1, TimestampFirst);
+ AddPseudorangeMeasurements(SimpleGraph, Measurements, SimpleConfig, TimestampFirst);
+
+ /** solve */
+ Options.minimizer_progress_to_stdout = false;
+ SimpleGraph.solve(Options);
+
+ /**add first state variables */
+ Graph.addState(POSITION_STATE, libRSF::StateType::Pose3, TimestampFirst);
+ Graph.addState(CLOCK_ERROR_STATE, libRSF::StateType::Val1, TimestampFirst);
+ Graph.addState(ORIENTATION_STATE, libRSF::StateType::Angle, TimestampFirst);
+ Graph.addState(CLOCK_DRIFT_STATE, libRSF::StateType::Val1, TimestampFirst);
+
+ /** copy values to real graph */
+ Graph.getStateData().getElement(POSITION_STATE, TimestampFirst).setMean(SimpleGraph.getStateData().getElement(POSITION_STATE, TimestampFirst).getMean());
+ Graph.getStateData().getElement(CLOCK_ERROR_STATE, TimestampFirst).setMean(SimpleGraph.getStateData().getElement(CLOCK_ERROR_STATE, TimestampFirst).getMean());
+
+ /** add first set of measurements */
+ AddPseudorangeMeasurements(Graph, Measurements, Config, TimestampFirst);
+}
+
+/** @brief Adds a pseudorange measurement to the graph
+ *
+ * @param Graph reference to the factor graph object
+ * @param Measurements reference to the dataset that contains the measurement
+ * @param Config reference to the factor graph config object that specifies the motion model
+ * @param Timestamp a double timestamp of the current position
+ * @return nothing
+ *
+ */
+void AddPseudorangeMeasurements(libRSF::FactorGraph &Graph,
+ libRSF::SensorDataSet &Measurements,
+ libRSF::FactorGraphConfig const &Config,
+ double Timestamp)
+{
+ libRSF::StateList ListPseudorange;
+ libRSF::SensorData Pseudorange;
+ libRSF::GaussianDiagonal<1> NoisePseudorange;
+
+ ListPseudorange.add(POSITION_STATE, Timestamp);
+ ListPseudorange.add(CLOCK_ERROR_STATE, Timestamp);
+
+ size_t SatNumber = Measurements.countElement("Range 3D", Timestamp);
+
+ for(size_t SatCounter = 0; SatCounter < SatNumber; ++SatCounter)
+ {
+ /** get measurement */
+ Pseudorange = Measurements.getElement("Range 3D", Timestamp, SatCounter);
+
+ /** add factor */
+ switch(Config.RangeErrorModel.Type)
+ {
+ case ROBUST_NONE:
+ NoisePseudorange.setStdDev(Pseudorange.getStdDev());
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorange);
+ break;
+
+ case ROBUST_DCS:
+ NoisePseudorange.setStdDev(Pseudorange.getStdDev());
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorange, new libRSF::DCSLoss(1.0));
+ break;
+
+ case ROBUST_CDCE:
+ NoisePseudorange.setStdDev(Eigen::Matrix::Ones());
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorange, new libRSF::cDCELoss(Pseudorange.getStdDev()[0]));
+ break;
+
+ case ROBUST_MM:
+ static libRSF::GaussianMixture<1> GMM_MM;
+ if (GMM_MM.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM_MM, GMM_N, 10);
+ }
+ static libRSF::MaxMix1 NoisePseudorangeMaxMix(GMM_MM);
+
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorangeMaxMix);
+
+ break;
+
+ case ROBUST_SM:
+ static libRSF::GaussianMixture<1> GMM_SM;
+ if (GMM_SM.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM_SM, GMM_N, 10);
+ }
+ static libRSF::SumMix1 NoisePseudorangeSumMix(GMM_SM);
+
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorangeSumMix);
+
+ break;
+
+ case ROBUST_STSM:
+ static libRSF::GaussianMixture<1> GMM_SMST;
+ if (GMM_SMST.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM_SMST, GMM_N, 10);
+ }
+ static libRSF::SumMix1 NoisePseudorangeSumMixST(GMM_SMST);
+
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorangeSumMixST);
+
+ break;
+
+ case ROBUST_STMM:
+ static libRSF::GaussianMixture<1> GMM_MMST;
+ if (GMM_MMST.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM_MMST, GMM_N, 10);
+ }
+ static libRSF::MaxMix1 NoisePseudorangeMaxMixST(GMM_MMST);
+
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorangeMaxMixST);
+
+ break;
+
+ case ROBUST_STSM_VBI:
+ /** fill empty GMM */
+ static libRSF::GaussianMixture<1> GMM_SMST_VBI;
+ if (GMM_SMST_VBI.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM_SMST_VBI, 2, 10);
+ }
+ static libRSF::SumMix1 NoisePseudorangeSumMixST_VBI(GMM_SMST_VBI);
+
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorangeSumMixST_VBI);
+
+ break;
+
+ case ROBUST_STMM_VBI:
+ /** fill empty GMM */
+ static libRSF::GaussianMixture<1> GMM_MMST_VBI;
+ if (GMM_MMST_VBI.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM_MMST_VBI, 2, 10);
+ }
+ static libRSF::MaxMix1 NoisePseudorangeMaxMixST_VBI(GMM_MMST_VBI);
+
+ Graph.addFactor(libRSF::FactorType::Pseudorange3, ListPseudorange, Pseudorange, NoisePseudorangeMaxMixST_VBI);
+
+ break;
+
+ default:
+ std::cerr << "Error: Wrong pseudorange error model model: " << Config.RangeErrorModel.Type << std::endl;
+ break;
+ }
+ }
+}
+
+/** @brief Use a GMM to estimate the error distribution of a factor
+*
+* @param Graph reference to the factor graph object
+* @param Config reference to the factor graph config object that specifies the motion model
+* @param Timestamp current timestamp
+* @return nothing
+*
+*/
+void TuneErrorModel(libRSF::FactorGraph &Graph,
+ libRSF::FactorGraphConfig &Config,
+ double Timestamp)
+{
+ if(Config.RangeErrorModel.Type == ROBUST_STSM || Config.RangeErrorModel.Type == ROBUST_STMM ||
+ Config.RangeErrorModel.Type == ROBUST_STSM_VBI || Config.RangeErrorModel.Type == ROBUST_STMM_VBI)
+ {
+ /** compute resudiuals of the factor graph */
+ std::vector ErrorData;
+ Graph.computeUnweightedError(libRSF::FactorType::Pseudorange3, ErrorData);
+
+ /** remove zeros in MM results (unused dimension!) */
+ if(Config.RangeErrorModel.Type == ROBUST_STMM || Config.RangeErrorModel.Type == ROBUST_STMM_VBI)
+ {
+ for(int i = ErrorData.size() - 1; i > 0; i -= 2)
+ {
+ ErrorData.erase(ErrorData.begin() + i);
+ }
+ }
+
+ if(Config.RangeErrorModel.Type == ROBUST_STSM || Config.RangeErrorModel.Type == ROBUST_STMM)
+ {
+ /** fill empty GMM */
+ libRSF::GaussianMixture<1> GMM;
+
+ if(GMM.getNumberOfComponents() == 0)
+ {
+ InitGMM(GMM, GMM_N, 10);
+ }
+
+ /** call the EM algorithm */
+ GMM.estimateWithEM(ErrorData);
+
+ /** remove offset of the first "LOS" component */
+ GMM.removeOffset();
+
+ /** apply error model */
+ if(Config.RangeErrorModel.Type == ROBUST_STSM)
+ {
+ libRSF::SumMix1 NewSMModel(GMM);
+ Graph.setNewErrorModel(libRSF::FactorType::Pseudorange3, NewSMModel);
+ }
+ else if(Config.RangeErrorModel.Type == ROBUST_STMM)
+ {
+ libRSF::MaxMix1 NewMMModel(GMM);
+ Graph.setNewErrorModel(libRSF::FactorType::Pseudorange3, NewMMModel);
+ }
+
+ }
+ else if(Config.RangeErrorModel.Type == ROBUST_STSM_VBI || Config.RangeErrorModel.Type == ROBUST_STMM_VBI)
+ {
+ /** initialize GMM */
+ static libRSF::GaussianMixture<1> GMMAdaptive;
+ libRSF::GaussianComponent<1> Component;
+
+ if(GMMAdaptive.getNumberOfComponents() == 0)
+ {
+ Component.setParamsStdDev((ceres::Vector(1) << 10.0).finished(),
+ (ceres::Vector(1) << 0.0).finished(),
+ (ceres::Vector(1) << 1.0).finished());
+ GMMAdaptive.addComponent(Component);
+ }
+
+ /** calculate statistics for GMM initialization*/
+ std::vector v = ErrorData;
+ double sum = std::accumulate(v.begin(), v.end(), 0.0);
+ double mean = sum / v.size();
+
+ std::vector diff(v.size());
+ std::transform(v.begin(), v.end(), diff.begin(), [mean](double x)
+ {
+ return x - mean;
+ });
+ double sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
+ double stdev = std::sqrt(sq_sum / v.size());
+
+ /** add just one component per timestamp */
+ if(GMMAdaptive.getNumberOfComponents() >= VBI_N_MAX)
+ {
+ GMMAdaptive.sortComponentsByWeight();
+ GMMAdaptive.removeLastComponent();
+ }
+
+ Component.setParamsStdDev((ceres::Vector(1) << stdev).finished(),
+ (ceres::Vector(1) << 0.0).finished(),
+ (ceres::Vector(1) << 1.0/GMMAdaptive.getNumberOfComponents()).finished());
+
+
+ GMMAdaptive.addComponent(Component);
+
+ /** estimate GMM with Variational Inference */
+ GMMAdaptive.estimateWithVBI(ErrorData, VBI_NU);
+
+ /** remove offset*/
+ GMMAdaptive.removeOffset();
+
+ /** apply error model */
+ if(Config.RangeErrorModel.Type == ROBUST_STSM_VBI)
+ {
+ libRSF::SumMix1 NewSMModel(GMMAdaptive);
+ Graph.setNewErrorModel(libRSF::FactorType::Pseudorange3, NewSMModel);
+ }
+ else if(Config.RangeErrorModel.Type == ROBUST_STMM_VBI)
+ {
+ libRSF::MaxMix1 NewMMModel(GMMAdaptive);
+ Graph.setNewErrorModel(libRSF::FactorType::Pseudorange3, NewMMModel);
+ }
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ google::InitGoogleLogging(argv[0]);
+ libRSF::FactorGraphConfig Config;
+
+ /** process command line parameter */
+ if(!Config.ReadCommandLineOptions(argc, argv))
+ {
+ std::cout << std::endl;
+ return 1;
+ }
+
+ /** configure the solver */
+ ceres::Solver::Options SolverOptions;
+ SolverOptions.minimizer_progress_to_stdout = false;
+ SolverOptions.use_nonmonotonic_steps = true;
+ SolverOptions.trust_region_strategy_type = ceres::TrustRegionStrategyType::DOGLEG;
+ SolverOptions.dogleg_type = ceres::DoglegType::SUBSPACE_DOGLEG;
+ SolverOptions.num_threads = 8;
+ SolverOptions.max_num_iterations = 100;
+
+ /** read input data */
+ libRSF::SensorDataSet InputData;
+ libRSF::ReadDataFromFile(Config.InputFile, InputData);
+
+ /** Build optimization problem from sensor data */
+ ceres::Problem::Options Options;
+ Options.enable_fast_removal = true;
+ libRSF::FactorGraph Graph(Options);
+
+ libRSF::StateDataSet Result;
+ libRSF::SensorData DeltaTime;
+
+ double Timestamp, TimestampFirst, TimestampOld, TimestampLast;
+ InputData.getFirstTimestamp("Range 3D", TimestampFirst);
+ InputData.getLastTimestamp("Range 3D", TimestampLast);
+ Timestamp = TimestampFirst;
+ TimestampOld = TimestampFirst;
+ int nTimestamp = 0;
+
+ /** add fist variables and factors */
+ InitGraph(Graph, InputData, Config, SolverOptions, TimestampFirst);
+
+ /** solve multiple times with refined model to achieve good initial convergence */
+ Graph.solve(SolverOptions);
+ TuneErrorModel(Graph, Config, Timestamp);
+ Graph.solve(SolverOptions);
+
+ /** safe result at first timestamp */
+ Result.addElement(POSITION_STATE, Graph.getStateData().getElement(POSITION_STATE, Timestamp, 0));
+
+ /** get odometry noise from first measurement */
+ libRSF::SensorData Odom = InputData.getElement("Odometry 3D", Timestamp);
+ ceres::Vector StdOdom4DOF(4);
+ StdOdom4DOF << Odom.getStdDev().head(3), Odom.getStdDev().tail(1);
+ libRSF::GaussianDiagonal<4> NoiseOdom4DOF(StdOdom4DOF);
+
+ /** hard coded constant clock error drift (CCED) model noise properties */
+ ceres::Vector StdCCED(2);
+
+ if(strcmp(Config.InputFile, "Data_Chemnitz.txt") == 0)
+ {
+ StdCCED << 0.1, 0.009; /** set CCED standard deviation for Chemnitz City dataset */
+ }
+ else
+ {
+ StdCCED << 0.05, 0.01; /** set CCED standard deviation for smartLoc datasets */
+ }
+
+ libRSF::GaussianDiagonal<2> NoiseCCED(StdCCED);
+
+ /** iterate over timestamps */
+ while(InputData.getNextTimestamp("Range 3D", Timestamp, Timestamp))
+ {
+ DeltaTime = GenerateDeltaTime(TimestampOld, Timestamp);
+
+ /** add position, orientation and clock error */
+ Graph.addState(POSITION_STATE, libRSF::StateType::Pose3, Timestamp);
+ Graph.addState(CLOCK_ERROR_STATE, libRSF::StateType::Val1, Timestamp);
+ Graph.addState(ORIENTATION_STATE, libRSF::StateType::Angle, Timestamp);
+ Graph.addState(CLOCK_DRIFT_STATE, libRSF::StateType::Val1, Timestamp);
+
+ /** add odometry */
+ libRSF::StateList MotionList;
+ MotionList.add(POSITION_STATE, TimestampOld);
+ MotionList.add(POSITION_STATE, Timestamp);
+ MotionList.add(ORIENTATION_STATE, TimestampOld);
+ MotionList.add(ORIENTATION_STATE, Timestamp);
+ Graph.addFactor(libRSF::FactorType::Odom4, MotionList, InputData.getElement("Odometry 3D", Timestamp), DeltaTime, NoiseOdom4DOF);
+
+ /** add clock drift model */
+ libRSF::StateList ClockList;
+ ClockList.add(CLOCK_ERROR_STATE, TimestampOld);
+ ClockList.add(CLOCK_ERROR_STATE, Timestamp);
+ ClockList.add(CLOCK_DRIFT_STATE, TimestampOld);
+ ClockList.add(CLOCK_DRIFT_STATE, Timestamp);
+ Graph.addFactor(libRSF::FactorType::ConstDrift1, ClockList, DeltaTime, NoiseCCED);
+
+ /** add all pseudo range measurements of with current timestamp */
+ AddPseudorangeMeasurements(Graph, InputData, Config, Timestamp);
+
+ /** tune self-tuning error model */
+ TuneErrorModel(Graph, Config, Timestamp);
+
+ /** solve the estimation problem */
+ Graph.solve(SolverOptions);
+
+ /** save data after optimization */
+ Result.addElement(POSITION_STATE, Graph.getStateData().getElement(POSITION_STATE, Timestamp, 0));
+
+ /** apply sliding window */
+ Graph.removeAllFactorsOutsideWindow(60, Timestamp);
+ Graph.removeAllStatesOutsideWindow(60, Timestamp);
+
+ /** save time stamp */
+ TimestampOld = Timestamp;
+
+ nTimestamp++;
+ }
+
+ /** print last report */
+ Graph.printReport();
+
+ /** write results to disk */
+ libRSF::WriteDataToFile(Config.OutputFile, POSITION_STATE, Result);
+
+ return 0;
+}
diff --git a/applications/IV19_GNSS.h b/applications/IV19_GNSS.h
new file mode 100644
index 0000000..27ad0e1
--- /dev/null
+++ b/applications/IV19_GNSS.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+ * libRSF - A Robust Sensor Fusion Library
+ *
+ * Copyright (C) 2018 Chair of Automation Technology / TU Chemnitz
+ * For more information see https://www.tu-chemnitz.de/etit/proaut/self-tuning
+ *
+ * libRSF is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libRSF is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with libRSF. If not, see .
+ *
+ * Author: Tim Pfeifer (tim.pfeifer@etit.tu-chemnitz.de)
+ ***************************************************************************/
+
+#ifndef ICRA19_GNSS_H_INCLUDED
+#define ICRA19_GNSS_H_INCLUDED
+
+#include
+#include
+
+#include
+#include "../include/FactorGraph.h"
+#include "../include/FactorGraphConfig.h"
+#include "../include/FileAccess.h"
+#include "../include/StateDataSet.h"
+#include "../include/SensorDataSet.h"
+
+/** pre-defined state names */
+#define POSITION_STATE "Position"
+#define ORIENTATION_STATE "Orientation"
+#define CLOCK_ERROR_STATE "ClockError"
+#define CLOCK_DRIFT_STATE "ClockDrift"
+
+/** configuration */
+#define VBI_NU 2.0 /**< degrees of freedom of the Wishart prior */
+#define VBI_N_MAX 8 /**< maximum number of GMM components */
+
+#define GMM_N 3 /**< fixed number of components (which is used in static case) */
+
+/** Generates a delta time measurement object from two timestamps */
+libRSF::SensorData GenerateDeltaTime(const double TimestampOld,
+ const double TimestampNew);
+
+/** Adds a pseudorange measurement to the graph */
+void AddPseudorangeMeasurements(libRSF::FactorGraph& Graph,
+ libRSF::SensorDataSet & Measurements,
+ libRSF::FactorGraphConfig const &Config,
+ double Timestamp);
+
+/** use EM algorithm to tune the gaussian mixture model */
+void TuneErrorModel(libRSF::FactorGraph &Graph,
+ libRSF::FactorGraphConfig &Config,
+ double Timestamp);
+
+/** init zero mean GMM with increasing uncertainty in each component */
+template
+void InitGMM(libRSF::GaussianMixture &GMM, unsigned int NumberOfComponents, double BaseStdDev)
+{
+ libRSF::GaussianComponent Component;
+
+ /** construct Gaussian parameters */
+ Eigen::Matrix Mean = Eigen::Matrix::Zero();
+ Eigen::Matrix Weight;
+ Weight(0) = 1.0/NumberOfComponents;
+
+ Eigen::Matrix SqrtInfo = Eigen::Matrix::Identity() * (1.0/BaseStdDev);
+
+ GMM.clear();
+ for(int nComponent = 0; nComponent < NumberOfComponents; ++nComponent)
+ {
+ Component.setParamsSqrtInformation(SqrtInfo*std::pow(0.1, nComponent), Mean, Weight);
+ GMM.addComponent(Component);
+ }
+}
+
+#endif // ICRA19_GNSS_H_INCLUDED
diff --git a/datasets/Chemnitz City/readme.txt b/datasets/Chemnitz City/readme.txt
index 474e6a3..e746482 100644
--- a/datasets/Chemnitz City/readme.txt
+++ b/datasets/Chemnitz City/readme.txt
@@ -17,10 +17,10 @@ Every column holds an measurement element corresponding to its identifier:
1 - range3
2 - time stamp [s]
3 - pseudorange mean [m]
-3 - pseudorange standard deviation [m]
-4 - satellite position ECEF-X [m]
-5 - satellite position ECEF-Y [m]
-6 - satellite position ECEF-Z [m]
+4 - pseudorange standard deviation [m]
+5 - satellite position ECEF-X [m]
+6 - satellite position ECEF-Y [m]
+7 - satellite position ECEF-Z [m]
8 - satellite ID
9 - satellite elevation angle [deg]
diff --git a/datasets/Indoor UWB/readme.txt b/datasets/Indoor UWB/readme.txt
index 7067276..c112481 100644
--- a/datasets/Indoor UWB/readme.txt
+++ b/datasets/Indoor UWB/readme.txt
@@ -17,10 +17,10 @@ Every column holds an measurement element corresponding to its identifier:
1 - range2
2 - time stamp [s]
3 - pseudorange mean [m]
-3 - pseudorange standard deviation [m]
-4 - UWB module position X [m]
-5 - UWB module position Y [m]
-6 - UWB module ID
+4 - pseudorange standard deviation [m]
+5 - UWB module position X [m]
+6 - UWB module position Y [m]
+7 - UWB module ID
## odometry ##
1 - odom2diff
diff --git a/datasets/smartLoc/readme.txt b/datasets/smartLoc/readme.txt
index eb3b4cc..44a69a0 100644
--- a/datasets/smartLoc/readme.txt
+++ b/datasets/smartLoc/readme.txt
@@ -17,10 +17,10 @@ Every column holds an measurement element corresponding to its identifier:
1 - range3
2 - time stamp [s]
3 - pseudorange mean [m]
-3 - pseudorange standard deviation [m]
-4 - satellite position ECEF-X [m]
-5 - satellite position ECEF-Y [m]
-6 - satellite position ECEF-Z [m]
+4 - pseudorange standard deviation [m]
+5 - satellite position ECEF-X [m]
+6 - satellite position ECEF-Y [m]
+7 - satellite position ECEF-Z [m]
8 - satellite ID
9 - satellite elevation angle [deg]
10 - carrier-to-noise density ratio [dBHz]
diff --git a/include/FactorGraphConfig.h b/include/FactorGraphConfig.h
index 46d8e31..f76e4a6 100644
--- a/include/FactorGraphConfig.h
+++ b/include/FactorGraphConfig.h
@@ -43,12 +43,8 @@
#define ROBUST_SM 6
#define ROBUST_STSM 9
#define ROBUST_STMM 10
-#define RANGE_MM_GR 12
-#define RANGE_SM_GR 13
-#define RANGE_STMM_GR 14
-#define RANGE_STSM_GR 15
-#define RANGE_STSM_VBI 16
-#define RANGE_STMM_VBI 17
+#define ROBUST_STSM_VBI 16
+#define ROBUST_STMM_VBI 17
namespace libRSF
{
diff --git a/include/Geometry.h b/include/Geometry.h
index b8f7775..5710cd0 100644
--- a/include/Geometry.h
+++ b/include/Geometry.h
@@ -66,40 +66,6 @@ namespace libRSF
return RelativeMotion;
}
- template
- void VectorDifference(const T1* const Vector1, const T2* const Vector2, T1* Difference)
- {
- for(int nDim = 0; nDim < Dimension; nDim++)
- {
- Difference[nDim] = Vector1[nDim] - Vector2[nDim];
- }
- }
-
- template
- T VectorLength(const T* const Vector)
- {
- T SquaredSum = T(0.0);
-
- for(int nDim = 0; nDim < Dimension; nDim++)
- {
- SquaredSum += ceres::pow(Vector[nDim], 2);
- }
-
- /** for stability of the derivation */
- if(SquaredSum < T(1e-10))
- SquaredSum += T(1e-10);
-
- return ceres::sqrt(SquaredSum);
- }
-
- template
- T1 VectorDistance(const T1* const Vector1, const T2* const Vector2)
- {
- T1 Difference[Dimension];
- VectorDifference(Vector1, Vector2, Difference);
- return T1(VectorLength(Difference));
- }
-
}
#endif // GEOMETRY_H
diff --git a/include/QuaternionCalc.h b/include/QuaternionCalc.h
index 2df4f54..7133f09 100644
--- a/include/QuaternionCalc.h
+++ b/include/QuaternionCalc.h
@@ -34,26 +34,10 @@
#include
#include
#include "Geometry.h"
+#include "VectorMath.h"
namespace libRSF
{
-
- /** @brief Converts a quaterion to Tait–Bryan angles
- * From:
- * Wikipedia contributors,
- * "Conversion between quaternions and Euler angles"
- * Wikipedia, The Free Encyclopedia,
- * https://en.wikipedia.org/w/index.php?title=Conversion_between_quaternions_and_Euler_angles&oldid=883423661
- * (accessed February 15, 2019).
- *
- * @param Quaternion ceres quaternion with ordering w,x,y,z
- * @param Yaw rotation around z
- * @param Pitch rotation around y
- * @param Roll rotation around x
- */
- template
- inline void QuaternionToRPY(const T* Quaternion, T* Roll, T* Pitch, T* Yaw);
-
/** @brief invert a quaternion
*
* @param Quaternion ceres quaternion with ordering w,x,y,z
@@ -65,36 +49,6 @@ namespace libRSF
template
inline T RotationBetween(const T* Vector1, const T* Vector2, T* Quaternion);
- template
- inline void QuaternionToRPY(const T* Quaternion, T* Roll, T* Pitch, T* Yaw)
- {
-
- const T& Qw = Quaternion[0];
- const T& Qx = Quaternion[1];
- const T& Qy = Quaternion[2];
- const T& Qz = Quaternion[3];
-
- /** conversion from Wikipedia */
- // roll (x-axis rotation)
- T sinr_cosp = +2.0 * (Qw * Qx + Qy * Qz);
- T cosr_cosp = +1.0 - 2.0 * (Qx * Qx + Qy * Qy);
- Roll[0] = ceres::atan2(sinr_cosp, cosr_cosp);
-
- // pitch (y-axis rotation)
- T sinp = +2.0 * (Qw * Qy - Qz * Qx);
- if (sinp >= T(1))
- Pitch[0] = T(M_PI / 2); // use 90 degrees if out of range
- else if (sinp <= T(-1))
- Pitch[0] = T(-M_PI / 2);
- else
- Pitch[0] = ceres::asin(sinp);
-
- // yaw (z-axis rotation)
- T siny_cosp = +2.0 * (Qw * Qz + Qx * Qy);
- T cosy_cosp = +1.0 - 2.0 * (Qy * Qy + Qz * Qz);
- Yaw[0] = ceres::atan2(siny_cosp, cosy_cosp);
- }
-
template
inline void InvertQuaterion(const T* Quaternion, T* QuaternionInv)
{
@@ -116,7 +70,7 @@ namespace libRSF
template
inline T NormalizeQuaternion(T* Quaternion)
{
- const T Norm = VectorDistance<4, T, T>(Quaternion, Quaternion);
+ const T Norm = VectorLength<4, T>(Quaternion);
Quaternion[0] /= Norm;
Quaternion[1] /= Norm;
Quaternion[2] /= Norm;
diff --git a/include/VectorMath.h b/include/VectorMath.h
index 4ebc36d..b594bd1 100644
--- a/include/VectorMath.h
+++ b/include/VectorMath.h
@@ -1,56 +1,98 @@
/***************************************************************************
- * libRSF - A Robust Sensor Fusion Libary
- *
- * Copyright (C) 2018 Chair of Automation Technology / TU Chemnitz
+ * libRSF - A Robust Sensor Fusion Library
+ *
+ * Copyright (C) 2019 Chair of Automation Technology / TU Chemnitz
* For more information see https://www.tu-chemnitz.de/etit/proaut/self-tuning
- *
+ *
* libRSF is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* libRSF is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with libRSF. If not, see .
- *
+ *
* Author: Tim Pfeifer (tim.pfeifer@etit.tu-chemnitz.de)
***************************************************************************/
+/**
+ * @file VectorMath.h
+ * @author Tim Pfeifer
+ * @date 07.03.2019
+ * @brief Derived vector type and some simple helper function.
+ * @copyright GNU Public License.
+ *
+ */
+
#ifndef VECTORMATH_H
#define VECTORMATH_H
+#include
+#include
#include
-namespace libisf2
+
+namespace libRSF
{
+ /** define vector and matrix types for libRSF */
+ typedef Eigen::Matrix Vector;
+ typedef Eigen::Matrix Vector1;
+ typedef Eigen::Matrix Vector2;
+ typedef Eigen::Matrix Vector3;
+ typedef Eigen::Matrix Vector4;
+ typedef Eigen::Matrix Vector6;
+ typedef Eigen::Matrix Vector9;
+ typedef Eigen::Matrix Vector12;
+ typedef Eigen::Matrix Vector15;
+
+ typedef Eigen::Matrix Matrix;
+ typedef Eigen::Matrix Matrix11;
+ typedef Eigen::Matrix Matrix22;
+ typedef Eigen::Matrix Matrix33;
+ typedef Eigen::Matrix Matrix44;
+
+ /** math with vector/matrix class */
+ template
+ Eigen::Matrix SquareRoot(Eigen::Matrix A)
+ {
+ Eigen::SelfAdjointEigenSolver> ES(A);
+ return ES.operatorSqrt();
+ }
+
+ template
+ Eigen::Matrix InverseSquareRoot(Eigen::Matrix A)
+ {
+ Eigen::SelfAdjointEigenSolver> ES(A);
+ return ES.operatorInverseSqrt();
+ }
+
+ /** vector math with raw pointers (compatible to ceres jet type) */
template
void VectorDifference(const T1* const Vector1, const T2* const Vector2, T1* Difference)
{
- for(int nDim = 0; nDim < Dimension; nDim++)
- {
- Difference[nDim] = Vector1[nDim] - Vector2[nDim];
- }
+ Eigen::Map > V1(Vector1);
+ Eigen::Map > V2(Vector2);
+ Eigen::Map > Diff(Difference);
+
+ Diff = V1 - V2;
}
template
T VectorLength(const T* const Vector)
{
- T SquaredSum = T(0.0);
-
- for(int nDim = 0; nDim < Dimension; nDim++)
- {
- SquaredSum += ceres::pow(Vector[nDim], 2);
- }
+ Eigen::Map > V(Vector);
+ T SquaredNorm = V.squaredNorm();
/** for stability of the derivation */
- if(SquaredSum < T(1e-10))
- SquaredSum += T(1e-10);
+ if(SquaredNorm < T(1e-10))
+ SquaredNorm += 1e-10;
- return ceres::sqrt(SquaredSum);
+ return ceres::sqrt(SquaredNorm);
}
template
@@ -58,8 +100,10 @@ namespace libisf2
{
T1 Difference[Dimension];
VectorDifference(Vector1, Vector2, Difference);
- return T1(VectorLength(Difference));
+ return VectorLength(Difference);
}
+
+ void RemoveColumn (Eigen::MatrixXd& Matrix, unsigned int ColToRemove);
}
#endif // VECTORMATH_H
diff --git a/include/error_models/GaussianComponent.h b/include/error_models/GaussianComponent.h
index 428a87d..f0050e5 100644
--- a/include/error_models/GaussianComponent.h
+++ b/include/error_models/GaussianComponent.h
@@ -34,6 +34,7 @@
#include
#include
+#include "../VectorMath.h"
namespace libRSF
{
@@ -48,7 +49,7 @@ namespace libRSF
~GaussianComponent() {};
- void setParamsStdDev(ceres::Vector StdDev, ceres::Vector Mean, ceres::Vector Weight)
+ void setParamsStdDev(Vector StdDev, Vector Mean, Vector Weight)
{
_Mean = Mean;
_Weight = Weight;
@@ -59,6 +60,24 @@ namespace libRSF
updateScaling();
}
+ void setParamsInformation(ceres::Matrix Information, Vector Mean, Vector Weight)
+ {
+ _Mean = Mean;
+ _Weight = Weight;
+ _SqrtInformation = SquareRoot(Information);
+
+ updateScaling();
+ }
+
+ void setParamsSqrtInformation(ceres::Matrix SqrtInformation, Vector Mean, Vector Weight)
+ {
+ _Mean = Mean;
+ _Weight = Weight;
+ _SqrtInformation = SqrtInformation;
+
+ updateScaling();
+ }
+
Eigen::Matrix getMean() const
{
return _Mean;
@@ -83,11 +102,12 @@ namespace libRSF
template
Eigen::Matrix < T, Dimension, 1 > getExponentialPart(T * const Error) const
{
- Eigen::Map > ErrorMap(Error);
+ Eigen::Map > ErrorMap(Error);
Eigen::Matrix WeightedError;
/** shift by mean */
WeightedError = ErrorMap + _Mean.template cast();
+
/** scale with information matrix */
WeightedError = _SqrtInformation.template cast() * WeightedError;
@@ -142,7 +162,7 @@ namespace libRSF
Covariance += (Errors.row(n) + _Mean).transpose() * (Errors.row(n) + _Mean) * Likelihoods(n);
}
Covariance.array() /= LikelihoodSum;
- _SqrtInformation = Covariance.inverse().llt().matrixL();
+ _SqrtInformation = InverseSquareRoot(Covariance);
/** check for degenerated SqrtInfo matrix */
if(!(_SqrtInformation.array().isFinite().all()))
diff --git a/include/error_models/GaussianMixture.h b/include/error_models/GaussianMixture.h
index 64655d6..adbbe10 100644
--- a/include/error_models/GaussianMixture.h
+++ b/include/error_models/GaussianMixture.h
@@ -35,6 +35,8 @@
#include
#include
+#include
+
#include "GaussianComponent.h"
#include "../StateData.h"
@@ -57,6 +59,11 @@ namespace libRSF
_Mixture.emplace_back(Gaussian);
}
+ void removeLastComponent()
+ {
+ _Mixture.pop_back();
+ }
+
void clear()
{
_Mixture.clear();
@@ -139,7 +146,6 @@ namespace libRSF
double LikelihoodChange = std::abs(LikelihoodSumOld - LikelihoodSumNew)/LikelihoodSumNew;
- /** remove small components */
if(Adaptive)
{
for(size_t m = 0; m < M; ++m)
@@ -175,6 +181,18 @@ namespace libRSF
return false;
}
+ /** variational bayesian inference */
+ void estimateWithVBI(std::vector &Data, double Nu);
+
+ void printParameter()
+ {
+ std::cout << "Mean" <<' ' << "StdDev" <<' ' << "Weight" <
Eigen::Matrix < T, Dimension, 1 > getExponentialPartOfComponent(size_t NumberOfComponent, T * const Error) const
diff --git a/include/factors/ConstantDriftFactor.h b/include/factors/ConstantDriftFactor.h
index 42490d9..8c16e27 100644
--- a/include/factors/ConstantDriftFactor.h
+++ b/include/factors/ConstantDriftFactor.h
@@ -34,7 +34,7 @@
#include
#include "MeasurementFactor.h"
-#include "../Geometry.h"
+#include "../VectorMath.h"
namespace libRSF
{
diff --git a/include/factors/OdometryFactor3D.h b/include/factors/OdometryFactor3D.h
index 44a620d..c7e6624 100644
--- a/include/factors/OdometryFactor3D.h
+++ b/include/factors/OdometryFactor3D.h
@@ -67,9 +67,9 @@ namespace libRSF
{
T PositionError[3];
T PositionErrorBody[3];
- T QuartOldInv [4];
- T QuartPose [4];
- T ZAxis[3], QuartOld[4], QuartYaw[4];
+ T QuatOldInv [4];
+ T QuatPose [4];
+ T ZAxis[3], QuatOld[4], QuatYaw[4];
Eigen::Matrix ErrorVector, Displacement;
@@ -78,17 +78,17 @@ namespace libRSF
ZAxis[1] = T(0.0);
ZAxis[2] = T(1.0);
- RotationBetween(ZAxis, Pos1, QuartPose);
+ RotationBetween(ZAxis, Pos1, QuatPose);
- QuartYaw[0] = ceres::cos(Yaw1[0] / 2.0);
- QuartYaw[1] = T(0.0);
- QuartYaw[2] = T(0.0);
- QuartYaw[3] = ceres::sin(Yaw1[0] / 2.0);
+ QuatYaw[0] = ceres::cos(Yaw1[0] / 2.0);
+ QuatYaw[1] = T(0.0);
+ QuatYaw[2] = T(0.0);
+ QuatYaw[3] = ceres::sin(Yaw1[0] / 2.0);
- ceres::QuaternionProduct(QuartPose, QuartYaw, QuartOld);
+ ceres::QuaternionProduct(QuatPose, QuatYaw, QuatOld);
/** invert quaternion */
- libRSF::InvertQuaterion(QuartOld, QuartOldInv);
+ libRSF::InvertQuaterion(QuatOld, QuatOldInv);
/** calculate translation */
PositionError[0] = Pos2[0] - Pos1[0];
@@ -96,7 +96,7 @@ namespace libRSF
PositionError[2] = Pos2[2] - Pos1[2];
/** rotate translation back in body frame */
- ceres::QuaternionRotatePoint(QuartOldInv, PositionError, PositionErrorBody);
+ ceres::QuaternionRotatePoint(QuatOldInv, PositionError, PositionErrorBody);
/** weight position error */
Displacement[0] = PositionErrorBody[0] ;
diff --git a/include/factors/PseudorangeFactor.h b/include/factors/PseudorangeFactor.h
index 9736587..bf1526c 100644
--- a/include/factors/PseudorangeFactor.h
+++ b/include/factors/PseudorangeFactor.h
@@ -34,7 +34,7 @@
#include
#include "MeasurementFactor.h"
-#include "../Geometry.h"
+#include "../VectorMath.h"
#include "RangeFactor.h"
/** physical constants for sagnac effect */
diff --git a/include/factors/RangeFactor.h b/include/factors/RangeFactor.h
index adf4812..692b9b5 100644
--- a/include/factors/RangeFactor.h
+++ b/include/factors/RangeFactor.h
@@ -34,7 +34,7 @@
#include
#include "MeasurementFactor.h"
-#include "../Geometry.h"
+#include "../VectorMath.h"
namespace libRSF
{
diff --git a/include/predictors/PredictOdometry.h b/include/predictors/PredictOdometry.h
index 06ab4d3..d76e56d 100644
--- a/include/predictors/PredictOdometry.h
+++ b/include/predictors/PredictOdometry.h
@@ -34,6 +34,7 @@
#include
#include "../Geometry.h"
+#include "../VectorMath.h"
namespace libRSF
{
@@ -44,18 +45,18 @@ namespace libRSF
void PredictOdometry4DOFGlobal(const T* PoseOld, T* PoseNew, const T* YawOld, T* YawNew, const ceres::Vector &Odometry, double DeltaTime)
{
Eigen::Matrix ZAxis, Axis;
- Eigen::Matrix QuartPose, QuartYaw, QuartOld;
+ Eigen::Matrix QuatPose, QuatYaw, QuatOld;
- QuartYaw << cos(YawOld[0]/2.0), T(0), T(0), sin(YawOld[0]/2.0);
+ QuatYaw << cos(YawOld[0]/2.0), T(0), T(0), sin(YawOld[0]/2.0);
ZAxis << T(0), T(0), T(1);
ceres::CrossProduct(ZAxis.data(), PoseOld, Axis.data());
- QuartPose[0] = ceres::DotProduct(ZAxis.data(), PoseOld) + VectorLength<3>(ZAxis.data())*VectorLength<3>(PoseOld);
- QuartPose[1] = Axis[0];
- QuartPose[2] = Axis[1];
- QuartPose[3] = Axis[2];
- ceres::QuaternionProduct(QuartPose.data(), QuartYaw.data(), QuartOld.data());
+ QuatPose[0] = ceres::DotProduct(ZAxis.data(), PoseOld) + VectorLength<3>(ZAxis.data())*VectorLength<3>(PoseOld);
+ QuatPose[1] = Axis[0];
+ QuatPose[2] = Axis[1];
+ QuatPose[3] = Axis[2];
+ ceres::QuaternionProduct(QuatPose.data(), QuatYaw.data(), QuatOld.data());
T Translation[3], TranslationRot[3];
@@ -63,7 +64,7 @@ namespace libRSF
Translation[1] = T(Odometry[1]*DeltaTime);
Translation[2] = T(Odometry[2]*DeltaTime);
- ceres::QuaternionRotatePoint(QuartOld.data(), Translation, TranslationRot);
+ ceres::QuaternionRotatePoint(QuatOld.data(), Translation, TranslationRot);
PoseNew[0] = TranslationRot[0] + PoseOld[0];
PoseNew[1] = TranslationRot[1] + PoseOld[1];
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 78850a1..a88e707 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -35,6 +35,7 @@ set(SOURCEFILES
FileAccess.cpp
Geometry.cpp
ListInTime.cpp
+ VectorMath.cpp
)
# factors of the graph
diff --git a/src/FactorGraphConfig.cpp b/src/FactorGraphConfig.cpp
index 2ce18c9..3d8ded8 100644
--- a/src/FactorGraphConfig.cpp
+++ b/src/FactorGraphConfig.cpp
@@ -75,6 +75,14 @@ namespace libRSF
{
RangeErrorModel.Type = ROBUST_STMM;
}
+ else if(strcmp(argv[ArgCounter], "stmm_vbi") == 0)
+ {
+ RangeErrorModel.Type = ROBUST_STMM_VBI;
+ }
+ else if(strcmp(argv[ArgCounter], "stsm_vbi") == 0)
+ {
+ RangeErrorModel.Type = ROBUST_STSM_VBI;
+ }
else
{
std::cerr << "Wrong range error model: " << argv[ArgCounter];
diff --git a/src/VectorMath.cpp b/src/VectorMath.cpp
index 2cae39d..70b7acf 100644
--- a/src/VectorMath.cpp
+++ b/src/VectorMath.cpp
@@ -1,23 +1,37 @@
/***************************************************************************
- * libRSF - A Robust Sensor Fusion Libary
- *
- * Copyright (C) 2018 Chair of Automation Technology / TU Chemnitz
+ * libRSF - A Robust Sensor Fusion Library
+ *
+ * Copyright (C) 2019 Chair of Automation Technology / TU Chemnitz
* For more information see https://www.tu-chemnitz.de/etit/proaut/self-tuning
- *
+ *
* libRSF is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* libRSF is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with libRSF. If not, see .
- *
+ *
* Author: Tim Pfeifer (tim.pfeifer@etit.tu-chemnitz.de)
***************************************************************************/
#include "VectorMath.h"
+
+namespace libRSF
+{
+ void RemoveColumn(Eigen::MatrixXd& Matrix, unsigned int ColToRemove)
+ {
+ unsigned int numRows = Matrix.rows();
+ unsigned int numCols = Matrix.cols()-1;
+
+ if( ColToRemove < numCols )
+ Matrix.block(0,ColToRemove,numRows,numCols-ColToRemove) = Matrix.rightCols(numCols-ColToRemove);
+
+ Matrix.conservativeResize(numRows,numCols);
+ }
+}
diff --git a/src/error_models/GaussianMixture.cpp b/src/error_models/GaussianMixture.cpp
index b6428ef..36a5f07 100644
--- a/src/error_models/GaussianMixture.cpp
+++ b/src/error_models/GaussianMixture.cpp
@@ -93,4 +93,188 @@ namespace libRSF
}
}
+ /** variational bayesian inference */
+ template <>
+ void GaussianMixture<1>::estimateWithVBI(std::vector &Data, double Nu)
+ {
+ /** only 1D problems !!! */
+ #define Dimension 1
+
+ /** set up problem */
+ size_t N = Data.size();
+ size_t M = _Mixture.size();
+
+ /** N x M matrix */
+ Eigen::MatrixXd Likelihood;
+ Likelihood.resize(N, M);
+
+ /** map data to eigen vector */
+ ErrorMatType DataVector = Eigen::Map(Data.data(), N, Dimension);
+ /** change sign to match own GMM definition */
+ DataVector.array() *= -1;
+
+ double DataMean = DataVector.array().mean();
+ double DataCov = (DataVector.array() - DataMean).square().sum()/(DataVector.size()-1);
+
+ /** convergence criterion */
+ double const LikelihoodTolerance = 1e-6;
+ int const MaxIterations = 1000;
+
+ /** pruning criterion */
+ double MinWeight = 1.0/N;
+
+ /** define priors */
+ double const Beta = 1e-6; /**< prior over mean */
+ double const V = DataCov/Nu; /**< Wishart prior for I */
+
+ /** initialize variables */
+ std::vector NuInfo;
+ std::vector VInfo;
+ std::vector InfoMean;
+ std::vector MeanMean;
+ std::vector Weights;
+
+ /** initialize estimated parameters */
+ for(size_t m = 0; m < M; ++m)
+ {
+ NuInfo.push_back(Nu);
+ VInfo.push_back(V);
+ InfoMean.push_back(0.0); /**< will be overwritten */
+ MeanMean.push_back(0.0); /**< will be overwritten */
+ Weights.push_back(_Mixture.at(m).getWeight()(0));
+ }
+
+ /** convergence criteria (not variational!)*/
+ double LikelihoodSumOld = 1e40;
+ double LikelihoodSumNew;
+
+ /** first likelihood estimation is not variational! */
+ LikelihoodSumNew = computeLikelihood(DataVector, Likelihood);
+
+ /** repeat until convergence or 100 iterations*/
+ int i;
+ for(i = 0; i < MaxIterations; ++i)
+ {
+ bool ReachedConvergence = false;
+ bool PerfomedRemove = false;
+
+ /** Expectation step */
+
+ /** pre-calculate some multi-use variables */
+ Eigen::VectorXd SumLike = Likelihood.colwise().sum();
+ Eigen::VectorXd SumLikeX = (Likelihood.array().colwise() * DataVector.array()).colwise().sum();
+ Eigen::VectorXd SumLikeXX = (Likelihood.array().colwise() * DataVector.array().square()).colwise().sum();
+
+ double MuMuEx, InfoEx, LogInfoEx;
+ /** iterate over GMM components */
+ for(size_t m = 0; m < M; ++m)
+ {
+
+ /** calculate mean */
+ InfoMean.at(m) = Beta + NuInfo.at(m) / VInfo.at(m) * SumLike(m);
+ MeanMean.at(m) = NuInfo.at(m) / VInfo.at(m) / InfoMean.at(m) * SumLikeX(m);
+
+ /** calculate variance */
+ NuInfo.at(m) = Nu + SumLike(m);
+ MuMuEx = 1.0/InfoMean.at(m) + MeanMean.at(m)*MeanMean.at(m);
+
+ VInfo.at(m) = V + SumLikeXX(m) - 2*MeanMean.at(m)*SumLikeX(m) + MuMuEx * SumLike(m);
+
+ /** variational likelihood */
+ InfoEx = NuInfo.at(m)/VInfo.at(m);
+ LogInfoEx = Eigen::numext::digamma(NuInfo.at(m)/2.0) + log(2.0) - log(VInfo.at(m));
+
+ Likelihood.col(m) = (LogInfoEx/2.0
+ + log(Weights.at(m))
+ - 0.5*InfoEx*
+ (
+ DataVector.array().square()
+ - 2.0 * MeanMean.at(m) * DataVector.array()
+ + MuMuEx
+ )
+ ).array().exp();
+ }
+
+ /** "Maximization step" */
+
+ /** remove NaNs */
+ Likelihood = (Likelihood.array().isFinite()).select(Likelihood, 0.0);
+
+ /** calculate relative likelihood */
+ LikelihoodSumNew = Likelihood.sum();
+ Eigen::VectorXd LikelihoodRowSum = Likelihood.rowwise().sum();
+
+ for(size_t m = 0; m < M; ++m)
+ {
+ Likelihood.col(m).array() /= LikelihoodRowSum.array();
+ }
+
+ /** remove NaNs (occur if sum of likelihoods is zero) */
+ Likelihood = (Likelihood.array().isFinite()).select(Likelihood, 1.0/M);
+
+ /** calculate weights */
+ for(size_t m = 0; m < M; ++m)
+ {
+ Weights.at(m)= Likelihood.col(m).sum() / Likelihood.rows();
+ }
+
+ /** remove useless components */
+ for(int m = M-1; m >=0 ; --m)
+ {
+ if(Weights.at(m) < MinWeight)
+ {
+ NuInfo.erase(NuInfo.begin() + m);
+ VInfo.erase(VInfo.begin() + m);
+ InfoMean.erase(InfoMean.begin() + m);
+ MeanMean.erase(MeanMean.begin() + m);
+ Weights.erase(Weights.begin() + m);
+ RemoveColumn(Likelihood,m);
+ _Mixture.erase(_Mixture.begin() + m);
+
+ M -= 1;
+ /** enforce an additional iteration after removal and reset likelihood */
+ PerfomedRemove = true;
+ LikelihoodSumNew = 1e40;
+ }
+ }
+
+ /** check for convergence */
+ double LikelihoodChange = std::abs(LikelihoodSumOld - LikelihoodSumNew)/LikelihoodSumNew;
+
+ if(LikelihoodChange < LikelihoodTolerance)
+ {
+ ReachedConvergence = true;
+ }
+ else
+ {
+ ReachedConvergence = false;
+ }
+
+ /** terminate loop */
+ if(ReachedConvergence && !PerfomedRemove)
+ {
+ break;
+ }
+ else
+ {
+ LikelihoodSumOld = LikelihoodSumNew;
+ }
+
+ }
+
+ /** update mixtures */
+ for(size_t m = 0; m < M; ++m)
+ {
+ Eigen::Matrix Mean;
+ Eigen::Matrix Info;
+ Eigen::Matrix Weight;
+
+ Mean << MeanMean.at(m);
+ Info << NuInfo.at(m)/VInfo.at(m);
+ Weight << Weights.at(m);
+
+ _Mixture.at(m).setParamsInformation(Info, Mean, Weight);
+ }
+
+ }
}