├── SC-A-LOAM
├── utils
│ └── python
│ │ ├── bone_table.npy
│ │ ├── jet_table.npy
│ │ ├── __pycache__
│ │ └── pypcdMyUtils.cpython-36.pyc
│ │ ├── pypcdMyUtils.py
│ │ └── makeMergedMap.py
├── launch
│ ├── kitti_helper.launch
│ ├── aloam_velodyne_HDL_64.launch
│ ├── aloam_velodyne_VLP_16.launch
│ ├── aloam_mulran.launch
│ ├── floam_velodyne_32.launch
│ └── floam_velodyne_64.launch
├── package.xml
├── include
│ ├── aloam_velodyne
│ │ ├── tic_toc.h
│ │ └── common.h
│ └── scancontext
│ │ ├── Scancontext.h
│ │ ├── KDTreeVectorOfVectorsAdaptor.h
│ │ └── Scancontext.cpp
├── CMakeLists.txt
├── README.md
├── src
│ ├── lidarFactor.hpp
│ ├── kittiHelper.cpp
│ └── scanRegistration.cpp
└── rviz_cfg
│ └── aloam_velodyne.rviz
├── floam
├── src
│ ├── lidar.cpp
│ ├── laserMappingNode.cpp
│ ├── laserProcessingNode.cpp
│ ├── lidarOptimization.cpp
│ ├── odomEstimationNode.cpp
│ ├── laserMappingClass.cpp
│ ├── laserProcessingClass.cpp
│ └── odomEstimationClass.cpp
├── include
│ ├── lidar.h
│ ├── laserProcessingClass.h
│ ├── laserMappingClass.h
│ ├── lidarOptimization.h
│ └── odomEstimationClass.h
├── package.xml
├── launch
│ ├── floam_velodyne.launch
│ ├── floam.launch
│ └── floam_mapping.launch
├── CMakeLists.txt
├── README.md
└── rviz
│ ├── floam_velodyne.rviz
│ ├── floam.rviz
│ └── floam_mapping.rviz
├── LICENSE
└── README.md
/SC-A-LOAM/utils/python/bone_table.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuge1995/SC-FLOAM/HEAD/SC-A-LOAM/utils/python/bone_table.npy
--------------------------------------------------------------------------------
/SC-A-LOAM/utils/python/jet_table.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuge1995/SC-FLOAM/HEAD/SC-A-LOAM/utils/python/jet_table.npy
--------------------------------------------------------------------------------
/SC-A-LOAM/utils/python/__pycache__/pypcdMyUtils.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cuge1995/SC-FLOAM/HEAD/SC-A-LOAM/utils/python/__pycache__/pypcdMyUtils.cpython-36.pyc
--------------------------------------------------------------------------------
/SC-A-LOAM/launch/kitti_helper.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/floam/src/lidar.cpp:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 |
5 | #include "lidar.h"
6 |
7 |
8 | lidar::Lidar::Lidar(){
9 |
10 | }
11 |
12 |
13 | void lidar::Lidar::setLines(double num_lines_in){
14 | num_lines=num_lines_in;
15 | }
16 |
17 |
18 | void lidar::Lidar::setVerticalAngle(double vertical_angle_in){
19 | vertical_angle = vertical_angle_in;
20 | }
21 |
22 |
23 | void lidar::Lidar::setVerticalResolution(double vertical_angle_resolution_in){
24 | vertical_angle_resolution = vertical_angle_resolution_in;
25 | }
26 |
27 |
28 | void lidar::Lidar::setScanPeriod(double scan_period_in){
29 | scan_period = scan_period_in;
30 | }
31 |
32 |
33 | void lidar::Lidar::setMaxDistance(double max_distance_in){
34 | max_distance = max_distance_in;
35 | }
36 |
37 | void lidar::Lidar::setMinDistance(double min_distance_in){
38 | min_distance = min_distance_in;
39 | }
--------------------------------------------------------------------------------
/floam/include/lidar.h:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 | #ifndef _LIDAR_H_
5 | #define _LIDAR_H_
6 |
7 | //define lidar parameter
8 |
9 | namespace lidar{
10 |
11 | class Lidar
12 | {
13 | public:
14 | Lidar();
15 |
16 | void setScanPeriod(double scan_period_in);
17 | void setLines(double num_lines_in);
18 | void setVerticalAngle(double vertical_angle_in);
19 | void setVerticalResolution(double vertical_angle_resolution_in);
20 | //by default is 100. pls do not change
21 | void setMaxDistance(double max_distance_in);
22 | void setMinDistance(double min_distance_in);
23 |
24 | double max_distance;
25 | double min_distance;
26 | int num_lines;
27 | double scan_period;
28 | int points_per_line;
29 | double horizontal_angle_resolution;
30 | double horizontal_angle;
31 | double vertical_angle_resolution;
32 | double vertical_angle;
33 | };
34 |
35 |
36 | }
37 |
38 |
39 | #endif // _LIDAR_H_
40 |
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jinlai Zhang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/floam/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | floam
4 | 0.0.0
5 |
6 |
7 | This work is created based on some optimization on ALOAM by HKUST and LOAM by CMU
8 |
9 |
10 | Han Wang
11 |
12 | BSD
13 | https://wanghan.pro
14 | Han Wang
15 |
16 | catkin
17 | geometry_msgs
18 | nav_msgs
19 | roscpp
20 | rospy
21 | std_msgs
22 | rosbag
23 | sensor_msgs
24 | tf
25 | eigen_conversions
26 | geometry_msgs
27 | nav_msgs
28 | sensor_msgs
29 | roscpp
30 | rospy
31 | std_msgs
32 | rosbag
33 | tf
34 | eigen_conversions
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/SC-A-LOAM/utils/python/pypcdMyUtils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pypcd # for the install, use this command: python3.x (use your python ver) -m pip install --user git+https://github.com/DanielPollithy/pypcd.git
3 | from pypcd import pypcd
4 |
5 | def make_xyzi_point_cloud(xyzl, label_type='f'):
6 | """ Make XYZL point cloud from numpy array.
7 | TODO i labels?
8 | """
9 | md = {'version': .7,
10 | 'fields': ['x', 'y', 'z', 'intensity'],
11 | 'count': [1, 1, 1, 1],
12 | 'width': len(xyzl),
13 | 'height': 1,
14 | 'viewpoint': [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
15 | 'points': len(xyzl),
16 | 'data': 'ASCII'}
17 | if label_type.lower() == 'f':
18 | md['size'] = [4, 4, 4, 4]
19 | md['type'] = ['F', 'F', 'F', 'F']
20 | elif label_type.lower() == 'u':
21 | md['size'] = [4, 4, 4, 1]
22 | md['type'] = ['F', 'F', 'F', 'U']
23 | else:
24 | raise ValueError('label type must be F or U')
25 | # TODO use .view()
26 | xyzl = xyzl.astype(np.float32)
27 | dt = np.dtype([('x', np.float32), ('y', np.float32), ('z', np.float32),
28 | ('intensity', np.float32)])
29 | pc_data = np.rec.fromarrays([xyzl[:, 0], xyzl[:, 1], xyzl[:, 2],
30 | xyzl[:, 3]], dtype=dt)
31 | pc = pypcd.PointCloud(md, pc_data)
32 | return pc
33 |
--------------------------------------------------------------------------------
/SC-A-LOAM/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | aloam_velodyne
4 | 0.0.0
5 |
6 |
7 | This is an advanced implentation of LOAM.
8 | LOAM is described in the following paper:
9 | J. Zhang and S. Singh. LOAM: Lidar Odometry and Mapping in Real-time.
10 | Robotics: Science and Systems Conference (RSS). Berkeley, CA, July 2014.
11 |
12 |
13 | qintong
14 |
15 | BSD
16 |
17 | Ji Zhang
18 |
19 | catkin
20 | geometry_msgs
21 | nav_msgs
22 | roscpp
23 | rospy
24 | std_msgs
25 | rosbag
26 | sensor_msgs
27 | tf
28 | image_transport
29 |
30 | geometry_msgs
31 | nav_msgs
32 | sensor_msgs
33 | roscpp
34 | rospy
35 | std_msgs
36 | rosbag
37 | tf
38 | image_transport
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/floam/include/laserProcessingClass.h:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 | #ifndef _LASER_PROCESSING_CLASS_H_
5 | #define _LASER_PROCESSING_CLASS_H_
6 |
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include "lidar.h"
17 |
18 | //points covariance class
19 | class Double2d{
20 | public:
21 | int id;
22 | double value;
23 | Double2d(int id_in, double value_in);
24 | };
25 | //points info class
26 | class PointsInfo{
27 | public:
28 | int layer;
29 | double time;
30 | PointsInfo(int layer_in, double time_in);
31 | };
32 |
33 |
34 | class LaserProcessingClass
35 | {
36 | public:
37 | LaserProcessingClass();
38 | void init(lidar::Lidar lidar_param_in);
39 | void featureExtraction(const pcl::PointCloud::Ptr& pc_in, pcl::PointCloud::Ptr& pc_out_edge, pcl::PointCloud::Ptr& pc_out_surf);
40 | void featureExtractionFromSector(const pcl::PointCloud::Ptr& pc_in, std::vector& cloudCurvature, pcl::PointCloud::Ptr& pc_out_edge, pcl::PointCloud::Ptr& pc_out_surf);
41 | private:
42 | lidar::Lidar lidar_param;
43 | };
44 |
45 |
46 |
47 | #endif // _LASER_PROCESSING_CLASS_H_
48 |
49 |
--------------------------------------------------------------------------------
/SC-A-LOAM/include/aloam_velodyne/tic_toc.h:
--------------------------------------------------------------------------------
1 | // Author: Tong Qin qintonguav@gmail.com
2 | // Shaozu Cao saozu.cao@connect.ust.hk
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | class TicToc
11 | {
12 | public:
13 | TicToc()
14 | {
15 | tic();
16 | }
17 |
18 | void tic()
19 | {
20 | start = std::chrono::system_clock::now();
21 | }
22 |
23 | double toc()
24 | {
25 | end = std::chrono::system_clock::now();
26 | std::chrono::duration elapsed_seconds = end - start;
27 | return elapsed_seconds.count() * 1000;
28 | }
29 |
30 | private:
31 | std::chrono::time_point start, end;
32 | };
33 |
34 | class TicTocV2
35 | {
36 | public:
37 | TicTocV2()
38 | {
39 | tic();
40 | }
41 |
42 | TicTocV2( bool _disp )
43 | {
44 | disp_ = _disp;
45 | tic();
46 | }
47 |
48 | void tic()
49 | {
50 | start = std::chrono::system_clock::now();
51 | }
52 |
53 | void toc( std::string _about_task )
54 | {
55 | end = std::chrono::system_clock::now();
56 | std::chrono::duration elapsed_seconds = end - start;
57 | double elapsed_ms = elapsed_seconds.count() * 1000;
58 |
59 | if( disp_ )
60 | {
61 | std::cout.precision(3); // 10 for sec, 3 for ms
62 | std::cout << _about_task << ": " << elapsed_ms << " msec." << std::endl;
63 | }
64 | }
65 |
66 | private:
67 | std::chrono::time_point start, end;
68 | bool disp_ = false;
69 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SC-FLOAM
2 |
3 | ## What is SC-FLOAM?
4 | - A real-time LiDAR SLAM package that integrates FLOAM and ScanContext.
5 | - **FLOAM** for odometry (i.e., consecutive motion estimation)
6 | - **ScanContext** for coarse global localization that can deal with big drifts (i.e., place recognition as kidnapped robot problem without initial pose)
7 | - and iSAM2 of GTSAM is used for pose-graph optimization.
8 |
9 | ## Dependencies
10 | - We mainly depend on ROS, Ceres and PCL(for FLOAM), and GTSAM (for pose-graph optimization).
11 | - For the details to install the prerequisites, please follow the FLOAM and LIO-SAM repositiory.
12 | - The below examples are done under ROS melodic (ubuntu 18) and GTSAM version 4.x.
13 |
14 | ## How to use?
15 | - First, install the abovementioned dependencies, and follow below lines.
16 | ```
17 | mkdir -p ~/catkin_scfloam_ws/src
18 | cd ~/catkin_scfloam_ws/src
19 | git clone https://github.com/cuge1995/SC-FLOAM.git
20 | cd ../
21 | catkin_make
22 | source ~/catkin_scfloam_ws/devel/setup.bash
23 | ```
24 |
25 | ## Example Results
26 |
27 |
28 | ### KITTI
29 | - For KITTI (HDL-64 sensor), run using the command (Download [KITTI sequence 05](https://drive.google.com/file/d/1eyO0Io3lX2z-yYsfGHawMKZa5Z0uYJ0W/view?usp=sharing) or [KITTI sequence 07](https://drive.google.com/file/d/1_qUfwUw88rEKitUpt1kjswv7Cv4GPs0b/view?usp=sharing) first)
30 | ```
31 | ##In different terminal
32 | roslaunch floam floam_velodyne.launch
33 | roslaunch aloam_velodyne floam_velodyne_64.launch
34 | rosbag play 2011_09_30_0018.bag
35 | ```
36 |
37 |
38 | ## Acknowledgements
39 | - Thanks to LOAM, FLOAM, SC-ALOAM and LIO-SAM code authors. The major codes in this repository are borrowed from their efforts.
40 |
41 |
--------------------------------------------------------------------------------
/SC-A-LOAM/launch/aloam_velodyne_HDL_64.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/floam/include/laserMappingClass.h:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 |
5 | #ifndef _LASER_MAPPING_H_
6 | #define _LASER_MAPPING_H_
7 |
8 | //PCL lib
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | //eigen lib
17 | #include
18 | #include
19 |
20 | //c++ lib
21 | #include
22 | #include
23 | #include
24 |
25 |
26 | #define LASER_CELL_WIDTH 50.0
27 | #define LASER_CELL_HEIGHT 50.0
28 | #define LASER_CELL_DEPTH 50.0
29 |
30 | //separate map as many sub point clouds
31 |
32 | #define LASER_CELL_RANGE_HORIZONTAL 2
33 | #define LASER_CELL_RANGE_VERTICAL 2
34 |
35 |
36 | class LaserMappingClass
37 | {
38 |
39 | public:
40 | LaserMappingClass();
41 | void init(double map_resolution);
42 | void updateCurrentPointsToMap(const pcl::PointCloud::Ptr& pc_in, const Eigen::Isometry3d& pose_current);
43 | pcl::PointCloud::Ptr getMap(void);
44 |
45 | private:
46 | int origin_in_map_x;
47 | int origin_in_map_y;
48 | int origin_in_map_z;
49 | int map_width;
50 | int map_height;
51 | int map_depth;
52 | std::vector::Ptr>>> map;
53 | pcl::VoxelGrid downSizeFilter;
54 |
55 | void addWidthCellNegative(void);
56 | void addWidthCellPositive(void);
57 | void addHeightCellNegative(void);
58 | void addHeightCellPositive(void);
59 | void addDepthCellNegative(void);
60 | void addDepthCellPositive(void);
61 | void checkPoints(int& x, int& y, int& z);
62 |
63 | };
64 |
65 |
66 | #endif // _LASER_MAPPING_H_
67 |
68 |
--------------------------------------------------------------------------------
/floam/launch/floam_velodyne.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/SC-A-LOAM/launch/aloam_velodyne_VLP_16.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/SC-A-LOAM/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(aloam_velodyne)
3 |
4 | set(CMAKE_BUILD_TYPE "Release")
5 | # set(CMAKE_CXX_FLAGS "-std=c++11")
6 | set(CMAKE_CXX_STANDARD 17)
7 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g")
8 |
9 | find_package(catkin REQUIRED COMPONENTS
10 | geometry_msgs
11 | nav_msgs
12 | sensor_msgs
13 | roscpp
14 | rospy
15 | rosbag
16 | std_msgs
17 | image_transport
18 | cv_bridge
19 | tf
20 | )
21 |
22 | #find_package(Eigen3 REQUIRED)
23 | find_package(PCL REQUIRED)
24 | find_package(OpenCV REQUIRED)
25 | find_package(Ceres REQUIRED)
26 |
27 | find_package(OpenMP REQUIRED)
28 | find_package(GTSAM REQUIRED QUIET)
29 |
30 | include_directories(
31 | include
32 | ${catkin_INCLUDE_DIRS}
33 | ${PCL_INCLUDE_DIRS}
34 | ${CERES_INCLUDE_DIRS}
35 | ${OpenCV_INCLUDE_DIRS}
36 | ${GTSAM_INCLUDE_DIR}
37 | )
38 |
39 | catkin_package(
40 | CATKIN_DEPENDS geometry_msgs nav_msgs roscpp rospy std_msgs
41 | DEPENDS EIGEN3 PCL
42 | INCLUDE_DIRS include
43 | )
44 |
45 |
46 | add_executable(ascanRegistration src/scanRegistration.cpp)
47 | target_link_libraries(ascanRegistration ${catkin_LIBRARIES} ${PCL_LIBRARIES})
48 |
49 | add_executable(alaserOdometry src/laserOdometry.cpp)
50 | target_link_libraries(alaserOdometry ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES})
51 |
52 | add_executable(alaserMapping src/laserMapping.cpp)
53 | target_link_libraries(alaserMapping ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES})
54 |
55 | add_executable(alaserPGO
56 | src/laserPosegraphOptimization.cpp
57 | include/scancontext/Scancontext.cpp
58 | )
59 | target_compile_options(alaserPGO
60 | PRIVATE ${OpenMP_CXX_FLAGS}
61 | )
62 | target_link_libraries(alaserPGO
63 | ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES}
64 | ${OpenMP_CXX_FLAGS}
65 | gtsam
66 | )
67 |
68 | add_executable(kittiHelper src/kittiHelper.cpp)
69 | target_link_libraries(kittiHelper ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBS})
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/floam/include/lidarOptimization.h:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 | #ifndef _LIDAR_OPTIMIZATION_ANALYTIC_H_
5 | #define _LIDAR_OPTIMIZATION_ANALYTIC_H_
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | void getTransformFromSe3(const Eigen::Matrix& se3, Eigen::Quaterniond& q, Eigen::Vector3d& t);
13 |
14 | Eigen::Matrix3d skew(Eigen::Vector3d& mat_in);
15 |
16 | class EdgeAnalyticCostFunction : public ceres::SizedCostFunction<1, 7> {
17 | public:
18 |
19 | EdgeAnalyticCostFunction(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_a_, Eigen::Vector3d last_point_b_);
20 | virtual ~EdgeAnalyticCostFunction() {}
21 | virtual bool Evaluate(double const *const *parameters, double *residuals, double **jacobians) const;
22 |
23 | Eigen::Vector3d curr_point;
24 | Eigen::Vector3d last_point_a;
25 | Eigen::Vector3d last_point_b;
26 | };
27 |
28 | class SurfNormAnalyticCostFunction : public ceres::SizedCostFunction<1, 7> {
29 | public:
30 | SurfNormAnalyticCostFunction(Eigen::Vector3d curr_point_, Eigen::Vector3d plane_unit_norm_, double negative_OA_dot_norm_);
31 | virtual ~SurfNormAnalyticCostFunction() {}
32 | virtual bool Evaluate(double const *const *parameters, double *residuals, double **jacobians) const;
33 |
34 | Eigen::Vector3d curr_point;
35 | Eigen::Vector3d plane_unit_norm;
36 | double negative_OA_dot_norm;
37 | };
38 |
39 | class PoseSE3Parameterization : public ceres::LocalParameterization {
40 | public:
41 |
42 | PoseSE3Parameterization() {}
43 | virtual ~PoseSE3Parameterization() {}
44 | virtual bool Plus(const double* x, const double* delta, double* x_plus_delta) const;
45 | virtual bool ComputeJacobian(const double* x, double* jacobian) const;
46 | virtual int GlobalSize() const { return 7; }
47 | virtual int LocalSize() const { return 6; }
48 | };
49 |
50 |
51 |
52 | #endif // _LIDAR_OPTIMIZATION_ANALYTIC_H_
53 |
54 |
--------------------------------------------------------------------------------
/SC-A-LOAM/launch/aloam_mulran.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/floam/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.3)
2 | project(floam)
3 |
4 | set(CMAKE_BUILD_TYPE "Release")
5 | set(CMAKE_CXX_FLAGS "-std=c++11")
6 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g")
7 |
8 | find_package(catkin REQUIRED COMPONENTS
9 | geometry_msgs
10 | nav_msgs
11 | sensor_msgs
12 | roscpp
13 | rospy
14 | rosbag
15 | std_msgs
16 | tf
17 | eigen_conversions
18 | )
19 |
20 | find_package(Eigen3)
21 | if(NOT EIGEN3_FOUND)
22 | # Fallback to cmake_modules
23 | find_package(cmake_modules REQUIRED)
24 | find_package(Eigen REQUIRED)
25 | set(EIGEN3_INCLUDE_DIRS ${EIGEN_INCLUDE_DIRS})
26 | set(EIGEN3_LIBRARIES ${EIGEN_LIBRARIES}) # Not strictly necessary as Eigen is head only
27 | # Possibly map additional variables to the EIGEN3_ prefix.
28 | else()
29 | set(EIGEN3_INCLUDE_DIRS ${EIGEN3_INCLUDE_DIR})
30 | endif()
31 | find_package(PCL REQUIRED)
32 | find_package(Ceres REQUIRED)
33 |
34 | include_directories(
35 | include
36 | ${catkin_INCLUDE_DIRS}
37 | ${PCL_INCLUDE_DIRS}
38 | ${CERES_INCLUDE_DIRS}
39 | ${EIGEN3_INCLUDE_DIRS}
40 | )
41 |
42 | link_directories(
43 | include
44 | ${PCL_LIBRARY_DIRS}
45 | ${CERES_LIBRARY_DIRS}
46 | )
47 |
48 |
49 | catkin_package(
50 | CATKIN_DEPENDS geometry_msgs nav_msgs roscpp rospy std_msgs
51 | DEPENDS EIGEN3 PCL Ceres
52 | INCLUDE_DIRS include
53 | )
54 |
55 |
56 |
57 | add_executable(floam_laser_processing_node src/laserProcessingNode.cpp src/laserProcessingClass.cpp src/lidar.cpp)
58 | target_link_libraries(floam_laser_processing_node ${EIGEN3_LIBRARIES} ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES})
59 |
60 | add_executable(floam_odom_estimation_node src/odomEstimationNode.cpp src/lidarOptimization.cpp src/lidar.cpp src/odomEstimationClass.cpp)
61 | target_link_libraries(floam_odom_estimation_node ${EIGEN3_LIBRARIES} ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES})
62 |
63 | add_executable(floam_laser_mapping_node src/laserMappingNode.cpp src/laserMappingClass.cpp src/lidar.cpp)
64 | target_link_libraries(floam_laser_mapping_node ${EIGEN3_LIBRARIES} ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES})
65 |
66 |
--------------------------------------------------------------------------------
/SC-A-LOAM/launch/floam_velodyne_32.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/SC-A-LOAM/launch/floam_velodyne_64.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/floam/launch/floam.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/floam/launch/floam_mapping.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/SC-A-LOAM/include/aloam_velodyne/common.h:
--------------------------------------------------------------------------------
1 | // This is an advanced implementation of the algorithm described in the following paper:
2 | // J. Zhang and S. Singh. LOAM: Lidar Odometry and Mapping in Real-time.
3 | // Robotics: Science and Systems Conference (RSS). Berkeley, CA, July 2014.
4 |
5 | // Modifier: Tong Qin qintonguav@gmail.com
6 | // Shaozu Cao saozu.cao@connect.ust.hk
7 |
8 |
9 | // Copyright 2013, Ji Zhang, Carnegie Mellon University
10 | // Further contributions copyright (c) 2016, Southwest Research Institute
11 | // All rights reserved.
12 | //
13 | // Redistribution and use in source and binary forms, with or without
14 | // modification, are permitted provided that the following conditions are met:
15 | //
16 | // 1. Redistributions of source code must retain the above copyright notice,
17 | // this list of conditions and the following disclaimer.
18 | // 2. Redistributions in binary form must reproduce the above copyright notice,
19 | // this list of conditions and the following disclaimer in the documentation
20 | // and/or other materials provided with the distribution.
21 | // 3. Neither the name of the copyright holder nor the names of its
22 | // contributors may be used to endorse or promote products derived from this
23 | // software without specific prior written permission.
24 | //
25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
29 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 | // POSSIBILITY OF SUCH DAMAGE.
36 |
37 | #pragma once
38 |
39 | #include
40 |
41 | #include
42 |
43 | typedef pcl::PointXYZI PointType;
44 |
45 | inline double rad2deg(double radians)
46 | {
47 | return radians * 180.0 / M_PI;
48 | }
49 |
50 | inline double deg2rad(double degrees)
51 | {
52 | return degrees * M_PI / 180.0;
53 | }
54 |
55 | struct Pose6D {
56 | double x;
57 | double y;
58 | double z;
59 | double roll;
60 | double pitch;
61 | double yaw;
62 | };
--------------------------------------------------------------------------------
/floam/include/odomEstimationClass.h:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 | #ifndef _ODOM_ESTIMATION_CLASS_H_
5 | #define _ODOM_ESTIMATION_CLASS_H_
6 |
7 | //std lib
8 | #include
9 | #include
10 | #include
11 |
12 | //PCL
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | //ceres
24 | #include
25 | #include
26 |
27 | //eigen
28 | #include
29 | #include
30 |
31 | //LOCAL LIB
32 | #include "lidar.h"
33 | #include "lidarOptimization.h"
34 | #include
35 |
36 | class OdomEstimationClass
37 | {
38 |
39 | public:
40 | OdomEstimationClass();
41 |
42 | void init(lidar::Lidar lidar_param, double map_resolution);
43 | void initMapWithPoints(const pcl::PointCloud::Ptr& edge_in, const pcl::PointCloud::Ptr& surf_in);
44 | void updatePointsToMap(const pcl::PointCloud::Ptr& edge_in, const pcl::PointCloud::Ptr& surf_in);
45 | void getMap(pcl::PointCloud::Ptr& laserCloudMap);
46 |
47 | Eigen::Isometry3d odom;
48 | pcl::PointCloud::Ptr laserCloudCornerMap;
49 | pcl::PointCloud::Ptr laserCloudSurfMap;
50 | private:
51 | //optimization variable
52 | double parameters[7] = {0, 0, 0, 1, 0, 0, 0};
53 | Eigen::Map q_w_curr = Eigen::Map(parameters);
54 | Eigen::Map t_w_curr = Eigen::Map(parameters + 4);
55 |
56 | Eigen::Isometry3d last_odom;
57 |
58 | //kd-tree
59 | pcl::KdTreeFLANN::Ptr kdtreeEdgeMap;
60 | pcl::KdTreeFLANN::Ptr kdtreeSurfMap;
61 |
62 | //points downsampling before add to map
63 | pcl::VoxelGrid downSizeFilterEdge;
64 | pcl::VoxelGrid downSizeFilterSurf;
65 |
66 | //local map
67 | pcl::CropBox cropBoxFilter;
68 |
69 | //optimization count
70 | int optimization_count;
71 |
72 | //function
73 | void addEdgeCostFactor(const pcl::PointCloud::Ptr& pc_in, const pcl::PointCloud::Ptr& map_in, ceres::Problem& problem, ceres::LossFunction *loss_function);
74 | void addSurfCostFactor(const pcl::PointCloud::Ptr& pc_in, const pcl::PointCloud::Ptr& map_in, ceres::Problem& problem, ceres::LossFunction *loss_function);
75 | void addPointsToMap(const pcl::PointCloud::Ptr& downsampledEdgeCloud, const pcl::PointCloud::Ptr& downsampledSurfCloud);
76 | void pointAssociateToMap(pcl::PointXYZI const *const pi, pcl::PointXYZI *const po);
77 | void downSamplingToMap(const pcl::PointCloud::Ptr& edge_pc_in, pcl::PointCloud::Ptr& edge_pc_out, const pcl::PointCloud::Ptr& surf_pc_in, pcl::PointCloud::Ptr& surf_pc_out);
78 | };
79 |
80 | #endif // _ODOM_ESTIMATION_CLASS_H_
81 |
82 |
--------------------------------------------------------------------------------
/floam/src/laserMappingNode.cpp:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 |
5 | //c++ lib
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | //ros lib
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | //pcl lib
21 | #include
22 | #include
23 | #include
24 |
25 | //local lib
26 | #include "laserMappingClass.h"
27 | #include "lidar.h"
28 |
29 |
30 | LaserMappingClass laserMapping;
31 | lidar::Lidar lidar_param;
32 | std::mutex mutex_lock;
33 | std::queue odometryBuf;
34 | std::queue pointCloudBuf;
35 |
36 | ros::Publisher map_pub;
37 | void odomCallback(const nav_msgs::Odometry::ConstPtr &msg)
38 | {
39 | mutex_lock.lock();
40 | odometryBuf.push(msg);
41 | mutex_lock.unlock();
42 | }
43 |
44 | void velodyneHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg)
45 | {
46 | mutex_lock.lock();
47 | pointCloudBuf.push(laserCloudMsg);
48 | mutex_lock.unlock();
49 | }
50 |
51 |
52 | void laser_mapping(){
53 | while(1){
54 | if(!odometryBuf.empty() && !pointCloudBuf.empty()){
55 |
56 | //read data
57 | mutex_lock.lock();
58 | if(!pointCloudBuf.empty() && pointCloudBuf.front()->header.stamp.toSec()header.stamp.toSec()-0.5*lidar_param.scan_period){
59 | ROS_WARN("time stamp unaligned error and pointcloud discarded, pls check your data --> laser mapping node");
60 | pointCloudBuf.pop();
61 | mutex_lock.unlock();
62 | continue;
63 | }
64 |
65 | if(!odometryBuf.empty() && odometryBuf.front()->header.stamp.toSec() < pointCloudBuf.front()->header.stamp.toSec()-0.5*lidar_param.scan_period){
66 | odometryBuf.pop();
67 | ROS_INFO("time stamp unaligned with path final, pls check your data --> laser mapping node");
68 | mutex_lock.unlock();
69 | continue;
70 | }
71 |
72 | //if time aligned
73 | pcl::PointCloud::Ptr pointcloud_in(new pcl::PointCloud());
74 | pcl::fromROSMsg(*pointCloudBuf.front(), *pointcloud_in);
75 | ros::Time pointcloud_time = (pointCloudBuf.front())->header.stamp;
76 |
77 | Eigen::Isometry3d current_pose = Eigen::Isometry3d::Identity();
78 | current_pose.rotate(Eigen::Quaterniond(odometryBuf.front()->pose.pose.orientation.w,odometryBuf.front()->pose.pose.orientation.x,odometryBuf.front()->pose.pose.orientation.y,odometryBuf.front()->pose.pose.orientation.z));
79 | current_pose.pretranslate(Eigen::Vector3d(odometryBuf.front()->pose.pose.position.x,odometryBuf.front()->pose.pose.position.y,odometryBuf.front()->pose.pose.position.z));
80 | pointCloudBuf.pop();
81 | odometryBuf.pop();
82 | mutex_lock.unlock();
83 |
84 |
85 | laserMapping.updateCurrentPointsToMap(pointcloud_in,current_pose);
86 |
87 | pcl::PointCloud::Ptr pc_map = laserMapping.getMap();
88 | sensor_msgs::PointCloud2 PointsMsg;
89 | pcl::toROSMsg(*pc_map, PointsMsg);
90 | PointsMsg.header.stamp = pointcloud_time;
91 | PointsMsg.header.frame_id = "map";
92 | map_pub.publish(PointsMsg);
93 |
94 |
95 |
96 | }
97 | //sleep 2 ms every time
98 | std::chrono::milliseconds dura(2);
99 | std::this_thread::sleep_for(dura);
100 | }
101 | }
102 |
103 | int main(int argc, char **argv)
104 | {
105 | ros::init(argc, argv, "main");
106 | ros::NodeHandle nh;
107 |
108 | int scan_line = 64;
109 | double vertical_angle = 2.0;
110 | double scan_period= 0.1;
111 | double max_dis = 60.0;
112 | double min_dis = 2.0;
113 | double map_resolution = 0.4;
114 | nh.getParam("/scan_period", scan_period);
115 | nh.getParam("/vertical_angle", vertical_angle);
116 | nh.getParam("/max_dis", max_dis);
117 | nh.getParam("/min_dis", min_dis);
118 | nh.getParam("/scan_line", scan_line);
119 | nh.getParam("/map_resolution", map_resolution);
120 |
121 | lidar_param.setScanPeriod(scan_period);
122 | lidar_param.setVerticalAngle(vertical_angle);
123 | lidar_param.setLines(scan_line);
124 | lidar_param.setMaxDistance(max_dis);
125 | lidar_param.setMinDistance(min_dis);
126 |
127 | laserMapping.init(map_resolution);
128 | ros::Subscriber subLaserCloud = nh.subscribe("/velodyne_points_filtered", 100, velodyneHandler);
129 | ros::Subscriber subOdometry = nh.subscribe("/odom", 100, odomCallback);
130 |
131 | map_pub = nh.advertise("/map", 100);
132 | std::thread laser_mapping_process{laser_mapping};
133 |
134 | ros::spin();
135 |
136 | return 0;
137 | }
138 |
--------------------------------------------------------------------------------
/floam/src/laserProcessingNode.cpp:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 |
5 | //c++ lib
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | //ros lib
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | //pcl lib
22 | #include
23 | #include
24 | #include
25 |
26 | //local lib
27 | #include "lidar.h"
28 | #include "laserProcessingClass.h"
29 |
30 |
31 | LaserProcessingClass laserProcessing;
32 | std::mutex mutex_lock;
33 | std::queue pointCloudBuf;
34 | lidar::Lidar lidar_param;
35 |
36 | ros::Publisher pubEdgePoints;
37 | ros::Publisher pubSurfPoints;
38 | ros::Publisher pubLaserCloudFiltered;
39 |
40 | void velodyneHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg)
41 | {
42 | mutex_lock.lock();
43 | pointCloudBuf.push(laserCloudMsg);
44 | mutex_lock.unlock();
45 |
46 | }
47 |
48 | double total_time =0;
49 | int total_frame=0;
50 |
51 | void laser_processing(){
52 | while(1){
53 | if(!pointCloudBuf.empty()){
54 | //read data
55 | mutex_lock.lock();
56 | pcl::PointCloud::Ptr pointcloud_in(new pcl::PointCloud());
57 | pcl::fromROSMsg(*pointCloudBuf.front(), *pointcloud_in);
58 | ros::Time pointcloud_time = (pointCloudBuf.front())->header.stamp;
59 | pointCloudBuf.pop();
60 | mutex_lock.unlock();
61 |
62 | pcl::PointCloud::Ptr pointcloud_edge(new pcl::PointCloud());
63 | pcl::PointCloud::Ptr pointcloud_surf(new pcl::PointCloud());
64 |
65 | std::chrono::time_point start, end;
66 | start = std::chrono::system_clock::now();
67 | laserProcessing.featureExtraction(pointcloud_in,pointcloud_edge,pointcloud_surf);
68 | end = std::chrono::system_clock::now();
69 | std::chrono::duration elapsed_seconds = end - start;
70 | total_frame++;
71 | float time_temp = elapsed_seconds.count() * 1000;
72 | total_time+=time_temp;
73 | //ROS_INFO("average laser processing time %f ms \n \n", total_time/total_frame);
74 |
75 | sensor_msgs::PointCloud2 laserCloudFilteredMsg;
76 | pcl::PointCloud::Ptr pointcloud_filtered(new pcl::PointCloud());
77 | *pointcloud_filtered+=*pointcloud_edge;
78 | *pointcloud_filtered+=*pointcloud_surf;
79 | pcl::toROSMsg(*pointcloud_filtered, laserCloudFilteredMsg);
80 | laserCloudFilteredMsg.header.stamp = pointcloud_time;
81 | laserCloudFilteredMsg.header.frame_id = "base_link";
82 | pubLaserCloudFiltered.publish(laserCloudFilteredMsg);
83 |
84 | sensor_msgs::PointCloud2 edgePointsMsg;
85 | pcl::toROSMsg(*pointcloud_edge, edgePointsMsg);
86 | edgePointsMsg.header.stamp = pointcloud_time;
87 | edgePointsMsg.header.frame_id = "base_link";
88 | pubEdgePoints.publish(edgePointsMsg);
89 |
90 |
91 | sensor_msgs::PointCloud2 surfPointsMsg;
92 | pcl::toROSMsg(*pointcloud_surf, surfPointsMsg);
93 | surfPointsMsg.header.stamp = pointcloud_time;
94 | surfPointsMsg.header.frame_id = "base_link";
95 | pubSurfPoints.publish(surfPointsMsg);
96 |
97 | }
98 | //sleep 2 ms every time
99 | std::chrono::milliseconds dura(2);
100 | std::this_thread::sleep_for(dura);
101 | }
102 | }
103 |
104 | int main(int argc, char **argv)
105 | {
106 | ros::init(argc, argv, "main");
107 | ros::NodeHandle nh;
108 |
109 | int scan_line = 64;
110 | double vertical_angle = 2.0;
111 | double scan_period= 0.1;
112 | double max_dis = 60.0;
113 | double min_dis = 2.0;
114 |
115 | nh.getParam("/scan_period", scan_period);
116 | nh.getParam("/vertical_angle", vertical_angle);
117 | nh.getParam("/max_dis", max_dis);
118 | nh.getParam("/min_dis", min_dis);
119 | nh.getParam("/scan_line", scan_line);
120 |
121 | lidar_param.setScanPeriod(scan_period);
122 | lidar_param.setVerticalAngle(vertical_angle);
123 | lidar_param.setLines(scan_line);
124 | lidar_param.setMaxDistance(max_dis);
125 | lidar_param.setMinDistance(min_dis);
126 |
127 | laserProcessing.init(lidar_param);
128 |
129 | ros::Subscriber subLaserCloud = nh.subscribe("/velodyne_points", 100, velodyneHandler);
130 |
131 | pubLaserCloudFiltered = nh.advertise("/velodyne_points_filtered", 100);
132 |
133 | pubEdgePoints = nh.advertise("/laser_cloud_edge", 100);
134 |
135 | pubSurfPoints = nh.advertise("/laser_cloud_surf", 100);
136 |
137 | std::thread laser_processing_process{laser_processing};
138 |
139 | ros::spin();
140 |
141 | return 0;
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/floam/README.md:
--------------------------------------------------------------------------------
1 | # FLOAM
2 | ## Fast LOAM (Lidar Odometry And Mapping)
3 |
4 | This work is an optimized version of A-LOAM and LOAM with the computational cost reduced by up to 3 times.
5 | This code is modified from [LOAM](https://github.com/laboshinl/loam_velodyne) and [A-LOAM](https://github.com/HKUST-Aerial-Robotics/A-LOAM) .
6 |
7 | **Modifier:** [Wang Han](http://wanghan.pro), Nanyang Technological University, Singapore
8 |
9 | ## 1. Demo Highlights
10 | Watch our demo at [Video Link](https://youtu.be/PzZly1SQtng)
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## 2. Evaluation
18 | ### 2.1. Computational efficiency evaluation
19 | Computational efficiency evaluation (based on KITTI dataset):
20 | Platform: Intel® Core™ i7-8700 CPU @ 3.20GHz
21 | | Dataset | ALOAM | FLOAM |
22 | |----------------------------------------------|----------------------------|------------------------|
23 | | `KITTI` | 151ms | 59ms |
24 |
25 | Localization error:
26 | | Dataset | ALOAM | FLOAM |
27 | |----------------------------------------------|----------------------------|------------------------|
28 | | `KITTI sequence 00` | 0.55% | 0.51% |
29 | | `KITTI sequence 02` | 3.93% | 1.25% |
30 | | `KITTI sequence 05` | 1.28% | 0.93% |
31 |
32 | ### 2.2. localization result
33 |
34 |
35 |
36 |
37 | ### 2.3. mapping result
38 |
39 |
40 |
41 |
42 |
43 |
44 | ## 3. Prerequisites
45 | ### 3.1 **Ubuntu** and **ROS**
46 | Ubuntu 64-bit 18.04.
47 |
48 | ROS Melodic. [ROS Installation](http://wiki.ros.org/ROS/Installation)
49 |
50 | ### 3.2. **Ceres Solver**
51 | Follow [Ceres Installation](http://ceres-solver.org/installation.html).
52 |
53 | ### 3.3. **PCL**
54 | Follow [PCL Installation](http://www.pointclouds.org/downloads/linux.html).
55 |
56 | ### 3.4. **Trajectory visualization**
57 | For visualization purpose, this package uses hector trajectory sever, you may install the package by
58 | ```
59 | sudo apt-get install ros-melodic-hector-trajectory-server
60 | ```
61 | Alternatively, you may remove the hector trajectory server node if trajectory visualization is not needed
62 |
63 | ## 4. Build
64 | ### 4.1 Clone repository:
65 | ```
66 | cd ~/catkin_ws/src
67 | git clone https://github.com/wh200720041/floam.git
68 | cd ..
69 | catkin_make
70 | source ~/catkin_ws/devel/setup.bash
71 | ```
72 | ### 4.2 Download test rosbag
73 | Download [KITTI sequence 05](https://drive.google.com/file/d/1eyO0Io3lX2z-yYsfGHawMKZa5Z0uYJ0W/view?usp=sharing) or [KITTI sequence 07](https://drive.google.com/file/d/1_qUfwUw88rEKitUpt1kjswv7Cv4GPs0b/view?usp=sharing)
74 |
75 | Unzip compressed file 2011_09_30_0018.zip. If your system does not have unzip. please install unzip by
76 | ```
77 | sudo apt-get install unzip
78 | ```
79 |
80 | And this may take a few minutes to unzip the file
81 | ```
82 | cd ~/Downloads
83 | unzip ~/Downloads/2011_09_30_0018.zip
84 | ```
85 |
86 | ### 4.3 Launch ROS
87 | ```
88 | roslaunch floam floam.launch
89 | ```
90 | if you would like to create the map at the same time, you can run (more cpu cost)
91 | ```
92 | roslaunch floam floam_mapping.launch
93 | ```
94 | If the mapping process is slow, you may wish to change the rosbag speed by replacing "--clock -r 0.5" with "--clock -r 0.2" in your launch file, or you can change the map publish frequency manually (default is 10 Hz)
95 |
96 |
97 | ## 5. Test on other sequence
98 | To generate rosbag file of kitti dataset, you may use the tools provided by
99 | [kitti_to_rosbag](https://github.com/ethz-asl/kitti_to_rosbag) or [kitti2bag](https://github.com/tomas789/kitti2bag)
100 |
101 | ## 6. Test on Velodyne VLP-16 or HDL-32
102 | You may wish to test FLOAM on your own platform and sensor such as VLP-16
103 | You can install the velodyne sensor driver by
104 | ```
105 | sudo apt-get install ros-melodic-velodyne-pointcloud
106 | ```
107 | launch floam for your own velodyne sensor
108 | ```
109 | roslaunch floam floam_velodyne.launch
110 | ```
111 | If you are using HDL-32 or other sensor, please change the scan_line in the launch file
112 |
113 |
114 | ## 7.Acknowledgements
115 | Thanks for [A-LOAM](https://github.com/HKUST-Aerial-Robotics/A-LOAM) and LOAM(J. Zhang and S. Singh. LOAM: Lidar Odometry and Mapping in Real-time) and [LOAM_NOTED](https://github.com/cuitaixiang/LOAM_NOTED).
116 |
117 |
118 | ## 8. Citation
119 | If you use this work for your research, you may want to cite
120 | ```
121 | @inproceedings{wang2021,
122 | author={H. {Wang} and C. {Wang} and C. {Chen} and L. {Xie}},
123 | booktitle={2021 IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)},
124 | title={F-LOAM : Fast LiDAR Odometry and Mapping},
125 | year={2020},
126 | volume={},
127 | number={}
128 | }
129 | ```
130 |
--------------------------------------------------------------------------------
/floam/src/lidarOptimization.cpp:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 |
5 | #include "lidarOptimization.h"
6 |
7 | EdgeAnalyticCostFunction::EdgeAnalyticCostFunction(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_a_, Eigen::Vector3d last_point_b_)
8 | : curr_point(curr_point_), last_point_a(last_point_a_), last_point_b(last_point_b_){
9 |
10 | }
11 |
12 | bool EdgeAnalyticCostFunction::Evaluate(double const *const *parameters, double *residuals, double **jacobians) const
13 | {
14 |
15 | Eigen::Map q_last_curr(parameters[0]);
16 | Eigen::Map t_last_curr(parameters[0] + 4);
17 | Eigen::Vector3d lp;
18 | lp = q_last_curr * curr_point + t_last_curr;
19 |
20 | Eigen::Vector3d nu = (lp - last_point_a).cross(lp - last_point_b);
21 | Eigen::Vector3d de = last_point_a - last_point_b;
22 | double de_norm = de.norm();
23 | residuals[0] = nu.norm()/de_norm;
24 |
25 | if(jacobians != NULL)
26 | {
27 | if(jacobians[0] != NULL)
28 | {
29 | Eigen::Matrix3d skew_lp = skew(lp);
30 | Eigen::Matrix dp_by_se3;
31 | dp_by_se3.block<3,3>(0,0) = -skew_lp;
32 | (dp_by_se3.block<3,3>(0, 3)).setIdentity();
33 | Eigen::Map > J_se3(jacobians[0]);
34 | J_se3.setZero();
35 | Eigen::Matrix3d skew_de = skew(de);
36 | J_se3.block<1,6>(0,0) = - nu.transpose() / nu.norm() * skew_de * dp_by_se3/de_norm;
37 |
38 | }
39 | }
40 |
41 | return true;
42 |
43 | }
44 |
45 |
46 | SurfNormAnalyticCostFunction::SurfNormAnalyticCostFunction(Eigen::Vector3d curr_point_, Eigen::Vector3d plane_unit_norm_, double negative_OA_dot_norm_)
47 | : curr_point(curr_point_), plane_unit_norm(plane_unit_norm_), negative_OA_dot_norm(negative_OA_dot_norm_){
48 |
49 | }
50 |
51 | bool SurfNormAnalyticCostFunction::Evaluate(double const *const *parameters, double *residuals, double **jacobians) const
52 | {
53 | Eigen::Map q_w_curr(parameters[0]);
54 | Eigen::Map t_w_curr(parameters[0] + 4);
55 | Eigen::Vector3d point_w = q_w_curr * curr_point + t_w_curr;
56 | residuals[0] = plane_unit_norm.dot(point_w) + negative_OA_dot_norm;
57 |
58 | if(jacobians != NULL)
59 | {
60 | if(jacobians[0] != NULL)
61 | {
62 | Eigen::Matrix3d skew_point_w = skew(point_w);
63 | Eigen::Matrix dp_by_se3;
64 | dp_by_se3.block<3,3>(0,0) = -skew_point_w;
65 | (dp_by_se3.block<3,3>(0, 3)).setIdentity();
66 | Eigen::Map > J_se3(jacobians[0]);
67 | J_se3.setZero();
68 | J_se3.block<1,6>(0,0) = plane_unit_norm.transpose() * dp_by_se3;
69 |
70 | }
71 | }
72 | return true;
73 |
74 | }
75 |
76 |
77 | bool PoseSE3Parameterization::Plus(const double *x, const double *delta, double *x_plus_delta) const
78 | {
79 | Eigen::Map trans(x + 4);
80 |
81 | Eigen::Quaterniond delta_q;
82 | Eigen::Vector3d delta_t;
83 | getTransformFromSe3(Eigen::Map>(delta), delta_q, delta_t);
84 | Eigen::Map quater(x);
85 | Eigen::Map quater_plus(x_plus_delta);
86 | Eigen::Map trans_plus(x_plus_delta + 4);
87 |
88 | quater_plus = delta_q * quater;
89 | trans_plus = delta_q * trans + delta_t;
90 |
91 | return true;
92 | }
93 |
94 | bool PoseSE3Parameterization::ComputeJacobian(const double *x, double *jacobian) const
95 | {
96 | Eigen::Map> j(jacobian);
97 | (j.topRows(6)).setIdentity();
98 | (j.bottomRows(1)).setZero();
99 |
100 | return true;
101 | }
102 |
103 | void getTransformFromSe3(const Eigen::Matrix& se3, Eigen::Quaterniond& q, Eigen::Vector3d& t){
104 | Eigen::Vector3d omega(se3.data());
105 | Eigen::Vector3d upsilon(se3.data()+3);
106 | Eigen::Matrix3d Omega = skew(omega);
107 |
108 | double theta = omega.norm();
109 | double half_theta = 0.5*theta;
110 |
111 | double imag_factor;
112 | double real_factor = cos(half_theta);
113 | if(theta<1e-10)
114 | {
115 | double theta_sq = theta*theta;
116 | double theta_po4 = theta_sq*theta_sq;
117 | imag_factor = 0.5-0.0208333*theta_sq+0.000260417*theta_po4;
118 | }
119 | else
120 | {
121 | double sin_half_theta = sin(half_theta);
122 | imag_factor = sin_half_theta/theta;
123 | }
124 |
125 | q = Eigen::Quaterniond(real_factor, imag_factor*omega.x(), imag_factor*omega.y(), imag_factor*omega.z());
126 |
127 |
128 | Eigen::Matrix3d J;
129 | if (theta<1e-10)
130 | {
131 | J = q.matrix();
132 | }
133 | else
134 | {
135 | Eigen::Matrix3d Omega2 = Omega*Omega;
136 | J = (Eigen::Matrix3d::Identity() + (1-cos(theta))/(theta*theta)*Omega + (theta-sin(theta))/(pow(theta,3))*Omega2);
137 | }
138 |
139 | t = J*upsilon;
140 | }
141 |
142 | Eigen::Matrix skew(Eigen::Matrix& mat_in){
143 | Eigen::Matrix skew_mat;
144 | skew_mat.setZero();
145 | skew_mat(0,1) = -mat_in(2);
146 | skew_mat(0,2) = mat_in(1);
147 | skew_mat(1,2) = -mat_in(0);
148 | skew_mat(1,0) = mat_in(2);
149 | skew_mat(2,0) = -mat_in(1);
150 | skew_mat(2,1) = mat_in(0);
151 | return skew_mat;
152 | }
--------------------------------------------------------------------------------
/SC-A-LOAM/include/scancontext/Scancontext.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "scancontext/nanoflann.hpp"
26 | #include "scancontext/KDTreeVectorOfVectorsAdaptor.h"
27 | #include "aloam_velodyne/tic_toc.h"
28 |
29 | using namespace Eigen;
30 | using namespace nanoflann;
31 |
32 | using std::cout;
33 | using std::endl;
34 | using std::make_pair;
35 |
36 | using std::atan2;
37 | using std::cos;
38 | using std::sin;
39 |
40 | using SCPointType = pcl::PointXYZI; // using xyz only. but a user can exchange the original bin encoding function (i.e., max hegiht) to max intensity (for detail, refer 20 ICRA Intensity Scan Context)
41 | using KeyMat = std::vector >;
42 | using InvKeyTree = KDTreeVectorOfVectorsAdaptor< KeyMat, float >;
43 |
44 |
45 | // namespace SC2
46 | // {
47 |
48 | void coreImportTest ( void );
49 |
50 |
51 | // sc param-independent helper functions
52 | float xy2theta( const float & _x, const float & _y );
53 | MatrixXd circshift( MatrixXd &_mat, int _num_shift );
54 | std::vector eig2stdvec( MatrixXd _eigmat );
55 |
56 |
57 | class SCManager
58 | {
59 | public:
60 | SCManager( ) = default; // reserving data space (of std::vector) could be considered. but the descriptor is lightweight so don't care.
61 |
62 | Eigen::MatrixXd makeScancontext( pcl::PointCloud & _scan_down );
63 | Eigen::MatrixXd makeRingkeyFromScancontext( Eigen::MatrixXd &_desc );
64 | Eigen::MatrixXd makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc );
65 |
66 | int fastAlignUsingVkey ( MatrixXd & _vkey1, MatrixXd & _vkey2 );
67 | double distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 ); // "d" (eq 5) in the original paper (IROS 18)
68 | std::pair distanceBtnScanContext ( MatrixXd &_sc1, MatrixXd &_sc2 ); // "D" (eq 6) in the original paper (IROS 18)
69 |
70 | // User-side API
71 | void makeAndSaveScancontextAndKeys( pcl::PointCloud & _scan_down );
72 | std::pair detectLoopClosureID( void ); // int: nearest node index, float: relative yaw
73 |
74 | // for ltslam
75 | // User-side API for multi-session
76 | void saveScancontextAndKeys( Eigen::MatrixXd _scd );
77 | std::pair detectLoopClosureIDBetweenSession ( std::vector& curr_key, Eigen::MatrixXd& curr_desc);
78 |
79 | const Eigen::MatrixXd& getConstRefRecentSCD(void);
80 |
81 | public:
82 | // hyper parameters ()
83 | const double LIDAR_HEIGHT = 2.0; // lidar height : add this for simply directly using lidar scan in the lidar local coord (not robot base coord) / if you use robot-coord-transformed lidar scans, just set this as 0.
84 |
85 | const int PC_NUM_RING = 20; // 20 in the original paper (IROS 18)
86 | const int PC_NUM_SECTOR = 60; // 60 in the original paper (IROS 18)
87 | double PC_MAX_RADIUS = 80.0; // 80 meter max in the original paper (IROS 18)
88 | const double PC_UNIT_SECTORANGLE = 360.0 / double(PC_NUM_SECTOR);
89 | const double PC_UNIT_RINGGAP = PC_MAX_RADIUS / double(PC_NUM_RING);
90 |
91 | // tree
92 | const int NUM_EXCLUDE_RECENT = 30; // simply just keyframe gap (related with loopClosureFrequency in yaml), but node position distance-based exclusion is ok.
93 | const int NUM_CANDIDATES_FROM_TREE = 3; // 10 is enough. (refer the IROS 18 paper)
94 |
95 | // loop thres
96 | const double SEARCH_RATIO = 0.1; // for fast comparison, no Brute-force, but search 10 % is okay. // not was in the original conf paper, but improved ver.
97 | // const double SC_DIST_THRES = 0.13; // empirically 0.1-0.2 is fine (rare false-alarms) for 20x60 polar context (but for 0.15 <, DCS or ICP fit score check (e.g., in LeGO-LOAM) should be required for robustness)
98 |
99 | double SC_DIST_THRES = 0.2; // 0.4-0.6 is good choice for using with robust kernel (e.g., Cauchy, DCS) + icp fitness threshold / if not, recommend 0.1-0.15
100 | // const double SC_DIST_THRES = 0.7; // 0.4-0.6 is good choice for using with robust kernel (e.g., Cauchy, DCS) + icp fitness threshold / if not, recommend 0.1-0.15
101 |
102 | // config
103 | const int TREE_MAKING_PERIOD_ = 30; // i.e., remaking tree frequency, to avoid non-mandatory every remaking, to save time cost / in the LeGO-LOAM integration, it is synchronized with the loop detection callback (which is 1Hz) so it means the tree is updated evrey 10 sec. But you can use the smaller value because it is enough fast ~ 5-50ms wrt N.
104 | int tree_making_period_conter = 0;
105 |
106 | // setter
107 | void setSCdistThres(double _new_thres);
108 | void setMaximumRadius(double _max_r);
109 |
110 | // data
111 | std::vector polarcontexts_timestamp_; // optional.
112 | std::vector polarcontexts_;
113 | std::vector polarcontext_invkeys_;
114 | std::vector polarcontext_vkeys_;
115 |
116 | KeyMat polarcontext_invkeys_mat_;
117 | KeyMat polarcontext_invkeys_to_search_;
118 | std::unique_ptr polarcontext_tree_;
119 |
120 | bool is_tree_batch_made = false;
121 | std::unique_ptr polarcontext_tree_batch_;
122 |
123 | }; // SCManager
124 |
125 | // } // namespace SC2
126 |
--------------------------------------------------------------------------------
/SC-A-LOAM/include/scancontext/KDTreeVectorOfVectorsAdaptor.h:
--------------------------------------------------------------------------------
1 | /***********************************************************************
2 | * Software License Agreement (BSD License)
3 | *
4 | * Copyright 2011-16 Jose Luis Blanco (joseluisblancoc@gmail.com).
5 | * All rights reserved.
6 | *
7 | * Redistribution and use in source and binary forms, with or without
8 | * modification, are permitted provided that the following conditions
9 | * are met:
10 | *
11 | * 1. Redistributions of source code must retain the above copyright
12 | * notice, this list of conditions and the following disclaimer.
13 | * 2. Redistributions in binary form must reproduce the above copyright
14 | * notice, this list of conditions and the following disclaimer in the
15 | * documentation and/or other materials provided with the distribution.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | *************************************************************************/
28 |
29 | #pragma once
30 |
31 | #include "scancontext/nanoflann.hpp"
32 |
33 | #include
34 |
35 | // ===== This example shows how to use nanoflann with these types of containers: =======
36 | //typedef std::vector > my_vector_of_vectors_t;
37 | //typedef std::vector my_vector_of_vectors_t; // This requires #include
38 | // =====================================================================================
39 |
40 |
41 | /** A simple vector-of-vectors adaptor for nanoflann, without duplicating the storage.
42 | * The i'th vector represents a point in the state space.
43 | *
44 | * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations.
45 | * \tparam num_t The type of the point coordinates (typically, double or float).
46 | * \tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.
47 | * \tparam IndexType The type for indices in the KD-tree index (typically, size_t of int)
48 | */
49 | template
50 | struct KDTreeVectorOfVectorsAdaptor
51 | {
52 | typedef KDTreeVectorOfVectorsAdaptor self_t;
53 | typedef typename Distance::template traits::distance_t metric_t;
54 | typedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType> index_t;
55 |
56 | index_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index.
57 |
58 | /// Constructor: takes a const ref to the vector of vectors object with the data points
59 | KDTreeVectorOfVectorsAdaptor(const size_t /* dimensionality */, const VectorOfVectorsType &mat, const int leaf_max_size = 10) : m_data(mat)
60 | {
61 | assert(mat.size() != 0 && mat[0].size() != 0);
62 | const size_t dims = mat[0].size();
63 | if (DIM>0 && static_cast(dims) != DIM)
64 | throw std::runtime_error("Data set dimensionality does not match the 'DIM' template argument");
65 | index = new index_t( static_cast(dims), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) );
66 | index->buildIndex();
67 | }
68 |
69 | ~KDTreeVectorOfVectorsAdaptor() {
70 | delete index;
71 | }
72 |
73 | const VectorOfVectorsType &m_data;
74 |
75 | /** Query for the \a num_closest closest points to a given point (entered as query_point[0:dim-1]).
76 | * Note that this is a short-cut method for index->findNeighbors().
77 | * The user can also call index->... methods as desired.
78 | * \note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface.
79 | */
80 | inline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const
81 | {
82 | nanoflann::KNNResultSet resultSet(num_closest);
83 | resultSet.init(out_indices, out_distances_sq);
84 | index->findNeighbors(resultSet, query_point, nanoflann::SearchParams());
85 | }
86 |
87 | /** @name Interface expected by KDTreeSingleIndexAdaptor
88 | * @{ */
89 |
90 | const self_t & derived() const {
91 | return *this;
92 | }
93 | self_t & derived() {
94 | return *this;
95 | }
96 |
97 | // Must return the number of data points
98 | inline size_t kdtree_get_point_count() const {
99 | return m_data.size();
100 | }
101 |
102 | // Returns the dim'th component of the idx'th point in the class:
103 | inline num_t kdtree_get_pt(const size_t idx, const size_t dim) const {
104 | return m_data[idx][dim];
105 | }
106 |
107 | // Optional bounding-box computation: return false to default to a standard bbox computation loop.
108 | // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it again.
109 | // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
110 | template
111 | bool kdtree_get_bbox(BBOX & /*bb*/) const {
112 | return false;
113 | }
114 |
115 | /** @} */
116 |
117 | }; // end of KDTreeVectorOfVectorsAdaptor
118 |
--------------------------------------------------------------------------------
/SC-A-LOAM/utils/python/makeMergedMap.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import time
4 | import copy
5 | from io import StringIO
6 |
7 | import pypcd # for the install, use this command: python3.x (use your python ver) -m pip install --user git+https://github.com/DanielPollithy/pypcd.git
8 | from pypcd import pypcd
9 |
10 | import numpy as np
11 | from numpy import linalg as LA
12 |
13 | import open3d as o3d
14 |
15 | from pypcdMyUtils import *
16 |
17 | jet_table = np.load('jet_table.npy')
18 | bone_table = np.load('bone_table.npy')
19 |
20 | color_table = bone_table
21 | color_table_len = color_table.shape[0]
22 |
23 |
24 | ##########################
25 | # User only consider this block
26 | ##########################
27 |
28 | data_dir = "../sample_data/Seosan01/" # should end with /
29 | scan_idx_range_to_stack = [0, 20] # if you want a whole map, use [0, len(scan_files)]
30 | node_skip = 1
31 |
32 | num_points_in_a_scan = 150000 # for reservation (save faster) // e.g., use 150000 for 128 ray lidars, 100000 for 64 ray lidars, 30000 for 16 ray lidars, if error occured, use the larger value.
33 |
34 | is_live_vis = False # recommend to use false
35 | is_o3d_vis = True
36 | intensity_color_max = 200
37 |
38 | is_near_removal = True
39 | thres_near_removal = 2 # meter (to remove platform-myself structure ghost points)
40 |
41 | ##########################
42 |
43 |
44 | #
45 | scan_dir = data_dir + "Scans"
46 | scan_files = os.listdir(scan_dir)
47 | scan_files.sort()
48 |
49 | poses = []
50 | f = open(data_dir+"optimized_poses.txt", 'r')
51 | while True:
52 | line = f.readline()
53 | if not line: break
54 | pose_SE3 = np.asarray([float(i) for i in line.split()])
55 | pose_SE3 = np.vstack( (np.reshape(pose_SE3, (3, 4)), np.asarray([0,0,0,1])) )
56 | poses.append(pose_SE3)
57 | f.close()
58 |
59 |
60 | #
61 | assert (scan_idx_range_to_stack[1] > scan_idx_range_to_stack[0])
62 | print("Merging scans from", scan_idx_range_to_stack[0], "to", scan_idx_range_to_stack[1])
63 |
64 |
65 | #
66 | if(is_live_vis):
67 | vis = o3d.visualization.Visualizer()
68 | vis.create_window('Map', visible = True)
69 |
70 | nodes_count = 0
71 | pcd_combined_for_vis = o3d.geometry.PointCloud()
72 | pcd_combined_for_save = None
73 |
74 | # The scans from 000000.pcd should be prepared if it is not used (because below code indexing is designed in a naive way)
75 |
76 | # manually reserve memory for fast write
77 | num_all_points_expected = int(num_points_in_a_scan * np.round((scan_idx_range_to_stack[1] - scan_idx_range_to_stack[0])/node_skip))
78 |
79 | np_xyz_all = np.empty([num_all_points_expected, 3])
80 | np_intensity_all = np.empty([num_all_points_expected, 1])
81 | curr_count = 0
82 |
83 | for node_idx in range(len(scan_files)):
84 | if(node_idx < scan_idx_range_to_stack[0] or node_idx >= scan_idx_range_to_stack[1]):
85 | continue
86 |
87 | nodes_count = nodes_count + 1
88 | if( nodes_count % node_skip is not 0):
89 | if(node_idx is not scan_idx_range_to_stack[0]): # to ensure the vis init
90 | continue
91 |
92 | print("read keyframe scan idx", node_idx)
93 |
94 | scan_pose = poses[node_idx]
95 |
96 | scan_path = os.path.join(scan_dir, scan_files[node_idx])
97 | scan_pcd = o3d.io.read_point_cloud(scan_path)
98 | scan_xyz_local = copy.deepcopy(np.asarray(scan_pcd.points))
99 |
100 | scan_pypcd_with_intensity = pypcd.PointCloud.from_path(scan_path)
101 | scan_intensity = scan_pypcd_with_intensity.pc_data['intensity']
102 | scan_intensity_colors_idx = np.round( (color_table_len-1) * np.minimum( 1, np.maximum(0, scan_intensity / intensity_color_max) ) )
103 | scan_intensity_colors = color_table[scan_intensity_colors_idx.astype(int)]
104 |
105 | scan_pcd_global = scan_pcd.transform(scan_pose) # global coord, note that this is not deepcopy
106 | scan_pcd_global.colors = o3d.utility.Vector3dVector(scan_intensity_colors)
107 | scan_xyz = np.asarray(scan_pcd_global.points)
108 |
109 | scan_intensity = np.expand_dims(scan_intensity, axis=1)
110 | scan_ranges = LA.norm(scan_xyz_local, axis=1)
111 |
112 | if(is_near_removal):
113 | eff_idxes = np.where (scan_ranges > thres_near_removal)
114 | scan_xyz = scan_xyz[eff_idxes[0], :]
115 | scan_intensity = scan_intensity[eff_idxes[0], :]
116 |
117 | scan_pcd_global = scan_pcd_global.select_by_index(eff_idxes[0])
118 |
119 | if(is_o3d_vis):
120 | pcd_combined_for_vis += scan_pcd_global # open3d pointcloud class append is fast
121 |
122 | if is_live_vis:
123 | if(node_idx is scan_idx_range_to_stack[0]): # to ensure the vis init
124 | vis.add_geometry(pcd_combined_for_vis)
125 |
126 | vis.update_geometry(pcd_combined_for_vis)
127 | vis.poll_events()
128 | vis.update_renderer()
129 |
130 | # save
131 | np_xyz_all[curr_count:curr_count + scan_xyz.shape[0], :] = scan_xyz
132 | np_intensity_all[curr_count:curr_count + scan_xyz.shape[0], :] = scan_intensity
133 |
134 | curr_count = curr_count + scan_xyz.shape[0]
135 | print(curr_count)
136 |
137 | #
138 | if(is_o3d_vis):
139 | print("draw the merged map.")
140 | o3d.visualization.draw_geometries([pcd_combined_for_vis])
141 |
142 |
143 | # save ply having intensity
144 | np_xyz_all = np_xyz_all[0:curr_count, :]
145 | np_intensity_all = np_intensity_all[0:curr_count, :]
146 |
147 | np_xyzi_all = np.hstack( (np_xyz_all, np_intensity_all) )
148 | xyzi = make_xyzi_point_cloud(np_xyzi_all)
149 |
150 | map_name = data_dir + "map_" + str(scan_idx_range_to_stack[0]) + "_to_" + str(scan_idx_range_to_stack[1]) + "_with_intensity.pcd"
151 | xyzi.save_pcd(map_name, compression='binary_compressed')
152 | print("intensity map is save (path:", map_name, ")")
153 |
154 | # save rgb colored points
155 | # map_name = data_dir + "map_" + str(scan_idx_range_to_stack[0]) + "_to_" + str(scan_idx_range_to_stack[1]) + ".pcd"
156 | # o3d.io.write_point_cloud(map_name, pcd_combined_for_vis)
157 | # print("the map is save (path:", map_name, ")")
158 |
159 |
160 |
--------------------------------------------------------------------------------
/SC-A-LOAM/README.md:
--------------------------------------------------------------------------------
1 | # SC-A-LOAM
2 |
3 | ## News
4 | - ``2021-07-16``: This repository's easy-to-use plug-and-play loop detection and pose graph optimization module (named [SC-PGO](https://github.com/gisbi-kim/SC-A-LOAM/blob/main/src/laserPosegraphOptimization.cpp)) is also integrated with FAST-LIO2! see [FAST_LIO_SLAM](https://github.com/gisbi-kim/FAST_LIO_SLAM).
5 |
6 | ## What is SC-A-LOAM?
7 | - A real-time LiDAR SLAM package that integrates A-LOAM and ScanContext.
8 | - **A-LOAM** for odometry (i.e., consecutive motion estimation)
9 | - **ScanContext** for coarse global localization that can deal with big drifts (i.e., place recognition as kidnapped robot problem without initial pose)
10 | - and iSAM2 of GTSAM is used for pose-graph optimization.
11 | - This package aims to show ScanContext's handy applicability.
12 | - The only things a user should do is just to include `Scancontext.h`, call `makeAndSaveScancontextAndKeys` and `detectLoopClosureID`.
13 |
14 | ## Features
15 | 1. A strong place recognition and loop closing
16 | - We integrated ScanContext as a loop detector into A-LOAM, and ISAM2-based pose-graph optimization is followed. (see https://youtu.be/okML_zNadhY?t=313 to enjoy the drift-closing moment)
17 | 2. A modular implementation
18 | - The only difference from A-LOAM is the addition of the `laserPosegraphOptimization.cpp` file. In the new file, we subscribe the point cloud topic and odometry topic (as a result of A-LOAM, published from `laserMapping.cpp`). That is, our implementation is generic to any front-end odometry methods. Thus, our pose-graph optimization module (i.e., `laserPosegraphOptimization.cpp`) can easily be integrated with any odometry algorithms such as non-LOAM family or even other sensors (e.g., visual odometry).
19 | - 
20 | 3. (optional) Altitude stabilization using consumer-level GPS
21 | - To make a result more trustworthy, we supports GPS (consumer-level price, such as U-Blox EVK-7P)-based altitude stabilization. The LOAM family of methods are known to be susceptible to z-errors in outdoors. We used the robust loss for only the altitude term. For the details, see the variable `robustGPSNoise` in the `laserPosegraphOptimization.cpp` file.
22 |
23 | ## Prerequisites (dependencies)
24 | - We mainly depend on ROS, Ceres (for A-LOAM), and GTSAM (for pose-graph optimization).
25 | - For the details to install the prerequisites, please follow the A-LOAM and LIO-SAM repositiory.
26 | - The below examples are done under ROS melodic (ubuntu 18) and GTSAM version 4.x.
27 |
28 | ## How to use?
29 | - First, install the abovementioned dependencies, and follow below lines.
30 | ```
31 | mkdir -p ~/catkin_scaloam_ws/src
32 | cd ~/catkin_scaloam_ws/src
33 | git clone https://github.com/gisbi-kim/SC-A-LOAM.git
34 | cd ../
35 | catkin_make
36 | source ~/catkin_scaloam_ws/devel/setup.bash
37 | roslaunch aloam_velodyne aloam_mulran.launch # for MulRan dataset setting
38 | ```
39 |
40 | ## Example Results
41 |
42 | ### Riverside 01, MulRan dataset
43 | - The MulRan dataset provides lidar scans (Ouster OS1-64, horizontally mounted, 10Hz) and consumer level gps (U-Blox EVK-7P, 4Hz) data.
44 | - About how to use (publishing data) data: see here https://github.com/irapkaist/file_player_mulran
45 | - example videos on Riverside 01 sequence.
46 | 1. with consumer level GPS-based altitude stabilization: https://youtu.be/FwAVX5TVm04
47 | 2. without the z stabilization: https://youtu.be/okML_zNadhY
48 | - example result:
49 |
50 | 
51 |
52 | ### KITTI 05
53 | - For KITTI (HDL-64 sensor), run using the command
54 | ```
55 | roslaunch aloam_velodyne aloam_velodyne_HDL_64.launch # for KITTI dataset setting
56 | ```
57 | - To publish KITTI scans, you can use mini-kitti publisher, a simple python script: https://github.com/gisbi-kim/mini-kitti-publisher
58 | - example video (no GPS used here): https://youtu.be/hk3Xx8SKkv4
59 | - example result:
60 |
61 | 
62 |
63 | ### Indoor
64 | - ScanContext also works at indoor environments (use smaller sc_max_radius value).
65 | - example video: https://youtu.be/Uv6_BRmxJho
66 | - example result:
67 | 
68 |
69 | ### For Livox LiDAR
70 | - Scan Context also works for Livox LiDAR data
71 | - In this example, Scan Context is integrated with FAST-LIO (https://github.com/hku-mars/FAST_LIO).
72 | - Note: No additional integration effort is required. A user just run seperately FAST-LIO node and SC-A-LOAM's posegraphoptimization.cpp node!
73 | - example video (tutoial and results): https://youtu.be/Fw9S6D6HozA
74 | - example result:
75 | 
76 |
77 | ### For Navtech Radar
78 | - Scan Context also works for Navtech Radar data!
79 | - For the details, please see
80 | - https://github.com/gisbi-kim/navtech-radar-slam
81 | - used the pose-graph optimization node of this repository (SC-A-LOAM)
82 | - [example video](https://www.youtube.com/watch?v=avtIQ8fesgU&t=128s)
83 |
84 | ## Utilities
85 |
86 | ### Data saver and Map construction
87 | - Similar to the [SC-LIO-SAM's saver utility](https://github.com/gisbi-kim/SC-LIO-SAM#applications), we support pose and scan saver per keyframes. Using these saved data, the map (within ROI) can be constructed offline. See the `utils/python/makeMergedMap.py` and [this tutorial](https://youtu.be/jmR3DH_A4Co).
88 | - Below is the example results of MulRan dataset KAIST 03's merged map, visualized using CloudCompare ([download the map data here](https://www.dropbox.com/sh/96jrpx3x6hh316j/AACb07kGbocnQWMIpksmU6MQa?dl=0)).
89 |
90 | 
91 |
92 | - A user also can remove dynamic points using these saved keyframe poses and scans. See [this tutorial](https://www.youtube.com/watch?v=UiYYrPMcIRU) and our [Removert project](https://github.com/irapkaist/removert).
93 |
94 | ## Acknowledgements
95 | - Thanks to LOAM, A-LOAM, and LIO-SAM code authors. The major codes in this repository are borrowed from their efforts.
96 |
97 | ## Maintainer
98 | - please contact me through `paulgkim@kaist.ac.kr`
99 |
100 | ## TODO
101 | - Delayed RS loop closings
102 | - SLAM with multi-session localization
103 | - More examples on other datasets (KITTI, complex urban dataset, etc.)
104 |
--------------------------------------------------------------------------------
/SC-A-LOAM/src/lidarFactor.hpp:
--------------------------------------------------------------------------------
1 | // Author: Tong Qin qintonguav@gmail.com
2 | // Shaozu Cao saozu.cao@connect.ust.hk
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | struct LidarEdgeFactor
13 | {
14 | LidarEdgeFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_a_,
15 | Eigen::Vector3d last_point_b_, double s_)
16 | : curr_point(curr_point_), last_point_a(last_point_a_), last_point_b(last_point_b_), s(s_) {}
17 |
18 | template
19 | bool operator()(const T *q, const T *t, T *residual) const
20 | {
21 |
22 | Eigen::Matrix cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
23 | Eigen::Matrix lpa{T(last_point_a.x()), T(last_point_a.y()), T(last_point_a.z())};
24 | Eigen::Matrix lpb{T(last_point_b.x()), T(last_point_b.y()), T(last_point_b.z())};
25 |
26 | //Eigen::Quaternion q_last_curr{q[3], T(s) * q[0], T(s) * q[1], T(s) * q[2]};
27 | Eigen::Quaternion q_last_curr{q[3], q[0], q[1], q[2]};
28 | Eigen::Quaternion q_identity{T(1), T(0), T(0), T(0)};
29 | q_last_curr = q_identity.slerp(T(s), q_last_curr);
30 | Eigen::Matrix t_last_curr{T(s) * t[0], T(s) * t[1], T(s) * t[2]};
31 |
32 | Eigen::Matrix lp;
33 | lp = q_last_curr * cp + t_last_curr;
34 |
35 | Eigen::Matrix nu = (lp - lpa).cross(lp - lpb);
36 | Eigen::Matrix de = lpa - lpb;
37 |
38 | residual[0] = nu.x() / de.norm();
39 | residual[1] = nu.y() / de.norm();
40 | residual[2] = nu.z() / de.norm();
41 |
42 | return true;
43 | }
44 |
45 | static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d last_point_a_,
46 | const Eigen::Vector3d last_point_b_, const double s_)
47 | {
48 | return (new ceres::AutoDiffCostFunction<
49 | LidarEdgeFactor, 3, 4, 3>(
50 | new LidarEdgeFactor(curr_point_, last_point_a_, last_point_b_, s_)));
51 | }
52 |
53 | Eigen::Vector3d curr_point, last_point_a, last_point_b;
54 | double s;
55 | };
56 |
57 | struct LidarPlaneFactor
58 | {
59 | LidarPlaneFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_j_,
60 | Eigen::Vector3d last_point_l_, Eigen::Vector3d last_point_m_, double s_)
61 | : curr_point(curr_point_), last_point_j(last_point_j_), last_point_l(last_point_l_),
62 | last_point_m(last_point_m_), s(s_)
63 | {
64 | ljm_norm = (last_point_j - last_point_l).cross(last_point_j - last_point_m);
65 | ljm_norm.normalize();
66 | }
67 |
68 | template
69 | bool operator()(const T *q, const T *t, T *residual) const
70 | {
71 |
72 | Eigen::Matrix cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
73 | Eigen::Matrix lpj{T(last_point_j.x()), T(last_point_j.y()), T(last_point_j.z())};
74 | //Eigen::Matrix lpl{T(last_point_l.x()), T(last_point_l.y()), T(last_point_l.z())};
75 | //Eigen::Matrix lpm{T(last_point_m.x()), T(last_point_m.y()), T(last_point_m.z())};
76 | Eigen::Matrix ljm{T(ljm_norm.x()), T(ljm_norm.y()), T(ljm_norm.z())};
77 |
78 | //Eigen::Quaternion q_last_curr{q[3], T(s) * q[0], T(s) * q[1], T(s) * q[2]};
79 | Eigen::Quaternion q_last_curr{q[3], q[0], q[1], q[2]};
80 | Eigen::Quaternion q_identity{T(1), T(0), T(0), T(0)};
81 | q_last_curr = q_identity.slerp(T(s), q_last_curr);
82 | Eigen::Matrix t_last_curr{T(s) * t[0], T(s) * t[1], T(s) * t[2]};
83 |
84 | Eigen::Matrix lp;
85 | lp = q_last_curr * cp + t_last_curr;
86 |
87 | residual[0] = (lp - lpj).dot(ljm);
88 |
89 | return true;
90 | }
91 |
92 | static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d last_point_j_,
93 | const Eigen::Vector3d last_point_l_, const Eigen::Vector3d last_point_m_,
94 | const double s_)
95 | {
96 | return (new ceres::AutoDiffCostFunction<
97 | LidarPlaneFactor, 1, 4, 3>(
98 | new LidarPlaneFactor(curr_point_, last_point_j_, last_point_l_, last_point_m_, s_)));
99 | }
100 |
101 | Eigen::Vector3d curr_point, last_point_j, last_point_l, last_point_m;
102 | Eigen::Vector3d ljm_norm;
103 | double s;
104 | };
105 |
106 | struct LidarPlaneNormFactor
107 | {
108 |
109 | LidarPlaneNormFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d plane_unit_norm_,
110 | double negative_OA_dot_norm_) : curr_point(curr_point_), plane_unit_norm(plane_unit_norm_),
111 | negative_OA_dot_norm(negative_OA_dot_norm_) {}
112 |
113 | template
114 | bool operator()(const T *q, const T *t, T *residual) const
115 | {
116 | Eigen::Quaternion q_w_curr{q[3], q[0], q[1], q[2]};
117 | Eigen::Matrix t_w_curr{t[0], t[1], t[2]};
118 | Eigen::Matrix cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
119 | Eigen::Matrix point_w;
120 | point_w = q_w_curr * cp + t_w_curr;
121 |
122 | Eigen::Matrix norm(T(plane_unit_norm.x()), T(plane_unit_norm.y()), T(plane_unit_norm.z()));
123 | residual[0] = norm.dot(point_w) + T(negative_OA_dot_norm);
124 | return true;
125 | }
126 |
127 | static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d plane_unit_norm_,
128 | const double negative_OA_dot_norm_)
129 | {
130 | return (new ceres::AutoDiffCostFunction<
131 | LidarPlaneNormFactor, 1, 4, 3>(
132 | new LidarPlaneNormFactor(curr_point_, plane_unit_norm_, negative_OA_dot_norm_)));
133 | }
134 |
135 | Eigen::Vector3d curr_point;
136 | Eigen::Vector3d plane_unit_norm;
137 | double negative_OA_dot_norm;
138 | };
139 |
140 |
141 | struct LidarDistanceFactor
142 | {
143 |
144 | LidarDistanceFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d closed_point_)
145 | : curr_point(curr_point_), closed_point(closed_point_){}
146 |
147 | template
148 | bool operator()(const T *q, const T *t, T *residual) const
149 | {
150 | Eigen::Quaternion q_w_curr{q[3], q[0], q[1], q[2]};
151 | Eigen::Matrix t_w_curr{t[0], t[1], t[2]};
152 | Eigen::Matrix cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
153 | Eigen::Matrix point_w;
154 | point_w = q_w_curr * cp + t_w_curr;
155 |
156 |
157 | residual[0] = point_w.x() - T(closed_point.x());
158 | residual[1] = point_w.y() - T(closed_point.y());
159 | residual[2] = point_w.z() - T(closed_point.z());
160 | return true;
161 | }
162 |
163 | static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d closed_point_)
164 | {
165 | return (new ceres::AutoDiffCostFunction<
166 | LidarDistanceFactor, 3, 4, 3>(
167 | new LidarDistanceFactor(curr_point_, closed_point_)));
168 | }
169 |
170 | Eigen::Vector3d curr_point;
171 | Eigen::Vector3d closed_point;
172 | };
--------------------------------------------------------------------------------
/floam/src/odomEstimationNode.cpp:
--------------------------------------------------------------------------------
1 | // Author of FLOAM: Wang Han
2 | // Email wh200720041@gmail.com
3 | // Homepage https://wanghan.pro
4 |
5 | //c++ lib
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | //ros lib
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | //pcl lib
21 | #include
22 | #include
23 | #include
24 |
25 | //local lib
26 | #include "lidar.h"
27 | #include "odomEstimationClass.h"
28 |
29 | OdomEstimationClass odomEstimation;
30 | std::mutex mutex_lock;
31 | std::queue