├── CMakeLists.txt
├── README.md
├── README_CN.md
├── config
└── localization_config.yaml
├── include
├── error_estimater
│ ├── error_estimater.h
│ └── voxelset.h
├── global_localization
│ ├── bbs_localization.h
│ ├── global_localization.h
│ └── occupancy_gridmap.h
├── laser_odometry
│ └── laser_odometry.h
└── localization
│ ├── UKF.h
│ ├── localization.h
│ └── tools.h
├── launch
├── kitti_test.py
├── localization.py
└── single_laser.py
├── map
├── frame.pcd
├── map.pcd
└── pos.txt
├── package.xml
├── src
├── error_estimater
│ ├── error_estimater.cpp
│ └── voxelset.cpp
├── global_localization
│ ├── bbs_localization.cpp
│ └── global_localization.cpp
├── laser_odometry
│ └── laser_odometry.cpp
├── localization
│ ├── UKF.cpp
│ ├── localization.cpp
│ └── tools.cpp
└── main.cpp
└── test
├── read_map.cpp
├── test_error.cpp
├── test_global_localization.cpp
├── test_kitti.cpp
├── test_laser_odom.cpp
└── test_odom.cpp
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.5)
2 | project(laser_localization)
3 |
4 | # Default to C99
5 | if(NOT CMAKE_C_STANDARD)
6 | set(CMAKE_C_STANDARD 99)
7 | endif()
8 |
9 | # Default to C++14
10 | if(NOT CMAKE_CXX_STANDARD)
11 | set(CMAKE_CXX_STANDARD 14)
12 | endif()
13 |
14 | #set(CMAKE_BUILD_TYPE "Release")
15 | #set(CMAKE_BUILD_TYPE "Debug")
16 |
17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3 -march=native ")
18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O3 -march=native")
19 |
20 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
21 | add_compile_options(-Wall -Wextra -Wpedantic)
22 | endif()
23 | add_definitions(-std=c++14 -msse -msse2 -msse3 -msse4 -msse4.1 -msse4.2)
24 | set(CMAKE_CXX_FLAGS "-std=c++14 -msse -msse2 -msse3 -msse4 -msse4.1 -msse4.2")
25 | find_package(OpenMP)
26 | if (OPENMP_FOUND)
27 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
28 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
29 | endif()
30 |
31 | # find dependencies
32 | find_package(ament_cmake REQUIRED)
33 | find_package(rclcpp REQUIRED)
34 | #find_package(agv_msgs REQUIRED)
35 | find_package(visualization_msgs REQUIRED)
36 | find_package(tf2_ros REQUIRED)
37 | find_package(tf2 REQUIRED)
38 | find_package(nav_msgs REQUIRED)
39 | find_package(sensor_msgs REQUIRED)
40 | find_package(pcl_conversions REQUIRED)
41 | find_package(teaserpp REQUIRED)
42 | find_package(Boost 1.58 REQUIRED)
43 | find_package(PCL REQUIRED COMPONENTS common features kdtree visualization)
44 | find_package(OpenCV REQUIRED)
45 | find_package(std_srvs REQUIRED)
46 | # 包含头文件目录
47 | include_directories(${PCL_INCLUDE_DIRS})
48 | # 设置依赖库链接目录
49 | link_directories(${PCL_LIBRARY_DIRS})
50 | # 添加预处理器和编译器标记
51 | add_definitions(${PCL_DEFINITIONS})
52 |
53 |
54 | include_directories(
55 | include
56 | include/global_localization
57 | include/laser_odometry
58 | )
59 |
60 | if(BUILD_TESTING)
61 | find_package(ament_lint_auto REQUIRED)
62 | # the following line skips the linter which checks for copyrights
63 | # uncomment the line when a copyright and license is not present in all source files
64 | #set(ament_cmake_copyright_FOUND TRUE)
65 | # the following line skips cpplint (only works in a git repo)
66 | # uncomment the line when this package is not in a git repo
67 | #set(ament_cmake_cpplint_FOUND TRUE)
68 | ament_lint_auto_find_test_dependencies()
69 | endif()
70 |
71 |
72 | ament_package()
73 |
74 | add_library(
75 | localization_lib
76 | src/localization/localization.cpp
77 | src/error_estimater/error_estimater.cpp
78 | src/localization/UKF.cpp
79 | src/global_localization/bbs_localization.cpp
80 | src/global_localization/global_localization.cpp
81 | src/laser_odometry/laser_odometry.cpp
82 | src/error_estimater/voxelset.cpp
83 | src/localization/tools.cpp
84 | )
85 | ament_target_dependencies(localization_lib rclcpp visualization_msgs tf2_ros tf2 std_srvs nav_msgs sensor_msgs pcl_conversions)
86 | target_link_libraries(
87 | localization_lib
88 | ${PCL_LIBRARIES}
89 | Eigen3::Eigen
90 | ${OpenCV_LIBS}
91 | )
92 |
93 |
94 | add_executable(laser_localization src/main.cpp)
95 | target_link_libraries(laser_localization
96 | ${PCL_LIBRARIES}
97 | Eigen3::Eigen
98 | ${OpenCV_LIBS}
99 | localization_lib
100 | )
101 | install(TARGETS
102 | laser_localization
103 | DESTINATION lib/${PROJECT_NAME}
104 | LIBRARY DESTINATION lib/${PROJECT_NAME}
105 | )
106 | ### test
107 |
108 | add_executable(test_laser_odom test/test_laser_odom.cpp)
109 | target_link_libraries(test_laser_odom
110 | ${PCL_LIBRARIES}
111 | Eigen3::Eigen
112 | localization_lib
113 | )
114 |
115 | add_executable(test_global_localization test/test_global_localization.cpp)
116 | target_link_libraries(test_global_localization
117 | ${PCL_LIBRARIES}
118 | Eigen3::Eigen
119 | localization_lib
120 | ${OpenCV_LIBS}
121 | )
122 |
123 | add_executable(test_error test/test_error.cpp)
124 | target_link_libraries(test_error
125 | ${PCL_LIBRARIES}
126 | Eigen3::Eigen
127 | localization_lib
128 | ${OpenCV_LIBS}
129 | )
130 |
131 | add_executable(read_map test/read_map.cpp)
132 | target_link_libraries(read_map ${PCL_LIBRARIES})
133 |
134 | add_executable(odom test/test_odom.cpp)
135 | target_link_libraries(odom ${PCL_LIBRARIES})
136 | ament_target_dependencies(odom rclcpp visualization_msgs tf2_ros tf2 nav_msgs sensor_msgs pcl_conversions)
137 |
138 | add_executable(kitti test/test_kitti.cpp)
139 | target_link_libraries(kitti ${PCL_LIBRARIES})
140 | ament_target_dependencies(kitti rclcpp visualization_msgs tf2_ros tf2 nav_msgs sensor_msgs pcl_conversions)
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # laser_localization | [CN](https://github.com/linyicheng1/laser_localization/blob/main/README_CN.md)
2 |
3 | laser_localization is a 3D LiDAR localization algorithm applied to small area scenes, typical application scenarios are industrial parks, neighborhoods or substations, etc. It combines 3D laser point cloud, wheeled odometer and IMU angle information to achieve high precision real-time positioning. A branch-and-bound search algorithm is used for global positioning, while NDT matching is used for local point clouds and global maps. The wheeled odometer and IMU angle information are used as the motion a priori parameters of the matching algorithm to accelerate laser matching and avoid falling into local optimum.
4 |
5 |
6 | [
](https://www.bilibili.com/video/BV12P4y1m7nH/?spm_id_from=333.999.0.0&vd_source=4dd69fa6d40221a0fa0733def5c4708a)
7 |
8 | [Demo Video](https://www.bilibili.com/video/BV12P4y1m7nH/?spm_id_from=333.999.0.0&vd_source=4dd69fa6d40221a0fa0733def5c4708a)
9 |
10 |
11 | | | | |
12 | |------------|--------------------|--------------------------|
13 | | **Scene Range** | 500m x 500m (without GPS) | \ (with GPS case not tested, theoretical in 1km above.) |
14 | | **Speed** | less than 0.8m/s (only 3d laser) | 1-2m/s (3d laser + Wheeled odometer) |
15 | | **Accuracy** | Typical 2-3cm | Typical 2-3cm |
16 |
17 |
18 | ## 1. Prerequisites
19 |
20 | #### 1.1 **Ubuntu** 和 **ROS**,Ubuntu 18.04. ROS Dashing && Foxy
21 |
22 | ```
23 | sudo apt install ros-YOUR_DISTRO-desktop ros-YOUR_DISTRO-pcl
24 | ```
25 | [ROS2 Install WIKI](https://docs.ros.org/en/dashing/Installation/Ubuntu-Development-Setup.html)
26 |
27 | ## 2. build laser_localization on ROS2
28 |
29 | Clone the repository and build:
30 |
31 | ```shell
32 | cd ~/catkin_ws/src
33 | git clone https://github.com/linyicheng1/laser_localization.git
34 | cd ../
35 | colcon build
36 | source ~/catkin_ws/install/setup.bash
37 | ```
38 |
39 | ## 3. Run with KITTI dataset
40 |
41 | Download KITTI dataset:
42 | [BaiDu Driver](https://pan.baidu.com/s/1BaVZKkQu8WT2Yo4k4Omjug?pwd=6hrm)|[Google Driver](https://drive.google.com/file/d/1_gsbxX-M7xfUJSB0JO8qOGX5Adfaw0q_/view?usp=sharing)
43 |
44 | Modify the path code in `./launch/kitti_test.py`:
45 |
46 | ```python
47 | 61| parameters=[
48 | 62| {"kitti_path": "${YOUR_PATH}"},
49 | 63| ]
50 |
51 | 40| {"global_map": "${YOUR_PATH}"},
52 | ```
53 |
54 | run cmd in shell:
55 |
56 | ```shell
57 | ros2 launch ~/catkin_ws/src/laser_localization/launch/kitti_test.py
58 | ```
59 |
60 | ## 4. Run with your device
61 |
62 | #### 4.0 Build point cloud map
63 |
64 | Point cloud maps can be constructed using the current mainstream 3D laser SLAM algorithm and saved at `. /map/map.pcd`.
65 |
66 | Recommended 3D point cloud algorithm: [hdl_graph_slam](https://github.com/koide3/hdl_graph_slam)、[ALOAM](https://github.com/tops666/Aloam)、[LIO-SAM](https://github.com/TixiaoShan/LIO-SAM)
67 |
68 | #### 4.1 Laser only mode
69 |
70 | The single laser mode requires only one 3d radar and can be used for rapid deployment to operate in low speed carts. The relative transformation relationship of `base_link->laser` in the `tf` transformation tree needs to be provided, and the 3d point cloud data topic `pointcloud2` needs to be provided.
71 |
72 | run cmd:
73 |
74 | ```shell
75 | ros2 launch ~/catkin_ws/src/laser_localization/launch/single_laser.py
76 | ```
77 |
78 | #### 4.2 Multi-sensor fusion localization
79 |
80 | Multi-sensor fusion positioning mode improves accuracy and speed, but requires more information.
81 |
82 | 1. The odometer data, `odom->base_link` in the `tf` transformation tree, is calculated for the wheel odometer. The calculation of the odometer requires calibration of the robot's wheel radius, calibration of the wheel distance over 10m straight ahead, and multiple rotations and calibration of the axis distance.
83 |
84 | Run in shell:
85 |
86 | ```shell
87 | ros2 launch ~/catkin_ws/src/laser_localization/launch/localization.py
88 | ```
89 |
90 | ## 5. Advantages
91 |
92 | - Laser odometer + laser point cloud with map matching, laser positioning solution using only 3d radar, can reach about 10-15hz output, very easy to deploy at low speed using directly.
93 |
94 | - Laser positioning is separated from wheel odometer, which can selectively use wheel odometer information to provide a priori for laser matching, effectively avoiding the influence of wheel slip and other disturbances.
95 |
96 | - Provide matching confidence information and real-time feedback on current positioning status.
97 |
98 | - Provide global positioning to obtain the initial value method, and include the function of program power down to save the current position.
99 |
100 | ## 6. Licence
101 |
102 | The source code is released under [GPLv3](http://www.gnu.org/licenses/) license.
103 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | # laser_localization
2 |
3 | laser_localization 是一个应用于小范围场景的3D激光雷达定位算法,典型应用场景为工业园区、小区或者变电站等。融合3D激光点云,轮式里程计以及IMU角度信息,实现高精实时定位。采用分支定界搜索算法进行全局定位,而局部点云和全局地图采用NDT匹配方法。轮式里程计和IMU角度信息作为匹配算法的运动先验参数,加速激光匹配,避免陷入局部最优。
4 |
5 |
6 | [
](https://www.bilibili.com/video/BV12P4y1m7nH/?spm_id_from=333.999.0.0&vd_source=4dd69fa6d40221a0fa0733def5c4708a)
7 |
8 | [演示视频](https://www.bilibili.com/video/BV12P4y1m7nH/?spm_id_from=333.999.0.0&vd_source=4dd69fa6d40221a0fa0733def5c4708a)
9 |
10 |
11 | | | | |
12 | |------------|--------------------|--------------------------|
13 | | **适用定位范围** | 500m x 500m (无GPS) | \ (有GPS 情况下未测试,理论在1km以上) |
14 | | **运行速度** | 小于 0.8m/s (单激光) | 1-2m/s (雷达 + 轮式里程计) |
15 | | **定位精度** | 典型值 2-3cm | 典型值 2-3cm |
16 |
17 |
18 | ## 1. 准备环境
19 |
20 | 1.1 **Ubuntu** 和 **ROS**,Ubuntu 18.04. ROS Dashing && Foxy
21 |
22 | ```
23 | sudo apt install ros-YOUR_DISTRO-desktop ros-YOUR_DISTRO-pcl
24 | ```
25 | [ROS2 安装 WIKI](https://docs.ros.org/en/dashing/Installation/Ubuntu-Development-Setup.html)
26 |
27 | ## 2. ROS2 中编译 laser_localization
28 |
29 | 克隆本项目然后编译:
30 |
31 | ```shell
32 | cd ~/catkin_ws/src
33 | git clone https://github.com/linyicheng1/laser_localization.git
34 | cd ../
35 | colcon build
36 | source ~/catkin_ws/install/setup.bash
37 | ```
38 |
39 | ## 3. KITTI 数据集上运行
40 |
41 | 下载KITTI数据集:
42 | [百度网盘]()|[谷歌网盘]()
43 |
44 | 修改代码 `./launch/kitti_test.py` 中的路径:
45 |
46 | ```python
47 | 61| parameters=[
48 | 62| {"kitti_path": "${YOUR_PATH}"},
49 | 63| ]
50 |
51 | 40| {"global_map": "${YOUR_PATH}"},
52 | ```
53 |
54 | 运行代码:
55 |
56 | ```shell
57 | ros2 launch ~/catkin_ws/src/laser_localization/launch/kitti_test.py
58 | ```
59 |
60 | ## 4. 在你的设备上运行
61 |
62 | #### 4.0 点云地图构建
63 |
64 | 可采用目前主流的3D激光SLAM算法构建点云地图并保存在`./map/map.pcd`处。
65 | 推荐的3D点云算法:[hdl_graph_slam](https://github.com/koide3/hdl_graph_slam)、[ALOAM](https://github.com/tops666/Aloam)、[LIO-SAM](https://github.com/TixiaoShan/LIO-SAM)
66 |
67 | #### 4.1 单激光模式
68 |
69 | 单激光模式仅需要一个3D雷达即可,可用于快速部署在低速小车中运行。需要提供 `tf` 变换树中 `base_link->laser` 的相对变换关系,并提供3d点云数据话题`pointcloud2`。
70 |
71 | 运行代码:
72 |
73 | ```shell
74 | ros2 launch ~/catkin_ws/src/laser_localization/launch/single_laser.py
75 | ```
76 |
77 | #### 4.2 多传感器融合定位
78 |
79 | 多传感器融合定位模式下精度和运行速度均能得以提升,但需要提供更多信息。
80 | 1. 里程计数据,`tf`变换树中 `odom->base_link`, 为轮式里程计计算得到。里程计的计算需要对机器人轮子半径进行标定,直走10m以上标定轮距,旋转多圈并标定轴距。
81 |
82 | 运行代码:
83 |
84 | ```shell
85 | ros2 launch ~/catkin_ws/src/laser_localization/launch/localization.py
86 | ```
87 |
88 | ## 5. 定位算法优势
89 |
90 |
91 | - 激光里程计 + 激光点云与地图匹配,只采用3d雷达的激光定位方案,可以达到10-15hz左右的输出,在低速时直接使用部署非常简单。
92 |
93 | - 激光定位与轮式里程计分离,可选择性的采用轮式里程计信息为激光匹配提供先验,有效避免车轮打滑等扰动影响。
94 |
95 | - 提供匹配置信度信息,实时反馈当前定位状态。
96 |
97 | - 提供全局定位获得初始值方法,并包含程序掉电保存当前位置的功能。
98 |
99 | ## 6. 许可证
100 |
101 | 本源代码在[GPLv3](http://www.gnu.org/licenses/)许可证下发布
102 |
--------------------------------------------------------------------------------
/config/localization_config.yaml:
--------------------------------------------------------------------------------
1 | # 1 --- use laser only
2 | # 2 --- laser with wheel odometer
3 | # 3 --- fake localization
4 | localization/mode: 1
5 | global_map: "/home/hy/code/robot_ws/src/inspection_3d_robot/localization/map/map.pcd"
6 | pose_save: "/home/hy/code/robot_ws/src/inspection_3d_robot/localization/map/pos.txt"
7 | initial_pose_save: "/home/hy/code/robot_ws/src/inspection_3d_robot/localization/map/pos.txt"
8 | global_resolution: 1.2
9 | global_view_resolution: 2
10 | localization/ndt_resolution: 1
11 | localization/ndt_step_size: 0.1
12 | localization/ndt_epsilon: 0.01
13 | filter/k1: 0.95
14 |
15 | odom_frame: "odom"
16 | base_link_frame: "base_link"
17 | laser_frame: "os_sensor"
18 | laser_topic: "/points"
19 | ####
20 |
21 | bbs/map_filter_resolution: 1
22 | bbs/scan_filter_resolution: 1
23 | bbs/max_range: 15.0
24 | # transformation search range
25 | bbs/min_tx: -200.0
26 | bbs/max_tx: 200.0
27 | bbs/min_ty: -200.0
28 | bbs/max_ty: 200.0
29 | bbs/min_theta: -3.14
30 | bbs/max_theta: 3.14
31 |
32 | # global map config
33 | # slice height range (map coordinate)
34 | bbs/map_min_z: 2.0
35 | bbs/map_max_z: 2.4
36 |
37 | # 2D map params
38 | bbs/map_width: 512
39 | bbs/map_height: 1024
40 | bbs/map_resolution: 0.5
41 | bbs/max_points_pre_cell: 5
42 |
43 | # precomp map pyramid
44 | bbs/map_pyramid_level: 6
45 |
46 | # scan slice height range (sensor coordinate)
47 | bbs/scan_min_z: -0.2
48 | bbs/scan_max_z: 0.2
49 |
50 |
51 | ####
52 |
53 | ndt/resolution: 1.0
54 | ndt/step_size: 0.1
55 | ndt/epsilon: 0.01
56 | ndt/max_iterations: 30.0
57 | ndt/frame_resolution: 1.0
58 | odom/kf_distance: 2.0
59 | odom/local_map_size: 20
60 | ndt/local_map_resolution: 1.0
--------------------------------------------------------------------------------
/include/error_estimater/error_estimater.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALIZATION_ERROR_ESTIMATER_H
2 | #define LOCALIZATION_ERROR_ESTIMATER_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "error_estimater/voxelset.h"
8 |
9 | namespace match_error
10 | {
11 | class MatchingCostEvaluaterFlann
12 | {
13 | public:
14 | MatchingCostEvaluaterFlann() {}
15 | ~MatchingCostEvaluaterFlann(){}
16 |
17 | void set_target(pcl::PointCloud::ConstPtr cloud, double max_correspondence_distance);
18 |
19 | double calc_matching_error(const pcl::PointCloud& cloud, const Eigen::Matrix4f& transformation, double* inlier_fraction);
20 |
21 | private:
22 | double max_correspondence_distance_sq;
23 | pcl::KdTreeFLANN::Ptr tree;
24 | pcl::PointCloud::ConstPtr map;
25 |
26 | std::unique_ptr voxels;
27 | };
28 | }
29 |
30 | #endif //LOCALIZATION_ERROR_ESTIMATER_H
31 |
--------------------------------------------------------------------------------
/include/error_estimater/voxelset.h:
--------------------------------------------------------------------------------
1 | #ifndef LASER_LOCALIZATION_VOXELSET_H
2 | #define LASER_LOCALIZATION_VOXELSET_H
3 | #include
4 | #include
5 | #include
6 |
7 | namespace match_error{
8 | class Vector3iHash {
9 | public:
10 | size_t operator()(const Eigen::Vector3i& x) const;
11 | };
12 |
13 | class VoxelSet {
14 | public:
15 | VoxelSet(double max_correspondence_distance);
16 |
17 | void set_cloud(pcl::PointCloud::ConstPtr cloud);
18 | double matching_error(const pcl::PointCloud& cloud, double* inlier_fraction) const;
19 |
20 | private:
21 | Eigen::Vector3i voxel_coord(const Eigen::Vector4f& x) const;
22 | Eigen::Vector4f voxel_center(const Eigen::Vector3i& coord) const;
23 |
24 | private:
25 | double resolution;
26 |
27 | using Voxels = std::unordered_set, Eigen::aligned_allocator>;
28 | Voxels voxels;
29 | };
30 | }
31 |
32 | #endif //LASER_LOCALIZATION_VOXELSET_H
33 |
--------------------------------------------------------------------------------
/include/global_localization/bbs_localization.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALIZATION_BBS_LOCALIZATION_H
2 | #define LOCALIZATION_BBS_LOCALIZATION_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 |
12 | namespace global_localization {
13 |
14 | class OccupancyGridMap;
15 |
16 | struct DiscreteTransformation {
17 | public:
18 | using Points = std::vector>;
19 |
20 | DiscreteTransformation();
21 | DiscreteTransformation(int level, int x, int y, int theta);
22 | ~DiscreteTransformation();
23 |
24 | bool operator<(const DiscreteTransformation& rhs) const;
25 |
26 | bool is_leaf() const;
27 |
28 | Eigen::Isometry2f transformation(double theta_resolution, const std::vector>& gridmap_pyramid);
29 |
30 | Points transform(const Points& points, double trans_resolution, double theta_resolution);
31 |
32 | double calc_score(const Points& points, double theta_resolution, const std::vector>& gridmap_pyramid);
33 |
34 | std::vector branch();
35 |
36 | public:
37 | double score;
38 | int level;
39 | int x;
40 | int y;
41 | int theta;
42 | };
43 |
44 | struct BBSParams {
45 | BBSParams() {
46 | max_range = 15.0;
47 | min_tx = min_ty = -50.0;
48 | max_tx = max_ty = 50.0;
49 | min_theta = -M_PI;
50 | max_theta = M_PI;
51 | }
52 |
53 | double max_range;
54 | double min_tx;
55 | double max_tx;
56 | double min_ty;
57 | double max_ty;
58 | double min_theta;
59 | double max_theta;
60 | };
61 |
62 | class BBSLocalization {
63 | public:
64 | using Points = std::vector>;
65 |
66 | BBSLocalization(const BBSParams& params = BBSParams());
67 | ~BBSLocalization();
68 |
69 | void set_map(const Points& map_points, double resolution, int width, int height, int pyramid_levels, int max_points_per_cell);
70 |
71 | boost::optional localize(const Points& scan_points, double min_score, double* best_score = nullptr);
72 |
73 | std::shared_ptr gridmap() const;
74 |
75 | private:
76 | std::priority_queue create_init_transset(const Points& scan_points) const;
77 |
78 | private:
79 | BBSParams params;
80 |
81 | double theta_resolution;
82 | std::vector> gridmap_pyramid;
83 | };
84 |
85 | } // namespace global_localization
86 |
87 | #endif //LOCALIZATION_BBS_LOCALIZATION_H
88 |
--------------------------------------------------------------------------------
/include/global_localization/global_localization.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALIZATION_GLOBAL_LOCALIZATION_H
2 | #define LOCALIZATION_GLOBAL_LOCALIZATION_H
3 |
4 | #include "bbs_localization.h"
5 | #include
6 | #include
7 | #include "rclcpp/rclcpp.hpp"
8 | #include
9 |
10 | struct GlobalLocalizationResult {
11 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW
12 | using Ptr = std::shared_ptr;
13 |
14 | GlobalLocalizationResult(double error, double inlier_fraction, const Eigen::Isometry3f& pose) : error(error), inlier_fraction(inlier_fraction), pose(pose) {}
15 |
16 | double error;
17 | double inlier_fraction;
18 | Eigen::Isometry3f pose;
19 | };
20 |
21 | struct GlobalLocalizationResults {
22 | GlobalLocalizationResults(const std::vector& results) : results(results) {}
23 |
24 | GlobalLocalizationResults& sort(int max_num_candidates) {
25 | auto remove_loc = std::remove_if(results.begin(), results.end(), [](const auto& result) { return result == nullptr; });
26 | results.erase(remove_loc, results.end());
27 |
28 | std::cout << "valid solutions:" << results.size() << std::endl;
29 |
30 | // std::sort(results.begin(), results.end(), [](const auto& lhs, const auto& rhs) { return lhs->error < rhs->error; });
31 | std::sort(results.begin(), results.end(), [](const auto& lhs, const auto& rhs) { return lhs->inlier_fraction > rhs->inlier_fraction; });
32 | if (results.size() > max_num_candidates) {
33 | results.erase(results.begin() + max_num_candidates, results.end());
34 | }
35 | return *this;
36 | }
37 | std::vector results;
38 | };
39 |
40 | namespace global_localization {
41 | class BBSLocalization;
42 |
43 | class GlobalLocalizationBBS {
44 | public:
45 | GlobalLocalizationBBS(rclcpp::Node *node);
46 | ~GlobalLocalizationBBS() ;
47 | // add api
48 |
49 | void set_global_map(pcl::PointCloud::ConstPtr cloud);
50 |
51 | bool globalLocalization(pcl::PointCloud::ConstPtr cloud, const std::string& predict_txt, Eigen::Matrix4f& result);
52 | bool globalLocalization(pcl::PointCloud::ConstPtr cloud, const Eigen::Matrix4f& predict_pose, Eigen::Matrix4f& result);
53 | bool globalLocalization(pcl::PointCloud::ConstPtr cloud, Eigen::Matrix4f& result);
54 |
55 | void set_match_map(pcl::PointCloud::ConstPtr cloud);
56 |
57 | GlobalLocalizationResults query(pcl::PointCloud::ConstPtr cloud);
58 |
59 | private:
60 | using Points2D = std::vector>;
61 | Points2D slice(const pcl::PointCloud& cloud, double min_z, double max_z) const;
62 |
63 | pcl::PointCloud::Ptr unslice(const Points2D& points);
64 | // raw map
65 | pcl::PointCloud::ConstPtr map_;
66 | pcl::NormalDistributionsTransform ndt;
67 | protected:
68 | std::unique_ptr bbs;
69 | rclcpp::Node *node_;
70 |
71 | };
72 | } // namespace global_localization
73 |
74 |
75 | #endif //LOCALIZATION_GLOBAL_LOCALIZATION_H
76 |
--------------------------------------------------------------------------------
/include/global_localization/occupancy_gridmap.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALIZATION_OCCUPANCY_GRIDMAP_H
2 | #define LOCALIZATION_OCCUPANCY_GRIDMAP_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace global_localization {
10 |
11 | class OccupancyGridMap {
12 | public:
13 | using Ptr = std::shared_ptr;
14 |
15 | OccupancyGridMap(double resolution, const cv::Mat& values) {
16 | this->resolution = resolution;
17 | this->values = values;
18 | }
19 |
20 | OccupancyGridMap(double resolution, int width, int height) {
21 | this->resolution = resolution;
22 | values = cv::Mat1f(height, width, 0.0f);
23 | }
24 |
25 | double grid_resolution() const { return resolution; }
26 | int width() const { return values.cols; }
27 | int height() const { return values.rows; }
28 | const float* data() const { return reinterpret_cast(values.data); }
29 |
30 | void insert_points(const std::vector>& points, int min_points_per_grid) {
31 | for (const auto& pt : points) {
32 | auto loc = grid_loc(pt);
33 | if (in_map(loc)) {
34 | value(loc) += 1;
35 | }
36 | }
37 |
38 | values /= min_points_per_grid;
39 | for (auto& x : values) {
40 | x = std::min(x, 1.0f);
41 | }
42 | }
43 |
44 | double calc_score(const std::vector>& points) const {
45 | double sum_dists = 0.0;
46 | for (const auto& pt : points) {
47 | auto loc = grid_loc(pt);
48 | loc.x() = std::max(0, std::min(values.cols - 1, loc.x()));
49 | loc.y() = std::max(0, std::min(values.rows - 1, loc.y()));
50 | sum_dists += value(loc);
51 | }
52 |
53 | return sum_dists;
54 | }
55 |
56 | OccupancyGridMap::Ptr pyramid_up() const {
57 | cv::Mat1f small_map(values.rows / 2, values.cols / 2);
58 | for (int i = 0; i < values.rows / 2; i++) {
59 | for (int j = 0; j < values.cols / 2; j++) {
60 | float x = values.at(i * 2, j * 2);
61 | x = std::max(x, values.at(i * 2 + 1, j * 2));
62 | x = std::max(x, values.at(i * 2, j * 2 + 1));
63 | x = std::max(x, values.at(i * 2 + 1, j * 2 + 1));
64 | small_map.at(i, j) = x;
65 | }
66 | }
67 |
68 | return std::make_shared(resolution * 2.0, small_map);
69 | }
70 |
71 | nav_msgs::msg::OccupancyGrid::ConstSharedPtr to_rosmsg() {
72 | nav_msgs::msg::OccupancyGrid::ConstSharedPtr msg(new nav_msgs::msg::OccupancyGrid);
73 | // TODO
74 | }
75 |
76 | private:
77 | bool in_map(const Eigen::Vector2i& pix) const {
78 | bool left_bound = (pix.array() >= Eigen::Array2i::Zero()).all();
79 | bool right_bound = (pix.array() < Eigen::Array2i(values.cols, values.rows)).all();
80 | return left_bound && right_bound;
81 | }
82 |
83 | float value(const Eigen::Vector2i& loc) const { return values.at(loc.y(), loc.x()); }
84 | float& value(const Eigen::Vector2i& loc) { return values.at(loc.y(), loc.x()); }
85 |
86 | Eigen::Vector2i grid_loc(const Eigen::Vector2f& pt) const {
87 | Eigen::Vector2i loc = (pt / resolution).array().floor().cast();
88 | Eigen::Vector2i offset = Eigen::Vector2i(values.cols / 2, values.rows / 2);
89 | return loc + offset;
90 | }
91 |
92 | private:
93 | double resolution;
94 | cv::Mat1f values;
95 | };
96 |
97 | } // namespace global_localization
98 |
99 | #endif //LOCALIZATION_OCCUPANCY_GRIDMAP_H
100 |
--------------------------------------------------------------------------------
/include/laser_odometry/laser_odometry.h:
--------------------------------------------------------------------------------
1 | #ifndef LASER_ODOMETRY_LASER_ODOMETRY_H
2 | #define LASER_ODOMETRY_LASER_ODOMETRY_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "rclcpp/rclcpp.hpp"
8 |
9 | class laserOdometry
10 | {
11 | public:
12 | laserOdometry(rclcpp::Node *node);
13 | Eigen::Matrix4f addFrame(const pcl::PointCloud::Ptr& point);
14 | Eigen::Matrix4f addFrame(const pcl::PointCloud::Ptr& point, const Eigen::Matrix4f& predict, bool &accept);
15 | pcl::PointCloud::Ptr downSample(const pcl::PointCloud::Ptr& cloud, float resolution);
16 | void updateLocalMap(const pcl::PointCloud::Ptr& keyFrame, const Eigen::Matrix4f& pose);
17 | pcl::PointCloud::Ptr getLocalMap(){return local_map_cloud;}
18 |
19 | private:
20 |
21 | std::deque::Ptr> local_map;
22 | std::deque local_map_pose;
23 | pcl::PointCloud::Ptr local_map_cloud;
24 | Eigen::Matrix4f odometry;
25 | Eigen::Matrix4f nextGuess;
26 | // pcl::PointCloud::Ptr lastKf;
27 | pcl::PointCloud::Ptr align;
28 | pcl::NormalDistributionsTransform ndt;
29 | rclcpp::Node *node_;
30 | };
31 |
32 | #endif //LASER_ODOMETRY_LASER_ODOMETRY_H
33 |
--------------------------------------------------------------------------------
/include/localization/UKF.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALIZATION_UKF_H
2 | #define LOCALIZATION_UKF_H
3 | #include "eigen3/Eigen/Core"
4 | #include "eigen3/Eigen/Geometry"
5 | #include "rclcpp/rclcpp.hpp"
6 | namespace laser_localization
7 | {
8 | class filter
9 | {
10 | public:
11 | filter(rclcpp::Node *node);
12 | Eigen::Vector3f predict(const Eigen::Matrix4f& update);
13 | Eigen::Vector3f update(const Eigen::Vector3f& p, const Eigen::Quaternionf& a);
14 | void set_pos(const Eigen::Vector3f& p, const Eigen::Quaternionf& a);
15 | Eigen::Matrix4f get_pos();
16 | private:
17 | float k1_;
18 | bool first_ = true;
19 |
20 | Eigen::Vector3f pos_;
21 | Eigen::Quaternionf angular_;
22 | rclcpp::Node *node_;
23 | };
24 |
25 | class UKF
26 | {
27 | public:
28 | UKF();
29 |
30 | };
31 | }
32 | #endif //LOCALIZATION_UKF_H
33 |
--------------------------------------------------------------------------------
/include/localization/localization.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALIZATION_LOCALIZATION_H
2 | #define LOCALIZATION_LOCALIZATION_H
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "localization/UKF.h"
9 | #include "error_estimater/error_estimater.h"
10 | #include
11 | #include
12 | #include "rclcpp/rclcpp.hpp"
13 |
14 | namespace laser_localization
15 | {
16 | class localization
17 | {
18 | public:
19 | localization(rclcpp::Node *node);
20 | void update_local_map(const Eigen::Matrix4f& pose);
21 | void update_pos(const geometry_msgs::msg::Twist::SharedPtr& cmd, float dt);
22 | void predict(const Eigen::Matrix4f& update);
23 | bool correct(pcl::PointCloud::Ptr cloud, int times = 10);
24 | void init_localization(const Eigen::Matrix4f& laser_base);
25 |
26 | void set_inv_odom_base(Eigen::Matrix4f p){inv_odom_base_ = p;}
27 | geometry_msgs::msg::Transform get_pos(){return pos_;}
28 | Eigen::Matrix4f get_estimate_pos(){return estimate_.get_pos();}
29 | pcl::PointCloud::Ptr get_map(){return map_points_;}
30 | pcl::PointCloud::Ptr get_view_map(){return map_points_view_;}
31 | pcl::PointCloud::Ptr get_aligned(){return aligned_;}
32 | private:
33 | Eigen::Matrix4f ndt_match(pcl::PointCloud::ConstPtr cloud, Eigen::Matrix4f gauss, int times);
34 | pcl::Registration::Ptr create_registration() ;
35 | pcl::PointCloud::Ptr map_points_;
36 | pcl::PointCloud::Ptr local_map_points_;
37 | Eigen::Matrix4f local_map_pose_;
38 | pcl::PointCloud::Ptr map_points_view_;
39 | pcl::PointCloud::Ptr aligned_;
40 | geometry_msgs::msg::Transform pos_;
41 | geometry_msgs::msg::Transform Zero;
42 | filter estimate_;
43 | pcl::Registration::Ptr ndt_;
44 | Eigen::Matrix4f inv_odom_base_;
45 | rclcpp::Node *node_;
46 |
47 | Eigen::Vector3f last_map_pos;
48 | pcl::PointCloud::Ptr local_map;
49 | };
50 | }// laser_localization
51 |
52 | #endif //LOCALIZATION_LOCALIZATION_H
53 |
--------------------------------------------------------------------------------
/include/localization/tools.h:
--------------------------------------------------------------------------------
1 | #ifndef LASER_LOCALIZATION_TOOLS_H
2 | #define LASER_LOCALIZATION_TOOLS_H
3 | #include
4 | #include
5 | #include
6 | #include "fstream"
7 | #include "unistd.h"
8 | #include "sys/types.h"
9 | #include "sys/stat.h"
10 | #include
11 |
12 | bool read_position(const std::string& file, Eigen::Matrix4f& base);
13 |
14 | void write_position(const std::string& file,const Eigen::Matrix4f& position);
15 |
16 | pcl::PointCloud::Ptr downSample(pcl::PointCloud::ConstPtr cloud, double resolution);
17 |
18 | #endif //LASER_LOCALIZATION_TOOLS_H
19 |
--------------------------------------------------------------------------------
/launch/kitti_test.py:
--------------------------------------------------------------------------------
1 | from launch import LaunchDescription
2 | from launch_ros.actions import Node
3 |
4 | def generate_launch_description():
5 | return LaunchDescription([
6 | Node(
7 | package='laser_localization',
8 | node_executable='laser_localization',
9 | node_name='laser_localization',
10 | parameters=[
11 | {"localization/mode": 1},
12 | {"bbs/max_range": 15.0},
13 | {"bbs/min_tx": -50.0},
14 | {"bbs/max_tx": 50.0},
15 | {"bbs/min_ty": -50.0},
16 | {"bbs/max_ty": 50.0},
17 | {"bbs/min_theta": -3.14},
18 | {"bbs/max_theta": 3.14},
19 | {"bbs/map_min_z": -0.2},
20 | {"bbs/map_max_z": 1.2},
21 | {"bbs/map_width": 512},
22 | {"bbs/map_height": 512},
23 | {"bbs/map_resolution": 0.5},
24 | {"bbs/max_points_pre_cell": 5},
25 | {"bbs/map_pyramid_level": 6},
26 | {"bbs/scan_min_z": -0.2},
27 | {"bbs/scan_max_z": 1.2},
28 | {"bbs/map_filter_resolution": 0.5},
29 | {"bbs/scan_filter_resolution": 0.5},
30 | {"global_map_width": 100.0},
31 | {"global_map_height": 100.0},
32 | {"ndt/resolution": 0.8},
33 | {"ndt/step_size": 0.2},
34 | {"ndt/epsilon": 0.01},
35 | {"ndt/max_iterations": 30.0},
36 | {"ndt/frame_resolution": 0.5},
37 | {"odom/kf_distance": 2.0},
38 | {"odom/local_map_size": 10},
39 | {"ndt/local_map_resolution": 0.5},
40 | {"global_map": "../map/kitti.pcd"},
41 | {"odom_frame": "odom"},
42 | {"base_link_frame": "base_link"},
43 | {"laser_frame": "os_sensor"},
44 | {"laser_topic": "/points"},
45 | {"pose_save": "../map/pos.txt"},
46 | {"correct_count": 5},
47 | {"global_resolution": 1.3},
48 | {"global_frame_resolution": 1},
49 | {"global_view_resolution": 3},
50 | {"local_map_size": 100},
51 | {"localization/ndt_resolution": 1},
52 | {"localization/ndt_step_size": 0.1},
53 | {"localization/ndt_epsilon": 0.01},
54 | {"initial_pose_save": "../map/init_pos.txt"},
55 | ]
56 | ),
57 | Node(
58 | package='laser_localization',
59 | node_executable='kitti',
60 | node_name='kitti',
61 | parameters=[
62 | {"kitti_path": "/media/lyc/软件/1-10/05/velodyne/"},
63 | ]
64 | ),
65 | ])
--------------------------------------------------------------------------------
/launch/localization.py:
--------------------------------------------------------------------------------
1 | from launch import LaunchDescription
2 | from launch_ros.actions import Node
3 |
4 | def generate_launch_description():
5 | return LaunchDescription([
6 | Node(
7 | package='laser_localization',
8 | node_executable='laser_localization',
9 | node_name='laser_localization',
10 | parameters=[
11 | {"localization/mode": 2},
12 | {"bbs/max_range": 15.0},
13 | {"bbs/min_tx": -50.0},
14 | {"bbs/max_tx": 50.0},
15 | {"bbs/min_ty": -50.0},
16 | {"bbs/max_ty": 50.0},
17 | {"bbs/min_theta": -3.14},
18 | {"bbs/max_theta": 3.14},
19 | {"bbs/map_min_z": -0.2},
20 | {"bbs/map_max_z": 1.2},
21 | {"bbs/map_width": 512},
22 | {"bbs/map_height": 512},
23 | {"bbs/map_resolution": 0.5},
24 | {"bbs/max_points_pre_cell": 5},
25 | {"bbs/map_pyramid_level": 6},
26 | {"bbs/scan_min_z": -0.2},
27 | {"bbs/scan_max_z": 1.2},
28 | {"bbs/map_filter_resolution": 0.5},
29 | {"bbs/scan_filter_resolution": 0.5},
30 | {"global_map_width": 100.0},
31 | {"global_map_height": 100.0},
32 | {"ndt/resolution": 0.8},
33 | {"ndt/step_size": 0.2},
34 | {"ndt/epsilon": 0.01},
35 | {"ndt/max_iterations": 30.0},
36 | {"ndt/frame_resolution": 0.5},
37 | {"odom/kf_distance": 2.0},
38 | {"odom/local_map_size": 10},
39 | {"ndt/local_map_resolution": 0.5},
40 | {"global_map": "../map/map.pcd"},
41 | {"odom_frame": "odom"},
42 | {"base_link_frame": "base_link"},
43 | {"laser_frame": "os_sensor"},
44 | {"laser_topic": "/points"},
45 | {"pose_save": "../map/pos.txt"},
46 | {"correct_count": 5},
47 | {"global_resolution": 1.3},
48 | {"global_frame_resolution": 1},
49 | {"global_view_resolution": 3},
50 | {"local_map_size": 100},
51 | {"localization/ndt_resolution": 1},
52 | {"localization/ndt_step_size": 0.1},
53 | {"localization/ndt_epsilon": 0.01},
54 | {"initial_pose_save": "../map/init_pos.txt"},
55 | ]
56 | ),
57 | ])
--------------------------------------------------------------------------------
/launch/single_laser.py:
--------------------------------------------------------------------------------
1 | from launch import LaunchDescription
2 | from launch_ros.actions import Node
3 |
4 | def generate_launch_description():
5 | return LaunchDescription([
6 | Node(
7 | package='laser_localization',
8 | node_executable='laser_localization',
9 | node_name='laser_localization',
10 | parameters=[
11 | {"localization/mode": 1},
12 | {"bbs/max_range": 15.0},
13 | {"bbs/min_tx": -50.0},
14 | {"bbs/max_tx": 50.0},
15 | {"bbs/min_ty": -50.0},
16 | {"bbs/max_ty": 50.0},
17 | {"bbs/min_theta": -3.14},
18 | {"bbs/max_theta": 3.14},
19 | {"bbs/map_min_z": -0.2},
20 | {"bbs/map_max_z": 1.2},
21 | {"bbs/map_width": 512},
22 | {"bbs/map_height": 512},
23 | {"bbs/map_resolution": 0.5},
24 | {"bbs/max_points_pre_cell": 5},
25 | {"bbs/map_pyramid_level": 6},
26 | {"bbs/scan_min_z": -0.2},
27 | {"bbs/scan_max_z": 1.2},
28 | {"bbs/map_filter_resolution": 0.5},
29 | {"bbs/scan_filter_resolution": 0.5},
30 | {"global_map_width": 100.0},
31 | {"global_map_height": 100.0},
32 | {"ndt/resolution": 0.8},
33 | {"ndt/step_size": 0.2},
34 | {"ndt/epsilon": 0.01},
35 | {"ndt/max_iterations": 30.0},
36 | {"ndt/frame_resolution": 0.5},
37 | {"odom/kf_distance": 2.0},
38 | {"odom/local_map_size": 10},
39 | {"ndt/local_map_resolution": 0.5},
40 | {"global_map": "/home/sa/code/robot_ws/src/inspection_3d_robot/laser_localization/map/map.pcd"},
41 | {"odom_frame": "odom"},
42 | {"base_link_frame": "base_link"},
43 | {"laser_frame": "os_sensor"},
44 | {"laser_topic": "/points"},
45 | {"pose_save": "/home/sa/code/robot_ws/src/inspection_3d_robot/laser_localization/map/pos.txt"},
46 | {"correct_count": 5},
47 | {"global_resolution": 1.3},
48 | {"global_frame_resolution": 1},
49 | {"global_view_resolution": 3},
50 | {"local_map_size": 100},
51 | {"localization/ndt_resolution": 1},
52 | {"localization/ndt_step_size": 0.1},
53 | {"localization/ndt_epsilon": 0.01},
54 | {"initial_pose_save": "/home/sa/code/robot_ws/src/inspection_3d_robot/laser_localization/map/init_pos.txt"},
55 | ]
56 | ),
57 | ])
--------------------------------------------------------------------------------
/map/map.pcd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linyicheng1/laser_localization/667855e6374c92c7d4af95fa464da2347f91746e/map/map.pcd
--------------------------------------------------------------------------------
/map/pos.txt:
--------------------------------------------------------------------------------
1 | 0.924372,-0.381237,0.0139672,0,0.381013,0.924426,0.0162771,0,-0.0191171,-0.00972437,0.99977,0,-1.31551,0.35245,0.0542225,1,
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | laser_localization
5 | 0.0.0
6 | TODO: Package description
7 | lyc
8 | TODO: License declaration
9 |
10 | ament_cmake
11 |
12 | ament_lint_auto
13 | ament_lint_common
14 |
15 |
16 | ament_cmake
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/error_estimater/error_estimater.cpp:
--------------------------------------------------------------------------------
1 | #include "error_estimater/error_estimater.h"
2 | #include
3 | #include
4 |
5 |
6 | namespace match_error
7 | {
8 |
9 | void MatchingCostEvaluaterFlann::set_target(pcl::PointCloud::ConstPtr cloud,
10 | double max_correspondence_distance)
11 | {
12 | map = cloud;
13 | max_correspondence_distance_sq = max_correspondence_distance * max_correspondence_distance;
14 | tree.reset(new pcl::KdTreeFLANN);
15 | tree->setInputCloud(cloud);
16 |
17 | // max_correspondence_distance_sq = max_correspondence_distance * max_correspondence_distance;
18 | //
19 | // voxels.reset(new VoxelSet(max_correspondence_distance));
20 | // voxels->set_cloud(cloud);
21 | }
22 |
23 | double MatchingCostEvaluaterFlann::calc_matching_error(const pcl::PointCloud &cloud,
24 | const Eigen::Matrix4f &transformation,
25 | double *inlier_fraction)
26 | {
27 | int num_inliers = 0;
28 | double matching_error = 0.0;
29 |
30 | pcl::PointCloud::Ptr transformed(new pcl::PointCloud());
31 | pcl::transformPointCloud(cloud, *transformed, transformation.inverse());
32 |
33 | // pcl::visualization::PCLVisualizer viewer("pcd viewer");
34 | // pcl::visualization::PointCloudColorHandlerCustom c1(transformed, 0, 255, 0);
35 | // pcl::visualization::PointCloudColorHandlerCustom c2(map, 255, 0, 0);
36 | // viewer.addPointCloud(map, c2, "map");
37 | // viewer.addPointCloud(transformed, c1, "aligned");
38 | // viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "aligned");
39 | // viewer.spin();
40 |
41 | std::vector indices;
42 | std::vector sq_dists;
43 | for (int i = 0; i < transformed->size(); i++)
44 | {
45 | tree->nearestKSearch(transformed->at(i), 1, indices, sq_dists);
46 | if (sq_dists[0] < max_correspondence_distance_sq)
47 | {
48 | num_inliers++;
49 | matching_error += sq_dists[0];
50 | }
51 | }
52 |
53 | if (inlier_fraction)
54 | {
55 | *inlier_fraction = static_cast(num_inliers) / cloud.size();
56 | }
57 | return num_inliers ? matching_error / num_inliers : std::numeric_limits::max();
58 | // int num_inliers = 0;
59 | // double matching_error = 0.0;
60 | //
61 | // pcl::PointCloud transformed;
62 | // pcl::transformPointCloud(cloud, transformed, transformation);
63 | //
64 | // return voxels->matching_error(transformed, inlier_fraction);
65 | }
66 | }
--------------------------------------------------------------------------------
/src/error_estimater/voxelset.cpp:
--------------------------------------------------------------------------------
1 | #include "error_estimater/voxelset.h"
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | namespace match_error {
10 |
11 | size_t Vector3iHash::operator()(const Eigen::Vector3i& x) const {
12 | size_t seed = 0;
13 | boost::hash_combine(seed, x[0]);
14 | boost::hash_combine(seed, x[1]);
15 | boost::hash_combine(seed, x[2]);
16 | return seed;
17 | }
18 |
19 | VoxelSet::VoxelSet(double max_correspondence_distance) : resolution(max_correspondence_distance) {}
20 |
21 | void VoxelSet::set_cloud(pcl::PointCloud::ConstPtr cloud) {
22 | voxels.clear();
23 |
24 | std::vector> offsets;
25 | offsets.reserve(9);
26 | for (int i = -1; i <= 1; i++) {
27 | for (int j = -1; j <= 1; j++) {
28 | for (int k = -1; k <= 1; k++) {
29 | offsets.push_back(Eigen::Vector3i(i, j, k));
30 | }
31 | }
32 | }
33 |
34 | for (const auto& pt : *cloud) {
35 | Eigen::Vector3i coord = voxel_coord(pt.getVector4fMap());
36 | for (const auto& offset : offsets) {
37 | Eigen::Vector4f center = voxel_center(coord + offset);
38 | if ((center - pt.getVector4fMap()).squaredNorm() < resolution * resolution) {
39 | voxels.insert(coord + offset);
40 | }
41 | }
42 | }
43 | }
44 |
45 | double VoxelSet::matching_error(const pcl::PointCloud& cloud, double* inlier_fraction) const {
46 | int num_inliers = 0;
47 | double errors = 0.0;
48 |
49 | for (const auto& pt : cloud) {
50 | Eigen::Vector3i coord = voxel_coord(pt.getVector4fMap());
51 | if (voxels.find(coord) != voxels.end()) {
52 | Eigen::Vector4f center = voxel_center(coord);
53 | num_inliers++;
54 | errors += (pt.getVector4fMap() - center).squaredNorm();
55 | }
56 | }
57 |
58 | *inlier_fraction = static_cast(num_inliers) / cloud.size();
59 | return num_inliers ? errors / num_inliers : std::numeric_limits::max();
60 | }
61 |
62 | Eigen::Vector3i VoxelSet::voxel_coord(const Eigen::Vector4f& x) const {
63 | return (x.array() / resolution - 0.5).floor().cast().head<3>();
64 | }
65 |
66 | Eigen::Vector4f VoxelSet::voxel_center(const Eigen::Vector3i& coord) const {
67 | Eigen::Vector3f origin = (coord.template cast().array() + 0.5) * resolution;
68 | Eigen::Vector3f offset = Eigen::Vector3f::Ones() * resolution / 2.0f;
69 | return (Eigen::Vector4f() << origin + offset, 1.0).finished();
70 | }
71 |
72 | } // namespace hdl_global_localization
73 |
--------------------------------------------------------------------------------
/src/global_localization/bbs_localization.cpp:
--------------------------------------------------------------------------------
1 | #include "bbs_localization.h"
2 | #include "occupancy_gridmap.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | namespace global_localization {
9 |
10 | DiscreteTransformation::DiscreteTransformation() {
11 | this->level = -1;
12 | }
13 |
14 | DiscreteTransformation::DiscreteTransformation(int level, int x, int y, int theta) {
15 | this->level = level;
16 | this->x = x;
17 | this->y = y;
18 | this->theta = theta;
19 | this->score = 0.0;
20 | }
21 |
22 | DiscreteTransformation::~DiscreteTransformation() {}
23 |
24 | bool DiscreteTransformation::operator<(const DiscreteTransformation& rhs) const {
25 | return score < rhs.score;
26 | }
27 |
28 | bool DiscreteTransformation::is_leaf() const {
29 | return level == 0;
30 | }
31 |
32 | Eigen::Isometry2f DiscreteTransformation::transformation(double theta_resolution, const std::vector>& gridmap_pyramid) {
33 | double trans_resolution = gridmap_pyramid[level]->grid_resolution();
34 |
35 | Eigen::Isometry2f trans = Eigen::Isometry2f::Identity();
36 | trans.linear() = Eigen::Rotation2Df(theta_resolution * theta).toRotationMatrix();
37 | trans.translation() = Eigen::Vector2f(trans_resolution * x, trans_resolution * y);
38 |
39 | return trans;
40 | }
41 |
42 | DiscreteTransformation::Points DiscreteTransformation::transform(const Points& points, double trans_resolution, double theta_resolution) {
43 | Points transformed(points.size());
44 |
45 | Eigen::Map> src(points[0].data(), 2, points.size());
46 | Eigen::Map> dst(transformed[0].data(), 2, points.size());
47 |
48 | Eigen::Matrix2f rot = Eigen::Rotation2Df(theta_resolution * theta).toRotationMatrix();
49 | Eigen::Vector2f trans(trans_resolution * x, trans_resolution * y);
50 |
51 | dst = (rot * src).colwise() + trans;
52 |
53 | return transformed;
54 | }
55 |
56 | double DiscreteTransformation::calc_score(const Points& points, double theta_resolution, const std::vector>& gridmap_pyramid) {
57 | const auto& gridmap = gridmap_pyramid[level];
58 | auto transformed = transform(points, gridmap->grid_resolution(), theta_resolution);
59 | score = gridmap->calc_score(transformed);
60 | return score;
61 | }
62 |
63 | std::vector DiscreteTransformation::branch() {
64 | std::vector b;
65 | b.reserve(4);
66 | b.emplace_back(DiscreteTransformation(level - 1, x * 2, y * 2, theta));
67 | b.emplace_back(DiscreteTransformation(level - 1, x * 2 + 1, y * 2, theta));
68 | b.emplace_back(DiscreteTransformation(level - 1, x * 2, y * 2 + 1, theta));
69 | b.emplace_back(DiscreteTransformation(level - 1, x * 2 + 1, y * 2 + 1, theta));
70 | return b;
71 | }
72 |
73 | BBSLocalization::BBSLocalization(const BBSParams& params) : params(params) {}
74 |
75 | BBSLocalization::~BBSLocalization() {}
76 |
77 | void BBSLocalization::set_map(const BBSLocalization::Points& map_points, double resolution, int width, int height, int pyramid_levels, int max_points_per_cell) {
78 | gridmap_pyramid.resize(pyramid_levels);
79 | gridmap_pyramid[0].reset(new OccupancyGridMap(resolution, width, height));
80 | gridmap_pyramid[0]->insert_points(map_points, max_points_per_cell);
81 |
82 | for (int i = 1; i < pyramid_levels; i++) {
83 | gridmap_pyramid[i] = gridmap_pyramid[i - 1]->pyramid_up();
84 | }
85 | }
86 |
87 | boost::optional BBSLocalization::localize(const BBSLocalization::Points& scan_points, double min_score, double* best_score) {
88 | theta_resolution = std::acos(1 - std::pow(gridmap_pyramid[0]->grid_resolution(), 2) / (2 * std::pow(params.max_range, 2)));
89 |
90 | double best_score_storage;
91 | best_score = best_score ? best_score : &best_score_storage;
92 |
93 | *best_score = min_score;
94 | boost::optional best_trans;
95 |
96 | auto trans_queue = create_init_transset(scan_points);
97 |
98 | while (!trans_queue.empty()) {
99 | // std::cout << trans_queue.size() << std::endl;
100 |
101 | auto trans = trans_queue.top();
102 | trans_queue.pop();
103 |
104 | if (trans.score < *best_score) {
105 | continue;
106 | }
107 |
108 | if (trans.is_leaf()) {
109 | best_trans = trans;
110 | *best_score = trans.score;
111 | } else {
112 | auto children = trans.branch();
113 | for (auto& child : children) {
114 | child.calc_score(scan_points, theta_resolution, gridmap_pyramid);
115 | trans_queue.push(child);
116 | }
117 | }
118 | }
119 |
120 | if (best_trans == boost::none) {
121 | return boost::none;
122 | }
123 |
124 | return best_trans->transformation(theta_resolution, gridmap_pyramid);
125 | }
126 |
127 | std::shared_ptr BBSLocalization::gridmap() const {
128 | return gridmap_pyramid[0];
129 | }
130 |
131 | std::priority_queue BBSLocalization::create_init_transset(const Points& scan_points) const {
132 | double trans_res = gridmap_pyramid.back()->grid_resolution();
133 | std::pair tx_range(std::floor(params.min_tx / trans_res), std::ceil(params.max_tx / trans_res));
134 | std::pair ty_range(std::floor(params.min_ty / trans_res), std::ceil(params.max_ty / trans_res));
135 | std::pair theta_range(std::floor(params.min_theta / theta_resolution), std::ceil(params.max_theta / theta_resolution));
136 |
137 | std::vector transset;
138 | transset.reserve((tx_range.second - tx_range.first) * (ty_range.second - ty_range.first) * (theta_range.second - theta_range.first));
139 | for (int tx = tx_range.first; tx <= tx_range.second; tx++) {
140 | for (int ty = ty_range.first; ty <= ty_range.second; ty++) {
141 | for (int theta = theta_range.first; theta <= theta_range.second; theta++) {
142 | int level = gridmap_pyramid.size() - 1;
143 | transset.emplace_back(DiscreteTransformation(level, tx, ty, theta));
144 | }
145 | }
146 | }
147 |
148 | #pragma omp parallel for
149 | for (int i = 0; i < transset.size(); i++) {
150 | auto& trans = transset[i];
151 | const auto& gridmap = gridmap_pyramid[trans.level];
152 | trans.calc_score(scan_points, theta_resolution, gridmap_pyramid);
153 | }
154 |
155 | return std::priority_queue(transset.begin(), transset.end());
156 | }
157 |
158 | } // namespace global_localization
159 |
--------------------------------------------------------------------------------
/src/global_localization/global_localization.cpp:
--------------------------------------------------------------------------------
1 | #include "global_localization.h"
2 | #include "localization/tools.h"
3 | #include
4 | #include
5 |
6 | namespace global_localization {
7 |
8 | GlobalLocalizationBBS::GlobalLocalizationBBS(rclcpp::Node *node): node_(node) {
9 | float ndt_resolution, ndt_step_size, ndt_epsilon, ndt_max_iterations;
10 | node_->get_parameter("ndt/resolution", ndt_resolution);
11 | node_->get_parameter("ndt/step_size", ndt_step_size);
12 | node_->get_parameter("ndt/epsilon", ndt_epsilon);
13 | node_->get_parameter("ndt/max_iterations", ndt_max_iterations);
14 | ndt.setResolution(ndt_resolution);
15 | ndt.setStepSize(ndt_step_size);
16 | ndt.setTransformationEpsilon(ndt_epsilon);
17 | ndt.setMaximumIterations(ndt_max_iterations);
18 | }
19 |
20 | GlobalLocalizationBBS ::~GlobalLocalizationBBS() {}
21 |
22 | // raw map
23 | void GlobalLocalizationBBS::set_global_map(pcl::PointCloud::ConstPtr cloud){
24 | map_ = cloud;
25 | }
26 |
27 | bool GlobalLocalizationBBS::globalLocalization(pcl::PointCloud::ConstPtr cloud, const std::string& predict_txt, Eigen::Matrix4f& result){
28 | // 1. read path from txt
29 | Eigen::Matrix4f T;
30 | bool read_success = read_position(predict_txt, T);
31 | if (!read_success ||
32 | T.block<3, 3>(0,0).determinant() < 0.8 ||
33 | T.block<3, 3>(0,0).determinant() == NAN ||
34 | T.block<3, 3>(0,0).determinant() > 1.2 ||
35 | T(0, 3) == NAN || T(1, 3) == NAN || T(2, 3) == NAN ){
36 | // T is not available
37 | return globalLocalization(cloud, result);
38 | }
39 | else{
40 | // match by ndt
41 | pcl::PointCloud align;
42 | ndt.setInputTarget(map_);
43 | ndt.setInputSource(cloud);
44 | ndt.align(align, T);
45 | double score = ndt.getFitnessScore();
46 | std::cout<<"score "<::ConstPtr cloud, const Eigen::Matrix4f& predict_pose, Eigen::Matrix4f& result){
53 | pcl::PointCloud::Ptr new_map(new pcl::PointCloud);
54 | // trans
55 | Eigen::Isometry3f center_t = Eigen::Isometry3f::Identity();
56 | center_t.translation() = -predict_pose.block<3, 1>(0, 3);
57 | pcl::transformPointCloud(*map_, *new_map, center_t);
58 | // filter
59 | float map_resolution, scan_resolution;
60 | node_->get_parameter("bbs/map_filter_resolution", map_resolution);
61 | node_->get_parameter("bbs/scan_filter_resolution", scan_resolution);
62 | new_map = downSample(new_map, map_resolution);
63 | auto filtered = downSample(cloud, scan_resolution);
64 | // set match map
65 | set_match_map(new_map);
66 | // query
67 | auto re = query(filtered);
68 | re.sort(1);
69 | Eigen::Matrix4f T = re.results.at(0)->pose.matrix();
70 | T(2, 3) = 0;
71 | // match by ndt
72 | pcl::PointCloud align;
73 | ndt.setInputTarget(new_map);
74 | ndt.setInputSource(cloud);
75 | ndt.align(align, T);
76 | double score = ndt.getFitnessScore();
77 | std::cout<<"score "<::ConstPtr cloud, Eigen::Matrix4f& result){
83 | pcl::PointCloud::Ptr new_map(new pcl::PointCloud);
84 | // trans
85 | Eigen::Vector4f centroid;
86 | pcl::compute3DCentroid(*map_, centroid);
87 | Eigen::Isometry3f center_t = Eigen::Isometry3f::Identity();
88 | center_t.translation() = -centroid.block<3, 1>(0, 0);
89 | pcl::transformPointCloud(*map_, *new_map, center_t);
90 | // filter
91 | float map_resolution, scan_resolution;
92 | node_->get_parameter("bbs/map_filter_resolution", map_resolution);
93 | node_->get_parameter("bbs/scan_filter_resolution", scan_resolution);
94 | new_map = downSample(new_map, map_resolution);
95 | auto filtered = downSample(cloud, scan_resolution);
96 | // set match map
97 | {
98 | BBSParams params;
99 | double width, height;
100 | node_->get_parameter("global_map_width", width);
101 | node_->get_parameter("global_map_height", height);
102 | node_->get_parameter("bbs/max_range", params.max_range);
103 | params.min_tx = -width/2;
104 | params.max_tx = width/2;
105 | params.min_ty = -height/2;
106 | params.max_ty = height/2;
107 | node_->get_parameter("bbs/min_theta", params.min_theta);
108 | node_->get_parameter("bbs/max_theta", params.max_theta);
109 | bbs.reset(new BBSLocalization(params));
110 |
111 | double map_min_z = 2.0, map_max_z = 2.4;
112 | node_->get_parameter("bbs/map_min_z", map_min_z);
113 | node_->get_parameter("bbs/map_max_z", map_max_z);
114 | auto map_2d = slice(*new_map, map_min_z, map_max_z);
115 |
116 | if (map_2d.size() < 128) {
117 | printf("Num points in the sliced map is too small!!");
118 | printf("Change the slice range parameters!!");
119 | }
120 |
121 | int map_width = 512, map_height = 1024, map_pyramid_level = 6, max_points_per_cell = 5;
122 | double map_resolution;
123 | node_->get_parameter("bbs/map_width", map_width);
124 | node_->get_parameter("bbs/map_height", map_height);
125 | node_->get_parameter("bbs/map_resolution", map_resolution);
126 | node_->get_parameter("bbs/map_pyramid_level", map_pyramid_level);
127 | node_->get_parameter("bbs/max_points_per_cell", max_points_per_cell);
128 |
129 | bbs->set_map(map_2d, map_resolution, map_width, map_height, map_pyramid_level, max_points_per_cell);
130 |
131 | auto map_3d = unslice(map_2d);
132 | map_3d->header.frame_id = "map";
133 | }
134 | // query
135 | auto re = query(filtered);
136 | re.sort(1);
137 | if (re.results.empty())
138 | return false;
139 |
140 | Eigen::Matrix4f T = re.results.at(0)->pose.matrix();
141 | T(2, 3) = 0;
142 | // match by ndt
143 | pcl::PointCloud align;
144 | ndt.setInputTarget(new_map);
145 | ndt.setInputSource(cloud);
146 | ndt.align(align, T);
147 | double score = ndt.getFitnessScore();
148 | std::cout<<"score "<::ConstPtr cloud) {
154 |
155 | BBSParams params;
156 | node_->get_parameter("bbs/max_range", params.max_range);
157 | node_->get_parameter("bbs/min_tx", params.min_tx);
158 | node_->get_parameter("bbs/max_tx", params.max_tx);
159 | node_->get_parameter("bbs/min_ty", params.min_ty);
160 | node_->get_parameter("bbs/max_ty", params.max_ty);
161 | node_->get_parameter("bbs/min_theta", params.min_theta);
162 | node_->get_parameter("bbs/max_theta", params.max_theta);
163 | bbs.reset(new BBSLocalization(params));
164 |
165 | double map_min_z = 2.0, map_max_z = 2.4;
166 | node_->get_parameter("bbs/map_min_z", map_min_z);
167 | node_->get_parameter("bbs/map_max_z", map_max_z);
168 | auto map_2d = slice(*cloud, map_min_z, map_max_z);
169 |
170 | if (map_2d.size() < 128) {
171 | printf("Num points in the sliced map is too small!!");
172 | printf("Change the slice range parameters!!");
173 | }
174 |
175 | int map_width = 512, map_height = 1024, map_pyramid_level = 6, max_points_per_cell = 5;
176 | double map_resolution;
177 | node_->get_parameter("bbs/map_width", map_width);
178 | node_->get_parameter("bbs/map_height", map_height);
179 | node_->get_parameter("bbs/map_resolution", map_resolution);
180 | node_->get_parameter("bbs/map_pyramid_level", map_pyramid_level);
181 | node_->get_parameter("bbs/max_points_per_cell", max_points_per_cell);
182 |
183 | bbs->set_map(map_2d, map_resolution, map_width, map_height, map_pyramid_level, max_points_per_cell);
184 |
185 | auto map_3d = unslice(map_2d);
186 | map_3d->header.frame_id = "map";
187 | }
188 |
189 | GlobalLocalizationResults GlobalLocalizationBBS::query(pcl::PointCloud::ConstPtr cloud) {
190 | double scan_min_z = -0.2, scan_max_z = 0.2;
191 | node_->get_parameter("bbs/scan_min_z", scan_min_z);
192 | node_->get_parameter("bbs/scan_max_z", scan_max_z);
193 | auto scan_2d = slice(*cloud, scan_min_z, scan_max_z);
194 |
195 | std::vector results;
196 |
197 | if (scan_2d.size() < 32) {
198 | return GlobalLocalizationResults(results);
199 | }
200 |
201 | double best_score = 0.0;
202 | auto trans_2d = bbs->localize(scan_2d, 0.0, &best_score);
203 | if (trans_2d == boost::none) {
204 | return GlobalLocalizationResults(results);
205 | }
206 |
207 | Eigen::Isometry3f trans_3d = Eigen::Isometry3f::Identity();
208 | trans_3d.linear().block<2, 2>(0, 0) = trans_2d->linear();
209 | trans_3d.translation().head<2>() = trans_2d->translation();
210 |
211 | results.resize(1);
212 | results[0].reset(new GlobalLocalizationResult(best_score, best_score, trans_3d));
213 |
214 | return GlobalLocalizationResults(results);
215 | }
216 |
217 | GlobalLocalizationBBS::Points2D GlobalLocalizationBBS::slice(const pcl::PointCloud& cloud, double min_z, double max_z) const {
218 | Points2D points_2d;
219 | points_2d.reserve(cloud.size());
220 | for (auto i : cloud) {
221 | if (min_z < i.z && i.z < max_z) {
222 | points_2d.push_back(i.getVector3fMap().head<2>());
223 | }
224 | }
225 | return points_2d;
226 | }
227 |
228 | pcl::PointCloud::Ptr GlobalLocalizationBBS::unslice(const Points2D& points) {
229 | pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
230 | cloud->resize(points.size());
231 | for (int i = 0; i < points.size(); i++) {
232 | cloud->at(i).getVector3fMap().head<2>() = points[i];
233 | cloud->at(i).z = 0.0f;
234 | }
235 |
236 | return cloud;
237 | }
238 | } // namespace global_localization
--------------------------------------------------------------------------------
/src/laser_odometry/laser_odometry.cpp:
--------------------------------------------------------------------------------
1 | #include "laser_odometry.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | laserOdometry::laserOdometry(rclcpp::Node *node):
8 | align(new pcl::PointCloud()),
9 | local_map_cloud(new pcl::PointCloud()),
10 | node_(node)
11 | {
12 | float ndt_resolution, ndt_step_size, ndt_epsilon, ndt_max_iterations;
13 | node_->get_parameter("ndt/resolution", ndt_resolution);
14 | node_->get_parameter("ndt/step_size", ndt_step_size);
15 | node_->get_parameter("ndt/epsilon", ndt_epsilon);
16 | node_->get_parameter("ndt/max_iterations", ndt_max_iterations);
17 |
18 | ndt.setResolution(ndt_resolution);
19 | ndt.setStepSize(ndt_step_size);
20 | ndt.setTransformationEpsilon(ndt_epsilon);
21 | ndt.setMaximumIterations(ndt_max_iterations);
22 | }
23 |
24 |
25 | Eigen::Matrix4f laserOdometry::addFrame(const pcl::PointCloud::Ptr& point, const Eigen::Matrix4f& predict, bool &accept)
26 | {
27 | auto s = std::chrono::steady_clock::now();
28 | float frame_resolution = 1;
29 | node_->get_parameter("ndt/frame_resolution", frame_resolution);
30 |
31 | auto filter = downSample(point, frame_resolution);
32 | if (filter->points.size() < 400){
33 | odometry = odometry * predict;
34 | accept = true;
35 | return odometry;
36 | }
37 | std::vector indices;
38 | pcl::removeNaNFromPointCloud(*filter, *filter, indices);
39 | if (local_map.empty())
40 | {// first frame
41 | odometry = Eigen::Matrix4f::Identity();
42 | nextGuess = Eigen::Matrix4f::Identity();
43 | updateLocalMap(filter, Eigen::Matrix4f::Identity());
44 | return odometry;
45 | }
46 | ndt.setInputSource(filter);
47 |
48 | Eigen::Matrix4f odom_predict = odometry * predict;
49 | // double dt = (nextGuess.block<3, 1>(0, 3) - odom_predict.block<3, 1>(0, 3)).norm();
50 | //
51 | // auto angle0 = nextGuess.block<3, 3>(0, 0).eulerAngles(2, 1, 0);
52 | // auto angle1 = odom_predict.block<3, 3>(0, 0).eulerAngles(2, 1, 0);
53 | // double da = std::abs(angle0.z() - angle1.z());
54 | // if (da > M_PI)
55 | // da = da - 2*M_PI;
56 | // else if (da < -M_PI)
57 | // da = da += 2*M_PI;
58 | // std::cout<<" t0: "< 3){
69 | nextGuess.block<3,3>(0,0) = odom_predict.block<3,3>(0,0);
70 | ndt.align(*align, nextGuess);
71 | accept = false;
72 | }
73 | Eigen::Matrix4f delta = odometry.inverse() * ndt.getFinalTransformation();
74 | nextGuess = odometry * delta;
75 | odometry = ndt.getFinalTransformation();
76 |
77 | auto e2 = std::chrono::steady_clock::now();
78 |
79 |
80 | Eigen::Matrix4f distance = local_map_pose.back().inverse() * odometry;
81 | float kf_distance;
82 | node_->get_parameter("odom/kf_distance", kf_distance);
83 |
84 | if (fabs(distance(0, 3)) + fabs(distance(1, 3)) > kf_distance)
85 | {// new key frame
86 | updateLocalMap(filter, odometry);
87 | std::cout<<" add new key frame !";
88 | }
89 | auto e3 = std::chrono::steady_clock::now();
90 |
91 | //std::cout<<" total cost: "<(e3 - s).count()<<" ms align cost: "<(e2 - e1).count()<<" ms ";
92 |
93 | // std::cout<<" x: "<::Ptr &point)
101 | {
102 | auto s = std::chrono::steady_clock::now();
103 | float frame_resolution = 1;
104 | node_->get_parameter("ndt/frame_resolution", frame_resolution);
105 |
106 | auto filter = downSample(point, frame_resolution);
107 | std::vector indices;
108 | pcl::removeNaNFromPointCloud(*filter, *filter, indices);
109 | if (local_map.empty())
110 | {// first frame
111 | odometry = Eigen::Matrix4f::Identity();
112 | nextGuess = Eigen::Matrix4f::Identity();
113 | updateLocalMap(filter, Eigen::Matrix4f::Identity());
114 | return odometry;
115 | }
116 | ndt.setInputSource(filter);
117 | auto e1 = std::chrono::steady_clock::now();
118 | ndt.align(*align, nextGuess);
119 | auto e2 = std::chrono::steady_clock::now();
120 |
121 |
122 | Eigen::Matrix4f delta = odometry.inverse() * ndt.getFinalTransformation();
123 | odometry = ndt.getFinalTransformation();
124 | nextGuess = odometry * delta;
125 |
126 | Eigen::Matrix4f distance = local_map_pose.back().inverse() * odometry;
127 | float kf_distance;
128 | node_->get_parameter("odom/kf_distance", kf_distance);
129 |
130 | if (fabs(distance(0, 3)) + fabs(distance(1, 3)) > kf_distance)
131 | {// new key frame
132 | updateLocalMap(filter, odometry);
133 | std::cout<<" add new key frame !";
134 | }
135 | auto e3 = std::chrono::steady_clock::now();
136 |
137 | //std::cout<<" total cost: "<(e3 - s).count()<<" ms align cost: "<(e2 - e1).count()<<" ms ";
138 |
139 | // std::cout<<" x: "<::Ptr laserOdometry::downSample(const pcl::PointCloud::Ptr& cloud, float resolution)
148 | {
149 | pcl::PointCloud::Ptr filtered(new pcl::PointCloud);
150 |
151 | pcl::ApproximateVoxelGrid voxelGrid;
152 | voxelGrid.setLeafSize(resolution, resolution, resolution);
153 | voxelGrid.setInputCloud(cloud);
154 | voxelGrid.filter(*filtered);
155 | return filtered;
156 | }
157 |
158 | void laserOdometry::updateLocalMap(const pcl::PointCloud::Ptr& kf, const Eigen::Matrix4f& pose)
159 | {
160 | size_t local_map_size;
161 | node_->get_parameter("odom/local_map_size", local_map_size);
162 | local_map.push_back(kf);
163 | local_map_pose.push_back(pose);
164 | while (local_map.size() > local_map_size)
165 | {
166 | local_map.pop_front();
167 | local_map_pose.pop_front();
168 | }
169 | local_map_cloud.reset(new pcl::PointCloud());
170 | pcl::PointCloud transformed;
171 |
172 | for (size_t i = 0;i < local_map.size(); ++i)
173 | {
174 | pcl::transformPointCloud(*local_map.at(i), transformed, local_map_pose.at(i));
175 | *local_map_cloud += transformed;
176 | //std::cout<<" add key frame "<get_parameter("ndt/local_map_resolution", local_map_resolution);
186 | auto filter = downSample(local_map_cloud, local_map_resolution);
187 | ndt.setInputTarget(local_map_cloud);
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/localization/UKF.cpp:
--------------------------------------------------------------------------------
1 | #include "localization/UKF.h"
2 |
3 | #include
4 |
5 | namespace laser_localization
6 | {
7 | filter::filter(rclcpp::Node *node):
8 | node_(node)
9 | {
10 | node_->get_parameter("filter/k1", k1_);
11 | pos_.x() = 0;
12 | pos_.y() = 0;
13 | pos_.z() = 0;
14 | }
15 |
16 | Eigen::Vector3f filter::predict(const Eigen::Matrix4f& update)
17 | {
18 | Eigen::Matrix4f p = Eigen::Matrix4f::Identity();
19 | p.block<3,1>(0,3) = pos_;
20 | p.block<3,3>(0,0) = angular_.toRotationMatrix();
21 | p = p * update;
22 | pos_ = p.block<3,1>(0,3);
23 | angular_ = Eigen::Quaternionf(p.block<3,3>(0,0));
24 | }
25 |
26 | Eigen::Vector3f filter::update(const Eigen::Vector3f& p, const Eigen::Quaternionf& a)
27 | {
28 | pos_ = pos_*k1_ + p * (1 - k1_);
29 | angular_ = angular_.coeffs() * k1_ + a.coeffs() * (1 - k1_);
30 | angular_ = angular_.normalized();
31 | }
32 |
33 | void filter::set_pos(const Eigen::Vector3f& p, const Eigen::Quaternionf& a)
34 | {
35 | pos_ = p;
36 | angular_ = a;
37 | }
38 |
39 | Eigen::Matrix4f filter::get_pos()
40 | {
41 | Eigen::Matrix4f T = Eigen::Matrix4f::Identity();
42 | T.block<3, 1>(0, 3) = pos_;
43 | T.block<3, 3>(0, 0) = angular_.toRotationMatrix();
44 | return T;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/localization/localization.cpp:
--------------------------------------------------------------------------------
1 | #include "localization/localization.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "localization/tools.h"
10 |
11 | namespace laser_localization
12 | {
13 | localization::localization(rclcpp::Node *node):
14 | estimate_(filter(node)), node_(node)
15 | {
16 | Zero.rotation.w= 1;
17 | Zero.rotation.x= 0;
18 | Zero.rotation.y= 0;
19 | Zero.rotation.z= 0;
20 | Zero.translation.x = 0;
21 | Zero.translation.y = 0;
22 | Zero.translation.z = 0;
23 | pos_ = Zero;
24 |
25 | // init
26 | map_points_ = boost::make_shared>();
27 | std::string global_map;
28 | node_->get_parameter("global_map", global_map);
29 | pcl::io::loadPCDFile(global_map, *map_points_);
30 |
31 | float global_resolution, global_view_resolution;
32 | node_->get_parameter("global_resolution", global_resolution);
33 | node_->get_parameter("global_view_resolution", global_view_resolution);
34 |
35 | map_points_ = downSample(map_points_, global_resolution);
36 | map_points_view_ = downSample(map_points_, global_view_resolution);
37 |
38 | ndt_ = create_registration();
39 | ndt_->setInputTarget(map_points_);
40 |
41 | aligned_ = pcl::PointCloud::Ptr(new pcl::PointCloud());
42 | local_map_points_ = pcl::PointCloud::Ptr(new pcl::PointCloud());
43 | }
44 |
45 | void localization::update_local_map(const Eigen::Matrix4f& pose)
46 | {
47 | static bool init = false;
48 | double distance;
49 | node_->get_parameter("local_map_size", distance);
50 | Eigen::Vector2f delta = (pose.block<2, 1>(0, 3) - local_map_pose_.block<2, 1>(0, 3));
51 | if (delta.norm() < distance / 3 && init)
52 | return;
53 | local_map_pose_ = pose;
54 | local_map_points_->points.clear();
55 | local_map_points_->points.reserve(map_points_->points.size());
56 | for (const auto& pt:map_points_->points){
57 | if (std::abs(pt.x - pose(0, 3)) + std::abs(pt.y - pose(1, 3)) < distance){
58 | local_map_points_->points.emplace_back(pt);
59 | }
60 | }
61 | ndt_ = create_registration();
62 | ndt_->setInputTarget(map_points_);
63 | init = true;
64 | }
65 |
66 | // odom
67 | void localization::update_pos(const geometry_msgs::msg::Twist::SharedPtr& cmd, float dt)
68 | {
69 | static float yaw = 0;
70 | pos_.translation.x = (pos_.translation.x + cmd->linear.x * cos(yaw) * dt - cmd->linear.y * sin(yaw) * dt);
71 | pos_.translation.y = (pos_.translation.y + cmd->linear.x * sin(yaw) * dt + cmd->linear.y * cos(yaw) * dt);
72 | yaw += (float)cmd->angular.z * dt;
73 | Eigen::Quaternionf q(Eigen::AngleAxisf(yaw, Eigen::Vector3f::UnitZ()));
74 | pos_.rotation.x = q.x();
75 | pos_.rotation.y = q.y();
76 | pos_.rotation.z = q.z();
77 | pos_.rotation.w = q.w();
78 | }
79 |
80 | void localization::predict(const Eigen::Matrix4f& update)
81 | {
82 | estimate_.predict(update);
83 | }
84 |
85 | bool localization::correct(pcl::PointCloud::Ptr cloud, int times)
86 | {
87 | std::cout<<"start correcting !!!"<get_parameter("global_frame_resolution", resolution);
91 | cloud = downSample(cloud, resolution);
92 | if (cloud->points.size() < 400)
93 | return true;
94 | // 1. predict laser pos
95 | Eigen::Matrix4f predict_pos = estimate_.get_pos();
96 | // 2. laser match
97 | Eigen::Matrix4f laser_pos = ndt_match(std::move(cloud), predict_pos, times);
98 | // 3. base_link pos
99 | Eigen::Matrix4f base_pos = laser_pos;
100 | Eigen::Quaternionf q(base_pos.block<3,3>(0,0));
101 | // 4. update filter, get estimate pos
102 | if (ndt_->getFitnessScore() < 5){
103 | estimate_.update(base_pos.block<3, 1>(0, 3), q);
104 | // 5. calculate map -->odom pos
105 | auto filter_pos = estimate_.get_pos();
106 | Eigen::Matrix4f map_odom = (filter_pos * inv_odom_base_);
107 | Eigen::Quaternionf q1(map_odom.block<3,3>(0,0));
108 | pos_.translation.x = map_odom(0, 3);
109 | pos_.translation.y = map_odom(1, 3);
110 | pos_.translation.z = map_odom(2, 3);
111 | pos_.rotation.x = q1.x();
112 | pos_.rotation.y = q1.y();
113 | pos_.rotation.z = q1.z();
114 | pos_.rotation.w = q1.w();
115 | std::cout<<"end correcting !!!"<getFitnessScore()<(0, 0);
130 | Eigen::Quaternionf q(R);
131 | estimate_.set_pos(laser_base.block<3, 1>(0 ,3), q);
132 |
133 | auto filter_pos = estimate_.get_pos();
134 |
135 | Eigen::Matrix4f map_odom = (filter_pos * inv_odom_base_);
136 | Eigen::Quaternionf q1(map_odom.block<3,3>(0,0));
137 | pos_.translation.x = map_odom(0, 3);
138 | pos_.translation.y = map_odom(1, 3);
139 | pos_.translation.z = map_odom(2, 3);
140 | pos_.rotation.x = q1.x();
141 | pos_.rotation.y = q1.y();
142 | pos_.rotation.z = q1.z();
143 | pos_.rotation.w = q1.w();
144 | }
145 |
146 |
147 | Eigen::Matrix4f localization::ndt_match(pcl::PointCloud::ConstPtr cloud, Eigen::Matrix4f gauss, int times)
148 | {
149 | ndt_->setMaximumIterations(times);
150 | ndt_->setInputSource(cloud);
151 | ndt_->align(*aligned_, gauss);
152 | if (ndt_->getFitnessScore() > 3){
153 | std::cout<<" aligned score "<getFitnessScore()<<" re matching "<setMaximumIterations(times*2);
155 | ndt_->align(*aligned_, gauss);
156 | }
157 | // pcl::visualization::PCLVisualizer viewer("pcd viewer");
158 | // pcl::visualization::PointCloudColorHandlerCustom c1(aligned_, 0, 255, 0);
159 | // pcl::visualization::PointCloudColorHandlerCustom c2(map_points_, 255, 0, 0);
160 | // viewer.addPointCloud(map_points_, c2, "map");
161 | // viewer.addPointCloud(aligned_, c1, "aligned");
162 | // viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, "aligned");
163 | // viewer.spin();
164 | return ndt_->getFinalTransformation();
165 | }
166 |
167 | pcl::Registration::Ptr localization::create_registration()
168 | {
169 | float resolution, step_size, epsilon;
170 | node_->get_parameter("localization/ndt_resolution", resolution);
171 | node_->get_parameter("localization/ndt_step_size", step_size);
172 | node_->get_parameter("localization/ndt_epsilon", epsilon);
173 |
174 | pcl::NormalDistributionsTransform::Ptr ndt(new pcl::NormalDistributionsTransform());
175 | ndt->setResolution(resolution);
176 | ndt->setStepSize(step_size);
177 | ndt->setTransformationEpsilon(epsilon);
178 | return ndt;
179 | }
180 | }
181 |
182 |
--------------------------------------------------------------------------------
/src/localization/tools.cpp:
--------------------------------------------------------------------------------
1 | #include "localization/tools.h"
2 |
3 | bool read_position(const std::string& file, Eigen::Matrix4f& base)
4 | {
5 | struct stat buffer{};
6 | if(stat (file.c_str(), &buffer) == 0)
7 | {
8 | int size = boost::filesystem::file_size(file);
9 | if(boost::filesystem::file_size(file) > 80)
10 | {
11 | std::ifstream readFile(file);
12 | for(int i = 0;i < 16; i++)
13 | {
14 | std::string s;
15 | getline(readFile, s, ',');
16 | base(i) = atof(s.c_str());
17 | }
18 | return true;
19 | }
20 | else
21 | {
22 | return false;
23 | }
24 | }
25 | else
26 | {
27 | auto cmd = "touch " + file;
28 | system(cmd.c_str());
29 | return false;
30 | }
31 | }
32 |
33 | void write_position(const std::string& file,const Eigen::Matrix4f& position)
34 | {
35 | std::ofstream out;
36 | out.open(file.c_str());
37 | for(int i = 0;i < 16;i ++)
38 | {
39 | out<::Ptr downSample(pcl::PointCloud::ConstPtr cloud, double resolution)
46 | {
47 | pcl::PointCloud::Ptr filtered(new pcl::PointCloud);
48 | pcl::ApproximateVoxelGrid voxelGrid;
49 | voxelGrid.setLeafSize(resolution, resolution, resolution);
50 | voxelGrid.setInputCloud(cloud);
51 | voxelGrid.filter(*filtered);
52 | return filtered;
53 | }
54 |
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include "localization/localization.h"
2 | #include "global_localization.h"
3 | #include
4 | #include
5 | #include
6 | #include "rclcpp/rclcpp.hpp"
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include "eigen3/Eigen/Core"
15 | #include "unistd.h"
16 | #include "thread"
17 | #include "fstream"
18 | #include "laser_odometry.h"
19 | #include "localization/tools.h"
20 | #include "std_srvs/srv/set_bool.hpp"
21 |
22 |
23 | using namespace std::chrono_literals;
24 |
25 | class Publisher : public rclcpp::Node
26 | {
27 | using PointT = pcl::PointXYZI;
28 | public:
29 | Publisher()
30 | :Node("my_localization")
31 | {
32 | // params
33 | this->declare_parameter("bbs/max_range", 15.0);
34 | this->declare_parameter("bbs/min_tx", -50.0);
35 | this->declare_parameter("bbs/max_tx", 50.0);
36 | this->declare_parameter("bbs/min_ty", -50.0);
37 | this->declare_parameter("bbs/max_ty", 50.0);
38 | this->declare_parameter("bbs/min_theta", -3.14);
39 | this->declare_parameter("bbs/max_theta", 3.14);
40 | this->declare_parameter("bbs/map_min_z", -0.2);
41 | this->declare_parameter("bbs/map_max_z", 1.2);
42 | this->declare_parameter("bbs/map_width", 512);
43 | this->declare_parameter("bbs/map_height", 512);
44 | this->declare_parameter("bbs/map_resolution", 0.5);
45 | this->declare_parameter("bbs/max_points_pre_cell", 5);
46 | this->declare_parameter("bbs/map_pyramid_level", 6);
47 | this->declare_parameter("bbs/scan_min_z", -0.2);
48 | this->declare_parameter("bbs/scan_max_z", 1.2);
49 | this->declare_parameter("bbs/map_filter_resolution", 0.5);
50 | this->declare_parameter