diff --git a/CMakeLists.txt b/CMakeLists.txt index 947ec901fc91899617a35ce9a5885d9ef15b8d5d..d86286f103b61f77bfed474e450c52e037f57a04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ find_package(catkin REQUIRED COMPONENTS iri_base_algorithm iri_adc_msgs) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) +find_package(Eigen3 REQUIRED) +find_package(Ceres REQUIRED) # ******************************************************************** # Add system and labrobotica dependencies here @@ -76,6 +78,8 @@ catkin_package( # ******************************************************************** include_directories(include) include_directories(${catkin_INCLUDE_DIRS}) +include_directories(${EIGEN3_INCLUDE_DIR}) +include_directories(${CERES_INCLUDE_DIRS}) # include_directories(${<dependency>_INCLUDE_DIRS}) ## Declare a cpp library @@ -88,6 +92,7 @@ add_executable(${PROJECT_NAME} src/adc_landmarks_slam_solver_alg.cpp src/adc_lan # Add the libraries # ******************************************************************** target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES}) +target_link_libraries(${PROJECT_NAME} ${CERES_LIBRARIES}) # target_link_libraries(${PROJECT_NAME} ${<dependency>_LIBRARIES}) # ******************************************************************** diff --git a/include/adc_landmarks_slam_solver_alg.h b/include/adc_landmarks_slam_solver_alg.h index 786fb6ad7716c8f97e46bd78179e283ac53fdefd..1a8e569bfa7b804056ac2d66a33564758a3dad33 100644 --- a/include/adc_landmarks_slam_solver_alg.h +++ b/include/adc_landmarks_slam_solver_alg.h @@ -26,9 +26,41 @@ #define _adc_landmarks_slam_solver_alg_h_ #include <iri_adc_landmarks_slam_solver/AdcLandmarksSlamSolverConfig.h> +#include <Eigen/Eigen> +#include "ceres/ceres.h" +#include "ceres/loss_function.h" + +#include "estimated_pose_residual_2D.h" +#include "odom_residual_2D.h" +#include "landmark_residual_2D.h" +#include "sensor_pose_residual_2D.h" //include adc_landmarks_slam_solver_alg main library +/** + * \struct LandmarkResidualData. + * + * \brief A struct with the necessary information to create a LandmarkErr object. + * + */ +typedef struct { + double r; ///< Range at detection + double theta; ///< Angle at detection +}LandmarkResidualData; + +/** + * \struct FrameData. + * + * \brief A struct with all the Ceres problem information. + * + */ +typedef struct { + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + Eigen::Vector3d robot_state; ///< X and y position and orientation. + ros::Time ts; ///< Time stamp + std::vector<LandmarkResidualData> landmark_residuals_data; ///< The LandmarkResidualData information at that frame. +}FrameData; + /** * \brief IRI ROS Specific Driver Class * @@ -36,6 +68,12 @@ */ class AdcLandmarksSlamSolverAlgorithm { + private: + ceres::Solver::Options solv_opt_; ///< Ceres solver options. + ceres::Problem problem_; ///< Ceres problem. + ceres::Covariance::Options cov_opt_; ///< Ceres covariance options. + + std::list<FrameData> frames_data_; ///< Frames information. protected: /** * \brief define config type @@ -48,6 +86,46 @@ class AdcLandmarksSlamSolverAlgorithm // private attributes and methods public: + /** + * \brief Function to solve the problem. + * + * \param _print_summary Flag to print ceres summary (default false). + * + */ + void solve_problem(bool _print_summary = false); + + /** + * \brief Function to set solver options. + * + * \param _solv_opt Options to be set. + * + */ + void set_ceres_solver_options(const ceres::Solver::Options& _solv_opt); + + /** + * \brief Function to get solver options. + * + * \param _solv_opt Solver options returned. + * + */ + void get_ceres_solver_options(ceres::Solver::Options& _solv_opt); + + /** + * \brief Function to set covariance options. + * + * \param _cov_opt Options to be set. + * + */ + void set_covariance_options(const ceres::Covariance::Options& _cov_opt); + + /** + * \brief Function to get covariance options. + * + * \param _cov_opt Covariance options returned. + * + */ + void get_covariance_options(ceres::Covariance::Options& _cov_opt); + /** * \brief define config type * diff --git a/include/estimated_pose_residual_2D.h b/include/estimated_pose_residual_2D.h new file mode 100644 index 0000000000000000000000000000000000000000..5e5720c0c3f4c166491e2678efb5e846ef0b6e73 --- /dev/null +++ b/include/estimated_pose_residual_2D.h @@ -0,0 +1,62 @@ +#include "ceres/ceres.h" +#include <math.h> +#include <Eigen/Eigen> + + +/** + * \brief Class to define the residuals when adding an estimated pose. + * + */ +class EstimatedPoseResidual2D { + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + /** + * \brief The constructor. + * + * \param _estimated_pose Vector with estimated x, y and yaw. + * + * \param _sigma_x Standard deviation of X coordenate. + * + * \param _sigma_y Standard deviation of Y coordenate. + * + * \param _sigma_yaw Standard deviation of orientation. + */ + EstimatedPoseResidual2D(const Eigen::Vector3d& _estimated_pose, const double& _sigma_x, const double& _sigma_y, const double& _sigma_yaw) + { + x_ = _estimated_pose(0); + y_ = _estimated_pose(1); + yaw_ = _estimated_pose(2); + sigma_x_ = _sigma_x; + sigma_y_ = _sigma_y; + sigma_yaw_ = _sigma_yaw; + } + + template <typename T> + /** + * \brief Function to calculate the error for Ceres optimization. + * + * \return True if all is ok. + */ + bool operator()(const T* const _estimated_robot_pose, + T* residual) const { + + T angle_res = T(yaw_) - _estimated_robot_pose[2]; + while (angle_res >= M_PI) + angle_res -= 2*M_PI; + while (angle_res < -M_PI) + angle_res += 2*M_PI; + + residual[0] = (T(x_) - _estimated_robot_pose[0])/T(sigma_x_); + residual[1] = (T(y_) - _estimated_robot_pose[1])/T(sigma_y_); + residual[2] = angle_res/T(sigma_yaw_); + return true; + } + + private: + double x_; ///< The estimated x. + double y_; ///< The estimated y. + double yaw_; ///< The estimated yaw. + double sigma_x_; ///< Estimate sigma in X direction. + double sigma_y_; ///< Estimate sigma in Y direction. + double sigma_yaw_; ///< Estimate angular sigma. +}; \ No newline at end of file diff --git a/include/landmark_residual_2D.h b/include/landmark_residual_2D.h new file mode 100644 index 0000000000000000000000000000000000000000..384ce5d3ab622d8553644c7cf3f1b14969fed440 --- /dev/null +++ b/include/landmark_residual_2D.h @@ -0,0 +1,84 @@ +#include "ceres/ceres.h" +#include <math.h> +#include <Eigen/Eigen> + +#define MAX_ROLL 0.2 +/** + * \brief Class to define the residuals between a robot pose and a landmark detected. + * + */ +class LandmarkResidual2D { + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + /** + * \brief The constructor. + * + * \param _r Detection range. + * + * \param _theta Detection angle. + * + * \param _sigma_r Sensor radial standard deviation. + * + * \param _sigma_th Sensor angular standard deviation. + * + * \param _sensor_roll Sensor roll to know if it is inverted. + * + */ + LandmarkResidual2D(const double& _r, const double& _theta, const double& _sigma_r, const double& _sigma_th, const double& _sensor_roll) + { + r_ = _r; + theta_ = _theta; + sensor_sigma_r_ = _sigma_r; + sensor_sigma_th_ = _sigma_th; + sensor_inverted_ = (std::fabs(_sensor_roll) > MAX_ROLL); + } + + template <typename T> + /** + * \brief Function to calculate the error for Ceres optimization. + * + * \return True if all is ok. + */ + bool operator()(const T* const _robot_pose, + const T* const _landmark_pos, + const T* const _tf_base_sensor, + T* residual) const { + + Eigen::Matrix<T,2,1> dist; + Eigen::Matrix<T,3,1> laser_local, laser_global, landmark_global, landmark_local; + Eigen::Matrix<T,3,1> trans; + Eigen::Matrix<T,3,3> rot; + + rot << cos(_robot_pose[2]), -sin(_robot_pose[2]), T(0.0), + sin(_robot_pose[2]), cos(_robot_pose[2]), T(0.0), + T(0.0), T(0.0), T(1.0); + trans << _robot_pose[0], _robot_pose[1], _robot_pose[2]; + laser_local << _tf_base_sensor[0], _tf_base_sensor[1], _tf_base_sensor[2]; + laser_global = rot*laser_local + trans; + + landmark_global << _landmark_pos[0], _landmark_pos[1], T(0.0); + trans << laser_global[0], laser_global[1], laser_global[2]; + rot << cos(laser_global[2]), sin(laser_global[2]), T(0.0), + -sin(laser_global[2]), cos(laser_global[2]), T(0.0), + T(0.0), T(0.0), T(1.0); + landmark_local = rot*(landmark_global - trans); + + dist << landmark_local(0), landmark_local(1)*(sensor_inverted_? T(-1): T(1)); + + T angle_res = T(theta_) - T(atan2(dist(1), dist(0))); + while (angle_res >= M_PI) + angle_res -= 2*M_PI; + while (angle_res < -M_PI) + angle_res += 2*M_PI; + + residual[0] = (T(r_) - dist.norm())/T(sensor_sigma_r_); + residual[1] = angle_res/T(sensor_sigma_th_); + return true; + } + private: + double r_; ///< The measured distance to the reflector center. + double theta_; ///< The measured angle to the reflector. + double sensor_sigma_r_; ///< Sensor radial sigma. + double sensor_sigma_th_; ///< Sensor angular sigma. + bool sensor_inverted_; ///<If the sensor is inverted +}; \ No newline at end of file diff --git a/include/odom_residual_2D.h b/include/odom_residual_2D.h new file mode 100644 index 0000000000000000000000000000000000000000..09494b5c99eb7a90c81cbd288f310fc51018d9f8 --- /dev/null +++ b/include/odom_residual_2D.h @@ -0,0 +1,104 @@ +#include "ceres/ceres.h" +#include <math.h> +#include <Eigen/Eigen> + +/** + * \brief Class to define the residuals between a two consecutives robot poses. + * + */ +class OdomResidual2D { + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + /** + * \brief The constructor. + * + * \param _last_odom Vector with last robot x, y and yaw values from odometry. + * + * \param _new_odom Vector with new robot x, y and yaw values from odometry. + * + * \param _fxy Linear factor to set linear standard deviation. + * + * \param _fth Angular factor to set angular standard deviation. + * + * \param _fxyth linear factor to increase angular standard deviation. + * + * \param _sigma_min Minimum standard deviation. + * + */ + OdomResidual2D(const Eigen::Vector3d& last_odom, const Eigen::Vector3d& new_odom, const double& _fxy, const double& _fth, const double& _fxyth, const double& _sigma_min) + { + Eigen::Matrix<double,3,1> inc; + Eigen::Matrix<double,3,3> rot; + + inc(0) = new_odom(0) - last_odom(0); + inc(1) = new_odom(1) - last_odom(1); + inc(2) = new_odom(2) - last_odom(2); + + while (inc(2) >= M_PI) + inc(2) -= 2*M_PI; + while (inc(2) < -M_PI) + inc(2) += 2*M_PI; + + rot << cos(-last_odom[2]), -sin(-last_odom[2]), 0.0, + sin(-last_odom[2]), cos(-last_odom[2]), 0.0, + 0.0, 0.0, 1.0; + + inc = rot*inc; + + x_inc_ = inc(0); + y_inc_ = inc(1); + yaw_inc_ = inc(2); + + sigma_x_ = std::fabs(x_inc_)*_fxy + _sigma_min; + sigma_y_ = std::fabs(y_inc_)*_fxy + _sigma_min; + sigma_th_ = std::fabs(yaw_inc_)*_fth + _sigma_min + std::sqrt(x_inc_*x_inc_ + y_inc_*y_inc_)*_fxyth; + } + + template <typename T> + /** + * \brief Function to calculate the error for Ceres optimization. + * + * \return True if all is ok. + */ + bool operator()(const T* const _old_robot_pose, + const T* const _new_robot_pose, + T* residual) const { + + Eigen::Matrix<T,3,1> inc; + Eigen::Matrix<T,3,3> rot; + + inc(0) = (_new_robot_pose[0] - _old_robot_pose[0]); + inc(1) = (_new_robot_pose[1] - _old_robot_pose[1]); + inc(2) = (_new_robot_pose[2] - _old_robot_pose[2]); + + while (inc(2) >= M_PI) + inc(2) -= 2*M_PI; + while (inc(2) < -M_PI) + inc(2) += 2*M_PI; + + rot << cos(-_old_robot_pose[2]), -sin(-_old_robot_pose[2]), T(0.0), + sin(-_old_robot_pose[2]), cos(-_old_robot_pose[2]), T(0.0), + T(0.0), T(0.0), T(1.0); + + inc = rot*inc; + + T angle_res = T(yaw_inc_) - inc(2); + while (angle_res >= M_PI) + angle_res -= 2*M_PI; + while (angle_res < -M_PI) + angle_res += 2*M_PI; + + residual[0] = (T(x_inc_) - inc(0))/T(sigma_x_); + residual[1] = (T(y_inc_) - inc(1))/T(sigma_y_); + residual[2] = angle_res/T(sigma_th_); + return true; + } + + private: + double x_inc_; ///< The measured x increment. + double y_inc_; ///< The measured y increment. + double yaw_inc_; ///< The measured yaw increment. + double sigma_x_; ///< Odometry sigma in X direction. + double sigma_y_; ///< Odometry sigma in Y direction. + double sigma_th_; ///< Odometry angular sigma. +}; \ No newline at end of file diff --git a/include/sensor_pose_residual_2D.h b/include/sensor_pose_residual_2D.h new file mode 100644 index 0000000000000000000000000000000000000000..d794da6e7140875777ae4c5fc3a444d845943817 --- /dev/null +++ b/include/sensor_pose_residual_2D.h @@ -0,0 +1,57 @@ +#include "ceres/ceres.h" +#include <math.h> +#include <Eigen/Eigen> + +/** + * \brief Class to define the residuals of the laser pose. + * + */ +class SensorPoseResidual2D { + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + /** + * \brief The constructor. + * + * \param _measured_pose The measured sensor pose from robot base link + * + * \param _sigma_xy Linear standard deviation of the measured pose. + * + * \param _sigma_th Angular standard deviation of the measured pose. + */ + SensorPoseResidual2D(const Eigen::Vector3d& _measured_pose, const double& _sigma_xy, const double& _sigma_th) + { + x_ = _measured_pose(0); + y_ = _measured_pose(1); + yaw_ = _measured_pose(2); + sigma_xy_ = _sigma_xy; + sigma_th_ = _sigma_th; + } + + template <typename T> + /** + * \brief Function to calculate the error for Ceres optimization. + * + * \return True if all is ok. + */ + bool operator()(const T* const _tf_base_sensor, + T* residual) const { + + T angle_res = T(yaw_) - _tf_base_sensor[2]; + while (angle_res >= M_PI) + angle_res -= 2*M_PI; + while (angle_res < -M_PI) + angle_res += 2*M_PI; + + residual[0] = (T(x_) - _tf_base_sensor[0])/T(sigma_xy_); + residual[1] = (T(y_) - _tf_base_sensor[1])/T(sigma_xy_); + residual[2] = angle_res/T(sigma_th_); + return true; + } + + private: + double x_; ///< The measured x. + double y_; ///< The measured y. + double yaw_; ///< The measured yaw. + double sigma_xy_; ///< Measure sigma in XY directions. + double sigma_th_; ///< Measure angular sigma. +}; \ No newline at end of file diff --git a/src/adc_landmarks_slam_solver_alg.cpp b/src/adc_landmarks_slam_solver_alg.cpp index d2e3c72a902a2afde45e4df1cd76dac48f7fabae..5689f306df68ec1a8b01fcc24e82a23ff07420b9 100644 --- a/src/adc_landmarks_slam_solver_alg.cpp +++ b/src/adc_landmarks_slam_solver_alg.cpp @@ -21,3 +21,30 @@ void AdcLandmarksSlamSolverAlgorithm::config_update(Config& config, uint32_t lev } // AdcLandmarksSlamSolverAlgorithm Public API +void AdcLandmarksSlamSolverAlgorithm::solve_problem(bool _print_summary) +{ + ceres::Solver::Summary summary; + ceres::Solve(this->solv_opt_, &(this->problem_), &summary); + if (_print_summary) + std::cout << summary.FullReport() << std::endl; +} + +void AdcLandmarksSlamSolverAlgorithm::set_ceres_solver_options(const ceres::Solver::Options& _solv_opt) +{ + this->solv_opt_ = _solv_opt; +} + +void AdcLandmarksSlamSolverAlgorithm::get_ceres_solver_options(ceres::Solver::Options& _solv_opt) +{ + _solv_opt = this->solv_opt_; +} + +void AdcLandmarksSlamSolverAlgorithm::set_covariance_options(const ceres::Covariance::Options& _cov_opt) +{ + this->cov_opt_ =_cov_opt; +} + +void AdcLandmarksSlamSolverAlgorithm::get_covariance_options(ceres::Covariance::Options& _cov_opt) +{ + _cov_opt = this->cov_opt_; +}