├── README.md └── SC-LIO-SAM ├── CMakeLists.txt ├── LICENSE ├── config ├── params_liosam.yaml └── params_mulran.yaml ├── doc ├── kaist02.png ├── removert_eaxmple.png ├── riverside03.png ├── saver.png └── utils_example.png ├── include ├── KDTreeVectorOfVectorsAdaptor.h ├── Scancontext.h ├── nanoflann.hpp ├── tictoc.h └── utility.h ├── launch ├── include │ ├── config │ │ ├── robot.urdf.xacro │ │ └── rviz.rviz │ ├── module_loam.launch │ ├── module_navsat.launch │ ├── module_robot_state_publisher.launch │ ├── module_rviz.launch │ └── rosconsole │ │ ├── rosconsole_error.conf │ │ ├── rosconsole_info.conf │ │ └── rosconsole_warn.conf ├── run.launch └── run_mulran.launch ├── msg └── cloud_info.msg ├── package.xml ├── src ├── Scancontext.cpp ├── featureExtraction.cpp ├── imageProjection.cpp ├── imuPreintegration.cpp └── mapOptmization.cpp └── tools ├── matlab ├── listdir.m └── pcd_viewer.m └── python ├── bone_table.npy ├── jet_table.npy ├── makeMergedMap.py └── pypcdMyUtils.py /README.md: -------------------------------------------------------------------------------- 1 | # SC-LIO-SAM 2 | version 2021-06-24 3 | 4 | ## What is SC-LIO-SAM? 5 | - SC-LIO-SAM is **a real-time lidar-inertial SLAM package.** 6 | - LiDAR-inertial SLAM: Scan Context + LIO-SAM 7 | - This repository is an example use-case of [Scan Context](https://github.com/irapkaist/scancontext), which is a fast and robust LiDAR place recognition method. 8 | - For more details for each algorithm please refer to
9 | Scan Context https://github.com/irapkaist/scancontext
10 | LIO-SAM https://github.com/TixiaoShan/LIO-SAM
11 | - You can also use the LiDAR-only versions of this project: [SC-LeGO-LOAM](https://github.com/irapkaist/SC-LeGO-LOAM) and [SC-A-LOAM](https://github.com/gisbi-kim/SC-A-LOAM). 12 | 13 | ## Scan Context: A fast and robust place recognition 14 | - Light-weight: a single header and cpp file named "Scancontext.h" and "Scancontext.cpp" 15 | - Our module has KDtree and we used nanoflann. nanoflann is an also single-header-program and that file is in our directory. 16 | - Easy to use: A user just remembers and uses only two API functions; ```makeAndSaveScancontextAndKeys``` and ```detectLoopClosureID```. 17 | - Fast: A single loop detection requires under 30 ms (for 20 x 60 size, 3 candidates) 18 | 19 | 20 | ## Examples 21 | 22 | We provide example results using [MulRan dataset](https://sites.google.com/view/mulran-pr/dataset), which provides LiDAR and 9dof IMU data. You can see the parameter file (i.e., params_mulran.yaml) modified for MulRan dataset. 23 | 24 | #### example 1: [KAIST02 of MulRan dataset](https://youtu.be/Il0YxztuZEU?t=280) 25 | 26 |

27 | 28 | 29 | #### example 2: [Riverside03 of MulRan dataset](https://youtu.be/Y6DXlC34qlc?t=459) 30 |

31 | 32 | - As seen in the above [video](https://youtu.be/Y6DXlC34qlc?t=459), the combination of Scan Context loop detector and LIO-SAM's odometry is robust to highly dynamic and less structured environments (e.g., a wide road on a bridge with many moving objects). 33 | 34 | ## How to use? 35 | - We provide a tutorial that runs SC-LIO-SAM on MulRan dataset, you can reproduce the above results by following these steps. 36 | 37 | 1. You can download the dataset at the [MulRan dataset website](https://sites.google.com/view/mulran-pr/home) 38 | 2. Place the directory `SC-LIO-SAM` under user catkin work space
39 | For example, 40 | ``` 41 | cd ~/catkin_ws/src 42 | git clone https://github.com/gisbi-kim/SC-LIO-SAM.git 43 | cd .. 44 | catkin_make 45 | source devel/setup.bash 46 | roslaunch lio_sam run.launch # or roslaunch lio_sam run_mulran.launch 47 | ``` 48 | 3. By following [this guideline](https://github.com/irapkaist/file_player_mulran), you can easily publish the MulRan dataset's LiDAR and IMU topics via ROS. 49 | 50 | 51 | ## Dependency 52 | - All dependencies are same as the original [LIO-SAM](https://github.com/TixiaoShan/LIO-SAM#dependency) 53 | 54 | 55 | ## Notes 56 | #### About performance 57 | - We used two types of loop detetions (i.e., radius search (RS)-based as already implemented in the original LIO-SAM and Scan context (SC)-based global revisit detection). See mapOptmization.cpp for details. ```performSCLoopClosure``` is good for correcting large drifts and ```performRSLoopClosure``` is good for fine-stitching. 58 | - To prevent the wrong map correction, we used Cauchy (but DCS can be used) kernel for loop factor. See mapOptmization.cpp for details. We found that Cauchy is emprically enough. 59 | 60 | #### Minor 61 | - We used C++14 to use std::make_unique in Scancontext.cpp but you can use C++11 with slightly modifying only that part. 62 | - We used a larger value for velocity upper bound (see ```failureDetection``` in imuPreintegration.cpp) for fast motions of a MulRan dataset's car platform. 63 | - The some code lines are adapted for Ouster LiDAR. Thus, if you use an other LiDAR, please refer [the original author's guideline](https://github.com/TixiaoShan/LIO-SAM#other-notes) and fix some lines. 64 | - A LiDAR scan of MulRan dataset has no ring information, thus we simply made a hardcoding like ```int rowIdn = (i % 64) + 1 ``` in imageProjection.cpp to make a ring index information that LIO-SAM requires, and it works. However, if you use an other LiDAR, you need to change this line. 65 | 66 | ## Applications 67 | - With our [save utility](https://github.com/gisbi-kim/SC-LIO-SAM/blob/1e1bfea4f7708fbd93d9eb471e63ae9804e1dd16/SC-LIO-SAM/src/mapOptmization.cpp#L203) accompanied with this repository, we can save a set of keyframe's time, estimated pose, a corresponding point cloud, and Scan Context descriptors. The estimated poses are saved as a file named optimized_poses.txt and its format is equivalent to the famous KITTI odometry dataset's pose.txt file. For example: 68 |

69 | 70 | - If you use the above saved files, you can feed these data to [Removert](https://github.com/irapkaist/removert) and can removing dynamic objects. No GT labels or external sensor data such as RTK-GPS is required. This [tutorial](https://youtu.be/UiYYrPMcIRU) guides steps from running SC-LIO-SAM to save data to Removert to remove dynamic objects in a scan. Example results are: 71 |

72 | 73 | - For the safe and light-weight map saver, we support off-line scan merging utils for the global map construction within user's ROI (see tools/python/makeMergedMap.py, for the details, see the [tutorial video](https://youtu.be/jmR3DH_A4Co)) 74 |

75 | 76 | ## Cite SC-LIO-SAM 77 | 78 | ``` 79 | @INPROCEEDINGS { gkim-2018-iros, 80 | author = {Kim, Giseop and Kim, Ayoung}, 81 | title = { Scan Context: Egocentric Spatial Descriptor for Place Recognition within {3D} Point Cloud Map }, 82 | booktitle = { Proceedings of the IEEE/RSJ International Conference on Intelligent Robots and Systems }, 83 | year = { 2018 }, 84 | month = { Oct. }, 85 | address = { Madrid } 86 | } 87 | ``` 88 | and 89 | ``` 90 | @inproceedings{liosam2020shan, 91 | title={LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing and Mapping}, 92 | author={Shan, Tixiao and Englot, Brendan and Meyers, Drew and Wang, Wei and Ratti, Carlo and Rus Daniela}, 93 | booktitle={IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)}, 94 | pages={5135-5142}, 95 | year={2020}, 96 | organization={IEEE} 97 | } 98 | ``` 99 | 100 | ## Contact 101 | - Maintainer: Giseop Kim (`paulgkim@kaist.ac.kr`) 102 | #### Contributors 103 | - Minwoo Jung: made the original LIO-SAM runs on the MulRan dataset. 104 | 105 | ## Acknowledgement 106 | - SC-LIO-SAM is based on LIO-SAM (Tixiao Shan et al., IROS 2020). We thank Tixiao Shan and the LIO-SAM authors for providing a great base framework. 107 | 108 | ## Update history 109 | - 2021.06.23 110 | - yaml file is reformatted to support the compatible form with the recent original LIO-SAM repository. 111 | - offline ROI global map construction python util is supported. 112 | 113 | ## TODO 114 | - About performance 115 | - improve better RS loop closing (ICP test sometimes fails in reverse directions) 116 | - support reverse-closing of SC loops with Scan Context initial yaw 117 | - support SC augmentation 118 | - lagged RS loop closing after large drifts solved 119 | - About funtions for convenience 120 | - [x] save extended data: nodes' time, 6D pose, node's point cloud, and corresponding SC descriptors 121 | - [x] make a static map and self-labeled dynamic points by combining SC-LIO-SAM and [removert](https://github.com/irapkaist/removert). 122 | - Minor (not related to the performance) 123 | - fix the visualization issue: disappearing map points after SC loop closing 124 | - fix safe-save for map points after closing the program 125 | -------------------------------------------------------------------------------- /SC-LIO-SAM/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(lio_sam) 3 | 4 | set(CMAKE_BUILD_TYPE "Release") 5 | # set(CMAKE_CXX_FLAGS "-std=c++14") 6 | set(CMAKE_CXX_STANDARD 14) 7 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -g -pthread") 8 | 9 | find_package(catkin REQUIRED COMPONENTS 10 | tf 11 | roscpp 12 | rospy 13 | cv_bridge 14 | # pcl library 15 | pcl_conversions 16 | # msgs 17 | std_msgs 18 | sensor_msgs 19 | geometry_msgs 20 | nav_msgs 21 | message_generation 22 | ) 23 | 24 | find_package(OpenMP REQUIRED) 25 | find_package(PCL REQUIRED QUIET) 26 | find_package(OpenCV REQUIRED QUIET) 27 | find_package(GTSAM REQUIRED QUIET) 28 | 29 | add_message_files( 30 | DIRECTORY msg 31 | FILES 32 | cloud_info.msg 33 | ) 34 | 35 | generate_messages( 36 | DEPENDENCIES 37 | geometry_msgs 38 | std_msgs 39 | nav_msgs 40 | sensor_msgs 41 | ) 42 | 43 | catkin_package( 44 | INCLUDE_DIRS include 45 | DEPENDS PCL GTSAM 46 | 47 | CATKIN_DEPENDS 48 | std_msgs 49 | nav_msgs 50 | geometry_msgs 51 | sensor_msgs 52 | message_runtime 53 | message_generation 54 | ) 55 | 56 | # include directories 57 | include_directories( 58 | include 59 | ${catkin_INCLUDE_DIRS} 60 | ${PCL_INCLUDE_DIRS} 61 | ${OpenCV_INCLUDE_DIRS} 62 | ${GTSAM_INCLUDE_DIR} 63 | ) 64 | 65 | # link directories 66 | link_directories( 67 | include 68 | ${PCL_LIBRARY_DIRS} 69 | ${OpenCV_LIBRARY_DIRS} 70 | ${GTSAM_LIBRARY_DIRS} 71 | ) 72 | 73 | ########### 74 | ## Build ## 75 | ########### 76 | 77 | # Range Image Projection 78 | add_executable(${PROJECT_NAME}_imageProjection src/imageProjection.cpp) 79 | add_dependencies(${PROJECT_NAME}_imageProjection ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_generate_messages_cpp) 80 | target_link_libraries(${PROJECT_NAME}_imageProjection ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES}) 81 | 82 | # Feature Association 83 | add_executable(${PROJECT_NAME}_featureExtraction src/featureExtraction.cpp) 84 | add_dependencies(${PROJECT_NAME}_featureExtraction ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_generate_messages_cpp) 85 | target_link_libraries(${PROJECT_NAME}_featureExtraction ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES}) 86 | 87 | # Mapping Optimization 88 | add_executable(${PROJECT_NAME}_mapOptmization 89 | src/mapOptmization.cpp 90 | src/Scancontext.cpp 91 | ) 92 | add_dependencies(${PROJECT_NAME}_mapOptmization ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_generate_messages_cpp) 93 | target_compile_options(${PROJECT_NAME}_mapOptmization PRIVATE ${OpenMP_CXX_FLAGS}) 94 | target_link_libraries(${PROJECT_NAME}_mapOptmization ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES} ${OpenMP_CXX_FLAGS} gtsam) 95 | 96 | # IMU Preintegration 97 | add_executable(${PROJECT_NAME}_imuPreintegration src/imuPreintegration.cpp) 98 | target_link_libraries(${PROJECT_NAME}_imuPreintegration ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBRARIES} gtsam) -------------------------------------------------------------------------------- /SC-LIO-SAM/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Tixiao Shan 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /SC-LIO-SAM/config/params_liosam.yaml: -------------------------------------------------------------------------------- 1 | lio_sam: 2 | 3 | # Topics 4 | pointCloudTopic: "/os_cloud_node/points" # Point cloud data 5 | imuTopic: "/imu/data" # IMU data 6 | odomTopic: "odometry/imu" # IMU pre-preintegration odometry, same frequency as IMU 7 | gpsTopic: "odometry/gpsz" # GPS odometry topic from navsat, see module_navsat.launch file 8 | 9 | # Frames 10 | lidarFrame: "base_link" 11 | baselinkFrame: "base_link" 12 | odometryFrame: "odom" 13 | mapFrame: "map" 14 | 15 | # GPS Settings 16 | useImuHeadingInitialization: true # if using GPS data, set to "true" 17 | useGpsElevation: false # if GPS elevation is bad, set to "false" 18 | gpsCovThreshold: 2.0 # m^2, threshold for using GPS data 19 | poseCovThreshold: 25.0 # m^2, threshold for using GPS data 20 | 21 | # Export settings 22 | savePCD: true # https://github.com/TixiaoShan/LIO-SAM/issues/3 23 | savePCDDirectory: "/home/user/Desktop/scliosam/data/" # use global path, and end with "/" 24 | # warning: if you have already data in the above savePCDDirectory, it will all remove and remake them. Thus, backup is recommended if pre-made data exist. 25 | 26 | # Sensor Settings 27 | sensor: ouster # lidar sensor type, either 'velodyne' or 'ouster' 28 | N_SCAN: 128 # number of lidar channel (i.e., 16, 32, 64, 128) 29 | Horizon_SCAN: 1024 # lidar horizontal resolution (Velodyne:1800, Ouster:512,1024,2048) 30 | downsampleRate: 1 # default: 1. Downsample your data if too many points. i.e., 16 = 64 / 4, 16 = 16 / 1 31 | lidarMinRange: 1.0 # default: 1.0, minimum lidar range to be used 32 | lidarMaxRange: 1000.0 # default: 1000.0, maximum lidar range to be used 33 | 34 | # IMU Settings 35 | imuAccNoise: 0.009939570888238808e-03 36 | imuGyrNoise: 0.005636343949698187e-03 37 | imuAccBiasN: 0.64356659353532566e-03 38 | imuGyrBiasN: 0.35640318696367613e-03 39 | imuGravity: 9.80511 40 | imuRPYWeight: 0.01 41 | 42 | # Extrinsics (lidar -> IMU) 43 | extrinsicTrans: [-0.1, 0.0, 0.0] 44 | extrinsicRot: [1, 0, 0, 45 | 0, 1, 0, 46 | 0, 0, 1] 47 | extrinsicRPY: [1, 0, 0, 48 | 0, 1, 0, 49 | 0, 0, 1] 50 | # extrinsicRot: [1, 0, 0, 51 | # 0, 1, 0, 52 | # 0, 0, 1] 53 | # extrinsicRPY: [1, 0, 0, 54 | # 0, 1, 0, 55 | # 0, 0, 1] 56 | 57 | # LOAM feature threshold 58 | edgeThreshold: 1.0 59 | surfThreshold: 0.1 60 | edgeFeatureMinValidNum: 10 61 | surfFeatureMinValidNum: 100 62 | 63 | # voxel filter paprams 64 | odometrySurfLeafSize: 0.4 # default: 0.4 - outdoor, 0.2 - indoor 65 | mappingCornerLeafSize: 0.2 # default: 0.2 - outdoor, 0.1 - indoor 66 | mappingSurfLeafSize: 0.4 # default: 0.4 - outdoor, 0.2 - indoor 67 | 68 | # robot motion constraint (in case you are using a 2D robot) 69 | z_tollerance: 1000 # meters 70 | rotation_tollerance: 1000 # radians 71 | 72 | # CPU Params 73 | numberOfCores: 4 # number of cores for mapping optimization 74 | mappingProcessInterval: 0.15 # seconds, regulate mapping frequency 75 | 76 | # Surrounding map 77 | surroundingkeyframeAddingDistThreshold: 1.0 # meters, regulate keyframe adding threshold 78 | surroundingkeyframeAddingAngleThreshold: 0.2 # radians, regulate keyframe adding threshold 79 | surroundingKeyframeDensity: 2.0 # meters, downsample surrounding keyframe poses 80 | surroundingKeyframeSearchRadius: 50.0 # meters, within n meters scan-to-map optimization (when loop closure disabled) 81 | 82 | # Loop closure 83 | loopClosureEnableFlag: true 84 | loopClosureFrequency: 1.0 # Hz, regulate loop closure constraint add frequency 85 | surroundingKeyframeSize: 50 # submap size (when loop closure enabled) 86 | historyKeyframeSearchRadius: 15.0 # meters, key frame that is within n meters from current pose will be considerd for loop closure 87 | historyKeyframeSearchTimeDiff: 30.0 # seconds, key frame that is n seconds older will be considered for loop closure 88 | historyKeyframeSearchNum: 25 # number of hostory key frames will be fused into a submap for loop closure 89 | historyKeyframeFitnessScore: 0.3 # icp threshold, the smaller the better alignment 90 | 91 | # Visualization 92 | globalMapVisualizationSearchRadius: 1000.0 # meters, global map visualization radius 93 | globalMapVisualizationPoseDensity: 10.0 # meters, global map visualization keyframe density 94 | globalMapVisualizationLeafSize: 0.2 # meters, global map visualization cloud density 95 | 96 | 97 | 98 | 99 | # Navsat (convert GPS coordinates to Cartesian) 100 | navsat: 101 | frequency: 50 102 | wait_for_datum: false 103 | delay: 0.0 104 | magnetic_declination_radians: 0 105 | yaw_offset: 0 106 | zero_altitude: true 107 | broadcast_utm_transform: false 108 | broadcast_utm_transform_as_parent_frame: false 109 | publish_filtered_gps: false 110 | 111 | # EKF for Navsat 112 | ekf_gps: 113 | publish_tf: false 114 | map_frame: map 115 | odom_frame: odom 116 | base_link_frame: base_link 117 | world_frame: odom 118 | 119 | frequency: 50 120 | two_d_mode: false 121 | sensor_timeout: 0.01 122 | # ------------------------------------- 123 | # External IMU: 124 | # ------------------------------------- 125 | imu0: imu_correct 126 | # make sure the input is aligned with ROS REP105. "imu_correct" is manually transformed by myself. EKF can also transform the data using tf between your imu and base_link 127 | imu0_config: [false, false, false, 128 | true, true, true, 129 | false, false, false, 130 | false, false, true, 131 | true, true, true] 132 | imu0_differential: false 133 | imu0_queue_size: 50 134 | imu0_remove_gravitational_acceleration: true 135 | # ------------------------------------- 136 | # Odometry (From Navsat): 137 | # ------------------------------------- 138 | odom0: odometry/gps 139 | odom0_config: [true, true, true, 140 | false, false, false, 141 | false, false, false, 142 | false, false, false, 143 | false, false, false] 144 | odom0_differential: false 145 | odom0_queue_size: 10 146 | 147 | # x y z r p y x_dot y_dot z_dot r_dot p_dot y_dot x_ddot y_ddot z_ddot 148 | process_noise_covariance: [ 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149 | 0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150 | 0, 0, 10.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151 | 0, 0, 0, 0.03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152 | 0, 0, 0, 0, 0.03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153 | 0, 0, 0, 0, 0, 0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154 | 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 0.04, 0, 0, 0, 0, 0, 0, 157 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 158 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 159 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0, 0, 0, 160 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 161 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 162 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.015] 163 | -------------------------------------------------------------------------------- /SC-LIO-SAM/config/params_mulran.yaml: -------------------------------------------------------------------------------- 1 | lio_sam: 2 | 3 | # Topics 4 | pointCloudTopic: "/os1_points" # Point cloud data 5 | imuTopic: "/imu/data_raw" # IMU data 6 | odomTopic: "odometry/imu" # IMU pre-preintegration odometry, same frequency as IMU 7 | gpsTopic: "odometry/gpsz" # GPS odometry topic from navsat, see module_navsat.launch file 8 | 9 | # Frames 10 | lidarFrame: "base_link" 11 | baselinkFrame: "base_link" 12 | odometryFrame: "odom" 13 | mapFrame: "map" 14 | 15 | # GPS Settings 16 | useImuHeadingInitialization: true # if using GPS data, set to "true" 17 | useGpsElevation: false # if GPS elevation is bad, set to "false" 18 | gpsCovThreshold: 2.0 # m^2, threshold for using GPS data 19 | poseCovThreshold: 25.0 # m^2, threshold for using GPS data 20 | 21 | # Export settings 22 | savePCD: true # https://github.com/TixiaoShan/LIO-SAM/issues/3 23 | savePCDDirectory: "/home/user/Desktop/scliosam/data/" # use global path, and end with "/" 24 | # warning: if you have already data in the above savePCDDirectory, it will all remove and remake them. Thus, backup is recommended if pre-made data exist. 25 | 26 | # Sensor Settings 27 | sensor: mulran # lidar sensor type, either 'velodyne' or 'ouster' 28 | N_SCAN: 64 # number of lidar channel (i.e., 16, 32, 64, 128) 29 | Horizon_SCAN: 1024 # lidar horizontal resolution (Velodyne:1800, Ouster:512,1024,2048) 30 | downsampleRate: 1 # default: 1. Downsample your data if too many points. i.e., 16 = 64 / 4, 16 = 16 / 1 31 | lidarMinRange: 1.0 # default: 1.0, minimum lidar range to be used 32 | lidarMaxRange: 1000.0 # default: 1000.0, maximum lidar range to be used 33 | 34 | # IMU Settings 35 | imuAccNoise: 0.009939570888238808e-03 36 | imuGyrNoise: 0.005636343949698187e-03 37 | imuAccBiasN: 0.64356659353532566e-03 38 | imuGyrBiasN: 0.35640318696367613e-03 39 | imuGravity: 9.80511 40 | imuRPYWeight: 0.01 41 | 42 | # Extrinsics (lidar -> IMU, of MulRan dataset) 43 | extrinsicTrans: [1.77, -0.00, -0.05] 44 | extrinsicRot: [-1, 0, 0, 45 | 0, -1, 0, 46 | 0, 0, 1] 47 | extrinsicRPY: [-1, 0, 0, 48 | 0, -1, 0, 49 | 0, 0, 1] 50 | # extrinsicRot: [1, 0, 0, 51 | # 0, 1, 0, 52 | # 0, 0, 1] 53 | # extrinsicRPY: [1, 0, 0, 54 | # 0, 1, 0, 55 | # 0, 0, 1] 56 | 57 | # LOAM feature threshold 58 | edgeThreshold: 1.0 59 | surfThreshold: 0.1 60 | edgeFeatureMinValidNum: 10 61 | surfFeatureMinValidNum: 100 62 | 63 | # voxel filter paprams 64 | odometrySurfLeafSize: 0.4 # default: 0.4 - outdoor, 0.2 - indoor 65 | mappingCornerLeafSize: 0.2 # default: 0.2 - outdoor, 0.1 - indoor 66 | mappingSurfLeafSize: 0.4 # default: 0.4 - outdoor, 0.2 - indoor 67 | 68 | # robot motion constraint (in case you are using a 2D robot) 69 | z_tollerance: 1000 # meters 70 | rotation_tollerance: 1000 # radians 71 | 72 | # CPU Params 73 | numberOfCores: 4 # number of cores for mapping optimization 74 | mappingProcessInterval: 0.15 # seconds, regulate mapping frequency 75 | 76 | # Surrounding map 77 | surroundingkeyframeAddingDistThreshold: 1.0 # meters, regulate keyframe adding threshold 78 | surroundingkeyframeAddingAngleThreshold: 0.2 # radians, regulate keyframe adding threshold 79 | surroundingKeyframeDensity: 2.0 # meters, downsample surrounding keyframe poses 80 | surroundingKeyframeSearchRadius: 50.0 # meters, within n meters scan-to-map optimization (when loop closure disabled) 81 | 82 | # Loop closure 83 | loopClosureEnableFlag: true 84 | loopClosureFrequency: 1.0 # Hz, regulate loop closure constraint add frequency 85 | surroundingKeyframeSize: 50 # submap size (when loop closure enabled) 86 | historyKeyframeSearchRadius: 15.0 # meters, key frame that is within n meters from current pose will be considerd for loop closure 87 | historyKeyframeSearchTimeDiff: 30.0 # seconds, key frame that is n seconds older will be considered for loop closure 88 | historyKeyframeSearchNum: 25 # number of hostory key frames will be fused into a submap for loop closure 89 | historyKeyframeFitnessScore: 0.3 # icp threshold, the smaller the better alignment 90 | 91 | # Visualization 92 | globalMapVisualizationSearchRadius: 1000.0 # meters, global map visualization radius 93 | globalMapVisualizationPoseDensity: 10.0 # meters, global map visualization keyframe density 94 | globalMapVisualizationLeafSize: 0.2 # meters, global map visualization cloud density 95 | 96 | 97 | 98 | 99 | # Navsat (convert GPS coordinates to Cartesian) 100 | navsat: 101 | frequency: 50 102 | wait_for_datum: false 103 | delay: 0.0 104 | magnetic_declination_radians: 0 105 | yaw_offset: 0 106 | zero_altitude: true 107 | broadcast_utm_transform: false 108 | broadcast_utm_transform_as_parent_frame: false 109 | publish_filtered_gps: false 110 | 111 | # EKF for Navsat 112 | ekf_gps: 113 | publish_tf: false 114 | map_frame: map 115 | odom_frame: odom 116 | base_link_frame: base_link 117 | world_frame: odom 118 | 119 | frequency: 50 120 | two_d_mode: false 121 | sensor_timeout: 0.01 122 | # ------------------------------------- 123 | # External IMU: 124 | # ------------------------------------- 125 | imu0: imu_correct 126 | # make sure the input is aligned with ROS REP105. "imu_correct" is manually transformed by myself. EKF can also transform the data using tf between your imu and base_link 127 | imu0_config: [false, false, false, 128 | true, true, true, 129 | false, false, false, 130 | false, false, true, 131 | true, true, true] 132 | imu0_differential: false 133 | imu0_queue_size: 50 134 | imu0_remove_gravitational_acceleration: true 135 | # ------------------------------------- 136 | # Odometry (From Navsat): 137 | # ------------------------------------- 138 | odom0: odometry/gps 139 | odom0_config: [true, true, true, 140 | false, false, false, 141 | false, false, false, 142 | false, false, false, 143 | false, false, false] 144 | odom0_differential: false 145 | odom0_queue_size: 10 146 | 147 | # x y z r p y x_dot y_dot z_dot r_dot p_dot y_dot x_ddot y_ddot z_ddot 148 | process_noise_covariance: [ 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149 | 0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150 | 0, 0, 10.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151 | 0, 0, 0, 0.03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152 | 0, 0, 0, 0, 0.03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153 | 0, 0, 0, 0, 0, 0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154 | 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 0.04, 0, 0, 0, 0, 0, 0, 157 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0, 158 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 159 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0, 0, 0, 160 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 161 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 162 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.015] 163 | -------------------------------------------------------------------------------- /SC-LIO-SAM/doc/kaist02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/doc/kaist02.png -------------------------------------------------------------------------------- /SC-LIO-SAM/doc/removert_eaxmple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/doc/removert_eaxmple.png -------------------------------------------------------------------------------- /SC-LIO-SAM/doc/riverside03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/doc/riverside03.png -------------------------------------------------------------------------------- /SC-LIO-SAM/doc/saver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/doc/saver.png -------------------------------------------------------------------------------- /SC-LIO-SAM/doc/utils_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/doc/utils_example.png -------------------------------------------------------------------------------- /SC-LIO-SAM/include/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 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-LIO-SAM/include/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 "nanoflann.hpp" 26 | #include "KDTreeVectorOfVectorsAdaptor.h" 27 | 28 | #include "tictoc.h" 29 | 30 | using namespace Eigen; 31 | using namespace nanoflann; 32 | 33 | using std::cout; 34 | using std::endl; 35 | using std::make_pair; 36 | 37 | using std::atan2; 38 | using std::cos; 39 | using std::sin; 40 | 41 | 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) 42 | using KeyMat = std::vector >; 43 | using InvKeyTree = KDTreeVectorOfVectorsAdaptor< KeyMat, float >; 44 | 45 | 46 | // namespace SC2 47 | // { 48 | 49 | void coreImportTest ( void ); 50 | 51 | 52 | // sc param-independent helper functions 53 | float xy2theta( const float & _x, const float & _y ); 54 | MatrixXd circshift( MatrixXd &_mat, int _num_shift ); 55 | std::vector eig2stdvec( MatrixXd _eigmat ); 56 | 57 | 58 | class SCManager 59 | { 60 | public: 61 | SCManager( ) = default; // reserving data space (of std::vector) could be considered. but the descriptor is lightweight so don't care. 62 | 63 | Eigen::MatrixXd makeScancontext( pcl::PointCloud & _scan_down ); 64 | Eigen::MatrixXd makeRingkeyFromScancontext( Eigen::MatrixXd &_desc ); 65 | Eigen::MatrixXd makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc ); 66 | 67 | int fastAlignUsingVkey ( MatrixXd & _vkey1, MatrixXd & _vkey2 ); 68 | double distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 ); // "d" (eq 5) in the original paper (IROS 18) 69 | std::pair distanceBtnScanContext ( MatrixXd &_sc1, MatrixXd &_sc2 ); // "D" (eq 6) in the original paper (IROS 18) 70 | 71 | // User-side API 72 | void makeAndSaveScancontextAndKeys( pcl::PointCloud & _scan_down ); 73 | std::pair detectLoopClosureID( void ); // int: nearest node index, float: relative yaw 74 | 75 | // for ltmapper 76 | const Eigen::MatrixXd& getConstRefRecentSCD(void); 77 | 78 | public: 79 | // hyper parameters () 80 | 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. 81 | 82 | const int PC_NUM_RING = 20; // 20 in the original paper (IROS 18) 83 | const int PC_NUM_SECTOR = 60; // 60 in the original paper (IROS 18) 84 | const double PC_MAX_RADIUS = 80.0; // 80 meter max in the original paper (IROS 18) 85 | const double PC_UNIT_SECTORANGLE = 360.0 / double(PC_NUM_SECTOR); 86 | const double PC_UNIT_RINGGAP = PC_MAX_RADIUS / double(PC_NUM_RING); 87 | 88 | // tree 89 | const int NUM_EXCLUDE_RECENT = 30; // simply just keyframe gap (related with loopClosureFrequency in yaml), but node position distance-based exclusion is ok. 90 | const int NUM_CANDIDATES_FROM_TREE = 3; // 10 is enough. (refer the IROS 18 paper) 91 | 92 | // loop thres 93 | 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. 94 | // 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) 95 | const double SC_DIST_THRES = 0.3; // 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 96 | // 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 97 | 98 | // config 99 | const int TREE_MAKING_PERIOD_ = 10; // 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. 100 | int tree_making_period_conter = 0; 101 | 102 | // data 103 | std::vector polarcontexts_timestamp_; // optional. 104 | std::vector polarcontexts_; 105 | std::vector polarcontext_invkeys_; 106 | std::vector polarcontext_vkeys_; 107 | 108 | KeyMat polarcontext_invkeys_mat_; 109 | KeyMat polarcontext_invkeys_to_search_; 110 | std::unique_ptr polarcontext_tree_; 111 | 112 | }; // SCManager 113 | 114 | // } // namespace SC2 115 | -------------------------------------------------------------------------------- /SC-LIO-SAM/include/tictoc.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 | #include 10 | #include 11 | 12 | class TicToc 13 | { 14 | public: 15 | TicToc() 16 | { 17 | tic(); 18 | } 19 | 20 | TicToc( bool _disp ) 21 | { 22 | disp_ = _disp; 23 | tic(); 24 | } 25 | 26 | void tic() 27 | { 28 | start = std::chrono::system_clock::now(); 29 | } 30 | 31 | void toc( std::string _about_task ) 32 | { 33 | end = std::chrono::system_clock::now(); 34 | std::chrono::duration elapsed_seconds = end - start; 35 | double elapsed_ms = elapsed_seconds.count() * 1000; 36 | 37 | if( disp_ ) 38 | { 39 | std::cout.precision(3); // 10 for sec, 3 for ms 40 | std::cout << _about_task << ": " << elapsed_ms << " msec." << std::endl; 41 | } 42 | } 43 | 44 | private: 45 | std::chrono::time_point start, end; 46 | bool disp_ = false; 47 | }; 48 | -------------------------------------------------------------------------------- /SC-LIO-SAM/include/utility.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | using namespace std; 57 | 58 | typedef std::numeric_limits< double > dbl; 59 | 60 | typedef pcl::PointXYZI PointType; 61 | 62 | enum class SensorType { MULRAN, VELODYNE, OUSTER }; 63 | 64 | class ParamServer 65 | { 66 | public: 67 | 68 | ros::NodeHandle nh; 69 | 70 | std::string robot_id; 71 | 72 | //Topics 73 | string pointCloudTopic; 74 | string imuTopic; 75 | string odomTopic; 76 | string gpsTopic; 77 | 78 | //Frames 79 | string lidarFrame; 80 | string baselinkFrame; 81 | string odometryFrame; 82 | string mapFrame; 83 | 84 | // GPS Settings 85 | bool useImuHeadingInitialization; 86 | bool useGpsElevation; 87 | float gpsCovThreshold; 88 | float poseCovThreshold; 89 | 90 | // Save pcd 91 | bool savePCD; 92 | string savePCDDirectory; 93 | 94 | // Velodyne Sensor Configuration: Velodyne 95 | SensorType sensor; 96 | int N_SCAN; 97 | int Horizon_SCAN; 98 | int downsampleRate; 99 | float lidarMinRange; 100 | float lidarMaxRange; 101 | 102 | // IMU 103 | float imuAccNoise; 104 | float imuGyrNoise; 105 | float imuAccBiasN; 106 | float imuGyrBiasN; 107 | float imuGravity; 108 | float imuRPYWeight; 109 | vector extRotV; 110 | vector extRPYV; 111 | vector extTransV; 112 | Eigen::Matrix3d extRot; 113 | Eigen::Matrix3d extRPY; 114 | Eigen::Vector3d extTrans; 115 | Eigen::Quaterniond extQRPY; 116 | 117 | // LOAM 118 | float edgeThreshold; 119 | float surfThreshold; 120 | int edgeFeatureMinValidNum; 121 | int surfFeatureMinValidNum; 122 | 123 | // voxel filter paprams 124 | float odometrySurfLeafSize; 125 | float mappingCornerLeafSize; 126 | float mappingSurfLeafSize ; 127 | 128 | float z_tollerance; 129 | float rotation_tollerance; 130 | 131 | // CPU Params 132 | int numberOfCores; 133 | double mappingProcessInterval; 134 | 135 | // Surrounding map 136 | float surroundingkeyframeAddingDistThreshold; 137 | float surroundingkeyframeAddingAngleThreshold; 138 | float surroundingKeyframeDensity; 139 | float surroundingKeyframeSearchRadius; 140 | 141 | // Loop closure 142 | bool loopClosureEnableFlag; 143 | float loopClosureFrequency; 144 | int surroundingKeyframeSize; 145 | float historyKeyframeSearchRadius; 146 | float historyKeyframeSearchTimeDiff; 147 | int historyKeyframeSearchNum; 148 | float historyKeyframeFitnessScore; 149 | 150 | // global map visualization radius 151 | float globalMapVisualizationSearchRadius; 152 | float globalMapVisualizationPoseDensity; 153 | float globalMapVisualizationLeafSize; 154 | 155 | ParamServer() 156 | { 157 | nh.param("/robot_id", robot_id, "roboat"); 158 | 159 | nh.param("lio_sam/pointCloudTopic", pointCloudTopic, "points_raw"); 160 | nh.param("lio_sam/imuTopic", imuTopic, "imu_correct"); 161 | nh.param("lio_sam/odomTopic", odomTopic, "odometry/imu"); 162 | nh.param("lio_sam/gpsTopic", gpsTopic, "odometry/gps"); 163 | 164 | nh.param("lio_sam/lidarFrame", lidarFrame, "base_link"); 165 | nh.param("lio_sam/baselinkFrame", baselinkFrame, "base_link"); 166 | nh.param("lio_sam/odometryFrame", odometryFrame, "odom"); 167 | nh.param("lio_sam/mapFrame", mapFrame, "map"); 168 | 169 | nh.param("lio_sam/useImuHeadingInitialization", useImuHeadingInitialization, false); 170 | nh.param("lio_sam/useGpsElevation", useGpsElevation, false); 171 | nh.param("lio_sam/gpsCovThreshold", gpsCovThreshold, 2.0); 172 | nh.param("lio_sam/poseCovThreshold", poseCovThreshold, 25.0); 173 | 174 | nh.param("lio_sam/savePCD", savePCD, false); 175 | nh.param("lio_sam/savePCDDirectory", savePCDDirectory, "/Downloads/LOAM/"); 176 | 177 | std::string sensorStr; 178 | nh.param("lio_sam/sensor", sensorStr, ""); 179 | if (sensorStr == "velodyne") 180 | { 181 | sensor = SensorType::VELODYNE; 182 | } 183 | else if (sensorStr == "ouster") 184 | { 185 | sensor = SensorType::OUSTER; 186 | } 187 | else if (sensorStr == "mulran") 188 | { 189 | sensor = SensorType::MULRAN; 190 | } 191 | else 192 | { 193 | ROS_ERROR_STREAM( 194 | "Invalid sensor type (must be either 'velodyne' or 'ouster' or 'mulran'): " << sensorStr); 195 | ros::shutdown(); 196 | } 197 | 198 | nh.param("lio_sam/N_SCAN", N_SCAN, 16); 199 | nh.param("lio_sam/Horizon_SCAN", Horizon_SCAN, 1800); 200 | nh.param("lio_sam/downsampleRate", downsampleRate, 1); 201 | nh.param("lio_sam/lidarMinRange", lidarMinRange, 1.0); 202 | nh.param("lio_sam/lidarMaxRange", lidarMaxRange, 1000.0); 203 | 204 | nh.param("lio_sam/imuAccNoise", imuAccNoise, 0.01); 205 | nh.param("lio_sam/imuGyrNoise", imuGyrNoise, 0.001); 206 | nh.param("lio_sam/imuAccBiasN", imuAccBiasN, 0.0002); 207 | nh.param("lio_sam/imuGyrBiasN", imuGyrBiasN, 0.00003); 208 | nh.param("lio_sam/imuGravity", imuGravity, 9.80511); 209 | nh.param("lio_sam/imuRPYWeight", imuRPYWeight, 0.01); 210 | nh.param>("lio_sam/extrinsicRot", extRotV, vector()); 211 | nh.param>("lio_sam/extrinsicRPY", extRPYV, vector()); 212 | nh.param>("lio_sam/extrinsicTrans", extTransV, vector()); 213 | extRot = Eigen::Map>(extRotV.data(), 3, 3); 214 | extRPY = Eigen::Map>(extRPYV.data(), 3, 3); 215 | extTrans = Eigen::Map>(extTransV.data(), 3, 1); 216 | extQRPY = Eigen::Quaterniond(extRPY); 217 | 218 | nh.param("lio_sam/edgeThreshold", edgeThreshold, 0.1); 219 | nh.param("lio_sam/surfThreshold", surfThreshold, 0.1); 220 | nh.param("lio_sam/edgeFeatureMinValidNum", edgeFeatureMinValidNum, 10); 221 | nh.param("lio_sam/surfFeatureMinValidNum", surfFeatureMinValidNum, 100); 222 | 223 | nh.param("lio_sam/odometrySurfLeafSize", odometrySurfLeafSize, 0.2); 224 | nh.param("lio_sam/mappingCornerLeafSize", mappingCornerLeafSize, 0.2); 225 | nh.param("lio_sam/mappingSurfLeafSize", mappingSurfLeafSize, 0.2); 226 | 227 | nh.param("lio_sam/z_tollerance", z_tollerance, FLT_MAX); 228 | nh.param("lio_sam/rotation_tollerance", rotation_tollerance, FLT_MAX); 229 | 230 | nh.param("lio_sam/numberOfCores", numberOfCores, 2); 231 | nh.param("lio_sam/mappingProcessInterval", mappingProcessInterval, 0.15); 232 | 233 | nh.param("lio_sam/surroundingkeyframeAddingDistThreshold", surroundingkeyframeAddingDistThreshold, 1.0); 234 | nh.param("lio_sam/surroundingkeyframeAddingAngleThreshold", surroundingkeyframeAddingAngleThreshold, 0.2); 235 | nh.param("lio_sam/surroundingKeyframeDensity", surroundingKeyframeDensity, 1.0); 236 | nh.param("lio_sam/surroundingKeyframeSearchRadius", surroundingKeyframeSearchRadius, 50.0); 237 | 238 | nh.param("lio_sam/loopClosureEnableFlag", loopClosureEnableFlag, false); 239 | nh.param("lio_sam/loopClosureFrequency", loopClosureFrequency, 1.0); 240 | nh.param("lio_sam/surroundingKeyframeSize", surroundingKeyframeSize, 50); 241 | nh.param("lio_sam/historyKeyframeSearchRadius", historyKeyframeSearchRadius, 10.0); 242 | nh.param("lio_sam/historyKeyframeSearchTimeDiff", historyKeyframeSearchTimeDiff, 30.0); 243 | nh.param("lio_sam/historyKeyframeSearchNum", historyKeyframeSearchNum, 25); 244 | nh.param("lio_sam/historyKeyframeFitnessScore", historyKeyframeFitnessScore, 0.3); 245 | 246 | nh.param("lio_sam/globalMapVisualizationSearchRadius", globalMapVisualizationSearchRadius, 1e3); 247 | nh.param("lio_sam/globalMapVisualizationPoseDensity", globalMapVisualizationPoseDensity, 10.0); 248 | nh.param("lio_sam/globalMapVisualizationLeafSize", globalMapVisualizationLeafSize, 1.0); 249 | 250 | usleep(100); 251 | } 252 | 253 | sensor_msgs::Imu imuConverter(const sensor_msgs::Imu& imu_in) 254 | { 255 | sensor_msgs::Imu imu_out = imu_in; 256 | // rotate acceleration 257 | Eigen::Vector3d acc(imu_in.linear_acceleration.x, imu_in.linear_acceleration.y, imu_in.linear_acceleration.z); 258 | acc = extRot * acc; 259 | imu_out.linear_acceleration.x = acc.x(); 260 | imu_out.linear_acceleration.y = acc.y(); 261 | imu_out.linear_acceleration.z = acc.z(); 262 | // rotate gyroscope 263 | Eigen::Vector3d gyr(imu_in.angular_velocity.x, imu_in.angular_velocity.y, imu_in.angular_velocity.z); 264 | gyr = extRot * gyr; 265 | imu_out.angular_velocity.x = gyr.x(); 266 | imu_out.angular_velocity.y = gyr.y(); 267 | imu_out.angular_velocity.z = gyr.z(); 268 | // rotate roll pitch yaw 269 | Eigen::Quaterniond q_from(imu_in.orientation.w, imu_in.orientation.x, imu_in.orientation.y, imu_in.orientation.z); 270 | Eigen::Quaterniond q_final = q_from * extQRPY; 271 | imu_out.orientation.x = q_final.x(); 272 | imu_out.orientation.y = q_final.y(); 273 | imu_out.orientation.z = q_final.z(); 274 | imu_out.orientation.w = q_final.w(); 275 | 276 | if (sqrt(q_final.x()*q_final.x() + q_final.y()*q_final.y() + q_final.z()*q_final.z() + q_final.w()*q_final.w()) < 0.1) 277 | { 278 | ROS_ERROR("Invalid quaternion, please use a 9-axis IMU!"); 279 | ros::shutdown(); 280 | } 281 | 282 | return imu_out; 283 | } 284 | }; 285 | 286 | 287 | sensor_msgs::PointCloud2 publishCloud(ros::Publisher *thisPub, pcl::PointCloud::Ptr thisCloud, ros::Time thisStamp, std::string thisFrame) 288 | { 289 | sensor_msgs::PointCloud2 tempCloud; 290 | pcl::toROSMsg(*thisCloud, tempCloud); 291 | tempCloud.header.stamp = thisStamp; 292 | tempCloud.header.frame_id = thisFrame; 293 | if (thisPub->getNumSubscribers() != 0) 294 | thisPub->publish(tempCloud); 295 | return tempCloud; 296 | } 297 | 298 | template 299 | double ROS_TIME(T msg) 300 | { 301 | return msg->header.stamp.toSec(); 302 | } 303 | 304 | 305 | template 306 | void imuAngular2rosAngular(sensor_msgs::Imu *thisImuMsg, T *angular_x, T *angular_y, T *angular_z) 307 | { 308 | *angular_x = thisImuMsg->angular_velocity.x; 309 | *angular_y = thisImuMsg->angular_velocity.y; 310 | *angular_z = thisImuMsg->angular_velocity.z; 311 | } 312 | 313 | 314 | template 315 | void imuAccel2rosAccel(sensor_msgs::Imu *thisImuMsg, T *acc_x, T *acc_y, T *acc_z) 316 | { 317 | *acc_x = thisImuMsg->linear_acceleration.x; 318 | *acc_y = thisImuMsg->linear_acceleration.y; 319 | *acc_z = thisImuMsg->linear_acceleration.z; 320 | } 321 | 322 | 323 | template 324 | void imuRPY2rosRPY(sensor_msgs::Imu *thisImuMsg, T *rosRoll, T *rosPitch, T *rosYaw) 325 | { 326 | double imuRoll, imuPitch, imuYaw; 327 | tf::Quaternion orientation; 328 | tf::quaternionMsgToTF(thisImuMsg->orientation, orientation); 329 | tf::Matrix3x3(orientation).getRPY(imuRoll, imuPitch, imuYaw); 330 | 331 | *rosRoll = imuRoll; 332 | *rosPitch = imuPitch; 333 | *rosYaw = imuYaw; 334 | } 335 | 336 | 337 | float pointDistance(PointType p) 338 | { 339 | return sqrt(p.x*p.x + p.y*p.y + p.z*p.z); 340 | } 341 | 342 | 343 | float pointDistance(PointType p1, PointType p2) 344 | { 345 | return sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y) + (p1.z-p2.z)*(p1.z-p2.z)); 346 | } 347 | 348 | void saveSCD(std::string fileName, Eigen::MatrixXd matrix, std::string delimiter = " ") 349 | { 350 | // delimiter: ", " or " " etc. 351 | 352 | int precision = 3; // or Eigen::FullPrecision, but SCD does not require such accruate precisions so 3 is enough. 353 | const static Eigen::IOFormat the_format(precision, Eigen::DontAlignCols, delimiter, "\n"); 354 | 355 | std::ofstream file(fileName); 356 | if (file.is_open()) 357 | { 358 | file << matrix.format(the_format); 359 | file.close(); 360 | } 361 | } 362 | 363 | std::string padZeros(int val, int num_digits = 6) { 364 | std::ostringstream out; 365 | out << std::internal << std::setfill('0') << std::setw(num_digits) << val; 366 | return out.str(); 367 | } 368 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/config/robot.urdf.xacro: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/config/rviz.rviz: -------------------------------------------------------------------------------- 1 | Panels: 2 | - Class: rviz/Displays 3 | Help Height: 191 4 | Name: Displays 5 | Property Tree Widget: 6 | Expanded: 7 | - /Mapping1 8 | Splitter Ratio: 0.47826087474823 9 | Tree Height: 701 10 | - Class: rviz/Selection 11 | Name: Selection 12 | - Class: rviz/Views 13 | Expanded: 14 | - /Current View1 15 | Name: Views 16 | Splitter Ratio: 0.5 17 | - Class: rviz/Tool Properties 18 | Expanded: ~ 19 | Name: Tool Properties 20 | Splitter Ratio: 0.5 21 | Preferences: 22 | PromptSaveOnExit: true 23 | Toolbars: 24 | toolButtonStyle: 2 25 | Visualization Manager: 26 | Class: "" 27 | Displays: 28 | - Class: rviz/Group 29 | Displays: 30 | - Class: rviz/TF 31 | Enabled: false 32 | Frame Timeout: 999 33 | Frames: 34 | All Enabled: false 35 | Marker Scale: 3 36 | Name: TF 37 | Show Arrows: true 38 | Show Axes: true 39 | Show Names: true 40 | Tree: 41 | {} 42 | Update Interval: 0 43 | Value: false 44 | - Class: rviz/Axes 45 | Enabled: true 46 | Length: 2 47 | Name: map 48 | Radius: 0.5 49 | Reference Frame: map 50 | Value: true 51 | - Class: rviz/Axes 52 | Enabled: true 53 | Length: 1 54 | Name: base_link 55 | Radius: 0.30000001192092896 56 | Reference Frame: base_link 57 | Value: true 58 | Enabled: true 59 | Name: links 60 | - Class: rviz/Group 61 | Displays: 62 | - Angle Tolerance: 0.10000000149011612 63 | Class: rviz/Odometry 64 | Covariance: 65 | Orientation: 66 | Alpha: 0.5 67 | Color: 255; 255; 127 68 | Color Style: Unique 69 | Frame: Local 70 | Offset: 1 71 | Scale: 1 72 | Value: true 73 | Position: 74 | Alpha: 0.30000001192092896 75 | Color: 204; 51; 204 76 | Scale: 1 77 | Value: true 78 | Value: false 79 | Enabled: true 80 | Keep: 300 81 | Name: Odom IMU 82 | Position Tolerance: 0.10000000149011612 83 | Shape: 84 | Alpha: 1 85 | Axes Length: 0.5 86 | Axes Radius: 0.10000000149011612 87 | Color: 255; 25; 0 88 | Head Length: 0.30000001192092896 89 | Head Radius: 0.10000000149011612 90 | Shaft Length: 1 91 | Shaft Radius: 0.05000000074505806 92 | Value: Axes 93 | Topic: /odometry/imu 94 | Unreliable: false 95 | Value: true 96 | - Angle Tolerance: 0.10000000149011612 97 | Class: rviz/Odometry 98 | Covariance: 99 | Orientation: 100 | Alpha: 0.5 101 | Color: 255; 255; 127 102 | Color Style: Unique 103 | Frame: Local 104 | Offset: 1 105 | Scale: 1 106 | Value: true 107 | Position: 108 | Alpha: 0.30000001192092896 109 | Color: 204; 51; 204 110 | Scale: 1 111 | Value: true 112 | Value: false 113 | Enabled: true 114 | Keep: 300 115 | Name: Odom GPS 116 | Position Tolerance: 0.10000000149011612 117 | Shape: 118 | Alpha: 1 119 | Axes Length: 1 120 | Axes Radius: 0.30000001192092896 121 | Color: 255; 25; 0 122 | Head Length: 0.30000001192092896 123 | Head Radius: 0.10000000149011612 124 | Shaft Length: 1 125 | Shaft Radius: 0.05000000074505806 126 | Value: Axes 127 | Topic: /odometry/gps 128 | Unreliable: false 129 | Value: true 130 | - Angle Tolerance: 0.10000000149011612 131 | Class: rviz/Odometry 132 | Covariance: 133 | Orientation: 134 | Alpha: 0.5 135 | Color: 255; 255; 127 136 | Color Style: Unique 137 | Frame: Local 138 | Offset: 1 139 | Scale: 1 140 | Value: true 141 | Position: 142 | Alpha: 0.30000001192092896 143 | Color: 204; 51; 204 144 | Scale: 1 145 | Value: true 146 | Value: false 147 | Enabled: false 148 | Keep: 300 149 | Name: Odom lidar 150 | Position Tolerance: 0.10000000149011612 151 | Shape: 152 | Alpha: 1 153 | Axes Length: 0.25 154 | Axes Radius: 0.10000000149011612 155 | Color: 255; 25; 0 156 | Head Length: 0.30000001192092896 157 | Head Radius: 0.10000000149011612 158 | Shaft Length: 1 159 | Shaft Radius: 0.05000000074505806 160 | Value: Axes 161 | Topic: /lio_sam/mapping/odometry 162 | Unreliable: false 163 | Value: false 164 | Enabled: false 165 | Name: Odometry 166 | - Class: rviz/Group 167 | Displays: 168 | - Alpha: 1 169 | Autocompute Intensity Bounds: true 170 | Autocompute Value Bounds: 171 | Max Value: 10 172 | Min Value: -10 173 | Value: true 174 | Axis: Z 175 | Channel Name: intensity 176 | Class: rviz/PointCloud2 177 | Color: 255; 255; 255 178 | Color Transformer: Intensity 179 | Decay Time: 0 180 | Enabled: true 181 | Invert Rainbow: false 182 | Max Color: 255; 255; 255 183 | Max Intensity: 7188 184 | Min Color: 0; 0; 0 185 | Min Intensity: 3 186 | Name: Velodyne 187 | Position Transformer: XYZ 188 | Queue Size: 10 189 | Selectable: false 190 | Size (Pixels): 2 191 | Size (m): 0.009999999776482582 192 | Style: Points 193 | Topic: /lio_sam/deskew/cloud_deskewed 194 | Unreliable: false 195 | Use Fixed Frame: true 196 | Use rainbow: true 197 | Value: true 198 | - Alpha: 1 199 | Autocompute Intensity Bounds: true 200 | Autocompute Value Bounds: 201 | Max Value: 10 202 | Min Value: -10 203 | Value: true 204 | Axis: Z 205 | Channel Name: intensity 206 | Class: rviz/PointCloud2 207 | Color: 255; 255; 255 208 | Color Transformer: Intensity 209 | Decay Time: 0 210 | Enabled: false 211 | Invert Rainbow: false 212 | Max Color: 255; 255; 255 213 | Max Intensity: 0.7900000214576721 214 | Min Color: 0; 0; 0 215 | Min Intensity: 0 216 | Name: Reg Cloud 217 | Position Transformer: XYZ 218 | Queue Size: 10 219 | Selectable: true 220 | Size (Pixels): 3 221 | Size (m): 0.009999999776482582 222 | Style: Points 223 | Topic: /lio_sam/mapping/cloud_registered 224 | Unreliable: false 225 | Use Fixed Frame: true 226 | Use rainbow: true 227 | Value: false 228 | Enabled: true 229 | Name: Point Cloud 230 | - Class: rviz/Group 231 | Displays: 232 | - Alpha: 1 233 | Autocompute Intensity Bounds: true 234 | Autocompute Value Bounds: 235 | Max Value: 10 236 | Min Value: -10 237 | Value: true 238 | Axis: Z 239 | Channel Name: intensity 240 | Class: rviz/PointCloud2 241 | Color: 0; 255; 0 242 | Color Transformer: FlatColor 243 | Decay Time: 0 244 | Enabled: true 245 | Invert Rainbow: false 246 | Max Color: 255; 255; 255 247 | Max Intensity: 15.077729225158691 248 | Min Color: 0; 0; 0 249 | Min Intensity: 0.019985778257250786 250 | Name: Edge Feature 251 | Position Transformer: XYZ 252 | Queue Size: 10 253 | Selectable: true 254 | Size (Pixels): 5 255 | Size (m): 0.009999999776482582 256 | Style: Points 257 | Topic: /lio_sam/feature/cloud_corner 258 | Unreliable: false 259 | Use Fixed Frame: true 260 | Use rainbow: true 261 | Value: true 262 | - Alpha: 1 263 | Autocompute Intensity Bounds: true 264 | Autocompute Value Bounds: 265 | Max Value: 10 266 | Min Value: -10 267 | Value: true 268 | Axis: Z 269 | Channel Name: intensity 270 | Class: rviz/PointCloud2 271 | Color: 255; 85; 255 272 | Color Transformer: FlatColor 273 | Decay Time: 0 274 | Enabled: true 275 | Invert Rainbow: false 276 | Max Color: 255; 255; 255 277 | Max Intensity: 15.100607872009277 278 | Min Color: 0; 0; 0 279 | Min Intensity: 0.0007188677554950118 280 | Name: Plannar Feature 281 | Position Transformer: XYZ 282 | Queue Size: 10 283 | Selectable: true 284 | Size (Pixels): 3 285 | Size (m): 0.009999999776482582 286 | Style: Points 287 | Topic: /lio_sam/feature/cloud_surface 288 | Unreliable: false 289 | Use Fixed Frame: true 290 | Use rainbow: true 291 | Value: true 292 | Enabled: false 293 | Name: Feature Cloud 294 | - Class: rviz/Group 295 | Displays: 296 | - Alpha: 0.30000001192092896 297 | Autocompute Intensity Bounds: true 298 | Autocompute Value Bounds: 299 | Max Value: 7.4858574867248535 300 | Min Value: -1.2309398651123047 301 | Value: true 302 | Axis: Z 303 | Channel Name: intensity 304 | Class: rviz/PointCloud2 305 | Color: 255; 255; 255 306 | Color Transformer: Intensity 307 | Decay Time: 300 308 | Enabled: false 309 | Invert Rainbow: false 310 | Max Color: 255; 255; 255 311 | Max Intensity: 2569 312 | Min Color: 0; 0; 0 313 | Min Intensity: 10 314 | Name: Map (cloud) 315 | Position Transformer: XYZ 316 | Queue Size: 10 317 | Selectable: false 318 | Size (Pixels): 2 319 | Size (m): 0.009999999776482582 320 | Style: Points 321 | Topic: /lio_sam/mapping/cloud_registered 322 | Unreliable: false 323 | Use Fixed Frame: true 324 | Use rainbow: true 325 | Value: false 326 | - Alpha: 1 327 | Autocompute Intensity Bounds: true 328 | Autocompute Value Bounds: 329 | Max Value: 36.61034393310547 330 | Min Value: -2.3476977348327637 331 | Value: true 332 | Axis: Z 333 | Channel Name: intensity 334 | Class: rviz/PointCloud2 335 | Color: 255; 255; 255 336 | Color Transformer: AxisColor 337 | Decay Time: 0 338 | Enabled: false 339 | Invert Rainbow: false 340 | Max Color: 255; 255; 255 341 | Max Intensity: 15.100604057312012 342 | Min Color: 0; 0; 0 343 | Min Intensity: 0.014545874670147896 344 | Name: Map (local) 345 | Position Transformer: XYZ 346 | Queue Size: 10 347 | Selectable: true 348 | Size (Pixels): 2 349 | Size (m): 0.10000000149011612 350 | Style: Flat Squares 351 | Topic: /lio_sam/mapping/map_local 352 | Unreliable: false 353 | Use Fixed Frame: true 354 | Use rainbow: true 355 | Value: false 356 | - Alpha: 1 357 | Autocompute Intensity Bounds: false 358 | Autocompute Value Bounds: 359 | Max Value: 10 360 | Min Value: -10 361 | Value: true 362 | Axis: Z 363 | Channel Name: intensity 364 | Class: rviz/PointCloud2 365 | Color: 255; 255; 255 366 | Color Transformer: Intensity 367 | Decay Time: 0 368 | Enabled: true 369 | Invert Rainbow: false 370 | Max Color: 255; 255; 255 371 | Max Intensity: 300 372 | Min Color: 0; 0; 0 373 | Min Intensity: 10 374 | Name: Map (global) 375 | Position Transformer: XYZ 376 | Queue Size: 10 377 | Selectable: true 378 | Size (Pixels): 2 379 | Size (m): 0.009999999776482582 380 | Style: Points 381 | Topic: /lio_sam/mapping/map_global 382 | Unreliable: false 383 | Use Fixed Frame: true 384 | Use rainbow: true 385 | Value: true 386 | - Alpha: 1 387 | Buffer Length: 1 388 | Class: rviz/Path 389 | Color: 238; 238; 236 390 | Enabled: true 391 | Head Diameter: 0.30000001192092896 392 | Head Length: 0.20000000298023224 393 | Length: 0.30000001192092896 394 | Line Style: Billboards 395 | Line Width: 1 396 | Name: Path (global) 397 | Offset: 398 | X: 0 399 | Y: 0 400 | Z: 0 401 | Pose Color: 255; 85; 255 402 | Pose Style: None 403 | Radius: 0.029999999329447746 404 | Shaft Diameter: 0.10000000149011612 405 | Shaft Length: 0.10000000149011612 406 | Topic: /lio_sam/mapping/path 407 | Unreliable: false 408 | Value: true 409 | - Alpha: 1 410 | Buffer Length: 1 411 | Class: rviz/Path 412 | Color: 255; 85; 255 413 | Enabled: true 414 | Head Diameter: 0.30000001192092896 415 | Head Length: 0.20000000298023224 416 | Length: 0.30000001192092896 417 | Line Style: Billboards 418 | Line Width: 0.10000000149011612 419 | Name: Path (local) 420 | Offset: 421 | X: 0 422 | Y: 0 423 | Z: 0 424 | Pose Color: 255; 85; 255 425 | Pose Style: None 426 | Radius: 0.029999999329447746 427 | Shaft Diameter: 0.10000000149011612 428 | Shaft Length: 0.10000000149011612 429 | Topic: /lio_sam/imu/path 430 | Unreliable: false 431 | Value: true 432 | - Alpha: 1 433 | Autocompute Intensity Bounds: true 434 | Autocompute Value Bounds: 435 | Max Value: 20.802837371826172 436 | Min Value: -0.03934507071971893 437 | Value: true 438 | Axis: Z 439 | Channel Name: intensity 440 | Class: rviz/PointCloud2 441 | Color: 255; 255; 255 442 | Color Transformer: AxisColor 443 | Decay Time: 0 444 | Enabled: false 445 | Invert Rainbow: false 446 | Max Color: 255; 255; 255 447 | Max Intensity: 15.100604057312012 448 | Min Color: 0; 0; 0 449 | Min Intensity: 0.014545874670147896 450 | Name: Loop closure 451 | Position Transformer: XYZ 452 | Queue Size: 10 453 | Selectable: true 454 | Size (Pixels): 2 455 | Size (m): 0.30000001192092896 456 | Style: Flat Squares 457 | Topic: /lio_sam/mapping/icp_loop_closure_corrected_cloud 458 | Unreliable: false 459 | Use Fixed Frame: true 460 | Use rainbow: true 461 | Value: false 462 | - Class: rviz/MarkerArray 463 | Enabled: true 464 | Marker Topic: /lio_sam/mapping/loop_closure_constraints 465 | Name: Loop constraint 466 | Namespaces: 467 | {} 468 | Queue Size: 100 469 | Value: true 470 | Enabled: true 471 | Name: Mapping 472 | Enabled: true 473 | Global Options: 474 | Background Color: 0; 0; 0 475 | Default Light: true 476 | Fixed Frame: map 477 | Frame Rate: 30 478 | Name: root 479 | Tools: 480 | - Class: rviz/Interact 481 | Hide Inactive Objects: true 482 | - Class: rviz/FocusCamera 483 | - Class: rviz/Measure 484 | Value: true 485 | Views: 486 | Current: 487 | Class: rviz/Orbit 488 | Distance: 143.16458129882812 489 | Enable Stereo Rendering: 490 | Stereo Eye Separation: 0.05999999865889549 491 | Stereo Focal Distance: 1 492 | Swap Stereo Eyes: false 493 | Value: false 494 | Focal Point: 495 | X: 1.5083352327346802 496 | Y: 2.730443000793457 497 | Z: -6.330533981323242 498 | Focal Shape Fixed Size: false 499 | Focal Shape Size: 0.05000000074505806 500 | Invert Z Axis: false 501 | Name: Current View 502 | Near Clip Distance: 0.009999999776482582 503 | Pitch: 0.7847971320152283 504 | Target Frame: base_link 505 | Value: Orbit (rviz) 506 | Yaw: 5.183582782745361 507 | Saved: ~ 508 | Window Geometry: 509 | Displays: 510 | collapsed: false 511 | Height: 1043 512 | Hide Left Dock: false 513 | Hide Right Dock: false 514 | QMainWindow State: 000000ff00000000fd00000004000000000000015b000003b9fc0200000009fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb0000000a0049006d006100670065000000002e000001340000000000000000fb000000100044006900730070006c006100790073010000003d000003b9000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730000000286000000a70000005c00ffffff0000000100000111000003b9fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000003b9000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004b000000040fc0100000002fb0000000800540069006d00650000000000000004b00000000000000000fb0000000800540069006d0065010000000000000450000000000000000000000508000003b900000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 515 | Selection: 516 | collapsed: false 517 | Tool Properties: 518 | collapsed: false 519 | Views: 520 | collapsed: false 521 | Width: 1920 522 | X: 3840 523 | Y: 0 524 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/module_loam.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/module_navsat.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/module_robot_state_publisher.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/module_rviz.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/rosconsole/rosconsole_error.conf: -------------------------------------------------------------------------------- 1 | # Set the default ros output to warning and higher 2 | log4j.logger.ros = ERROR -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/rosconsole/rosconsole_info.conf: -------------------------------------------------------------------------------- 1 | # Set the default ros output to warning and higher 2 | log4j.logger.ros = INFO -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/include/rosconsole/rosconsole_warn.conf: -------------------------------------------------------------------------------- 1 | # Set the default ros output to warning and higher 2 | log4j.logger.ros = WARN -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/run.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SC-LIO-SAM/launch/run_mulran.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SC-LIO-SAM/msg/cloud_info.msg: -------------------------------------------------------------------------------- 1 | # Cloud Info 2 | Header header 3 | 4 | int32[] startRingIndex 5 | int32[] endRingIndex 6 | 7 | int32[] pointColInd # point column index in range image 8 | float32[] pointRange # point range 9 | 10 | int64 imuAvailable 11 | int64 odomAvailable 12 | 13 | # Attitude for LOAM initialization 14 | float32 imuRollInit 15 | float32 imuPitchInit 16 | float32 imuYawInit 17 | 18 | # Initial guess from imu pre-integration 19 | float32 initialGuessX 20 | float32 initialGuessY 21 | float32 initialGuessZ 22 | float32 initialGuessRoll 23 | float32 initialGuessPitch 24 | float32 initialGuessYaw 25 | 26 | # Point cloud messages 27 | sensor_msgs/PointCloud2 cloud_deskewed # original cloud deskewed 28 | sensor_msgs/PointCloud2 cloud_corner # extracted corner feature 29 | sensor_msgs/PointCloud2 cloud_surface # extracted surface feature -------------------------------------------------------------------------------- /SC-LIO-SAM/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | lio_sam 4 | 1.0.0 5 | Lidar Odometry 6 | 7 | Tixiao Shan 8 | 9 | TODO 10 | 11 | Tixiao Shan 12 | 13 | catkin 14 | 15 | roscpp 16 | roscpp 17 | rospy 18 | rospy 19 | 20 | tf 21 | tf 22 | 23 | cv_bridge 24 | cv_bridge 25 | 26 | pcl_conversions 27 | pcl_conversions 28 | 29 | std_msgs 30 | std_msgs 31 | sensor_msgs 32 | sensor_msgs 33 | geometry_msgs 34 | geometry_msgs 35 | nav_msgs 36 | nav_msgs 37 | 38 | message_generation 39 | message_generation 40 | message_runtime 41 | message_runtime 42 | 43 | GTSAM 44 | GTSAM 45 | 46 | 47 | -------------------------------------------------------------------------------- /SC-LIO-SAM/src/Scancontext.cpp: -------------------------------------------------------------------------------- 1 | #include "Scancontext.h" 2 | 3 | // namespace SC2 4 | // { 5 | 6 | void coreImportTest (void) 7 | { 8 | cout << "scancontext lib is successfully imported." << endl; 9 | } // coreImportTest 10 | 11 | 12 | float rad2deg(float radians) 13 | { 14 | return radians * 180.0 / M_PI; 15 | } 16 | 17 | float deg2rad(float degrees) 18 | { 19 | return degrees * M_PI / 180.0; 20 | } 21 | 22 | 23 | float xy2theta( const float & _x, const float & _y ) 24 | { 25 | if ( (_x >= 0) & (_y >= 0)) 26 | return (180/M_PI) * atan(_y / _x); 27 | 28 | if ( (_x < 0) & (_y >= 0)) 29 | return 180 - ( (180/M_PI) * atan(_y / (-_x)) ); 30 | 31 | if ( (_x < 0) & (_y < 0)) 32 | return 180 + ( (180/M_PI) * atan(_y / _x) ); 33 | 34 | if ( (_x >= 0) & (_y < 0)) 35 | return 360 - ( (180/M_PI) * atan((-_y) / _x) ); 36 | } // xy2theta 37 | 38 | 39 | MatrixXd circshift( MatrixXd &_mat, int _num_shift ) 40 | { 41 | // shift columns to right direction 42 | assert(_num_shift >= 0); 43 | 44 | if( _num_shift == 0 ) 45 | { 46 | MatrixXd shifted_mat( _mat ); 47 | return shifted_mat; // Early return 48 | } 49 | 50 | MatrixXd shifted_mat = MatrixXd::Zero( _mat.rows(), _mat.cols() ); 51 | for ( int col_idx = 0; col_idx < _mat.cols(); col_idx++ ) 52 | { 53 | int new_location = (col_idx + _num_shift) % _mat.cols(); 54 | shifted_mat.col(new_location) = _mat.col(col_idx); 55 | } 56 | 57 | return shifted_mat; 58 | 59 | } // circshift 60 | 61 | 62 | std::vector eig2stdvec( MatrixXd _eigmat ) 63 | { 64 | std::vector vec( _eigmat.data(), _eigmat.data() + _eigmat.size() ); 65 | return vec; 66 | } // eig2stdvec 67 | 68 | 69 | double SCManager::distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 ) 70 | { 71 | int num_eff_cols = 0; // i.e., to exclude all-nonzero sector 72 | double sum_sector_similarity = 0; 73 | for ( int col_idx = 0; col_idx < _sc1.cols(); col_idx++ ) 74 | { 75 | VectorXd col_sc1 = _sc1.col(col_idx); 76 | VectorXd col_sc2 = _sc2.col(col_idx); 77 | 78 | if( (col_sc1.norm() == 0) | (col_sc2.norm() == 0) ) 79 | continue; // don't count this sector pair. 80 | 81 | double sector_similarity = col_sc1.dot(col_sc2) / (col_sc1.norm() * col_sc2.norm()); 82 | 83 | sum_sector_similarity = sum_sector_similarity + sector_similarity; 84 | num_eff_cols = num_eff_cols + 1; 85 | } 86 | 87 | double sc_sim = sum_sector_similarity / num_eff_cols; 88 | return 1.0 - sc_sim; 89 | 90 | } // distDirectSC 91 | 92 | 93 | int SCManager::fastAlignUsingVkey( MatrixXd & _vkey1, MatrixXd & _vkey2) 94 | { 95 | int argmin_vkey_shift = 0; 96 | double min_veky_diff_norm = 10000000; 97 | for ( int shift_idx = 0; shift_idx < _vkey1.cols(); shift_idx++ ) 98 | { 99 | MatrixXd vkey2_shifted = circshift(_vkey2, shift_idx); 100 | 101 | MatrixXd vkey_diff = _vkey1 - vkey2_shifted; 102 | 103 | double cur_diff_norm = vkey_diff.norm(); 104 | if( cur_diff_norm < min_veky_diff_norm ) 105 | { 106 | argmin_vkey_shift = shift_idx; 107 | min_veky_diff_norm = cur_diff_norm; 108 | } 109 | } 110 | 111 | return argmin_vkey_shift; 112 | 113 | } // fastAlignUsingVkey 114 | 115 | 116 | std::pair SCManager::distanceBtnScanContext( MatrixXd &_sc1, MatrixXd &_sc2 ) 117 | { 118 | // 1. fast align using variant key (not in original IROS18) 119 | MatrixXd vkey_sc1 = makeSectorkeyFromScancontext( _sc1 ); 120 | MatrixXd vkey_sc2 = makeSectorkeyFromScancontext( _sc2 ); 121 | int argmin_vkey_shift = fastAlignUsingVkey( vkey_sc1, vkey_sc2 ); 122 | 123 | const int SEARCH_RADIUS = round( 0.5 * SEARCH_RATIO * _sc1.cols() ); // a half of search range 124 | std::vector shift_idx_search_space { argmin_vkey_shift }; 125 | for ( int ii = 1; ii < SEARCH_RADIUS + 1; ii++ ) 126 | { 127 | shift_idx_search_space.push_back( (argmin_vkey_shift + ii + _sc1.cols()) % _sc1.cols() ); 128 | shift_idx_search_space.push_back( (argmin_vkey_shift - ii + _sc1.cols()) % _sc1.cols() ); 129 | } 130 | std::sort(shift_idx_search_space.begin(), shift_idx_search_space.end()); 131 | 132 | // 2. fast columnwise diff 133 | int argmin_shift = 0; 134 | double min_sc_dist = 10000000; 135 | for ( int num_shift: shift_idx_search_space ) 136 | { 137 | MatrixXd sc2_shifted = circshift(_sc2, num_shift); 138 | double cur_sc_dist = distDirectSC( _sc1, sc2_shifted ); 139 | if( cur_sc_dist < min_sc_dist ) 140 | { 141 | argmin_shift = num_shift; 142 | min_sc_dist = cur_sc_dist; 143 | } 144 | } 145 | 146 | return make_pair(min_sc_dist, argmin_shift); 147 | 148 | } // distanceBtnScanContext 149 | 150 | 151 | MatrixXd SCManager::makeScancontext( pcl::PointCloud & _scan_down ) 152 | { 153 | TicToc t_making_desc; 154 | 155 | int num_pts_scan_down = _scan_down.points.size(); 156 | 157 | // main 158 | const int NO_POINT = -1000; 159 | MatrixXd desc = NO_POINT * MatrixXd::Ones(PC_NUM_RING, PC_NUM_SECTOR); 160 | 161 | SCPointType pt; 162 | float azim_angle, azim_range; // wihtin 2d plane 163 | int ring_idx, sctor_idx; 164 | for (int pt_idx = 0; pt_idx < num_pts_scan_down; pt_idx++) 165 | { 166 | pt.x = _scan_down.points[pt_idx].x; 167 | pt.y = _scan_down.points[pt_idx].y; 168 | pt.z = _scan_down.points[pt_idx].z + LIDAR_HEIGHT; // naive adding is ok (all points should be > 0). 169 | 170 | // xyz to ring, sector 171 | azim_range = sqrt(pt.x * pt.x + pt.y * pt.y); 172 | azim_angle = xy2theta(pt.x, pt.y); 173 | 174 | // if range is out of roi, pass 175 | if( azim_range > PC_MAX_RADIUS ) 176 | continue; 177 | 178 | ring_idx = std::max( std::min( PC_NUM_RING, int(ceil( (azim_range / PC_MAX_RADIUS) * PC_NUM_RING )) ), 1 ); 179 | sctor_idx = std::max( std::min( PC_NUM_SECTOR, int(ceil( (azim_angle / 360.0) * PC_NUM_SECTOR )) ), 1 ); 180 | 181 | // taking maximum z 182 | if ( desc(ring_idx-1, sctor_idx-1) < pt.z ) // -1 means cpp starts from 0 183 | desc(ring_idx-1, sctor_idx-1) = pt.z; // update for taking maximum value at that bin 184 | } 185 | 186 | // reset no points to zero (for cosine dist later) 187 | for ( int row_idx = 0; row_idx < desc.rows(); row_idx++ ) 188 | for ( int col_idx = 0; col_idx < desc.cols(); col_idx++ ) 189 | if( desc(row_idx, col_idx) == NO_POINT ) 190 | desc(row_idx, col_idx) = 0; 191 | 192 | t_making_desc.toc("PolarContext making"); 193 | 194 | return desc; 195 | } // SCManager::makeScancontext 196 | 197 | 198 | MatrixXd SCManager::makeRingkeyFromScancontext( Eigen::MatrixXd &_desc ) 199 | { 200 | /* 201 | * summary: rowwise mean vector 202 | */ 203 | Eigen::MatrixXd invariant_key(_desc.rows(), 1); 204 | for ( int row_idx = 0; row_idx < _desc.rows(); row_idx++ ) 205 | { 206 | Eigen::MatrixXd curr_row = _desc.row(row_idx); 207 | invariant_key(row_idx, 0) = curr_row.mean(); 208 | } 209 | 210 | return invariant_key; 211 | } // SCManager::makeRingkeyFromScancontext 212 | 213 | 214 | MatrixXd SCManager::makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc ) 215 | { 216 | /* 217 | * summary: columnwise mean vector 218 | */ 219 | Eigen::MatrixXd variant_key(1, _desc.cols()); 220 | for ( int col_idx = 0; col_idx < _desc.cols(); col_idx++ ) 221 | { 222 | Eigen::MatrixXd curr_col = _desc.col(col_idx); 223 | variant_key(0, col_idx) = curr_col.mean(); 224 | } 225 | 226 | return variant_key; 227 | } // SCManager::makeSectorkeyFromScancontext 228 | 229 | 230 | const Eigen::MatrixXd& SCManager::getConstRefRecentSCD(void) 231 | { 232 | return polarcontexts_.back(); 233 | } 234 | 235 | 236 | void SCManager::makeAndSaveScancontextAndKeys( pcl::PointCloud & _scan_down ) 237 | { 238 | Eigen::MatrixXd sc = makeScancontext(_scan_down); // v1 239 | Eigen::MatrixXd ringkey = makeRingkeyFromScancontext( sc ); 240 | Eigen::MatrixXd sectorkey = makeSectorkeyFromScancontext( sc ); 241 | std::vector polarcontext_invkey_vec = eig2stdvec( ringkey ); 242 | 243 | polarcontexts_.push_back( sc ); 244 | polarcontext_invkeys_.push_back( ringkey ); 245 | polarcontext_vkeys_.push_back( sectorkey ); 246 | polarcontext_invkeys_mat_.push_back( polarcontext_invkey_vec ); 247 | 248 | // cout < SCManager::detectLoopClosureID ( void ) 254 | { 255 | int loop_id { -1 }; // init with -1, -1 means no loop (== LeGO-LOAM's variable "closestHistoryFrameID") 256 | 257 | auto curr_key = polarcontext_invkeys_mat_.back(); // current observation (query) 258 | auto curr_desc = polarcontexts_.back(); // current observation (query) 259 | 260 | /* 261 | * step 1: candidates from ringkey tree_ 262 | */ 263 | if( (int)polarcontext_invkeys_mat_.size() < NUM_EXCLUDE_RECENT + 1) 264 | { 265 | std::pair result {loop_id, 0.0}; 266 | return result; // Early return 267 | } 268 | 269 | // tree_ reconstruction (not mandatory to make everytime) 270 | if( tree_making_period_conter % TREE_MAKING_PERIOD_ == 0) // to save computation cost 271 | { 272 | TicToc t_tree_construction; 273 | 274 | polarcontext_invkeys_to_search_.clear(); 275 | polarcontext_invkeys_to_search_.assign( polarcontext_invkeys_mat_.begin(), polarcontext_invkeys_mat_.end() - NUM_EXCLUDE_RECENT ) ; 276 | 277 | polarcontext_tree_.reset(); 278 | polarcontext_tree_ = std::make_unique(PC_NUM_RING /* dim */, polarcontext_invkeys_to_search_, 10 /* max leaf */ ); 279 | // tree_ptr_->index->buildIndex(); // inernally called in the constructor of InvKeyTree (for detail, refer the nanoflann and KDtreeVectorOfVectorsAdaptor) 280 | t_tree_construction.toc("Tree construction"); 281 | } 282 | tree_making_period_conter = tree_making_period_conter + 1; 283 | 284 | double min_dist = 10000000; // init with somthing large 285 | int nn_align = 0; 286 | int nn_idx = 0; 287 | 288 | // knn search 289 | std::vector candidate_indexes( NUM_CANDIDATES_FROM_TREE ); 290 | std::vector out_dists_sqr( NUM_CANDIDATES_FROM_TREE ); 291 | 292 | TicToc t_tree_search; 293 | nanoflann::KNNResultSet knnsearch_result( NUM_CANDIDATES_FROM_TREE ); 294 | knnsearch_result.init( &candidate_indexes[0], &out_dists_sqr[0] ); 295 | polarcontext_tree_->index->findNeighbors( knnsearch_result, &curr_key[0] /* query */, nanoflann::SearchParams(10) ); 296 | t_tree_search.toc("Tree search"); 297 | 298 | /* 299 | * step 2: pairwise distance (find optimal columnwise best-fit using cosine distance) 300 | */ 301 | TicToc t_calc_dist; 302 | for ( int candidate_iter_idx = 0; candidate_iter_idx < NUM_CANDIDATES_FROM_TREE; candidate_iter_idx++ ) 303 | { 304 | MatrixXd polarcontext_candidate = polarcontexts_[ candidate_indexes[candidate_iter_idx] ]; 305 | std::pair sc_dist_result = distanceBtnScanContext( curr_desc, polarcontext_candidate ); 306 | 307 | double candidate_dist = sc_dist_result.first; 308 | int candidate_align = sc_dist_result.second; 309 | 310 | if( candidate_dist < min_dist ) 311 | { 312 | min_dist = candidate_dist; 313 | nn_align = candidate_align; 314 | 315 | nn_idx = candidate_indexes[candidate_iter_idx]; 316 | } 317 | } 318 | t_calc_dist.toc("Distance calc"); 319 | 320 | /* 321 | * loop threshold check 322 | */ 323 | if( min_dist < SC_DIST_THRES ) 324 | { 325 | loop_id = nn_idx; 326 | 327 | // std::cout.precision(3); 328 | cout << "[Loop found] Nearest distance: " << min_dist << " btn " << polarcontexts_.size()-1 << " and " << nn_idx << "." << endl; 329 | cout << "[Loop found] yaw diff: " << nn_align * PC_UNIT_SECTORANGLE << " deg." << endl; 330 | } 331 | else 332 | { 333 | std::cout.precision(3); 334 | cout << "[Not loop] Nearest distance: " << min_dist << " btn " << polarcontexts_.size()-1 << " and " << nn_idx << "." << endl; 335 | cout << "[Not loop] yaw diff: " << nn_align * PC_UNIT_SECTORANGLE << " deg." << endl; 336 | } 337 | 338 | // To do: return also nn_align (i.e., yaw diff) 339 | float yaw_diff_rad = deg2rad(nn_align * PC_UNIT_SECTORANGLE); 340 | std::pair result {loop_id, yaw_diff_rad}; 341 | 342 | return result; 343 | 344 | } // SCManager::detectLoopClosureID 345 | 346 | // } // namespace SC2 -------------------------------------------------------------------------------- /SC-LIO-SAM/src/featureExtraction.cpp: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | #include "lio_sam/cloud_info.h" 3 | 4 | struct smoothness_t{ 5 | float value; 6 | size_t ind; 7 | }; 8 | 9 | struct by_value{ 10 | bool operator()(smoothness_t const &left, smoothness_t const &right) { 11 | return left.value < right.value; 12 | } 13 | }; 14 | 15 | class FeatureExtraction : public ParamServer 16 | { 17 | 18 | public: 19 | 20 | ros::Subscriber subLaserCloudInfo; 21 | 22 | ros::Publisher pubLaserCloudInfo; 23 | ros::Publisher pubCornerPoints; 24 | ros::Publisher pubSurfacePoints; 25 | 26 | pcl::PointCloud::Ptr extractedCloud; 27 | pcl::PointCloud::Ptr cornerCloud; 28 | pcl::PointCloud::Ptr surfaceCloud; 29 | 30 | pcl::VoxelGrid downSizeFilter; 31 | 32 | lio_sam::cloud_info cloudInfo; 33 | std_msgs::Header cloudHeader; 34 | 35 | std::vector cloudSmoothness; 36 | float *cloudCurvature; 37 | int *cloudNeighborPicked; 38 | int *cloudLabel; 39 | 40 | FeatureExtraction() 41 | { 42 | subLaserCloudInfo = nh.subscribe("lio_sam/deskew/cloud_info", 1, &FeatureExtraction::laserCloudInfoHandler, this, ros::TransportHints().tcpNoDelay()); 43 | 44 | pubLaserCloudInfo = nh.advertise ("lio_sam/feature/cloud_info", 1); 45 | pubCornerPoints = nh.advertise("lio_sam/feature/cloud_corner", 1); 46 | pubSurfacePoints = nh.advertise("lio_sam/feature/cloud_surface", 1); 47 | 48 | initializationValue(); 49 | } 50 | 51 | void initializationValue() 52 | { 53 | cloudSmoothness.resize(N_SCAN*Horizon_SCAN); 54 | 55 | downSizeFilter.setLeafSize(odometrySurfLeafSize, odometrySurfLeafSize, odometrySurfLeafSize); 56 | 57 | extractedCloud.reset(new pcl::PointCloud()); 58 | cornerCloud.reset(new pcl::PointCloud()); 59 | surfaceCloud.reset(new pcl::PointCloud()); 60 | 61 | cloudCurvature = new float[N_SCAN*Horizon_SCAN]; 62 | cloudNeighborPicked = new int[N_SCAN*Horizon_SCAN]; 63 | cloudLabel = new int[N_SCAN*Horizon_SCAN]; 64 | } 65 | 66 | void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr& msgIn) 67 | { 68 | cloudInfo = *msgIn; // new cloud info 69 | cloudHeader = msgIn->header; // new cloud header 70 | pcl::fromROSMsg(msgIn->cloud_deskewed, *extractedCloud); // new cloud for extraction 71 | 72 | calculateSmoothness(); 73 | 74 | markOccludedPoints(); 75 | 76 | extractFeatures(); 77 | 78 | publishFeatureCloud(); 79 | } 80 | 81 | void calculateSmoothness() 82 | { 83 | int cloudSize = extractedCloud->points.size(); 84 | for (int i = 5; i < cloudSize - 5; i++) 85 | { 86 | float diffRange = cloudInfo.pointRange[i-5] + cloudInfo.pointRange[i-4] 87 | + cloudInfo.pointRange[i-3] + cloudInfo.pointRange[i-2] 88 | + cloudInfo.pointRange[i-1] - cloudInfo.pointRange[i] * 10 89 | + cloudInfo.pointRange[i+1] + cloudInfo.pointRange[i+2] 90 | + cloudInfo.pointRange[i+3] + cloudInfo.pointRange[i+4] 91 | + cloudInfo.pointRange[i+5]; 92 | 93 | cloudCurvature[i] = diffRange*diffRange;//diffX * diffX + diffY * diffY + diffZ * diffZ; 94 | 95 | cloudNeighborPicked[i] = 0; 96 | cloudLabel[i] = 0; 97 | // cloudSmoothness for sorting 98 | cloudSmoothness[i].value = cloudCurvature[i]; 99 | cloudSmoothness[i].ind = i; 100 | } 101 | } 102 | 103 | void markOccludedPoints() 104 | { 105 | int cloudSize = extractedCloud->points.size(); 106 | // mark occluded points and parallel beam points 107 | for (int i = 5; i < cloudSize - 6; ++i) 108 | { 109 | // occluded points 110 | float depth1 = cloudInfo.pointRange[i]; 111 | float depth2 = cloudInfo.pointRange[i+1]; 112 | int columnDiff = std::abs(int(cloudInfo.pointColInd[i+1] - cloudInfo.pointColInd[i])); 113 | 114 | if (columnDiff < 10){ 115 | // 10 pixel diff in range image 116 | if (depth1 - depth2 > 0.3){ 117 | cloudNeighborPicked[i - 5] = 1; 118 | cloudNeighborPicked[i - 4] = 1; 119 | cloudNeighborPicked[i - 3] = 1; 120 | cloudNeighborPicked[i - 2] = 1; 121 | cloudNeighborPicked[i - 1] = 1; 122 | cloudNeighborPicked[i] = 1; 123 | }else if (depth2 - depth1 > 0.3){ 124 | cloudNeighborPicked[i + 1] = 1; 125 | cloudNeighborPicked[i + 2] = 1; 126 | cloudNeighborPicked[i + 3] = 1; 127 | cloudNeighborPicked[i + 4] = 1; 128 | cloudNeighborPicked[i + 5] = 1; 129 | cloudNeighborPicked[i + 6] = 1; 130 | } 131 | } 132 | // parallel beam 133 | float diff1 = std::abs(float(cloudInfo.pointRange[i-1] - cloudInfo.pointRange[i])); 134 | float diff2 = std::abs(float(cloudInfo.pointRange[i+1] - cloudInfo.pointRange[i])); 135 | 136 | if (diff1 > 0.02 * cloudInfo.pointRange[i] && diff2 > 0.02 * cloudInfo.pointRange[i]) 137 | cloudNeighborPicked[i] = 1; 138 | } 139 | } 140 | 141 | void extractFeatures() 142 | { 143 | cornerCloud->clear(); 144 | surfaceCloud->clear(); 145 | 146 | pcl::PointCloud::Ptr surfaceCloudScan(new pcl::PointCloud()); 147 | pcl::PointCloud::Ptr surfaceCloudScanDS(new pcl::PointCloud()); 148 | 149 | for (int i = 0; i < N_SCAN; i++) 150 | { 151 | surfaceCloudScan->clear(); 152 | 153 | for (int j = 0; j < 6; j++) 154 | { 155 | 156 | int sp = (cloudInfo.startRingIndex[i] * (6 - j) + cloudInfo.endRingIndex[i] * j) / 6; 157 | int ep = (cloudInfo.startRingIndex[i] * (5 - j) + cloudInfo.endRingIndex[i] * (j + 1)) / 6 - 1; 158 | 159 | if (sp >= ep) 160 | continue; 161 | 162 | std::sort(cloudSmoothness.begin()+sp, cloudSmoothness.begin()+ep, by_value()); 163 | 164 | int largestPickedNum = 0; 165 | for (int k = ep; k >= sp; k--) 166 | { 167 | int ind = cloudSmoothness[k].ind; 168 | if (cloudNeighborPicked[ind] == 0 && cloudCurvature[ind] > edgeThreshold) 169 | { 170 | largestPickedNum++; 171 | if (largestPickedNum <= 20){ 172 | cloudLabel[ind] = 1; 173 | cornerCloud->push_back(extractedCloud->points[ind]); 174 | } else { 175 | break; 176 | } 177 | 178 | cloudNeighborPicked[ind] = 1; 179 | for (int l = 1; l <= 5; l++) 180 | { 181 | int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l - 1])); 182 | if (columnDiff > 10) 183 | break; 184 | cloudNeighborPicked[ind + l] = 1; 185 | } 186 | for (int l = -1; l >= -5; l--) 187 | { 188 | int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l + 1])); 189 | if (columnDiff > 10) 190 | break; 191 | cloudNeighborPicked[ind + l] = 1; 192 | } 193 | } 194 | } 195 | 196 | for (int k = sp; k <= ep; k++) 197 | { 198 | int ind = cloudSmoothness[k].ind; 199 | if (cloudNeighborPicked[ind] == 0 && cloudCurvature[ind] < surfThreshold) 200 | { 201 | 202 | cloudLabel[ind] = -1; 203 | cloudNeighborPicked[ind] = 1; 204 | 205 | for (int l = 1; l <= 5; l++) { 206 | 207 | int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l - 1])); 208 | if (columnDiff > 10) 209 | break; 210 | 211 | cloudNeighborPicked[ind + l] = 1; 212 | } 213 | for (int l = -1; l >= -5; l--) { 214 | 215 | int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l + 1])); 216 | if (columnDiff > 10) 217 | break; 218 | 219 | cloudNeighborPicked[ind + l] = 1; 220 | } 221 | } 222 | } 223 | 224 | for (int k = sp; k <= ep; k++) 225 | { 226 | if (cloudLabel[k] <= 0){ 227 | surfaceCloudScan->push_back(extractedCloud->points[k]); 228 | } 229 | } 230 | } 231 | 232 | surfaceCloudScanDS->clear(); 233 | downSizeFilter.setInputCloud(surfaceCloudScan); 234 | downSizeFilter.filter(*surfaceCloudScanDS); 235 | 236 | *surfaceCloud += *surfaceCloudScanDS; 237 | } 238 | } 239 | 240 | void freeCloudInfoMemory() 241 | { 242 | cloudInfo.startRingIndex.clear(); 243 | cloudInfo.endRingIndex.clear(); 244 | cloudInfo.pointColInd.clear(); 245 | cloudInfo.pointRange.clear(); 246 | } 247 | 248 | void publishFeatureCloud() 249 | { 250 | // free cloud info memory 251 | freeCloudInfoMemory(); 252 | // save newly extracted features 253 | cloudInfo.cloud_corner = publishCloud(&pubCornerPoints, cornerCloud, cloudHeader.stamp, lidarFrame); 254 | cloudInfo.cloud_surface = publishCloud(&pubSurfacePoints, surfaceCloud, cloudHeader.stamp, lidarFrame); 255 | // publish to mapOptimization 256 | pubLaserCloudInfo.publish(cloudInfo); 257 | } 258 | }; 259 | 260 | 261 | int main(int argc, char** argv) 262 | { 263 | ros::init(argc, argv, "lio_sam"); 264 | 265 | FeatureExtraction FE; 266 | 267 | ROS_INFO("\033[1;32m----> Feature Extraction Started.\033[0m"); 268 | 269 | ros::spin(); 270 | 271 | return 0; 272 | } -------------------------------------------------------------------------------- /SC-LIO-SAM/src/imageProjection.cpp: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | #include "lio_sam/cloud_info.h" 3 | 4 | struct VelodynePointXYZIRT 5 | { 6 | PCL_ADD_POINT4D 7 | PCL_ADD_INTENSITY; 8 | uint16_t ring; 9 | float time; 10 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 11 | } EIGEN_ALIGN16; 12 | POINT_CLOUD_REGISTER_POINT_STRUCT (VelodynePointXYZIRT, 13 | (float, x, x) (float, y, y) (float, z, z) (float, intensity, intensity) 14 | (uint16_t, ring, ring) (float, time, time) 15 | ) 16 | 17 | struct OusterPointXYZIRT { 18 | PCL_ADD_POINT4D; 19 | float intensity; 20 | uint32_t t; 21 | uint16_t reflectivity; 22 | uint8_t ring; 23 | uint16_t noise; 24 | uint32_t range; 25 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 26 | } EIGEN_ALIGN16; 27 | POINT_CLOUD_REGISTER_POINT_STRUCT(OusterPointXYZIRT, 28 | (float, x, x) (float, y, y) (float, z, z) (float, intensity, intensity) 29 | (uint32_t, t, t) (uint16_t, reflectivity, reflectivity) 30 | (uint8_t, ring, ring) (uint16_t, noise, noise) (uint32_t, range, range) 31 | ) 32 | 33 | struct MulranPointXYZIRT { // from the file player's topic https://github.com/irapkaist/file_player_mulran, see https://github.com/irapkaist/file_player_mulran/blob/17da0cb6ef66b4971ec943ab8d234aa25da33e7e/src/ROSThread.cpp#L7 34 | PCL_ADD_POINT4D; 35 | float intensity; 36 | uint32_t t; 37 | int ring; 38 | 39 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 40 | }EIGEN_ALIGN16; 41 | POINT_CLOUD_REGISTER_POINT_STRUCT (MulranPointXYZIRT, 42 | (float, x, x) (float, y, y) (float, z, z) (float, intensity, intensity) 43 | (uint32_t, t, t) (int, ring, ring) 44 | ) 45 | 46 | // Use the Velodyne point format as a common representation 47 | using PointXYZIRT = VelodynePointXYZIRT; 48 | 49 | const int queueLength = 2000; 50 | 51 | class ImageProjection : public ParamServer 52 | { 53 | private: 54 | 55 | std::mutex imuLock; 56 | std::mutex odoLock; 57 | 58 | ros::Subscriber subLaserCloud; 59 | ros::Publisher pubLaserCloud; 60 | 61 | ros::Publisher pubExtractedCloud; 62 | ros::Publisher pubLaserCloudInfo; 63 | 64 | ros::Subscriber subImu; 65 | std::deque imuQueue; 66 | 67 | ros::Subscriber subOdom; 68 | std::deque odomQueue; 69 | 70 | std::deque cloudQueue; 71 | sensor_msgs::PointCloud2 currentCloudMsg; 72 | 73 | double *imuTime = new double[queueLength]; 74 | double *imuRotX = new double[queueLength]; 75 | double *imuRotY = new double[queueLength]; 76 | double *imuRotZ = new double[queueLength]; 77 | 78 | int imuPointerCur; 79 | bool firstPointFlag; 80 | Eigen::Affine3f transStartInverse; 81 | 82 | pcl::PointCloud::Ptr laserCloudIn; 83 | pcl::PointCloud::Ptr tmpOusterCloudIn; 84 | pcl::PointCloud::Ptr tmpMulranCloudIn; 85 | pcl::PointCloud::Ptr fullCloud; 86 | pcl::PointCloud::Ptr extractedCloud; 87 | 88 | int deskewFlag; 89 | cv::Mat rangeMat; 90 | 91 | bool odomDeskewFlag; 92 | float odomIncreX; 93 | float odomIncreY; 94 | float odomIncreZ; 95 | 96 | lio_sam::cloud_info cloudInfo; 97 | double timeScanCur; 98 | double timeScanEnd; 99 | std_msgs::Header cloudHeader; 100 | 101 | 102 | public: 103 | ImageProjection(): 104 | deskewFlag(0) 105 | { 106 | subImu = nh.subscribe(imuTopic, 2000, &ImageProjection::imuHandler, this, ros::TransportHints().tcpNoDelay()); 107 | subOdom = nh.subscribe(odomTopic+"_incremental", 2000, &ImageProjection::odometryHandler, this, ros::TransportHints().tcpNoDelay()); 108 | subLaserCloud = nh.subscribe(pointCloudTopic, 5, &ImageProjection::cloudHandler, this, ros::TransportHints().tcpNoDelay()); 109 | 110 | pubExtractedCloud = nh.advertise ("lio_sam/deskew/cloud_deskewed", 1); 111 | pubLaserCloudInfo = nh.advertise ("lio_sam/deskew/cloud_info", 1); 112 | 113 | allocateMemory(); 114 | resetParameters(); 115 | 116 | pcl::console::setVerbosityLevel(pcl::console::L_ERROR); 117 | } 118 | 119 | void allocateMemory() 120 | { 121 | laserCloudIn.reset(new pcl::PointCloud()); 122 | tmpOusterCloudIn.reset(new pcl::PointCloud()); 123 | tmpMulranCloudIn.reset(new pcl::PointCloud()); 124 | fullCloud.reset(new pcl::PointCloud()); 125 | extractedCloud.reset(new pcl::PointCloud()); 126 | 127 | fullCloud->points.resize(N_SCAN*Horizon_SCAN); 128 | 129 | cloudInfo.startRingIndex.assign(N_SCAN, 0); 130 | cloudInfo.endRingIndex.assign(N_SCAN, 0); 131 | 132 | cloudInfo.pointColInd.assign(N_SCAN*Horizon_SCAN, 0); 133 | cloudInfo.pointRange.assign(N_SCAN*Horizon_SCAN, 0); 134 | 135 | resetParameters(); 136 | } 137 | 138 | void resetParameters() 139 | { 140 | laserCloudIn->clear(); 141 | extractedCloud->clear(); 142 | // reset range matrix for range image projection 143 | rangeMat = cv::Mat(N_SCAN, Horizon_SCAN, CV_32F, cv::Scalar::all(FLT_MAX)); 144 | 145 | imuPointerCur = 0; 146 | firstPointFlag = true; 147 | odomDeskewFlag = false; 148 | 149 | for (int i = 0; i < queueLength; ++i) 150 | { 151 | imuTime[i] = 0; 152 | imuRotX[i] = 0; 153 | imuRotY[i] = 0; 154 | imuRotZ[i] = 0; 155 | } 156 | } 157 | 158 | ~ImageProjection(){} 159 | 160 | void imuHandler(const sensor_msgs::Imu::ConstPtr& imuMsg) 161 | { 162 | sensor_msgs::Imu thisImu = imuConverter(*imuMsg); 163 | 164 | std::lock_guard lock1(imuLock); 165 | imuQueue.push_back(thisImu); 166 | 167 | // debug IMU data 168 | // cout << std::setprecision(6); 169 | // cout << "IMU acc: " << endl; 170 | // cout << "x: " << thisImu.linear_acceleration.x << 171 | // ", y: " << thisImu.linear_acceleration.y << 172 | // ", z: " << thisImu.linear_acceleration.z << endl; 173 | // cout << "IMU gyro: " << endl; 174 | // cout << "x: " << thisImu.angular_velocity.x << 175 | // ", y: " << thisImu.angular_velocity.y << 176 | // ", z: " << thisImu.angular_velocity.z << endl; 177 | // double imuRoll, imuPitch, imuYaw; 178 | // tf::Quaternion orientation; 179 | // tf::quaternionMsgToTF(thisImu.orientation, orientation); 180 | // tf::Matrix3x3(orientation).getRPY(imuRoll, imuPitch, imuYaw); 181 | // cout << "IMU roll pitch yaw: " << endl; 182 | // cout << "roll: " << imuRoll << ", pitch: " << imuPitch << ", yaw: " << imuYaw << endl << endl; 183 | } 184 | 185 | void odometryHandler(const nav_msgs::Odometry::ConstPtr& odometryMsg) 186 | { 187 | std::lock_guard lock2(odoLock); 188 | odomQueue.push_back(*odometryMsg); 189 | } 190 | 191 | void cloudHandler(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg) 192 | { 193 | if (!cachePointCloud(laserCloudMsg)) 194 | return; 195 | 196 | if (!deskewInfo()) 197 | return; 198 | 199 | projectPointCloud(); 200 | 201 | cloudExtraction(); 202 | 203 | publishClouds(); 204 | 205 | resetParameters(); 206 | } 207 | 208 | bool cachePointCloud(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg) 209 | { 210 | // cache point cloud 211 | cloudQueue.push_back(*laserCloudMsg); 212 | if (cloudQueue.size() <= 2) 213 | return false; 214 | 215 | // convert cloud 216 | currentCloudMsg = std::move(cloudQueue.front()); 217 | 218 | cloudHeader = currentCloudMsg.header; 219 | timeScanCur = cloudHeader.stamp.toSec(); 220 | 221 | cloudQueue.pop_front(); 222 | 223 | if (sensor == SensorType::VELODYNE) 224 | { 225 | pcl::moveFromROSMsg(currentCloudMsg, *laserCloudIn); 226 | } 227 | else if (sensor == SensorType::OUSTER) 228 | { 229 | // Convert to Velodyne format 230 | pcl::moveFromROSMsg(currentCloudMsg, *tmpOusterCloudIn); 231 | laserCloudIn->points.resize(tmpOusterCloudIn->size()); 232 | laserCloudIn->is_dense = tmpOusterCloudIn->is_dense; 233 | for (size_t i = 0; i < tmpOusterCloudIn->size(); i++) 234 | { 235 | auto &src = tmpOusterCloudIn->points[i]; 236 | auto &dst = laserCloudIn->points[i]; 237 | dst.x = src.x; 238 | dst.y = src.y; 239 | dst.z = src.z; 240 | dst.intensity = src.intensity; 241 | dst.ring = src.ring; 242 | dst.time = src.t * 1e-9f; 243 | } 244 | } 245 | else if (sensor == SensorType::MULRAN) 246 | { 247 | // Convert to Velodyne format 248 | pcl::moveFromROSMsg(currentCloudMsg, *tmpMulranCloudIn); 249 | laserCloudIn->points.resize(tmpMulranCloudIn->size()); 250 | laserCloudIn->is_dense = tmpMulranCloudIn->is_dense; 251 | for (size_t i = 0; i < tmpMulranCloudIn->size(); i++) 252 | { 253 | auto &src = tmpMulranCloudIn->points[i]; 254 | auto &dst = laserCloudIn->points[i]; 255 | dst.x = src.x; 256 | dst.y = src.y; 257 | dst.z = src.z; 258 | dst.intensity = src.intensity; 259 | dst.ring = src.ring; 260 | dst.time = float(src.t); 261 | } 262 | } 263 | else 264 | { 265 | ROS_ERROR_STREAM("Unknown sensor type: " << int(sensor)); 266 | ros::shutdown(); 267 | } 268 | 269 | // get timestamp 270 | timeScanEnd = timeScanCur + laserCloudIn->points.back().time; 271 | 272 | // check dense flag 273 | if (laserCloudIn->is_dense == false) 274 | { 275 | ROS_ERROR("Point cloud is not in dense format, please remove NaN points first!"); 276 | ros::shutdown(); 277 | } 278 | 279 | // check ring channel 280 | static int ringFlag = 0; 281 | if (ringFlag == 0) 282 | { 283 | ringFlag = -1; 284 | for (int i = 0; i < (int)currentCloudMsg.fields.size(); ++i) 285 | { 286 | if (currentCloudMsg.fields[i].name == "ring") 287 | { 288 | ringFlag = 1; 289 | break; 290 | } 291 | } 292 | if (ringFlag == -1) 293 | { 294 | ROS_ERROR("Point cloud ring channel not available, please configure your point cloud data!"); 295 | ros::shutdown(); 296 | } 297 | } 298 | 299 | // check point time 300 | if (deskewFlag == 0) 301 | { 302 | deskewFlag = -1; 303 | for (auto &field : currentCloudMsg.fields) 304 | { 305 | if (field.name == "time" || field.name == "t") 306 | { 307 | deskewFlag = 1; 308 | break; 309 | } 310 | } 311 | if (deskewFlag == -1) 312 | ROS_WARN("Point cloud timestamp not available, deskew function disabled, system will drift significantly!"); 313 | } 314 | 315 | return true; 316 | } 317 | 318 | bool deskewInfo() 319 | { 320 | std::lock_guard lock1(imuLock); 321 | std::lock_guard lock2(odoLock); 322 | 323 | // make sure IMU data available for the scan 324 | if (imuQueue.empty() || imuQueue.front().header.stamp.toSec() > timeScanCur || imuQueue.back().header.stamp.toSec() < timeScanEnd) 325 | { 326 | ROS_DEBUG("Waiting for IMU data ..."); 327 | return false; 328 | } 329 | 330 | imuDeskewInfo(); 331 | 332 | odomDeskewInfo(); 333 | 334 | return true; 335 | } 336 | 337 | void imuDeskewInfo() 338 | { 339 | cloudInfo.imuAvailable = false; 340 | 341 | while (!imuQueue.empty()) 342 | { 343 | if (imuQueue.front().header.stamp.toSec() < timeScanCur - 0.01) 344 | imuQueue.pop_front(); 345 | else 346 | break; 347 | } 348 | 349 | if (imuQueue.empty()) 350 | return; 351 | 352 | imuPointerCur = 0; 353 | 354 | for (int i = 0; i < (int)imuQueue.size(); ++i) 355 | { 356 | sensor_msgs::Imu thisImuMsg = imuQueue[i]; 357 | double currentImuTime = thisImuMsg.header.stamp.toSec(); 358 | 359 | // get roll, pitch, and yaw estimation for this scan 360 | if (currentImuTime <= timeScanCur) 361 | imuRPY2rosRPY(&thisImuMsg, &cloudInfo.imuRollInit, &cloudInfo.imuPitchInit, &cloudInfo.imuYawInit); 362 | 363 | if (currentImuTime > timeScanEnd + 0.01) 364 | break; 365 | 366 | if (imuPointerCur == 0){ 367 | imuRotX[0] = 0; 368 | imuRotY[0] = 0; 369 | imuRotZ[0] = 0; 370 | imuTime[0] = currentImuTime; 371 | ++imuPointerCur; 372 | continue; 373 | } 374 | 375 | // get angular velocity 376 | double angular_x, angular_y, angular_z; 377 | imuAngular2rosAngular(&thisImuMsg, &angular_x, &angular_y, &angular_z); 378 | 379 | // integrate rotation 380 | double timeDiff = currentImuTime - imuTime[imuPointerCur-1]; 381 | imuRotX[imuPointerCur] = imuRotX[imuPointerCur-1] + angular_x * timeDiff; 382 | imuRotY[imuPointerCur] = imuRotY[imuPointerCur-1] + angular_y * timeDiff; 383 | imuRotZ[imuPointerCur] = imuRotZ[imuPointerCur-1] + angular_z * timeDiff; 384 | imuTime[imuPointerCur] = currentImuTime; 385 | ++imuPointerCur; 386 | } 387 | 388 | --imuPointerCur; 389 | 390 | if (imuPointerCur <= 0) 391 | return; 392 | 393 | cloudInfo.imuAvailable = true; 394 | } 395 | 396 | void odomDeskewInfo() 397 | { 398 | cloudInfo.odomAvailable = false; 399 | 400 | while (!odomQueue.empty()) 401 | { 402 | if (odomQueue.front().header.stamp.toSec() < timeScanCur - 0.01) 403 | odomQueue.pop_front(); 404 | else 405 | break; 406 | } 407 | 408 | if (odomQueue.empty()) 409 | return; 410 | 411 | if (odomQueue.front().header.stamp.toSec() > timeScanCur) 412 | return; 413 | 414 | // get start odometry at the beinning of the scan 415 | nav_msgs::Odometry startOdomMsg; 416 | 417 | for (int i = 0; i < (int)odomQueue.size(); ++i) 418 | { 419 | startOdomMsg = odomQueue[i]; 420 | 421 | if (ROS_TIME(&startOdomMsg) < timeScanCur) 422 | continue; 423 | else 424 | break; 425 | } 426 | 427 | tf::Quaternion orientation; 428 | tf::quaternionMsgToTF(startOdomMsg.pose.pose.orientation, orientation); 429 | 430 | double roll, pitch, yaw; 431 | tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw); 432 | 433 | // Initial guess used in mapOptimization 434 | cloudInfo.initialGuessX = startOdomMsg.pose.pose.position.x; 435 | cloudInfo.initialGuessY = startOdomMsg.pose.pose.position.y; 436 | cloudInfo.initialGuessZ = startOdomMsg.pose.pose.position.z; 437 | cloudInfo.initialGuessRoll = roll; 438 | cloudInfo.initialGuessPitch = pitch; 439 | cloudInfo.initialGuessYaw = yaw; 440 | 441 | cloudInfo.odomAvailable = true; 442 | 443 | // get end odometry at the end of the scan 444 | odomDeskewFlag = false; 445 | 446 | if (odomQueue.back().header.stamp.toSec() < timeScanEnd) 447 | return; 448 | 449 | nav_msgs::Odometry endOdomMsg; 450 | 451 | for (int i = 0; i < (int)odomQueue.size(); ++i) 452 | { 453 | endOdomMsg = odomQueue[i]; 454 | 455 | if (ROS_TIME(&endOdomMsg) < timeScanEnd) 456 | continue; 457 | else 458 | break; 459 | } 460 | 461 | if (int(round(startOdomMsg.pose.covariance[0])) != int(round(endOdomMsg.pose.covariance[0]))) 462 | return; 463 | 464 | Eigen::Affine3f transBegin = pcl::getTransformation(startOdomMsg.pose.pose.position.x, startOdomMsg.pose.pose.position.y, startOdomMsg.pose.pose.position.z, roll, pitch, yaw); 465 | 466 | tf::quaternionMsgToTF(endOdomMsg.pose.pose.orientation, orientation); 467 | tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw); 468 | Eigen::Affine3f transEnd = pcl::getTransformation(endOdomMsg.pose.pose.position.x, endOdomMsg.pose.pose.position.y, endOdomMsg.pose.pose.position.z, roll, pitch, yaw); 469 | 470 | Eigen::Affine3f transBt = transBegin.inverse() * transEnd; 471 | 472 | float rollIncre, pitchIncre, yawIncre; 473 | pcl::getTranslationAndEulerAngles(transBt, odomIncreX, odomIncreY, odomIncreZ, rollIncre, pitchIncre, yawIncre); 474 | 475 | odomDeskewFlag = true; 476 | } 477 | 478 | void findRotation(double pointTime, float *rotXCur, float *rotYCur, float *rotZCur) 479 | { 480 | *rotXCur = 0; *rotYCur = 0; *rotZCur = 0; 481 | 482 | int imuPointerFront = 0; 483 | while (imuPointerFront < imuPointerCur) 484 | { 485 | if (pointTime < imuTime[imuPointerFront]) 486 | break; 487 | ++imuPointerFront; 488 | } 489 | 490 | if (pointTime > imuTime[imuPointerFront] || imuPointerFront == 0) 491 | { 492 | *rotXCur = imuRotX[imuPointerFront]; 493 | *rotYCur = imuRotY[imuPointerFront]; 494 | *rotZCur = imuRotZ[imuPointerFront]; 495 | } else { 496 | int imuPointerBack = imuPointerFront - 1; 497 | double ratioFront = (pointTime - imuTime[imuPointerBack]) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]); 498 | double ratioBack = (imuTime[imuPointerFront] - pointTime) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]); 499 | *rotXCur = imuRotX[imuPointerFront] * ratioFront + imuRotX[imuPointerBack] * ratioBack; 500 | *rotYCur = imuRotY[imuPointerFront] * ratioFront + imuRotY[imuPointerBack] * ratioBack; 501 | *rotZCur = imuRotZ[imuPointerFront] * ratioFront + imuRotZ[imuPointerBack] * ratioBack; 502 | } 503 | } 504 | 505 | void findPosition(double relTime, float *posXCur, float *posYCur, float *posZCur) 506 | { 507 | *posXCur = 0; *posYCur = 0; *posZCur = 0; 508 | 509 | // If the sensor moves relatively slow, like walking speed, positional deskew seems to have little benefits. Thus code below is commented. 510 | 511 | // if (cloudInfo.odomAvailable == false || odomDeskewFlag == false) 512 | // return; 513 | 514 | // float ratio = relTime / (timeScanEnd - timeScanCur); 515 | 516 | // *posXCur = ratio * odomIncreX; 517 | // *posYCur = ratio * odomIncreY; 518 | // *posZCur = ratio * odomIncreZ; 519 | } 520 | 521 | PointType deskewPoint(PointType *point, double relTime) 522 | { 523 | if (deskewFlag == -1 || cloudInfo.imuAvailable == false) 524 | return *point; 525 | 526 | double pointTime = timeScanCur + relTime; 527 | 528 | float rotXCur, rotYCur, rotZCur; 529 | findRotation(pointTime, &rotXCur, &rotYCur, &rotZCur); 530 | 531 | float posXCur, posYCur, posZCur; 532 | findPosition(relTime, &posXCur, &posYCur, &posZCur); 533 | 534 | if (firstPointFlag == true) 535 | { 536 | transStartInverse = (pcl::getTransformation(posXCur, posYCur, posZCur, rotXCur, rotYCur, rotZCur)).inverse(); 537 | firstPointFlag = false; 538 | } 539 | 540 | // transform points to start 541 | Eigen::Affine3f transFinal = pcl::getTransformation(posXCur, posYCur, posZCur, rotXCur, rotYCur, rotZCur); 542 | Eigen::Affine3f transBt = transStartInverse * transFinal; 543 | 544 | PointType newPoint; 545 | newPoint.x = transBt(0,0) * point->x + transBt(0,1) * point->y + transBt(0,2) * point->z + transBt(0,3); 546 | newPoint.y = transBt(1,0) * point->x + transBt(1,1) * point->y + transBt(1,2) * point->z + transBt(1,3); 547 | newPoint.z = transBt(2,0) * point->x + transBt(2,1) * point->y + transBt(2,2) * point->z + transBt(2,3); 548 | newPoint.intensity = point->intensity; 549 | 550 | return newPoint; 551 | } 552 | 553 | void projectPointCloud() 554 | { 555 | int cloudSize = laserCloudIn->points.size(); 556 | // range image projection 557 | for (int i = 0; i < cloudSize; ++i) 558 | { 559 | PointType thisPoint; 560 | thisPoint.x = laserCloudIn->points[i].x; 561 | thisPoint.y = laserCloudIn->points[i].y; 562 | thisPoint.z = laserCloudIn->points[i].z; 563 | thisPoint.intensity = laserCloudIn->points[i].intensity; 564 | 565 | float range = pointDistance(thisPoint); 566 | if (range < lidarMinRange || range > lidarMaxRange) 567 | continue; 568 | 569 | int rowIdn = laserCloudIn->points[i].ring; 570 | // int rowIdn = (i % 64) + 1 ; // for MulRan dataset, Ouster OS1-64 .bin file, giseop 571 | 572 | if (rowIdn < 0 || rowIdn >= N_SCAN) 573 | continue; 574 | 575 | if (rowIdn % downsampleRate != 0) 576 | continue; 577 | 578 | float horizonAngle = atan2(thisPoint.x, thisPoint.y) * 180 / M_PI; 579 | 580 | static float ang_res_x = 360.0/float(Horizon_SCAN); 581 | int columnIdn = -round((horizonAngle-90.0)/ang_res_x) + Horizon_SCAN/2; 582 | if (columnIdn >= Horizon_SCAN) 583 | columnIdn -= Horizon_SCAN; 584 | 585 | if (columnIdn < 0 || columnIdn >= Horizon_SCAN) 586 | continue; 587 | 588 | if (rangeMat.at(rowIdn, columnIdn) != FLT_MAX) 589 | continue; 590 | 591 | thisPoint = deskewPoint(&thisPoint, laserCloudIn->points[i].time); 592 | 593 | rangeMat.at(rowIdn, columnIdn) = range; 594 | 595 | int index = columnIdn + rowIdn * Horizon_SCAN; 596 | fullCloud->points[index] = thisPoint; 597 | } 598 | } 599 | 600 | void cloudExtraction() 601 | { 602 | int count = 0; 603 | // extract segmented cloud for lidar odometry 604 | for (int i = 0; i < N_SCAN; ++i) 605 | { 606 | cloudInfo.startRingIndex[i] = count - 1 + 5; 607 | 608 | for (int j = 0; j < Horizon_SCAN; ++j) 609 | { 610 | if (rangeMat.at(i,j) != FLT_MAX) 611 | { 612 | // mark the points' column index for marking occlusion later 613 | cloudInfo.pointColInd[count] = j; 614 | // save range info 615 | cloudInfo.pointRange[count] = rangeMat.at(i,j); 616 | // save extracted cloud 617 | extractedCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]); 618 | // size of extracted cloud 619 | ++count; 620 | } 621 | } 622 | cloudInfo.endRingIndex[i] = count -1 - 5; 623 | } 624 | } 625 | 626 | void publishClouds() 627 | { 628 | cloudInfo.header = cloudHeader; 629 | cloudInfo.cloud_deskewed = publishCloud(&pubExtractedCloud, extractedCloud, cloudHeader.stamp, lidarFrame); 630 | pubLaserCloudInfo.publish(cloudInfo); 631 | } 632 | }; 633 | 634 | int main(int argc, char** argv) 635 | { 636 | ros::init(argc, argv, "lio_sam"); 637 | 638 | ImageProjection IP; 639 | 640 | ROS_INFO("\033[1;32m----> Image Projection Started.\033[0m"); 641 | 642 | ros::MultiThreadedSpinner spinner(3); 643 | spinner.spin(); 644 | 645 | return 0; 646 | } 647 | -------------------------------------------------------------------------------- /SC-LIO-SAM/src/imuPreintegration.cpp: -------------------------------------------------------------------------------- 1 | #include "utility.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | using gtsam::symbol_shorthand::X; // Pose3 (x,y,z,r,p,y) 20 | using gtsam::symbol_shorthand::V; // Vel (xdot,ydot,zdot) 21 | using gtsam::symbol_shorthand::B; // Bias (ax,ay,az,gx,gy,gz) 22 | 23 | class TransformFusion : public ParamServer 24 | { 25 | public: 26 | std::mutex mtx; 27 | 28 | ros::Subscriber subImuOdometry; 29 | ros::Subscriber subLaserOdometry; 30 | 31 | ros::Publisher pubImuOdometry; 32 | ros::Publisher pubImuPath; 33 | 34 | Eigen::Affine3f lidarOdomAffine; 35 | Eigen::Affine3f imuOdomAffineFront; 36 | Eigen::Affine3f imuOdomAffineBack; 37 | 38 | tf::TransformListener tfListener; 39 | tf::StampedTransform lidar2Baselink; 40 | 41 | double lidarOdomTime = -1; 42 | deque imuOdomQueue; 43 | 44 | TransformFusion() 45 | { 46 | if(lidarFrame != baselinkFrame) 47 | { 48 | try 49 | { 50 | tfListener.waitForTransform(lidarFrame, baselinkFrame, ros::Time(0), ros::Duration(3.0)); 51 | tfListener.lookupTransform(lidarFrame, baselinkFrame, ros::Time(0), lidar2Baselink); 52 | } 53 | catch (tf::TransformException ex) 54 | { 55 | ROS_ERROR("%s",ex.what()); 56 | } 57 | } 58 | 59 | subLaserOdometry = nh.subscribe("lio_sam/mapping/odometry", 5, &TransformFusion::lidarOdometryHandler, this, ros::TransportHints().tcpNoDelay()); 60 | subImuOdometry = nh.subscribe(odomTopic+"_incremental", 2000, &TransformFusion::imuOdometryHandler, this, ros::TransportHints().tcpNoDelay()); 61 | 62 | pubImuOdometry = nh.advertise(odomTopic, 2000); 63 | pubImuPath = nh.advertise ("lio_sam/imu/path", 1); 64 | } 65 | 66 | Eigen::Affine3f odom2affine(nav_msgs::Odometry odom) 67 | { 68 | double x, y, z, roll, pitch, yaw; 69 | x = odom.pose.pose.position.x; 70 | y = odom.pose.pose.position.y; 71 | z = odom.pose.pose.position.z; 72 | tf::Quaternion orientation; 73 | tf::quaternionMsgToTF(odom.pose.pose.orientation, orientation); 74 | tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw); 75 | return pcl::getTransformation(x, y, z, roll, pitch, yaw); 76 | } 77 | 78 | void lidarOdometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg) 79 | { 80 | std::lock_guard lock(mtx); 81 | 82 | lidarOdomAffine = odom2affine(*odomMsg); 83 | 84 | lidarOdomTime = odomMsg->header.stamp.toSec(); 85 | } 86 | 87 | void imuOdometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg) 88 | { 89 | // static tf 90 | static tf::TransformBroadcaster tfMap2Odom; 91 | static tf::Transform map_to_odom = tf::Transform(tf::createQuaternionFromRPY(0, 0, 0), tf::Vector3(0, 0, 0)); 92 | tfMap2Odom.sendTransform(tf::StampedTransform(map_to_odom, odomMsg->header.stamp, mapFrame, odometryFrame)); 93 | 94 | std::lock_guard lock(mtx); 95 | 96 | imuOdomQueue.push_back(*odomMsg); 97 | 98 | // get latest odometry (at current IMU stamp) 99 | if (lidarOdomTime == -1) 100 | return; 101 | while (!imuOdomQueue.empty()) 102 | { 103 | if (imuOdomQueue.front().header.stamp.toSec() <= lidarOdomTime) 104 | imuOdomQueue.pop_front(); 105 | else 106 | break; 107 | } 108 | Eigen::Affine3f imuOdomAffineFront = odom2affine(imuOdomQueue.front()); 109 | Eigen::Affine3f imuOdomAffineBack = odom2affine(imuOdomQueue.back()); 110 | Eigen::Affine3f imuOdomAffineIncre = imuOdomAffineFront.inverse() * imuOdomAffineBack; 111 | Eigen::Affine3f imuOdomAffineLast = lidarOdomAffine * imuOdomAffineIncre; 112 | float x, y, z, roll, pitch, yaw; 113 | pcl::getTranslationAndEulerAngles(imuOdomAffineLast, x, y, z, roll, pitch, yaw); 114 | 115 | // publish latest odometry 116 | nav_msgs::Odometry laserOdometry = imuOdomQueue.back(); 117 | laserOdometry.pose.pose.position.x = x; 118 | laserOdometry.pose.pose.position.y = y; 119 | laserOdometry.pose.pose.position.z = z; 120 | laserOdometry.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw); 121 | pubImuOdometry.publish(laserOdometry); 122 | 123 | // publish tf 124 | static tf::TransformBroadcaster tfOdom2BaseLink; 125 | tf::Transform tCur; 126 | tf::poseMsgToTF(laserOdometry.pose.pose, tCur); 127 | if(lidarFrame != baselinkFrame) 128 | tCur = tCur * lidar2Baselink; 129 | tf::StampedTransform odom_2_baselink = tf::StampedTransform(tCur, odomMsg->header.stamp, odometryFrame, baselinkFrame); 130 | tfOdom2BaseLink.sendTransform(odom_2_baselink); 131 | 132 | // publish IMU path 133 | static nav_msgs::Path imuPath; 134 | static double last_path_time = -1; 135 | double imuTime = imuOdomQueue.back().header.stamp.toSec(); 136 | if (imuTime - last_path_time > 0.1) 137 | { 138 | last_path_time = imuTime; 139 | geometry_msgs::PoseStamped pose_stamped; 140 | pose_stamped.header.stamp = imuOdomQueue.back().header.stamp; 141 | pose_stamped.header.frame_id = odometryFrame; 142 | pose_stamped.pose = laserOdometry.pose.pose; 143 | imuPath.poses.push_back(pose_stamped); 144 | while(!imuPath.poses.empty() && imuPath.poses.front().header.stamp.toSec() < lidarOdomTime - 1.0) 145 | imuPath.poses.erase(imuPath.poses.begin()); 146 | if (pubImuPath.getNumSubscribers() != 0) 147 | { 148 | imuPath.header.stamp = imuOdomQueue.back().header.stamp; 149 | imuPath.header.frame_id = odometryFrame; 150 | pubImuPath.publish(imuPath); 151 | } 152 | } 153 | } 154 | }; 155 | 156 | class IMUPreintegration : public ParamServer 157 | { 158 | public: 159 | 160 | std::mutex mtx; 161 | 162 | ros::Subscriber subImu; 163 | ros::Subscriber subOdometry; 164 | ros::Publisher pubImuOdometry; 165 | 166 | bool systemInitialized = false; 167 | 168 | gtsam::noiseModel::Diagonal::shared_ptr priorPoseNoise; 169 | gtsam::noiseModel::Diagonal::shared_ptr priorVelNoise; 170 | gtsam::noiseModel::Diagonal::shared_ptr priorBiasNoise; 171 | gtsam::noiseModel::Diagonal::shared_ptr correctionNoise; 172 | gtsam::noiseModel::Diagonal::shared_ptr correctionNoise2; 173 | gtsam::Vector noiseModelBetweenBias; 174 | 175 | 176 | gtsam::PreintegratedImuMeasurements *imuIntegratorOpt_; 177 | gtsam::PreintegratedImuMeasurements *imuIntegratorImu_; 178 | 179 | std::deque imuQueOpt; 180 | std::deque imuQueImu; 181 | 182 | gtsam::Pose3 prevPose_; 183 | gtsam::Vector3 prevVel_; 184 | gtsam::NavState prevState_; 185 | gtsam::imuBias::ConstantBias prevBias_; 186 | 187 | gtsam::NavState prevStateOdom; 188 | gtsam::imuBias::ConstantBias prevBiasOdom; 189 | 190 | bool doneFirstOpt = false; 191 | double lastImuT_imu = -1; 192 | double lastImuT_opt = -1; 193 | 194 | gtsam::ISAM2 optimizer; 195 | gtsam::NonlinearFactorGraph graphFactors; 196 | gtsam::Values graphValues; 197 | 198 | const double delta_t = 0; 199 | 200 | int key = 1; 201 | 202 | gtsam::Pose3 imu2Lidar = gtsam::Pose3(gtsam::Rot3(1, 0, 0, 0), gtsam::Point3(-extTrans.x(), -extTrans.y(), -extTrans.z())); 203 | gtsam::Pose3 lidar2Imu = gtsam::Pose3(gtsam::Rot3(1, 0, 0, 0), gtsam::Point3(extTrans.x(), extTrans.y(), extTrans.z())); 204 | 205 | IMUPreintegration() 206 | { 207 | subImu = nh.subscribe (imuTopic, 2000, &IMUPreintegration::imuHandler, this, ros::TransportHints().tcpNoDelay()); 208 | subOdometry = nh.subscribe("lio_sam/mapping/odometry_incremental", 5, &IMUPreintegration::odometryHandler, this, ros::TransportHints().tcpNoDelay()); 209 | 210 | pubImuOdometry = nh.advertise (odomTopic+"_incremental", 2000); 211 | 212 | boost::shared_ptr p = gtsam::PreintegrationParams::MakeSharedU(imuGravity); 213 | p->accelerometerCovariance = gtsam::Matrix33::Identity(3,3) * pow(imuAccNoise, 2); // acc white noise in continuous 214 | p->gyroscopeCovariance = gtsam::Matrix33::Identity(3,3) * pow(imuGyrNoise, 2); // gyro white noise in continuous 215 | p->integrationCovariance = gtsam::Matrix33::Identity(3,3) * pow(1e-4, 2); // error committed in integrating position from velocities 216 | gtsam::imuBias::ConstantBias prior_imu_bias((gtsam::Vector(6) << 0, 0, 0, 0, 0, 0).finished());; // assume zero initial bias 217 | 218 | priorPoseNoise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 1e-2, 1e-2, 1e-2, 1e-2, 1e-2, 1e-2).finished()); // rad,rad,rad,m, m, m 219 | priorVelNoise = gtsam::noiseModel::Isotropic::Sigma(3, 1e4); // m/s 220 | priorBiasNoise = gtsam::noiseModel::Isotropic::Sigma(6, 1e-3); // 1e-2 ~ 1e-3 seems to be good 221 | correctionNoise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 0.05, 0.05, 0.05, 0.1, 0.1, 0.1).finished()); // rad,rad,rad,m, m, m 222 | correctionNoise2 = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 1, 1, 1, 1, 1, 1).finished()); // rad,rad,rad,m, m, m 223 | noiseModelBetweenBias = (gtsam::Vector(6) << imuAccBiasN, imuAccBiasN, imuAccBiasN, imuGyrBiasN, imuGyrBiasN, imuGyrBiasN).finished(); 224 | 225 | imuIntegratorImu_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for IMU message thread 226 | imuIntegratorOpt_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for optimization 227 | } 228 | 229 | void resetOptimization() 230 | { 231 | gtsam::ISAM2Params optParameters; 232 | optParameters.relinearizeThreshold = 0.1; 233 | optParameters.relinearizeSkip = 1; 234 | optimizer = gtsam::ISAM2(optParameters); 235 | 236 | gtsam::NonlinearFactorGraph newGraphFactors; 237 | graphFactors = newGraphFactors; 238 | 239 | gtsam::Values NewGraphValues; 240 | graphValues = NewGraphValues; 241 | } 242 | 243 | void resetParams() 244 | { 245 | lastImuT_imu = -1; 246 | doneFirstOpt = false; 247 | systemInitialized = false; 248 | } 249 | 250 | void odometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg) 251 | { 252 | std::lock_guard lock(mtx); 253 | 254 | double currentCorrectionTime = ROS_TIME(odomMsg); 255 | 256 | // make sure we have imu data to integrate 257 | if (imuQueOpt.empty()) 258 | return; 259 | 260 | float p_x = odomMsg->pose.pose.position.x; 261 | float p_y = odomMsg->pose.pose.position.y; 262 | float p_z = odomMsg->pose.pose.position.z; 263 | float r_x = odomMsg->pose.pose.orientation.x; 264 | float r_y = odomMsg->pose.pose.orientation.y; 265 | float r_z = odomMsg->pose.pose.orientation.z; 266 | float r_w = odomMsg->pose.pose.orientation.w; 267 | bool degenerate = (int)odomMsg->pose.covariance[0] == 1 ? true : false; 268 | gtsam::Pose3 lidarPose = gtsam::Pose3(gtsam::Rot3::Quaternion(r_w, r_x, r_y, r_z), gtsam::Point3(p_x, p_y, p_z)); 269 | 270 | 271 | // 0. initialize system 272 | if (systemInitialized == false) 273 | { 274 | resetOptimization(); 275 | 276 | // pop old IMU message 277 | while (!imuQueOpt.empty()) 278 | { 279 | if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t) 280 | { 281 | lastImuT_opt = ROS_TIME(&imuQueOpt.front()); 282 | imuQueOpt.pop_front(); 283 | } 284 | else 285 | break; 286 | } 287 | // initial pose 288 | prevPose_ = lidarPose.compose(lidar2Imu); 289 | gtsam::PriorFactor priorPose(X(0), prevPose_, priorPoseNoise); 290 | graphFactors.add(priorPose); 291 | // initial velocity 292 | prevVel_ = gtsam::Vector3(0, 0, 0); 293 | gtsam::PriorFactor priorVel(V(0), prevVel_, priorVelNoise); 294 | graphFactors.add(priorVel); 295 | // initial bias 296 | prevBias_ = gtsam::imuBias::ConstantBias(); 297 | gtsam::PriorFactor priorBias(B(0), prevBias_, priorBiasNoise); 298 | graphFactors.add(priorBias); 299 | // add values 300 | graphValues.insert(X(0), prevPose_); 301 | graphValues.insert(V(0), prevVel_); 302 | graphValues.insert(B(0), prevBias_); 303 | // optimize once 304 | optimizer.update(graphFactors, graphValues); 305 | graphFactors.resize(0); 306 | graphValues.clear(); 307 | 308 | imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_); 309 | imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_); 310 | 311 | key = 1; 312 | systemInitialized = true; 313 | return; 314 | } 315 | 316 | 317 | // reset graph for speed 318 | if (key == 100) 319 | { 320 | // get updated noise before reset 321 | gtsam::noiseModel::Gaussian::shared_ptr updatedPoseNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(X(key-1))); 322 | gtsam::noiseModel::Gaussian::shared_ptr updatedVelNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(V(key-1))); 323 | gtsam::noiseModel::Gaussian::shared_ptr updatedBiasNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(B(key-1))); 324 | // reset graph 325 | resetOptimization(); 326 | // add pose 327 | gtsam::PriorFactor priorPose(X(0), prevPose_, updatedPoseNoise); 328 | graphFactors.add(priorPose); 329 | // add velocity 330 | gtsam::PriorFactor priorVel(V(0), prevVel_, updatedVelNoise); 331 | graphFactors.add(priorVel); 332 | // add bias 333 | gtsam::PriorFactor priorBias(B(0), prevBias_, updatedBiasNoise); 334 | graphFactors.add(priorBias); 335 | // add values 336 | graphValues.insert(X(0), prevPose_); 337 | graphValues.insert(V(0), prevVel_); 338 | graphValues.insert(B(0), prevBias_); 339 | // optimize once 340 | optimizer.update(graphFactors, graphValues); 341 | graphFactors.resize(0); 342 | graphValues.clear(); 343 | 344 | key = 1; 345 | } 346 | 347 | 348 | // 1. integrate imu data and optimize 349 | while (!imuQueOpt.empty()) 350 | { 351 | // pop and integrate imu data that is between two optimizations 352 | sensor_msgs::Imu *thisImu = &imuQueOpt.front(); 353 | double imuTime = ROS_TIME(thisImu); 354 | if (imuTime < currentCorrectionTime - delta_t) 355 | { 356 | double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt); 357 | imuIntegratorOpt_->integrateMeasurement( 358 | gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z), 359 | gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt); 360 | 361 | lastImuT_opt = imuTime; 362 | imuQueOpt.pop_front(); 363 | } 364 | else 365 | break; 366 | } 367 | // add imu factor to graph 368 | const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast(*imuIntegratorOpt_); 369 | gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu); 370 | graphFactors.add(imu_factor); 371 | // add imu bias between factor 372 | graphFactors.add(gtsam::BetweenFactor(B(key - 1), B(key), gtsam::imuBias::ConstantBias(), 373 | gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias))); 374 | // add pose factor 375 | gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu); 376 | gtsam::PriorFactor pose_factor(X(key), curPose, degenerate ? correctionNoise2 : correctionNoise); 377 | graphFactors.add(pose_factor); 378 | // insert predicted values 379 | gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_); 380 | graphValues.insert(X(key), propState_.pose()); 381 | graphValues.insert(V(key), propState_.v()); 382 | graphValues.insert(B(key), prevBias_); 383 | // optimize 384 | optimizer.update(graphFactors, graphValues); 385 | optimizer.update(); 386 | graphFactors.resize(0); 387 | graphValues.clear(); 388 | // Overwrite the beginning of the preintegration for the next step. 389 | gtsam::Values result = optimizer.calculateEstimate(); 390 | prevPose_ = result.at(X(key)); 391 | prevVel_ = result.at(V(key)); 392 | prevState_ = gtsam::NavState(prevPose_, prevVel_); 393 | prevBias_ = result.at(B(key)); 394 | // Reset the optimization preintegration object. 395 | imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_); 396 | // check optimization 397 | if (failureDetection(prevVel_, prevBias_)) 398 | { 399 | resetParams(); 400 | return; 401 | } 402 | 403 | 404 | // 2. after optiization, re-propagate imu odometry preintegration 405 | prevStateOdom = prevState_; 406 | prevBiasOdom = prevBias_; 407 | // first pop imu message older than current correction data 408 | double lastImuQT = -1; 409 | while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t) 410 | { 411 | lastImuQT = ROS_TIME(&imuQueImu.front()); 412 | imuQueImu.pop_front(); 413 | } 414 | // repropogate 415 | if (!imuQueImu.empty()) 416 | { 417 | // reset bias use the newly optimized bias 418 | imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom); 419 | // integrate imu message from the beginning of this optimization 420 | for (int i = 0; i < (int)imuQueImu.size(); ++i) 421 | { 422 | sensor_msgs::Imu *thisImu = &imuQueImu[i]; 423 | double imuTime = ROS_TIME(thisImu); 424 | double dt = (lastImuQT < 0) ? (1.0 / 500.0) :(imuTime - lastImuQT); 425 | 426 | imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z), 427 | gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y, thisImu->angular_velocity.z), dt); 428 | lastImuQT = imuTime; 429 | } 430 | } 431 | 432 | ++key; 433 | doneFirstOpt = true; 434 | } 435 | 436 | bool failureDetection(const gtsam::Vector3& velCur, const gtsam::imuBias::ConstantBias& biasCur) 437 | { 438 | Eigen::Vector3f vel(velCur.x(), velCur.y(), velCur.z()); 439 | if (vel.norm() > 30) 440 | { 441 | ROS_WARN("Large velocity, reset IMU-preintegration!"); 442 | return true; 443 | } 444 | 445 | Eigen::Vector3f ba(biasCur.accelerometer().x(), biasCur.accelerometer().y(), biasCur.accelerometer().z()); 446 | Eigen::Vector3f bg(biasCur.gyroscope().x(), biasCur.gyroscope().y(), biasCur.gyroscope().z()); 447 | if (ba.norm() > 1.0 || bg.norm() > 1.0) 448 | { 449 | ROS_WARN("Large bias, reset IMU-preintegration!"); 450 | return true; 451 | } 452 | 453 | return false; 454 | } 455 | 456 | void imuHandler(const sensor_msgs::Imu::ConstPtr& imu_raw) 457 | { 458 | std::lock_guard lock(mtx); 459 | 460 | sensor_msgs::Imu thisImu = imuConverter(*imu_raw); 461 | 462 | imuQueOpt.push_back(thisImu); 463 | imuQueImu.push_back(thisImu); 464 | 465 | if (doneFirstOpt == false) 466 | return; 467 | 468 | double imuTime = ROS_TIME(&thisImu); 469 | double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu); 470 | lastImuT_imu = imuTime; 471 | 472 | // integrate this single imu message 473 | imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y, thisImu.linear_acceleration.z), 474 | gtsam::Vector3(thisImu.angular_velocity.x, thisImu.angular_velocity.y, thisImu.angular_velocity.z), dt); 475 | 476 | // predict odometry 477 | gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom); 478 | 479 | // publish odometry 480 | nav_msgs::Odometry odometry; 481 | odometry.header.stamp = thisImu.header.stamp; 482 | odometry.header.frame_id = odometryFrame; 483 | odometry.child_frame_id = "odom_imu"; 484 | 485 | // transform imu pose to ldiar 486 | gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position()); 487 | gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar); 488 | 489 | odometry.pose.pose.position.x = lidarPose.translation().x(); 490 | odometry.pose.pose.position.y = lidarPose.translation().y(); 491 | odometry.pose.pose.position.z = lidarPose.translation().z(); 492 | odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x(); 493 | odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y(); 494 | odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z(); 495 | odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w(); 496 | 497 | odometry.twist.twist.linear.x = currentState.velocity().x(); 498 | odometry.twist.twist.linear.y = currentState.velocity().y(); 499 | odometry.twist.twist.linear.z = currentState.velocity().z(); 500 | odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x(); 501 | odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y(); 502 | odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z(); 503 | pubImuOdometry.publish(odometry); 504 | } 505 | }; 506 | 507 | 508 | int main(int argc, char** argv) 509 | { 510 | ros::init(argc, argv, "roboat_loam"); 511 | 512 | IMUPreintegration ImuP; 513 | 514 | TransformFusion TF; 515 | 516 | ROS_INFO("\033[1;32m----> IMU Preintegration Started.\033[0m"); 517 | 518 | ros::MultiThreadedSpinner spinner(4); 519 | spinner.spin(); 520 | 521 | return 0; 522 | } 523 | -------------------------------------------------------------------------------- /SC-LIO-SAM/tools/matlab/listdir.m: -------------------------------------------------------------------------------- 1 | function [files] = listdir(dir_path) 2 | 3 | files = dir(dir_path); 4 | files(1:2) = []; % remove . and .. 5 | files = {files(:).name}; 6 | 7 | end 8 | 9 | -------------------------------------------------------------------------------- /SC-LIO-SAM/tools/matlab/pcd_viewer.m: -------------------------------------------------------------------------------- 1 | pcd_dir = "/home/user/Desktop/data/kaist01/sc-lio-sam/Scans/"; 2 | 3 | pcd_names = listdir(pcd_dir); 4 | 5 | % sort names 6 | pcd_names_idx = []; 7 | for ii = 1:length(pcd_names) 8 | pcd_idx = str2double(pcd_names{ii}(1:end-4)); 9 | pcd_names_idx = [pcd_names_idx, pcd_idx]; 10 | end 11 | [~, sorted_loc] = sort(pcd_names_idx); 12 | 13 | pcd_names_sorted = pcd_names(sorted_loc); 14 | 15 | % viz 16 | for ii = 1:length(pcd_names) 17 | 18 | figure(1);clf; 19 | pc = pcread(fullfile(pcd_dir, pcd_names_sorted{ii})); 20 | pcshow(pc); 21 | 22 | title(pcd_names_sorted{ii}); 23 | 24 | caxis([0, 200]); 25 | 26 | xlim([-80, 80]); 27 | ylim([-80, 80]); 28 | zlim([-2, 15]); 29 | 30 | end -------------------------------------------------------------------------------- /SC-LIO-SAM/tools/python/bone_table.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/tools/python/bone_table.npy -------------------------------------------------------------------------------- /SC-LIO-SAM/tools/python/jet_table.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gisbi-kim/SC-LIO-SAM/d43ca00d97a756303c10975e32e8d66bfabb337d/SC-LIO-SAM/tools/python/jet_table.npy -------------------------------------------------------------------------------- /SC-LIO-SAM/tools/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 = "/home/user/Desktop/data/scliosam/mytest1/" # should end with / 29 | scan_idx_range_to_stack = [0, 100] # 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-LIO-SAM/tools/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 | --------------------------------------------------------------------------------