.
\ No newline at end of file
diff --git a/src/ground_slam/README.md b/src/ground_slam/README.md
new file mode 100644
index 0000000..9c4fee8
--- /dev/null
+++ b/src/ground_slam/README.md
@@ -0,0 +1,138 @@
+GroundSLAM: A Robust Visual SLAM System for Warehouse Robots Using Ground Textures
+ Kuan Xu1,
+ Zheng Yang1,
+ Lihua Xie1,
+ Chen Wang2
+
+
+
+ 1: Centre for Advanced Robotics Technology Innovation (CARTIN), Nanyang Technological University
+ 2: Spatial AI & Robotics (SAIR) Lab, Computer Science and Engineering, University at Buffalo
+
+
+
+
+
+ 🎥 [Youtube] |
+ 🎥 [Bilibili]
+ 📊 [Supplementary]
+
+
+
+
+
+
+GroundSLAM is a novel **feature-free** and **ground-texture-based** SLAM system for **the warehouse robot**. Our system can provide robust pose estimation and localization in environments with many **dynamic** objects or **open spaces**, such as warehouses, which is very challenging for localization systems with a forward-facing camera or LiDAR. GroundSLAM consists of three components: feature-free visual odometry, ground-texture-based loop detection and map optimization, and map reuse. Specifically, we introduce a kernel cross-correlator for **image-level pose tracking**, loop detection, and map reuse to improve localization accuracy and robustness, and incorporate adaptive pruning strategies to enhance efficiency. Due to these specific designs, GroundSLAM more **robust** and **accurate** when dealing with ground images with **few textures** or with many **repetitive patterns** than the feature-based methods.
+
+
+
+**Video:**
+
+
+
+
+
+
+## Test Environment
+### Dependencies
+* OpenCV 4.2
+* Eigen 3
+* Ceres 2.0.0
+* FFTW3
+* ROS noetic
+* Boost
+* yaml-cpp
+* VTK
+
+## Build
+```
+ cd ~/catkin_ws/src
+ git clone https://github.com/sair-lab/GroundSLAM.git
+ cd ../
+ catkin_make
+ source ~/catkin_ws/devel/setup.bash
+```
+
+## Run
+
+Modify the configuration file in [configs](configs) and then run
+
+```
+rosrun ground_slam ground_slam src/GroundSLAM/configs/your_config.yaml
+```
+
+## Data
+
+### PathTex Dataset
+
+
+
+
+Our data collection platform is a modified Weston SCOUT Robot. The robot is equipped with an IDS uEye monocular camera, which is positioned at the bottom and facing downward, placed at a height of 0.1m above the ground. To ensure constant illumination, a set of LED lights are arranged around the camera. For ground truth, a prism is installed on the top of the robot, and its position is tracked by a Leica Nova MS60 MultiStation laser tracker.
+
+We collect the data of 10 common ground textures, including 6 $\color{lightblue}{outdoor}$ textures and 4 $\color{red}{indoor}$ textures. The table below provides detailed information and download links for each sequence. The **camera parameters** can be found [here](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EQqXW2eDcXNAkBWlSGgq6bgBe2yYdjdcuSJ8HEFey3haGg?e=nOg2ym).
+
+
+
+
+Sequence Name|Total Size|Total Images|Download Link
+:--:|:--:|:--:|:--:
+Brick_seq1|1.0g|3119|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EUMENxrPbQBFmzAQmfhMw4YB4df6_uq6ua7H1fo9A7NC7g?e=5Mzy9Q)
+Brick_seq2|0.9g|5328|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EY1-hFr2taZJqjxE4k6-W6oB-XenzRAk_RW8DME6VvqUmg?e=PSQEi6)
+Carpet1_seq1|1.7g|8458|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/ETuQX1ePORdKmQJ_wFXT1mUBS6MqJ3LoZ-eeUsDhhXouug?e=AmMgBX)
+Carpet1_seq2|1.7g|8499|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EQwzCVxFMT1Hh9BMMNljwSgBM27RXd7v8gdM3KMNWm5mDQ?e=aSoH1v)
+Carpet2_seq1|3.0g|15481|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EZ6Y6xsmk6xBjkkU7JNrVQIB_zTovWY8-AOPdW7AN7XJLw?e=GvlK03)
+Carpet3_seq1|0.7g|4500|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EXxxHyPOvsFBh8judFQqhtEB-kI6uBzFmKcCiNDZZBzBIw?e=917b5c)
+Carpet3_seq2|0.7g|4385|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EQHoNL0xtpdKj6e6mhBBLyEBl-7s5N0cJ6U47jzT1tJheg?e=ot4hSH)
+Carpet3_seq3|1.0g|6428|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EcE3o-HN0UJEpc57_EJM3kcBSVSIQ8M7oCjkWsZlUQRKLw?e=qG8MUM)
+Coarse_asphalt_seq1|1.2g|5897|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EQbDBVemPH9Cp7bqV2ZwfFIBD_gsHQ-d3atPfSY8__6DKQ?e=ASludI)
+Concrete_seq1|1.0g|5850|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EcIYh8RIhClKs7ogwgFmS_QBlflxcx-eR33BwRVerNXFgg?e=8McLDA)
+Concrete_seq2|0.9g|5975|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EbNJ5FdpQSJMqK-2zyoEPNoBNCO4ZzhuzjzTL2m1AYZQKQ?e=feQNUX)
+Fine_asphalt_seq1|1.1g|5119|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EY22RWOcM89ApAYEr0E5I5EB6QhINnL5iGsbgli8INjuXg?e=HArfqb)
+Fine_asphalt_seq2|1.3g|11897|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/ERBTClOvkpBHgNeAEc5co4sBI5E-ubQ2MqNFwlfys07mbQ?e=WDd0uP)
+Granite_tiles_seq1|1.2g|7194|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/ERNkKMy_nvFFmORT7LU_MxEB7TDzhAsNKEEfBf_cNG4Zbg?e=sjDkYW)
+Granite_tiles_seq2|1.6g|10633|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EU-8ocb82WZDpErJuOGfR6oBbQwXEpAzR-nykA6wXk9zeg?e=Vx4P1t)
+Gravel_road1_seq1|0.8g|4883|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/ERRQQR0VTCVEqSdo9pM85zwBNMQFRBcgev4VVIZtjg68Vg?e=KFxJST)
+Gravel_road2_seq1|2.1g|11776|[Link](https://entuedu-my.sharepoint.com/:u:/g/personal/kuan_xu_staff_main_ntu_edu_sg/EZS9BNL-ct1Nu3_Ag1Z0YuIBt6ah02i8vojro4eHexs4ww?e=hQOLIF)
+
+
+### Run with Your Data
+
+The data should be organized in the following format:
+```
+dataroot
+├── image_names.txt
+├── rgb
+│ ├── 00001.png
+│ ├── 00002.png
+│ ├── 00003.png
+│ └── ......
+└── times.txt
+```
+where `image_names.txt` contains the image names in `/dataroot/rgb` and `times.txt` contains the corresponding double type timestamps.
+
+## Experiments
+
+### Data Association
+
+
+
+We compare the data association of our system with ORB and SIFT on the HD Ground dataset. The numbers of features and matching inliers are given. For our method, the correction results are projected to three coordinate axes and represent the estimation of the 3-DOF movement. The vertical axis is the confidence of estimated movement on the horizontal axis. The higher the value of the peak relative to other positions, the greater the confidence of motion estimation. The results show that the data association of our system is more stable for various ground texture images.
+
+### Visual Odometry
+
+

+

+
+
+This experiment is conducted on our PathTex dataset. The left figure shows the trajectories produced by our system and GT-SLAM on 4 sequences. The right figure provides the comparison of error distributions of different systems on the Gravel_road2_seq1 sequence, where the vertical axis is the proportion of pose errors that are less than the given error threshold on the horizontal axis.
+
+### Loop Closure
+
+
+

+

+
+
+These two figures show the performance difference of GroundSLAM with and without loop correction on the Fine_asphalt_seq2 sequence. It is seen that the pose errors are significantly decreased after the loop correction.
diff --git a/src/ground_slam/cmake/FindEigen3.cmake b/src/ground_slam/cmake/FindEigen3.cmake
new file mode 100755
index 0000000..0bb8c71
--- /dev/null
+++ b/src/ground_slam/cmake/FindEigen3.cmake
@@ -0,0 +1,94 @@
+# - Try to find Eigen3 lib
+#
+# This module supports requiring a minimum version, e.g. you can do
+# find_package(Eigen3 3.1.2)
+# to require version 3.1.2 or newer of Eigen3.
+#
+# Once done this will define
+#
+# EIGEN3_FOUND - system has eigen lib with correct version
+# EIGEN3_INCLUDE_DIR - the eigen include directory
+# EIGEN3_VERSION - eigen version
+
+# Copyright (c) 2006, 2007 Montel Laurent,
+# Copyright (c) 2008, 2009 Gael Guennebaud,
+# Copyright (c) 2009 Benoit Jacob
+# Redistribution and use is allowed according to the terms of the 2-clause BSD license.
+
+
+if(NOT Eigen3_FIND_VERSION)
+ if(NOT Eigen3_FIND_VERSION_MAJOR)
+ set(Eigen3_FIND_VERSION_MAJOR 2)
+ endif(NOT Eigen3_FIND_VERSION_MAJOR)
+ if(NOT Eigen3_FIND_VERSION_MINOR)
+ set(Eigen3_FIND_VERSION_MINOR 91)
+ endif(NOT Eigen3_FIND_VERSION_MINOR)
+ if(NOT Eigen3_FIND_VERSION_PATCH)
+ set(Eigen3_FIND_VERSION_PATCH 0)
+ endif(NOT Eigen3_FIND_VERSION_PATCH)
+
+ set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
+endif(NOT Eigen3_FIND_VERSION)
+
+macro(_eigen3_check_version)
+ file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
+
+ string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
+ set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
+ string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
+ set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
+ string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
+ set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")
+
+ set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
+ if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
+ set(EIGEN3_VERSION_OK FALSE)
+ else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
+ set(EIGEN3_VERSION_OK TRUE)
+ endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
+
+ if(NOT EIGEN3_VERSION_OK)
+
+ message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
+ "but at least version ${Eigen3_FIND_VERSION} is required")
+ endif(NOT EIGEN3_VERSION_OK)
+endmacro(_eigen3_check_version)
+
+if (EIGEN3_INCLUDE_DIR)
+
+ # in cache already
+ _eigen3_check_version()
+ set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})
+
+else (EIGEN3_INCLUDE_DIR)
+
+ # specific additional paths for some OS
+ if (WIN32)
+ set(EIGEN_ADDITIONAL_SEARCH_PATHS ${EIGEN_ADDITIONAL_SEARCH_PATHS} "C:/Program Files/Eigen/include" "C:/Program Files (x86)/Eigen/include")
+ endif(WIN32)
+
+ find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
+ PATHS
+ ${CMAKE_INSTALL_PREFIX}/include
+ ${EIGEN_ADDITIONAL_SEARCH_PATHS}
+ ${KDE4_INCLUDE_DIR}
+ PATH_SUFFIXES eigen3 eigen
+ )
+
+ if(EIGEN3_INCLUDE_DIR)
+ _eigen3_check_version()
+ endif(EIGEN3_INCLUDE_DIR)
+
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)
+
+ mark_as_advanced(EIGEN3_INCLUDE_DIR)
+
+endif(EIGEN3_INCLUDE_DIR)
+
+if(EIGEN3_FOUND)
+ include_directories(${EIGEN3_INCLUDE_DIR})
+ link_directories(${EIGEN3_LIBRARIES})
+else(EIGEN3_FOUND)
+ MESSAGE(FATAL_ERROR "Eigen3 library not found")
+endif(EIGEN3_FOUND)
\ No newline at end of file
diff --git a/src/ground_slam/cmake/FindFFTW3.cmake b/src/ground_slam/cmake/FindFFTW3.cmake
new file mode 100755
index 0000000..ff45261
--- /dev/null
+++ b/src/ground_slam/cmake/FindFFTW3.cmake
@@ -0,0 +1,18 @@
+# - Find FFTW3
+# Find the native FFTW3 includes and library
+#
+# FFTW3_INCLUDE_DIRS - where to find fftw3.h
+# FFTW3_LIBRARIES - List of libraries when using FFTW3.
+# FFTW3_FOUND - True if FFTW3 found.
+
+if (FFTW3_INCLUDE_DIRS)
+ set(FFTW3_FIND_QUIETLY TRUE)
+endif()
+
+find_path(FFTW3_INCLUDE_DIRS fftw3.h)
+find_library(FFTW3_LIBRARIES NAMES fftw3)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFTW3 DEFAULT_MSG FFTW3_LIBRARIES FFTW3_INCLUDE_DIRS)
+
+mark_as_advanced(FFTW3_LIBRARIES FFTW3_INCLUDE_DIRS)
diff --git a/src/ground_slam/configs/config_HD.yaml b/src/ground_slam/configs/config_HD.yaml
new file mode 100644
index 0000000..9749322
--- /dev/null
+++ b/src/ground_slam/configs/config_HD.yaml
@@ -0,0 +1,49 @@
+dataset:
+ camera_config: /media/zheng/xukuanssd/NTU/ground_texture/HD_ground/camera.yaml
+ dataroot: /media/zheng/xukuanssd/NTU/ground_texture/HD_ground/ramp_rubber/database/seq0033
+ image_dir_name: rgb
+
+correlation_flow:
+ gaussian:
+ sigma: 0.2
+ height: 1200
+ kernel: 0 # {0: "polynomial", 1: "gaussian"}
+ lambda: 0.1
+ polynomial:
+ offset: 0.1
+ power: 3
+ rotation_channel: 480
+ rotation_divisor: 720
+ width: 1600
+
+keyframe_selection:
+ lower_response_thr: 30
+ max_angle: 6.3
+ max_distance: 0.01
+ upper_response_thr: 90
+
+map:
+ grid_scale: 0.1 # the true size of one grid
+
+loop_closure:
+ to_find_loop: true
+ angle_response_thr: 60
+ distance_thr: 5
+ frame_gap_thr: 100
+ position_response_thr: 60
+
+map_sticther:
+ cell_size: 1000
+ stitch_map: true
+
+visualization:
+ frame_id: map
+ topic:
+ image: /kcc_slam/image
+ frame_pose: /kcc_slam/frame_pose
+ kcc_pose: /kcc_slam/kcc_pose
+ map: /kcc_slam/occupancy_map
+
+saving:
+ save_pose: true
+ saving_root: /home/xukuan/debug/kcc
diff --git a/src/ground_slam/configs/config_geekplus.yaml b/src/ground_slam/configs/config_geekplus.yaml
new file mode 100644
index 0000000..ff333d3
--- /dev/null
+++ b/src/ground_slam/configs/config_geekplus.yaml
@@ -0,0 +1,50 @@
+dataset:
+ dataroot: "/home/zheng/datasets/loopclosure/"
+ camera_config: "/home/zheng/datasets/loopclosure/camera.yaml"
+ image_dir_name: ""
+
+correlation_flow:
+ width: 448
+ height: 448
+ rotation_divisor: 720 #720 # Higher means higher resolution, but slower
+ rotation_channel: 480 #480 # Lower means faster. Too lower, e.g., less than 10, may produce error about 1/rotation_divisor*360 degrees. 64 may work well.
+ lambda: 0.1 # For regularization
+ kernel: 0 #1 # {0: "polynomial", 1: "gaussian"}
+ polynomial: # (X^T Y + offset).pow(power)
+ offset: 0.1
+ power: 3
+ gaussian: # exp(-(|X-Y|^2/(2*sigma^2)))
+ sigma: 0.2 # For Gaussian Kernel
+
+keyframe_selection:
+ to_find_loop: true
+ max_distance: 0.2 # in normalized plane, real size equal max_distance*camera_height
+ max_angle: 0.5 # rad
+ lower_response_thr: 30
+ upper_response_thr: 60
+
+map:
+ grid_scale: 0.1 # the true size of one grid
+
+loop_closure:
+ to_find_loop: true
+ position_response_thr: 60 # this should be tuned together with width and height: higher width*height -> higher threshold
+ angle_response_thr: 60 # this should be tuned together with rotation_divisor and rotation_channel: higher divisor*channel -> higher threshold
+ frame_gap_thr: 100
+ distance_thr: 5 # in normalized plane, real size equal distance_thr*camera_height
+
+map_sticther:
+ stitch_map: true # whether to stitch map
+ cell_size: 1000
+
+visualization:
+ frame_id: "map"
+ topic:
+ image: /kcc_slam/undistort_image
+ kcc_pose: "/kcc_slam/kcc_pose"
+ frame_pose: "/kcc_slam/frame_pose"
+ map: "/kcc_slam/occupancy_map"
+
+saving:
+ save_pose: true
+ saving_root: "/home/zheng/projects/ros_ws/src/kcc_slam/saving/experiments"
\ No newline at end of file
diff --git a/src/ground_slam/configs/config_ntu.yaml b/src/ground_slam/configs/config_ntu.yaml
new file mode 100644
index 0000000..0678a70
--- /dev/null
+++ b/src/ground_slam/configs/config_ntu.yaml
@@ -0,0 +1,49 @@
+dataset:
+ camera_config: /media/data/datasets/ntu/NTU_Ground_Texture_Dataset/camera.yaml
+ dataroot: /media/data/datasets/ntu/NTU_Ground_Texture_Dataset/brick-V3
+ image_dir_name: rgb
+
+correlation_flow:
+ gaussian:
+ sigma: 0.2
+ height: 480
+ kernel: 0 # {0: "polynomial", 1: "gaussian"}
+ lambda: 0.1
+ polynomial:
+ offset: 0.1
+ power: 3
+ rotation_channel: 480
+ rotation_divisor: 720
+ width: 640
+
+keyframe_selection:
+ lower_response_thr: 30
+ max_angle: 0.052359877
+ max_distance: 0.4
+ upper_response_thr: 90
+
+map:
+ grid_scale: 0.1 # the true size of one grid
+
+loop_closure:
+ to_find_loop: true
+ angle_response_thr: 60
+ distance_thr: 5
+ frame_gap_thr: 100
+ position_response_thr: 60
+
+map_sticther:
+ cell_size: 1000
+ stitch_map: true
+
+visualization:
+ frame_id: map
+ topic:
+ image: /kcc_slam/image
+ frame_pose: /kcc_slam/frame_pose
+ kcc_pose: /kcc_slam/kcc_pose
+ map: /kcc_slam/occupancy_map
+
+saving:
+ save_pose: true
+ saving_root: /home/xukuan/debug/kcc
diff --git a/src/ground_slam/figures/data_association.jpg b/src/ground_slam/figures/data_association.jpg
new file mode 100644
index 0000000..b59c5b5
Binary files /dev/null and b/src/ground_slam/figures/data_association.jpg differ
diff --git a/src/ground_slam/figures/features_small.jpg b/src/ground_slam/figures/features_small.jpg
new file mode 100644
index 0000000..32efc20
Binary files /dev/null and b/src/ground_slam/figures/features_small.jpg differ
diff --git a/src/ground_slam/figures/fig1.jpg b/src/ground_slam/figures/fig1.jpg
new file mode 100644
index 0000000..c61fbe2
Binary files /dev/null and b/src/ground_slam/figures/fig1.jpg differ
diff --git a/src/ground_slam/figures/loop_error.jpg b/src/ground_slam/figures/loop_error.jpg
new file mode 100644
index 0000000..8a9b23e
Binary files /dev/null and b/src/ground_slam/figures/loop_error.jpg differ
diff --git a/src/ground_slam/figures/loop_trajectory.jpg b/src/ground_slam/figures/loop_trajectory.jpg
new file mode 100644
index 0000000..e911c72
Binary files /dev/null and b/src/ground_slam/figures/loop_trajectory.jpg differ
diff --git a/src/ground_slam/figures/pipeline.png b/src/ground_slam/figures/pipeline.png
new file mode 100644
index 0000000..d0cb802
Binary files /dev/null and b/src/ground_slam/figures/pipeline.png differ
diff --git a/src/ground_slam/figures/rmse_curve.jpg b/src/ground_slam/figures/rmse_curve.jpg
new file mode 100644
index 0000000..6197e4d
Binary files /dev/null and b/src/ground_slam/figures/rmse_curve.jpg differ
diff --git a/src/ground_slam/figures/sample_images.jpg b/src/ground_slam/figures/sample_images.jpg
new file mode 100644
index 0000000..d5caa81
Binary files /dev/null and b/src/ground_slam/figures/sample_images.jpg differ
diff --git a/src/ground_slam/figures/trajectory.jpg b/src/ground_slam/figures/trajectory.jpg
new file mode 100644
index 0000000..85f7585
Binary files /dev/null and b/src/ground_slam/figures/trajectory.jpg differ
diff --git a/src/ground_slam/figures/video.png b/src/ground_slam/figures/video.png
new file mode 100644
index 0000000..9a3b242
Binary files /dev/null and b/src/ground_slam/figures/video.png differ
diff --git a/src/ground_slam/include/camera.h b/src/ground_slam/include/camera.h
new file mode 100644
index 0000000..b8447b2
--- /dev/null
+++ b/src/ground_slam/include/camera.h
@@ -0,0 +1,59 @@
+#ifndef CAMERA_H_
+#define CAMERA_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class Camera{
+public:
+ Camera();
+ Camera(const std::string& camera_file);
+ Camera& operator=(const Camera& camera);
+
+ void UndistortImage(cv::Mat& image, cv::Mat& undistort_image);
+ void GetNewCameraMatrix(cv::Mat& camera_matrix);
+ double GetImageHeight();
+ double GetImageWidth();
+ double GetHeight();
+ bool HeightIsAccurate();
+ void GetExtrinsics(Eigen::Matrix3d& extrinsics);
+ double GetLengthOfPixel();
+
+ Eigen::Vector3d ConvertPrincipalToCenter(const Eigen::Vector3d& image_plane_pose);
+ Eigen::Vector3d ConvertCenterToPrincipal(const Eigen::Vector3d& image_center_pose);
+
+ // Image plane: pixel plane, principal point on the image is the origin
+ // Camera: normalized plane,
+ // Robot: robot body coordinate system.
+ bool ConvertImagePlanePoseToCamera(Eigen::Vector3d& image_plane_pose, Eigen::Vector3d& camera_pose);
+ bool ConvertCameraPoseToImagePlane(Eigen::Vector3d& image_plane_pose, Eigen::Vector3d& camera_pose);
+ bool ConvertCameraPoseToRobot(Eigen::Vector3d& camera_pose, Eigen::Vector3d& robot_pose);
+ bool ConvertRobotPoseToCamera(Eigen::Vector3d& camera_pose, Eigen::Vector3d& robot_pose);
+ bool ConvertImagePlanePoseToRobot(Eigen::Vector3d& image_plane_pose, Eigen::Vector3d& robot_pose);
+ bool ConvertRobotPoseToImagePlane(Eigen::Vector3d& image_plane_pose, Eigen::Vector3d& robot_pose);
+
+private:
+ int _image_height;
+ int _image_width;
+ double _scale;
+ double _new_scale;
+ int _new_width;
+ int _new_height;
+ double _height;
+ bool _accurate_height;
+ cv::Mat _K;
+ cv::Mat _new_K;
+ cv::Mat _D;
+ cv::Mat _map1;
+ cv::Mat _map2;
+ Eigen::Matrix3d _extrinsics;
+};
+
+typedef std::shared_ptr CameraPtr;
+
+#endif // CAMERA_H_
\ No newline at end of file
diff --git a/src/ground_slam/include/circ_shift.h b/src/ground_slam/include/circ_shift.h
new file mode 100644
index 0000000..dd37b42
--- /dev/null
+++ b/src/ground_slam/include/circ_shift.h
@@ -0,0 +1,252 @@
+// circ_shift.h
+// https://stackoverflow.com/questions/46077242/eigen-modifyable-custom-expression/46301503#46301503
+// this file implements circShift, fftshift, and ifftshift for Eigen vectors/matrices.
+//
+
+#pragma once
+#include
+
+template using bool_constant = std::integral_constant;
+
+namespace helper
+{
+namespace detail
+{
+template
+constexpr std::true_type is_matrix(Eigen::MatrixBase);
+std::false_type constexpr is_matrix(...);
+
+template
+constexpr std::true_type is_array(Eigen::ArrayBase);
+std::false_type constexpr is_array(...);
+}
+
+
+template
+struct is_matrix : decltype(detail::is_matrix(std::declval>()))
+{
+};
+
+template
+struct is_array : decltype(detail::is_array(std::declval>()))
+{
+};
+
+template
+using is_matrix_or_array = bool_constant::value || is_matrix::value>;
+
+/*
+ * Index something if it's not an scalar
+ */
+template ::value, int>::type = 0>
+auto index_if_necessary(T&& thing, Eigen::Index idx)
+{
+ return thing(idx);
+}
+
+/*
+ * Overload for scalar.
+ */
+template >::value, int>::type = 0>
+ auto index_if_necessary(T&& thing, Eigen::Index)
+{
+ return thing;
+}
+}
+
+namespace Eigen
+{
+template
+class CircShiftedView;
+
+namespace internal
+{
+template
+struct traits>
+ : traits
+{
+ enum
+ {
+ RowsAtCompileTime = traits::RowsAtCompileTime,
+ ColsAtCompileTime = traits::ColsAtCompileTime,
+ MaxRowsAtCompileTime = (RowsAtCompileTime != Dynamic
+ ? int(RowsAtCompileTime)
+ : int(traits::MaxRowsAtCompileTime)),
+ MaxColsAtCompileTime = (ColsAtCompileTime != Dynamic
+ ? int(ColsAtCompileTime)
+ : int(traits::MaxColsAtCompileTime)),
+ XprTypeIsRowMajor = (int(traits::Flags) & RowMajorBit) != 0,
+ IsRowMajor = ((MaxRowsAtCompileTime == 1 && MaxColsAtCompileTime != 1) ? 1
+ : (MaxColsAtCompileTime == 1 && MaxRowsAtCompileTime != 1) ? 0
+ : XprTypeIsRowMajor),
+ FlagsRowMajorBit = IsRowMajor ? RowMajorBit : 0,
+ FlagsLvalueBit = is_lvalue::value ? LvalueBit : 0,
+ Flags = (traits::Flags & HereditaryBits) | FlagsLvalueBit | FlagsRowMajorBit
+ };
+};
+}
+
+template
+class CircShiftedViewImpl;
+
+
+template
+class CircShiftedView : public CircShiftedViewImpl::StorageKind>
+{
+public:
+ typedef typename CircShiftedViewImpl::StorageKind>::Base Base;
+ EIGEN_GENERIC_PUBLIC_INTERFACE(CircShiftedView)
+ EIGEN_INHERIT_ASSIGNMENT_OPERATORS(CircShiftedView)
+
+ typedef typename internal::ref_selector::non_const_type MatrixTypeNested;
+ typedef typename internal::remove_all::type NestedExpression;
+
+ template
+ CircShiftedView(XprType& xpr, const T0& rowShift, const T1& colShift)
+ : m_xpr(xpr), m_rowShift(rowShift), m_colShift(colShift)
+ {
+ for (auto c = 0; c < xpr.cols(); ++c)
+ assert(std::abs(helper::index_if_necessary(m_rowShift, c)) < m_xpr.rows()); // row shift must be within +- rows()-1
+ for (auto r = 0; r < xpr.rows(); ++r)
+ assert(std::abs(helper::index_if_necessary(m_colShift, r)) < m_xpr.cols()); // col shift must be within +- cols()-1
+ }
+
+ /** \returns number of rows */
+ Index rows() const { return m_xpr.rows(); }
+
+ /** \returns number of columns */
+ Index cols() const { return m_xpr.cols(); }
+
+ /** \returns the nested expression */
+ const typename internal::remove_all::type&
+ nestedExpression() const { return m_xpr; }
+
+ /** \returns the nested expression */
+ typename internal::remove_reference::type&
+ nestedExpression() { return m_xpr.const_cast_derived(); }
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
+ Index getRowIdx(Index row, Index col) const
+ {
+ Index R = m_xpr.rows();
+ assert(row >= 0 && row < R && col >= 0 && col < m_xpr.cols());
+ Index r = row - helper::index_if_necessary(m_rowShift, col);
+ if (r >= R)
+ return r - R;
+ if (r < 0)
+ return r + R;
+ return r;
+ }
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
+ Index getColIdx(Index row, Index col) const
+ {
+ Index C = m_xpr.cols();
+ assert(row >= 0 && row < m_xpr.rows() && col >= 0 && col < C);
+ Index c = col - helper::index_if_necessary(m_colShift, row);
+ if (c >= C)
+ return c - C;
+ if (c < 0)
+ return c + C;
+ return c;
+ }
+
+protected:
+ MatrixTypeNested m_xpr;
+ RowShift m_rowShift;
+ ColShift m_colShift;
+};
+
+
+// Generic API dispatcher
+template
+class CircShiftedViewImpl
+ : public internal::generic_xpr_base>::type
+{
+ public:
+ typedef typename internal::generic_xpr_base>::type Base;
+};
+
+namespace internal
+{
+template
+struct unary_evaluator, IndexBased>
+ : evaluator_base>
+{
+ typedef CircShiftedView XprType;
+
+ enum
+ {
+ CoeffReadCost = (evaluator::CoeffReadCost
+ + NumTraits::AddCost /* for comparison */
+ + NumTraits::AddCost) /* for addition */,
+ Flags = (evaluator::Flags & HereditaryBits),
+ Alignment = 0
+ };
+
+ EIGEN_DEVICE_FUNC explicit unary_evaluator(const XprType& xpr) : m_argImpl(xpr.nestedExpression()), m_xpr(xpr)
+ {
+ EIGEN_INTERNAL_CHECK_COST_VALUE(CoeffReadCost);
+ }
+
+ typedef typename XprType::Scalar Scalar;
+ typedef typename XprType::CoeffReturnType CoeffReturnType;
+
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
+ CoeffReturnType coeff(Index row, Index col) const
+ {
+ return m_argImpl.coeff(m_xpr.getRowIdx(row, col), m_xpr.getColIdx(row, col));
+ }
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
+ CoeffReturnType coeff(Index idx) const
+ {
+ if (m_xpr.cols() == 1)
+ return m_argImpl.coeff(m_xpr.getRowIdx(idx, 1), 1);
+ if (m_xpr.rows() == 1)
+ return m_argImpl.coeff(1, m_xpr.getColIdx(1, idx));
+ assert(m_xpr.cols() == 1 || m_xpr.rows() == 1);
+ // default no-assert case - assume col vector
+ return m_argImpl.coeff(m_xpr.getRowIdx(idx, 1), 1);
+ }
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE
+ Scalar& coeffRef(Index row, Index col)
+ {
+ assert(row >= 0 && row < m_xpr.rows() && col >= 0 && col < m_xpr.cols());
+
+ return m_argImpl.coeffRef(m_xpr.getRowIdx(row, col), m_xpr.getColIdx(row, col));
+ }
+
+ protected:
+
+ evaluator m_argImpl;
+ const XprType& m_xpr;
+};
+} // end namespace internal
+} // end namespace Eigen
+
+template
+auto circShift(Eigen::DenseBase& x, RowShift r, ColShift c)
+{
+ return Eigen::CircShiftedView(x.derived(), r, c);
+}
+
+template
+auto fftshift(Eigen::DenseBase& x)
+{
+ Eigen::Index rs = x.rows() / 2;
+ Eigen::Index cs = x.cols() / 2;
+ return Eigen::CircShiftedView(x.derived(), rs, cs);
+}
+
+template
+auto ifftshift(Eigen::DenseBase& x)
+{
+ Eigen::Index rs = (x.rows() + 1) / 2;
+ Eigen::Index cs = (x.cols() + 1) / 2;
+ return Eigen::CircShiftedView(x.derived(), rs, cs);
+}
\ No newline at end of file
diff --git a/src/ground_slam/include/correlation_flow.h b/src/ground_slam/include/correlation_flow.h
new file mode 100644
index 0000000..bbe3d96
--- /dev/null
+++ b/src/ground_slam/include/correlation_flow.h
@@ -0,0 +1,36 @@
+#ifndef CORRELATION_FLOW_H
+#define CORRELATION_FLOW_H
+
+#include
+#include "utils.h"
+#include "read_configs.h"
+
+class CorrelationFlow{
+
+public:
+ CorrelationFlow(CFConfig& cf_config, double &image_height, double &image_width);
+ void ComputeIntermedium(const Eigen::ArrayXXf&, Eigen::ArrayXXcf&, Eigen::ArrayXXcf&);
+ Eigen::Vector3d ComputePose(const Eigen::ArrayXXcf&, const Eigen::ArrayXXf&, const Eigen::ArrayXXcf&, const Eigen::ArrayXXcf&, Eigen::Vector3d&, bool);
+
+private:
+ CFConfig cfg;
+ Eigen::ArrayXXcf target_fft;
+ Eigen::ArrayXXcf target_rotation_fft;
+ Eigen::ArrayXXcf FFT(const Eigen::ArrayXXf&);
+ Eigen::ArrayXXf IFFT(const Eigen::ArrayXXcf&);
+ Eigen::ArrayXXcf GetTargetFFT(int, int);
+ inline Eigen::ArrayXXf RemoveZeroComponent(const Eigen::ArrayXXf&);
+ inline Eigen::ArrayXXcf gaussian_kernel(const Eigen::ArrayXXcf&, int, int);
+ inline Eigen::ArrayXXcf gaussian_kernel(const Eigen::ArrayXXcf&, const Eigen::ArrayXXcf&, int, int);
+ inline Eigen::ArrayXXcf polynomial_kernel(const Eigen::ArrayXXcf&, int, int);
+ inline Eigen::ArrayXXcf polynomial_kernel(const Eigen::ArrayXXcf&, const Eigen::ArrayXXcf&, int, int);
+ float EstimateTrans(const Eigen::ArrayXXcf&, const Eigen::ArrayXXcf&, const Eigen::ArrayXXcf&, int, int, Eigen::Vector2d&);
+ inline Eigen::ArrayXXf polar(const Eigen::ArrayXXf&);
+ inline float GetInfo(const Eigen::ArrayXXf&, float);
+
+ // void PrintArrayToFile (const Eigen::ArrayXXf target);
+};
+
+typedef std::shared_ptr CorrelationFlowPtr;
+
+#endif // CORRELATION_FLOW_H
\ No newline at end of file
diff --git a/src/ground_slam/include/dataset.h b/src/ground_slam/include/dataset.h
new file mode 100644
index 0000000..31834f8
--- /dev/null
+++ b/src/ground_slam/include/dataset.h
@@ -0,0 +1,30 @@
+#ifndef DATASET_H_
+#define DATASET_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "utils.h"
+
+
+class Dataset{
+public:
+ Dataset(const std::string& dataroot, const std::string& image_dir_name);
+ size_t GetDatasetLength();
+ bool GetImage(cv::Mat& image, size_t idx);
+ double GetTimestamp(size_t idx);
+
+private:
+ std::string _dataroot;
+ std::string _image_dir;
+ std::string _image_name_file_path;
+ std::vector _image_names;
+ std::string _time_file_path;
+ std::vector _timestamps;
+};
+
+#endif // DATASET_H_
\ No newline at end of file
diff --git a/src/ground_slam/include/edge.h b/src/ground_slam/include/edge.h
new file mode 100644
index 0000000..bf4581b
--- /dev/null
+++ b/src/ground_slam/include/edge.h
@@ -0,0 +1,31 @@
+#ifndef EDGE_H_
+#define EDGE_H_
+
+#include
+#include
+#include
+
+#include "utils.h"
+
+struct Edge{
+ enum Type {
+ Odom = 0,
+ KCC = 1,
+ Loop = 2,
+ Others = 3,
+ };
+
+ int _edge_id;
+ Type _type;
+ int _from;
+ int _to;
+ Eigen::Vector3d _T;
+ Eigen::Matrix3d _information;
+
+ Edge();
+ Edge(int edge_id, Type type, int from, int to, Eigen::Vector3d& T, Eigen::Matrix3d& information);
+};
+
+typedef std::shared_ptr EdgePtr;
+
+#endif // EDGE_H_
\ No newline at end of file
diff --git a/src/ground_slam/include/frame.h b/src/ground_slam/include/frame.h
new file mode 100644
index 0000000..836f082
--- /dev/null
+++ b/src/ground_slam/include/frame.h
@@ -0,0 +1,45 @@
+#ifndef FRAME_H_
+#define FRAME_H_
+
+#include
+#include
+#include
+
+#include "utils.h"
+
+
+class Frame{
+public:
+ Frame();
+ Frame(int frame_id);
+ Frame(int frame_id, double timestamp, Eigen::ArrayXXf&, Eigen::ArrayXXcf&, Eigen::ArrayXXcf&);
+ Frame& operator=(const Frame& other);
+
+ void SetFrameId(int frame_id);
+ int GetFrameId();
+ double GetTimestamp();
+ Eigen::ArrayXXf GetFrame();
+ void SetFFTResult(Eigen::ArrayXXcf& fft_result);
+ void SetFFTResult(Eigen::ArrayXXcf& fft_result, Eigen::ArrayXXcf& depth_fft_result);
+ void GetFFTResult(Eigen::ArrayXXcf& fft_result);
+ void GetFFTResult(Eigen::ArrayXXcf& fft_result, Eigen::ArrayXXcf& depth_fft_result);
+ void SetPose(Eigen::Vector3d& pose);
+ void GetPose(Eigen::Vector3d& pose);
+ void AddEdge(int edge_id);
+ void GetEdgeIds(std::vector& edge_ids);
+ void SaveToDisk(const std::string root_dir);
+
+private:
+ int _frame_id;
+ double _timestamp;
+ Eigen::ArrayXXf _frame;
+ Eigen::ArrayXXcf _fft_result;
+ Eigen::ArrayXXcf _fft_polar;
+ Eigen::ArrayXXcf _depth_fft_result;
+ Eigen::Vector3d _pose;
+ std::vector _edge_ids;
+};
+
+typedef std::shared_ptr FramePtr;
+
+#endif // FRAME_H_
\ No newline at end of file
diff --git a/src/ground_slam/include/loop_closure.h b/src/ground_slam/include/loop_closure.h
new file mode 100644
index 0000000..274dd9c
--- /dev/null
+++ b/src/ground_slam/include/loop_closure.h
@@ -0,0 +1,43 @@
+#ifndef LOOP_CLOSURE_H_
+#define LOOP_CLOSURE_H_
+
+#include "read_configs.h"
+#include "correlation_flow.h"
+#include "map.h"
+
+
+struct LoopClosureResult{
+ bool found;
+ Eigen::Vector3d response;
+ FramePtr current_frame;
+ FramePtr loop_frame;
+ Eigen::Vector3d relative_pose;
+
+ LoopClosureResult(): found(false), response(-1.0, -1.0, -1.0) {}
+ LoopClosureResult(bool _found, Eigen::Vector3d _resopnse): found(_found), response(_resopnse) {}
+ LoopClosureResult& operator=(const LoopClosureResult& other){
+ found = other.found;
+ response = other.response;
+ current_frame = other.current_frame;
+ loop_frame = other.loop_frame;
+ relative_pose = other.relative_pose;
+ return *this;
+ }
+};
+
+class LoopClosure{
+public:
+ LoopClosure(LoopClosureConfig& loop_closure_config, CorrelationFlowPtr correlation_flow, MapPtr map);
+ LoopClosureResult FindLoopClosure(Eigen::ArrayXXf& image, FramePtr& current_frame);
+ LoopClosureResult FindLoopClosure(Eigen::ArrayXXf& image, FramePtr& current_frame, Eigen::Vector3d& prior_pose);
+ LoopClosureResult FindLoopClosure(Eigen::ArrayXXf& image, FramePtr& current_frame, std::vector& frames);
+
+private:
+ LoopClosureConfig _loop_thr;
+ CorrelationFlowPtr _correlation_flow;
+ MapPtr _map;
+};
+
+typedef std::shared_ptr LoopClosurePtr;
+
+#endif // LOOP_CLOSURE_H_
\ No newline at end of file
diff --git a/src/ground_slam/include/map.h b/src/ground_slam/include/map.h
new file mode 100644
index 0000000..c501023
--- /dev/null
+++ b/src/ground_slam/include/map.h
@@ -0,0 +1,81 @@
+#ifndef MAP_H_
+#define MAP_H_
+
+#include
+#include