├── CMakeLists.txt ├── README.md ├── config └── show.rviz ├── include ├── common.h ├── filter_extract_lib.hpp └── tic_toc.h ├── launch └── launch_filter_extract.launch ├── package.xml ├── resources └── edge_planar_points.gif └── src ├── extract_feature_node.cpp └── filter_node.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(filter_extract_feature) 3 | 4 | set(CMAKE_BUILD_TYPE "Release") 5 | set(CMAKE_CXX_FLAGS "-std=c++11 -fext-numeric-literals") 6 | set(CMAKE_CXX_FLAGS_RELEASE "-O2 -Wall -g") 7 | 8 | find_package(catkin REQUIRED COMPONENTS 9 | geometry_msgs 10 | nav_msgs 11 | sensor_msgs 12 | roscpp 13 | rospy 14 | rosbag 15 | std_msgs 16 | image_transport 17 | cv_bridge 18 | tf 19 | livox_ros_driver 20 | ) 21 | 22 | find_package(Eigen3 REQUIRED) 23 | find_package(PCL REQUIRED) 24 | find_package(OpenCV REQUIRED) 25 | find_package(Ceres REQUIRED) 26 | 27 | include_directories( 28 | include 29 | ${catkin_INCLUDE_DIRS} 30 | ${PCL_INCLUDE_DIRS} 31 | ${CERES_INCLUDE_DIRS} 32 | ${OpenCV_INCLUDE_DIRS}) 33 | 34 | 35 | catkin_package( 36 | CATKIN_DEPENDS geometry_msgs nav_msgs roscpp rospy std_msgs livox_ros_driver 37 | DEPENDS EIGEN3 PCL 38 | INCLUDE_DIRS include 39 | ) 40 | 41 | 42 | add_executable(filter_node src/filter_node.cpp) 43 | target_link_libraries(filter_node ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBS}) 44 | 45 | add_executable(extract_feature_node src/extract_feature_node.cpp) 46 | target_link_libraries(extract_feature_node ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${OpenCV_LIBS}) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 激光雷达点云滤波和特征提取 2 | ## 一、介绍 3 | 在激光雷达里程计和建图(Lidar Odometry and Mapping, LOAM)任务中,前端在里程计计算位姿前需要对传感器的点云流数据进行滤波(去除噪点)和特征提取处理。 4 | 5 | 其中点云滤波往往需要根据激光雷达具体的扫描方式、型号、特性以及后续任务等进行调整[4];在点云特征提取方面,论文[3]中率先提出了将点云曲率较大的边缘点(Edge Points)和曲率较小的平面点(Planar Points)作为点云的特征,进行后续ICP匹配,从而减少内存消耗量,后续LOAM论文基本沿用这一方法。 6 | 7 | 本仓库参考Horizon loam[5]中的点云滤波和特征点提取方法,其中Horizon激光雷达CustomMsg的Tag信息[7]可以用于基本的滤波操作。 8 | 9 | 调试环境: 10 | 11 | * Ubuntu 18.04 ROS melodic. [ROS installation](http://wiki.ros.org/ROS/Installation) 12 | * PCL 1.8. [PCL installation](https://pointclouds.org/downloads/#linux) 13 | * 硬件: livox Horizon激光雷达 14 | 15 | ## 二、使用方法 16 | 17 | 1. 安装ROS 18 | 2. 安装livox ROS驱动[1, 2, 3] 19 | 3. 安装本仓库 20 | ``` 21 | mkdir -p ~/xxx/src 22 | cd ~/xxx/src 23 | catkin_init_workspace 24 | git clone https://github.com/GCaptainNemo/fusion-lidar-camera-ROS.git 25 | cd .. 26 | catkin_make 27 | ``` 28 | 4. 运行激光雷达设备驱动 29 | 30 | ``` 31 | roslaunch livox_ros_driver livox_lidar_msg.launch 32 | ``` 33 | 34 | 5. 运行filter_node和extract_feature_node节点,并在rviz中显示 35 | ``` 36 | roslaunch filter_extract_feature launch_filter_extract.launch 37 | ``` 38 | 39 | ## 三、效果 40 |

41 |
提取边缘点(红色)和平面点(蓝色)
42 | 43 | ## 四、参考资料 44 | [1] [livox 驱动安装](https://github.com/Livox-SDK/livox_ros_driver) 45 | 46 | [2] [livox SDK安装](https://github.com/Livox-SDK/Livox-SDK) 47 | 48 | [3] Ji Z , Singh S . LOAM: Lidar Odometry and Mapping in Real-time[C]// Robotics: Science and Systems Conference. 2014. 49 | 50 | [4] Lin J , Zhang F . Loam_livox: A fast, robust, high-precision LiDAR odometry and mapping package for LiDARs of small FoV[J]. 2019. 51 | 52 | [5] [livox-horizon-loam](https://github.com/Livox-SDK/livox_horizon_loam) 53 | 54 | [6] [livox-CustomPoint格式](http://docs.ros.org/en/kinetic/api/livox_ros_driver/html/msg/CustomPoint.html) 55 | 56 | [7] [livox-CustomPoint-tag信息含义](https://livox-wiki-cn.readthedocs.io/zh_CN/latest/introduction/Point_Cloud_Characteristics_and_Coordinate_System%20.html) 57 | -------------------------------------------------------------------------------- /config/show.rviz: -------------------------------------------------------------------------------- 1 | Panels: 2 | - Class: rviz/Displays 3 | Help Height: 138 4 | Name: Displays 5 | Property Tree Widget: 6 | Expanded: 7 | - /Global Options1 8 | - /Status1 9 | - /Grid1 10 | - /PointCloud21 11 | Splitter Ratio: 0.5 12 | Tree Height: 1537 13 | - Class: rviz/Selection 14 | Name: Selection 15 | - Class: rviz/Tool Properties 16 | Expanded: 17 | - /2D Pose Estimate1 18 | - /2D Nav Goal1 19 | - /Publish Point1 20 | Name: Tool Properties 21 | Splitter Ratio: 0.5886790156364441 22 | - Class: rviz/Views 23 | Expanded: 24 | - /Current View1 25 | Name: Views 26 | Splitter Ratio: 0.5 27 | - Class: rviz/Time 28 | Experimental: false 29 | Name: Time 30 | SyncMode: 0 31 | SyncSource: PointCloud2 32 | Preferences: 33 | PromptSaveOnExit: true 34 | Toolbars: 35 | toolButtonStyle: 2 36 | Visualization Manager: 37 | Class: "" 38 | Displays: 39 | - Alpha: 0.5 40 | Cell Size: 1 41 | Class: rviz/Grid 42 | Color: 160; 160; 164 43 | Enabled: true 44 | Line Style: 45 | Line Width: 0.029999999329447746 46 | Value: Lines 47 | Name: Grid 48 | Normal Cell Count: 0 49 | Offset: 50 | X: 0 51 | Y: 0 52 | Z: 0 53 | Plane: XY 54 | Plane Cell Count: 10 55 | Reference Frame: 56 | Value: true 57 | - Alpha: 1 58 | Autocompute Intensity Bounds: true 59 | Autocompute Value Bounds: 60 | Max Value: 10 61 | Min Value: -10 62 | Value: true 63 | Axis: Z 64 | Channel Name: intensity 65 | Class: rviz/PointCloud2 66 | Color: 255; 255; 255 67 | Color Transformer: RGB8 68 | Decay Time: 0 69 | Enabled: true 70 | Invert Rainbow: false 71 | Max Color: 255; 255; 255 72 | Min Color: 0; 0; 0 73 | Name: PointCloud2 74 | Position Transformer: XYZ 75 | Queue Size: 10 76 | Selectable: true 77 | Size (Pixels): 3 78 | Size (m): 0.009999999776482582 79 | Style: Flat Squares 80 | Topic: /feature_less_color_sharp_flat 81 | Unreliable: false 82 | Use Fixed Frame: true 83 | Use rainbow: false 84 | Value: true 85 | Enabled: true 86 | Global Options: 87 | Background Color: 48; 48; 48 88 | Default Light: true 89 | Fixed Frame: aft_extract 90 | Frame Rate: 30 91 | Name: root 92 | Tools: 93 | - Class: rviz/Interact 94 | Hide Inactive Objects: true 95 | - Class: rviz/MoveCamera 96 | - Class: rviz/Select 97 | - Class: rviz/FocusCamera 98 | - Class: rviz/Measure 99 | - Class: rviz/SetInitialPose 100 | Theta std deviation: 0.2617993950843811 101 | Topic: /initialpose 102 | X std deviation: 0.5 103 | Y std deviation: 0.5 104 | - Class: rviz/SetGoal 105 | Topic: /move_base_simple/goal 106 | - Class: rviz/PublishPoint 107 | Single click: true 108 | Topic: /clicked_point 109 | Value: true 110 | Views: 111 | Current: 112 | Class: rviz/Orbit 113 | Distance: 6.619870185852051 114 | Enable Stereo Rendering: 115 | Stereo Eye Separation: 0.05999999865889549 116 | Stereo Focal Distance: 1 117 | Swap Stereo Eyes: false 118 | Value: false 119 | Focal Point: 120 | X: 0 121 | Y: 0 122 | Z: 0 123 | Focal Shape Fixed Size: true 124 | Focal Shape Size: 0.05000000074505806 125 | Invert Z Axis: false 126 | Name: Current View 127 | Near Clip Distance: 0.009999999776482582 128 | Pitch: 0.2303980439901352 129 | Target Frame: 130 | Value: Orbit (rviz) 131 | Yaw: 0.11039817333221436 132 | Saved: ~ 133 | Window Geometry: 134 | Displays: 135 | collapsed: false 136 | Height: 2049 137 | Hide Left Dock: false 138 | Hide Right Dock: false 139 | QMainWindow State: 000000ff00000000fd0000000400000000000001f7000006f9fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b000000b000fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000b0fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000006e000006f90000018200fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000015f000006f9fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000006e000006f90000013200fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e1000001970000000300000e7a00000060fc0100000002fb0000000800540069006d0065010000000000000e7a0000057100fffffffb0000000800540069006d0065010000000000000450000000000000000000000b0c000006f900000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 140 | Selection: 141 | collapsed: false 142 | Time: 143 | collapsed: false 144 | Tool Properties: 145 | collapsed: false 146 | Views: 147 | collapsed: false 148 | Width: 3706 149 | X: 134 150 | Y: 55 151 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef pcl::PointXYZINormal PointType; 9 | typedef pcl::PointCloud PointCloudXYZI; 10 | inline double rad2deg(double radians) { return radians * 180.0 / M_PI; } 11 | 12 | inline double deg2rad(double degrees) { return degrees * M_PI / 180.0; } 13 | -------------------------------------------------------------------------------- /include/filter_extract_lib.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | constexpr bool dbg_show_id = false; 6 | 7 | // 原位操作,避免push_back耗时操作 8 | template 9 | void removeClosedPointCloud(const pcl::PointCloud &cloud_in, 10 | pcl::PointCloud &cloud_out, float thres) { 11 | if (&cloud_in != &cloud_out) { 12 | cloud_out.header = cloud_in.header; 13 | cloud_out.points.resize(cloud_in.points.size()); 14 | } 15 | size_t j = 0; 16 | float thresh_hold = thres * thres; 17 | for (size_t i = 0; i < cloud_in.points.size(); ++i) { 18 | if (cloud_in.points[i].x * cloud_in.points[i].x + 19 | cloud_in.points[i].y * cloud_in.points[i].y + 20 | cloud_in.points[i].z * cloud_in.points[i].z < 21 | thresh_hold) 22 | continue; 23 | cloud_out.points[j] = cloud_in.points[i]; 24 | j++; 25 | } 26 | if (j != cloud_in.points.size()) { 27 | cloud_out.points.resize(j); 28 | } 29 | 30 | cloud_out.height = 1; 31 | cloud_out.width = static_cast(j); 32 | cloud_out.is_dense = true; 33 | } 34 | 35 | template 36 | void VisualizeCurvature(float *v_curv, int *v_label, 37 | const pcl::PointCloud &pcl_in, 38 | const std_msgs::Header &hdr) { 39 | ROS_ASSERT(pcl_in.size() < 400000); 40 | 41 | /// Same marker attributes 42 | visualization_msgs::Marker txt_mk; 43 | txt_mk.header = hdr; 44 | txt_mk.type = visualization_msgs::Marker::TEXT_VIEW_FACING; 45 | txt_mk.ns = "default"; 46 | txt_mk.id = 0; 47 | txt_mk.action = visualization_msgs::Marker::ADD; 48 | txt_mk.pose.orientation.x = 0; 49 | txt_mk.pose.orientation.y = 0; 50 | txt_mk.pose.orientation.z = 0; 51 | txt_mk.pose.orientation.w = 1; 52 | txt_mk.scale.z = 0.05; 53 | txt_mk.color.a = 1; 54 | txt_mk.color.r = 0; 55 | txt_mk.color.g = 1; 56 | txt_mk.color.b = 0; 57 | 58 | static visualization_msgs::MarkerArray curv_txt_msg; 59 | 60 | 61 | /// Marger array message 62 | static size_t pre_pt_num = 0; 63 | size_t pt_num = pcl_in.size(); 64 | 65 | if (pre_pt_num == 0) { 66 | curv_txt_msg.markers.reserve(400000); 67 | } 68 | if (pre_pt_num > pt_num) { 69 | curv_txt_msg.markers.resize(pre_pt_num); 70 | } else { 71 | curv_txt_msg.markers.resize(pt_num); 72 | } 73 | 74 | int edge_num = 0, edgeless_num = 0, flat_num = 0, flatless_num = 0, nn = 0; 75 | 76 | /// Add marker and namespace 77 | for (size_t i = 0; i < pcl_in.size(); ++i) { 78 | auto curv = v_curv[i]; 79 | auto label = v_label[i]; /// -1: flat, 0: less-flat, 1:less-edge, 2:edge 80 | const auto &pt = pcl_in[i]; 81 | 82 | switch (label) { 83 | case 2: { 84 | /// edge 85 | auto &mk_i = curv_txt_msg.markers[i]; 86 | mk_i = txt_mk; 87 | mk_i.ns = "edge"; 88 | mk_i.id = i; 89 | mk_i.pose.position.x = pt.x; 90 | mk_i.pose.position.y = pt.y; 91 | mk_i.pose.position.z = pt.z; 92 | mk_i.color.a = 1; 93 | mk_i.color.r = 1; 94 | mk_i.color.g = 0; 95 | mk_i.color.b = 0; 96 | char cstr[10]; 97 | snprintf(cstr, 9, "%.2f", curv); 98 | mk_i.text = std::string(cstr); 99 | /// debug 100 | if (dbg_show_id) { 101 | mk_i.text = std::to_string(i); 102 | } 103 | 104 | edge_num++; 105 | break; 106 | } 107 | case 1: { 108 | /// less edge 109 | auto &mk_i = curv_txt_msg.markers[i]; 110 | mk_i = txt_mk; 111 | mk_i.ns = "edgeless"; 112 | mk_i.id = i; 113 | mk_i.pose.position.x = pt.x; 114 | mk_i.pose.position.y = pt.y; 115 | mk_i.pose.position.z = pt.z; 116 | mk_i.color.a = 0.5; 117 | mk_i.color.r = 0.5; 118 | mk_i.color.g = 0; 119 | mk_i.color.b = 0.8; 120 | char cstr[10]; 121 | snprintf(cstr, 9, "%.2f", curv); 122 | mk_i.text = std::string(cstr); 123 | /// debug 124 | if (dbg_show_id) { 125 | mk_i.text = std::to_string(i); 126 | } 127 | 128 | edgeless_num++; 129 | break; 130 | } 131 | case 0: { 132 | /// less flat 133 | auto &mk_i = curv_txt_msg.markers[i]; 134 | mk_i = txt_mk; 135 | mk_i.ns = "flatless"; 136 | mk_i.id = i; 137 | mk_i.pose.position.x = pt.x; 138 | mk_i.pose.position.y = pt.y; 139 | mk_i.pose.position.z = pt.z; 140 | mk_i.color.a = 0.5; 141 | mk_i.color.r = 0; 142 | mk_i.color.g = 0.5; 143 | mk_i.color.b = 0.8; 144 | char cstr[10]; 145 | snprintf(cstr, 9, "%.2f", curv); 146 | mk_i.text = std::string(cstr); 147 | /// debug 148 | if (dbg_show_id) { 149 | mk_i.text = std::to_string(i); 150 | } 151 | 152 | flatless_num++; 153 | break; 154 | } 155 | case -1: { 156 | /// flat 157 | auto &mk_i = curv_txt_msg.markers[i]; 158 | mk_i = txt_mk; 159 | mk_i.ns = "flat"; 160 | mk_i.id = i; 161 | mk_i.pose.position.x = pt.x; 162 | mk_i.pose.position.y = pt.y; 163 | mk_i.pose.position.z = pt.z; 164 | mk_i.color.a = 1; 165 | mk_i.color.r = 0; 166 | mk_i.color.g = 1; 167 | mk_i.color.b = 0; 168 | char cstr[10]; 169 | snprintf(cstr, 9, "%.2f", curv); 170 | mk_i.text = std::string(cstr); 171 | /// debug 172 | if (dbg_show_id) { 173 | mk_i.text = std::to_string(i); 174 | } 175 | 176 | flat_num++; 177 | break; 178 | } 179 | default: { 180 | /// Un-reliable 181 | /// Do nothing for label=99 182 | // ROS_ASSERT_MSG(false, "%d", label); 183 | auto &mk_i = curv_txt_msg.markers[i]; 184 | mk_i = txt_mk; 185 | mk_i.ns = "unreliable"; 186 | mk_i.id = i; 187 | mk_i.pose.position.x = pt.x; 188 | mk_i.pose.position.y = pt.y; 189 | mk_i.pose.position.z = pt.z; 190 | mk_i.color.a = 0; 191 | mk_i.color.r = 0; 192 | mk_i.color.g = 0; 193 | mk_i.color.b = 0; 194 | char cstr[10]; 195 | snprintf(cstr, 9, "%.2f", curv); 196 | mk_i.text = std::string(cstr); 197 | 198 | nn++; 199 | break; 200 | } 201 | } 202 | } 203 | ROS_INFO("edge/edgeless/flatless/flat/nn num: [%d / %d / %d / %d / %d] - %lu", 204 | edge_num, edgeless_num, flatless_num, flat_num, nn, pt_num); 205 | 206 | /// Delete old points 207 | if (pre_pt_num > pt_num) { 208 | ROS_WARN("%lu > %lu", pre_pt_num, pt_num); 209 | // curv_txt_msg.markers.resize(pre_pt_num); 210 | for (size_t i = pt_num; i < pre_pt_num; ++i) { 211 | auto &mk_i = curv_txt_msg.markers[i]; 212 | mk_i.action = visualization_msgs::Marker::DELETE; 213 | mk_i.color.a = 0; 214 | mk_i.color.r = 0; 215 | mk_i.color.g = 0; 216 | mk_i.color.b = 0; 217 | mk_i.ns = "old"; 218 | mk_i.text = ""; 219 | } 220 | } 221 | pre_pt_num = pt_num; 222 | 223 | // pub_curvature.publish(curv_txt_msg); 224 | } 225 | -------------------------------------------------------------------------------- /include/tic_toc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class TicToc { 8 | public: 9 | TicToc() { tic(); } 10 | 11 | void tic() { start = std::chrono::system_clock::now(); } 12 | 13 | double toc() { 14 | end = std::chrono::system_clock::now(); 15 | std::chrono::duration elapsed_seconds = end - start; // 微秒 16 | return elapsed_seconds.count() * 1000; 17 | } 18 | 19 | private: 20 | std::chrono::time_point start, end; 21 | }; 22 | -------------------------------------------------------------------------------- /launch/launch_filter_extract.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | filter_extract_feature 4 | 0.0.0 5 | 6 | 7 | This is an advanced implementation of the following open source project: 8 | 9 | 10 | why 11 | 12 | BSD 13 | 14 | livox 15 | 16 | catkin 17 | geometry_msgs 18 | nav_msgs 19 | roscpp 20 | rospy 21 | std_msgs 22 | rosbag 23 | sensor_msgs 24 | tf 25 | image_transport 26 | livox_ros_driver 27 | 28 | geometry_msgs 29 | nav_msgs 30 | sensor_msgs 31 | roscpp 32 | rospy 33 | std_msgs 34 | rosbag 35 | tf 36 | image_transport 37 | livox_ros_driver 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /resources/edge_planar_points.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GCaptainNemo/LOAM-Preprocessing/d782cf35942ece064280efcda2e8378c216992be/resources/edge_planar_points.gif -------------------------------------------------------------------------------- /src/extract_feature_node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 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 | #include 16 | #include "filter_extract_lib.hpp" 17 | #include "common.h" 18 | #include "tic_toc.h" 19 | 20 | using std::atan2; 21 | using std::cos; 22 | using std::sin; 23 | 24 | 25 | constexpr bool b_viz_curv = false; 26 | constexpr bool b_voxel_filter = false; 27 | constexpr bool b_normalize_curv = true; 28 | 29 | const double scanPeriod = 0.1; 30 | 31 | const int systemDelay = 0; 32 | int systemInitCount = 0; 33 | bool systemInited = false; 34 | int N_SCANS = 0; 35 | 36 | float cloudCurvature[400000]; 37 | int cloudSortInd[400000]; 38 | int cloudNeighborPicked[400000]; // not picked 0, picked 1(不能作为特征点) 39 | // cloudLabel: 40 | // normal 0, curvature < kThresholdFlat -1, too far or too near 99, 41 | // edgePointsLessSharp 1, edgePointsSharp 2 42 | int cloudLabel[400000]; 43 | 44 | bool comp(int i, int j) { return (cloudCurvature[i] < cloudCurvature[j]); } 45 | 46 | ros::Publisher pubLaserCloud; 47 | ros::Publisher pubEdgePointsSharp; 48 | ros::Publisher pubEdgePointsLessSharp; 49 | ros::Publisher pubPlanarPointsFlat; 50 | ros::Publisher pubPlanarPointsLessFlat; 51 | ros::Publisher pubColorEdgePlanarPoints; 52 | ros::Publisher pubColorLessEdgePlanarPoints; 53 | 54 | std::vector pubEachScan; 55 | 56 | 57 | bool b_PUB_EACH_LINE = false; 58 | 59 | double MINIMUM_RANGE = 0.1; 60 | double THRESHOLD_FLAT = 0.01; 61 | double THRESHOLD_SHARP = 0.01; 62 | 63 | int kNumCurvSize = 5; 64 | constexpr int kNumRegion = 50; // 6 65 | constexpr int kNumEdge = 2; // 2 最大edge points数目 66 | constexpr int kNumFlat = 4; // 4 最大 planar points数目 67 | constexpr int kNumEdgeNeighbor = 5; // 5; 68 | constexpr int kNumFlatNeighbor = 5; // 5; 69 | float kThresholdSharp = 50; // 0.1; 70 | float kThresholdFlat = 30; // 0.1; 71 | constexpr float kThresholdLessflat = 0.1; 72 | 73 | constexpr float kDistanceFaraway = 25; 74 | 75 | 76 | void initialize_and_filter(const int &cloudSize, const pcl::PointCloud::Ptr &laserCloud) 77 | { 78 | // 1. 计算曲率 2. 初始化cloudSortInd, cloudNeighborPicked, cloudLabel 3. unreliable points(太近或太远) 79 | for (int i = 5; i < cloudSize - 5; i++) { 80 | float dis = sqrt(laserCloud->points[i].x * laserCloud->points[i].x + 81 | laserCloud->points[i].y * laserCloud->points[i].y + 82 | laserCloud->points[i].z * laserCloud->points[i].z); 83 | if (dis > kDistanceFaraway) { 84 | kNumCurvSize = 2; 85 | } 86 | float diffX = 0, diffY = 0, diffZ = 0; 87 | for (int j = 1; j <= kNumCurvSize; ++j) { 88 | diffX += laserCloud->points[i - j].x + laserCloud->points[i + j].x; 89 | diffY += laserCloud->points[i - j].y + laserCloud->points[i + j].y; 90 | diffZ += laserCloud->points[i - j].z + laserCloud->points[i + j].z; 91 | } 92 | diffX -= 2 * kNumCurvSize * laserCloud->points[i].x; 93 | diffY -= 2 * kNumCurvSize * laserCloud->points[i].y; 94 | diffZ -= 2 * kNumCurvSize * laserCloud->points[i].z; 95 | 96 | 97 | float tmp2 = diffX * diffX + diffY * diffY + diffZ * diffZ; 98 | float tmp = sqrt(tmp2); 99 | // 在一个邻域内计算扫描方向点云的Laplacian算子,作为点云在该点的曲率 100 | cloudCurvature[i] = tmp2; 101 | if (b_normalize_curv) { 102 | /// use normalized curvature(论文LOAM中提出) 103 | cloudCurvature[i] = tmp / (2 * kNumCurvSize * dis + 1e-3); 104 | } 105 | cloudSortInd[i] = i; 106 | cloudNeighborPicked[i] = 0; 107 | cloudLabel[i] = 0; 108 | 109 | /// Mark un-reliable points 110 | constexpr float kMaxFeatureDis = 1e4; 111 | if (fabs(dis) > kMaxFeatureDis || fabs(dis) < 1e-4 || !std::isfinite(dis)) { 112 | cloudLabel[i] = 99; 113 | cloudNeighborPicked[i] = 1; // 标签为1不能作为特征点 114 | } 115 | } 116 | } 117 | 118 | void filter_sharp_points(const int &cloudSize, const pcl::PointCloud::Ptr &laserCloud) 119 | { 120 | // 一个点距前一个点和距后一个点的距离都太远就舍弃(尖锐的点) 121 | for (int i = 5; i < cloudSize - 6; i++) { 122 | float diffX = laserCloud->points[i + 1].x - laserCloud->points[i].x; 123 | float diffY = laserCloud->points[i + 1].y - laserCloud->points[i].y; 124 | float diffZ = laserCloud->points[i + 1].z - laserCloud->points[i].z; 125 | float diff = diffX * diffX + diffY * diffY + diffZ * diffZ; 126 | 127 | float diffX2 = laserCloud->points[i].x - laserCloud->points[i - 1].x; 128 | float diffY2 = laserCloud->points[i].y - laserCloud->points[i - 1].y; 129 | float diffZ2 = laserCloud->points[i].z - laserCloud->points[i - 1].z; 130 | float diff2 = diffX2 * diffX2 + diffY2 * diffY2 + diffZ2 * diffZ2; 131 | float dis = laserCloud->points[i].x * laserCloud->points[i].x + 132 | laserCloud->points[i].y * laserCloud->points[i].y + 133 | laserCloud->points[i].z * laserCloud->points[i].z; 134 | 135 | if (diff > 0.00015 * dis && diff2 > 0.00015 * dis) { 136 | cloudNeighborPicked[i] = 1; 137 | } 138 | } 139 | } 140 | 141 | void extract_edge_points(pcl::PointCloud &edgePointsSharp, pcl::PointCloud &edgePointsLessSharp, const pcl::PointCloud::Ptr &laserCloud, 142 | const int &sp, const int &ep) 143 | { 144 | // //////////////////////////////////////////////////////////////////////// 145 | // 提取edge point 146 | // //////////////////////////////////////////////////////////////////////// 147 | int largestPickedNum = 0; 148 | for (int k = ep; k >= sp; k--) { 149 | int ind = cloudSortInd[k]; 150 | 151 | if (cloudNeighborPicked[ind] != 0) continue; 152 | // 提取edge points(曲率很大的点) 153 | if (cloudCurvature[ind] > kThresholdSharp) { 154 | largestPickedNum++; 155 | // kNumEdge,edge points个数有上界,2个以内Sharp,20个以内lessSharp 156 | if (largestPickedNum <= kNumEdge) { 157 | cloudLabel[ind] = 2; 158 | edgePointsSharp.push_back(laserCloud->points[ind]); 159 | edgePointsLessSharp.push_back(laserCloud->points[ind]); 160 | } else if (largestPickedNum <= 20) { 161 | cloudLabel[ind] = 1; 162 | edgePointsLessSharp.push_back(laserCloud->points[ind]); 163 | } else { 164 | break; 165 | } 166 | 167 | cloudNeighborPicked[ind] = 1; 168 | 169 | // 如果在周围5个点之内的点的距离小于0.02m,就认为周围点被picked了 170 | for (int l = 1; l <= kNumEdgeNeighbor; l++) { 171 | float diffX = laserCloud->points[ind + l].x - 172 | laserCloud->points[ind + l - 1].x; 173 | float diffY = laserCloud->points[ind + l].y - 174 | laserCloud->points[ind + l - 1].y; 175 | float diffZ = laserCloud->points[ind + l].z - 176 | laserCloud->points[ind + l - 1].z; 177 | if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.02) { 178 | break; 179 | } 180 | 181 | cloudNeighborPicked[ind + l] = 1; 182 | } 183 | for (int l = -1; l >= -kNumEdgeNeighbor; l--) { 184 | float diffX = laserCloud->points[ind + l].x - 185 | laserCloud->points[ind + l + 1].x; 186 | float diffY = laserCloud->points[ind + l].y - 187 | laserCloud->points[ind + l + 1].y; 188 | float diffZ = laserCloud->points[ind + l].z - 189 | laserCloud->points[ind + l + 1].z; 190 | if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.02) { 191 | break; 192 | } 193 | 194 | cloudNeighborPicked[ind + l] = 1; 195 | } 196 | } 197 | } 198 | } 199 | 200 | void extract_planar_points(pcl::PointCloud &planarPointsFlat, pcl::PointCloud::Ptr planarPointsLessFlatScan, 201 | const pcl::PointCloud::Ptr &laserCloud, const int &sp, const int &ep) 202 | { 203 | int smallestPickedNum = 0; 204 | for (int k = sp; k <= ep; k++) 205 | { 206 | int ind = cloudSortInd[k]; 207 | 208 | if (cloudNeighborPicked[ind] != 0) continue; 209 | 210 | if (cloudCurvature[ind] < kThresholdFlat) { 211 | cloudLabel[ind] = -1; 212 | planarPointsFlat.push_back(laserCloud->points[ind]); 213 | cloudNeighborPicked[ind] = 1; 214 | 215 | smallestPickedNum++; 216 | if (smallestPickedNum >= kNumFlat) { 217 | // 4 points 218 | break; 219 | } 220 | // 如果在周围5个点之内的点的距离小于0.02m,就认为周围点被picked了 221 | for (int l = 1; l <= kNumFlatNeighbor; l++) { 222 | float diffX = laserCloud->points[ind + l].x - 223 | laserCloud->points[ind + l - 1].x; 224 | float diffY = laserCloud->points[ind + l].y - 225 | laserCloud->points[ind + l - 1].y; 226 | float diffZ = laserCloud->points[ind + l].z - 227 | laserCloud->points[ind + l - 1].z; 228 | if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.02) { 229 | break; 230 | } 231 | 232 | cloudNeighborPicked[ind + l] = 1; 233 | } 234 | for (int l = -1; l >= -kNumFlatNeighbor; l--) { 235 | float diffX = laserCloud->points[ind + l].x - 236 | laserCloud->points[ind + l + 1].x; 237 | float diffY = laserCloud->points[ind + l].y - 238 | laserCloud->points[ind + l + 1].y; 239 | float diffZ = laserCloud->points[ind + l].z - 240 | laserCloud->points[ind + l + 1].z; 241 | if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.02) { 242 | break; 243 | } 244 | 245 | cloudNeighborPicked[ind + l] = 1; 246 | } 247 | } 248 | } 249 | // 标签小于等于0,激光点云的点曲率小于阈值0.1 lessflat,planar points多多益善 250 | for (int k = sp; k <= ep; k++) { 251 | if (cloudLabel[k] <= 0 && cloudCurvature[k] < kThresholdLessflat) { 252 | planarPointsLessFlatScan->push_back(laserCloud->points[k]); 253 | } 254 | } 255 | } 256 | 257 | void pub_color( const pcl::PointCloud &edgePointsSharp, 258 | const pcl::PointCloud &edgePointsLessSharp, 259 | const pcl::PointCloud &planarPointsFlat, 260 | const pcl::PointCloud &planarPointsLessFlat, const ros::Time &stamp) 261 | { 262 | 263 | pcl::PointXYZRGB point; 264 | pcl::PointCloud sharp_flat_points; 265 | pcl::PointCloud less_sharp_flat_points; 266 | for(int i=0; i < edgePointsSharp.size(); ++i) 267 | { 268 | point.x = edgePointsSharp.points[i].x; 269 | point.y = edgePointsSharp.points[i].y; 270 | point.z = edgePointsSharp.points[i].z; 271 | point.r = 255; 272 | point.g = 0; 273 | point.b = 0; 274 | sharp_flat_points.push_back(point); 275 | } 276 | for(int i=0; i < planarPointsFlat.size(); ++i) 277 | { 278 | point.x = planarPointsFlat.points[i].x; 279 | point.y = planarPointsFlat.points[i].y; 280 | point.z = planarPointsFlat.points[i].z; 281 | point.r = 0; 282 | point.g = 0; 283 | point.b = 255; 284 | sharp_flat_points.push_back(point); 285 | } 286 | // ///////////////////////////////////////////// 287 | for(int i=0; i < edgePointsLessSharp.size(); ++i) 288 | { 289 | point.x = edgePointsLessSharp.points[i].x; 290 | point.y = edgePointsLessSharp.points[i].y; 291 | point.z = edgePointsLessSharp.points[i].z; 292 | point.r = 255; 293 | point.g = 0; 294 | point.b = 0; 295 | less_sharp_flat_points.push_back(point); 296 | } 297 | for(int i=0; i < planarPointsLessFlat.size(); ++i) 298 | { 299 | point.x = planarPointsLessFlat.points[i].x; 300 | point.y = planarPointsLessFlat.points[i].y; 301 | point.z = planarPointsLessFlat.points[i].z; 302 | point.r = 0; 303 | point.g = 0; 304 | point.b = 255; 305 | less_sharp_flat_points.push_back(point); 306 | } 307 | sensor_msgs::PointCloud2 sharp_flat_points2; 308 | pcl::toROSMsg(sharp_flat_points, sharp_flat_points2); 309 | sharp_flat_points2.header.stamp = stamp; 310 | sharp_flat_points2.header.frame_id = "/aft_extract"; 311 | pubColorEdgePlanarPoints.publish(sharp_flat_points2); 312 | 313 | sensor_msgs::PointCloud2 less_sharp_flat_points2; 314 | pcl::toROSMsg(less_sharp_flat_points, less_sharp_flat_points2); 315 | less_sharp_flat_points2.header.stamp = stamp; 316 | less_sharp_flat_points2.header.frame_id = "/aft_extract"; 317 | pubColorLessEdgePlanarPoints.publish(less_sharp_flat_points2); 318 | 319 | }; 320 | 321 | 322 | 323 | void laserCloudHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg) { 324 | if (!systemInited) { 325 | systemInitCount++; 326 | if (systemInitCount >= systemDelay) { 327 | systemInited = true; 328 | } else 329 | return; 330 | } 331 | 332 | TicToc t_whole; 333 | TicToc t_prepare; 334 | std::vector scanStartInd(N_SCANS, 0); 335 | std::vector scanEndInd(N_SCANS, 0); 336 | 337 | pcl::PointCloud laserCloudIn; 338 | pcl::fromROSMsg(*laserCloudMsg, laserCloudIn); 339 | std::vector indices; 340 | // 1. filtering 341 | pcl::removeNaNFromPointCloud(laserCloudIn, laserCloudIn, indices); 342 | removeClosedPointCloud(laserCloudIn, laserCloudIn, MINIMUM_RANGE); 343 | int cloudSize = laserCloudIn.points.size(); 344 | 345 | // 2. order according to scan num and merge 346 | PointType point; 347 | std::vector> laserCloudScans(N_SCANS); // 6个SCAN 348 | for (int i = 0; i < cloudSize; i++) { 349 | point.x = laserCloudIn.points[i].x; 350 | point.y = laserCloudIn.points[i].y; 351 | point.z = laserCloudIn.points[i].z; 352 | point.intensity = laserCloudIn.points[i].intensity; // intensity 整数是scan的数目,小数是时间戳 353 | point.curvature = laserCloudIn.points[i].curvature; // curvature 是0.1的反射率 354 | int scanID = 0; 355 | // main函数中改成了6 356 | if (N_SCANS == 6) { 357 | scanID = (int)point.intensity; 358 | } 359 | laserCloudScans[scanID].push_back(point); 360 | } 361 | 362 | // printf("points size %d \n", cloudSize); 363 | // PointType -> PointXYZINormal(merge) 364 | pcl::PointCloud::Ptr laserCloud(new pcl::PointCloud()); 365 | for (int i = 0; i < N_SCANS; i++) { 366 | scanStartInd[i] = laserCloud->size() + 5; 367 | *laserCloud += laserCloudScans[i]; 368 | scanEndInd[i] = laserCloud->size() - 6; 369 | } 370 | // printf("prepare time %f \n", t_prepare.toc()); 371 | 372 | 373 | // 3. 三个内容: 1. 计算曲率 2. 初始化cloudSortInd, cloudNeighborPicked, cloudLabel 3. unreliable points(太近或太远) 374 | initialize_and_filter(cloudSize, laserCloud); 375 | 376 | // 4. 一个点距前一个点和距后一个点的距离都太远就舍弃(尖锐的点,或是某种空间上的噪点) 377 | filter_sharp_points(cloudSize, laserCloud); 378 | 379 | TicToc t_pts; 380 | 381 | pcl::PointCloud edgePointsSharp; 382 | pcl::PointCloud edgePointsLessSharp; 383 | pcl::PointCloud planarPointsFlat; 384 | pcl::PointCloud planarPointsLessFlat; 385 | 386 | if (b_normalize_curv) { 387 | kThresholdFlat = THRESHOLD_FLAT; 388 | kThresholdSharp = THRESHOLD_SHARP; 389 | } 390 | 391 | float t_q_sort = 0; 392 | // kNumCurvSize计算曲率的邻居数6; kNumRegion:把一个扫描线再分成若干区域(50) 393 | for (int i = 0; i < N_SCANS; i++) { 394 | if (scanEndInd[i] - scanStartInd[i] < kNumCurvSize) continue; 395 | pcl::PointCloud::Ptr planarPointsLessFlatScan(new pcl::PointCloud); 396 | for (int j = 0; j < kNumRegion; j++) { 397 | // start point 398 | int sp = 399 | scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * j / kNumRegion; 400 | int ep = scanStartInd[i] + 401 | (scanEndInd[i] - scanStartInd[i]) * (j + 1) / kNumRegion - 1; 402 | 403 | TicToc t_tmp; 404 | // sort the curvatures from small to large: 按照曲率由小到大排序,不改变点,改变id 405 | for (int k = sp + 1; k <= ep; k++) { 406 | for (int l = k; l >= sp + 1; l--) { 407 | if (cloudCurvature[cloudSortInd[l]] < cloudCurvature[cloudSortInd[l - 1]]) 408 | { 409 | int temp = cloudSortInd[l - 1]; 410 | cloudSortInd[l - 1] = cloudSortInd[l]; 411 | cloudSortInd[l] = temp; 412 | } 413 | } 414 | } 415 | 416 | float SumCurRegion = 0.0; 417 | float MaxCurRegion = cloudCurvature[cloudSortInd[ep]]; //the largest curvature in sp ~ ep 418 | for (int k = ep - 1; k >= sp; k--) { 419 | SumCurRegion += cloudCurvature[cloudSortInd[k]]; 420 | } 421 | 422 | if (MaxCurRegion > 3 * SumCurRegion) 423 | cloudNeighborPicked[cloudSortInd[ep]] = 1; // 如果edge_points曲率大于三倍的曲率的和,则该点picked,是噪点,不能作为edge point 424 | 425 | t_q_sort += t_tmp.toc(); 426 | // //////////////////////////////////////////////////////////////////////// 427 | // 提取edge point 428 | // //////////////////////////////////////////////////////////////////////// 429 | extract_edge_points(edgePointsSharp, edgePointsLessSharp, laserCloud, sp, ep); 430 | 431 | 432 | // //////////////////////////////////////////////////////////////////////// 433 | // 提取planar point 434 | // //////////////////////////////////////////////////////////////////////// 435 | extract_planar_points(planarPointsFlat, planarPointsLessFlatScan, laserCloud, sp, ep); 436 | 437 | } 438 | // ///////////////////////////////////////////////////////// 439 | // 对surfPointsLessFlat体素滤波 440 | // ///////////////////////////////////////////////////////// 441 | planarPointsLessFlat += planarPointsFlat; 442 | edgePointsLessSharp += edgePointsSharp; 443 | /// Whether downsample less-flat points 444 | if (b_voxel_filter) { 445 | pcl::PointCloud planarPointsLessFlatScanDS; 446 | // 体素滤波 447 | pcl::VoxelGrid downSizeFilter; 448 | downSizeFilter.setInputCloud(planarPointsLessFlatScan); 449 | downSizeFilter.setLeafSize(0.2, 0.2, 0.2); 450 | downSizeFilter.filter(planarPointsLessFlatScanDS); 451 | planarPointsLessFlat += planarPointsLessFlatScanDS; 452 | } else { 453 | planarPointsLessFlat += *planarPointsLessFlatScan; 454 | } 455 | } 456 | // printf("sort q time %f \n", t_q_sort); 457 | // printf("seperate points time %f \n", t_pts.toc()); 458 | 459 | /// Visualize curvature 460 | if (b_viz_curv) { 461 | std_msgs::Header ros_hdr = laserCloudMsg->header; 462 | ros_hdr.frame_id = "/aft_extract"; 463 | VisualizeCurvature(cloudCurvature, cloudLabel, *laserCloud, ros_hdr); 464 | } 465 | const auto& stamp = laserCloudMsg->header.stamp; 466 | sensor_msgs::PointCloud2 laserCloudOutMsg; 467 | pcl::toROSMsg(*laserCloud, laserCloudOutMsg); 468 | laserCloudOutMsg.header.stamp = stamp; 469 | laserCloudOutMsg.header.frame_id = "/aft_extract"; 470 | pubLaserCloud.publish(laserCloudOutMsg); 471 | 472 | sensor_msgs::PointCloud2 edgePointsSharpMsg; 473 | pcl::toROSMsg(edgePointsSharp, edgePointsSharpMsg); 474 | edgePointsSharpMsg.header.stamp = stamp; 475 | edgePointsSharpMsg.header.frame_id = "/aft_extract"; 476 | pubEdgePointsSharp.publish(edgePointsSharpMsg); 477 | 478 | sensor_msgs::PointCloud2 edgePointsLessSharpMsg; 479 | pcl::toROSMsg(edgePointsLessSharp, edgePointsLessSharpMsg); 480 | edgePointsLessSharpMsg.header.stamp = stamp; 481 | edgePointsLessSharpMsg.header.frame_id = "/aft_extract"; 482 | pubEdgePointsLessSharp.publish(edgePointsLessSharpMsg); 483 | 484 | sensor_msgs::PointCloud2 planarPointsFlat2; 485 | pcl::toROSMsg(planarPointsFlat, planarPointsFlat2); 486 | planarPointsFlat2.header.stamp = stamp; 487 | planarPointsFlat2.header.frame_id = "/aft_extract"; 488 | pubPlanarPointsFlat.publish(planarPointsFlat2); 489 | 490 | sensor_msgs::PointCloud2 planarPointsLessFlat2; 491 | pcl::toROSMsg(planarPointsLessFlat, planarPointsLessFlat2); 492 | planarPointsLessFlat2.header.stamp = stamp; 493 | planarPointsLessFlat2.header.frame_id = "/aft_extract"; 494 | pubPlanarPointsLessFlat.publish(planarPointsLessFlat2); 495 | // //////////////////////////////////////////////////////// 496 | // publish color 497 | // //////////////////////////////////////////////////////// 498 | pub_color(edgePointsSharp, edgePointsLessSharp, planarPointsFlat, planarPointsLessFlat, stamp); 499 | 500 | // pub each scam 501 | if (b_PUB_EACH_LINE) { 502 | for (int i = 0; i < N_SCANS; i++) { 503 | sensor_msgs::PointCloud2 scanMsg; 504 | pcl::toROSMsg(laserCloudScans[i], scanMsg); 505 | scanMsg.header.stamp = stamp; 506 | scanMsg.header.frame_id = "/aft_extract"; 507 | pubEachScan[i].publish(scanMsg); 508 | } 509 | } 510 | 511 | printf("scan registration time %f ms *************\n", t_whole.toc()); 512 | if (t_whole.toc() > 100) ROS_WARN("scan registration process over 100ms"); 513 | } 514 | 515 | int main(int argc, char **argv) { 516 | ros::init(argc, argv, "extract_feature"); 517 | ros::NodeHandle nh; 518 | 519 | nh.param("scan_line", N_SCANS, 6); // Horizon has 6 scan lines 520 | nh.param("threshold_flat", THRESHOLD_FLAT, 0.01); 521 | nh.param("threshold_sharp", THRESHOLD_SHARP, 0.01); 522 | 523 | printf("scan line number %d \n", N_SCANS); 524 | 525 | if (N_SCANS != 6) { 526 | printf("only support livox horizon lidar!"); 527 | return 0; 528 | } 529 | 530 | ros::Subscriber subLaserCloud = nh.subscribe( 531 | "/livox_repub", 100, laserCloudHandler); 532 | 533 | // 滤波后的全部点 534 | pubLaserCloud = 535 | nh.advertise("/cloud_filter", 100); 536 | 537 | pubEdgePointsSharp = 538 | nh.advertise("/feature_sharp", 100); 539 | 540 | pubEdgePointsLessSharp = 541 | nh.advertise("/feature_less_sharp", 100); 542 | 543 | pubPlanarPointsFlat = 544 | nh.advertise("/feature_flat", 100); 545 | 546 | pubPlanarPointsLessFlat = 547 | nh.advertise("/feature_less_flat", 100); 548 | 549 | pubColorEdgePlanarPoints = 550 | nh.advertise("/feature_color_sharp_flat", 100); 551 | 552 | pubColorLessEdgePlanarPoints = 553 | nh.advertise("/feature_less_color_sharp_flat", 100); 554 | 555 | 556 | if (b_PUB_EACH_LINE) { 557 | for (int i = 0; i < N_SCANS; i++) { 558 | ros::Publisher tmp = nh.advertise( 559 | "/laser_scanid_" + std::to_string(i), 100); 560 | pubEachScan.push_back(tmp); 561 | } 562 | } 563 | ros::spin(); 564 | 565 | return 0; 566 | } 567 | 568 | -------------------------------------------------------------------------------- /src/filter_node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "livox_ros_driver/CustomMsg.h" 4 | #include "common.h" 5 | 6 | ros::Publisher pub_pcl_out1; 7 | std::vector livox_data; 8 | void LivoxMsgCallback(const livox_ros_driver::CustomMsgConstPtr& livox_msg_in) { 9 | livox_data.push_back(livox_msg_in); 10 | if (livox_data.size() == 0) return; 11 | pcl::PointCloud pcl_in; 12 | 13 | for (size_t j = 0; j < livox_data.size(); j++) { 14 | auto& livox_msg = livox_data[j]; 15 | auto time_end = livox_msg->points.back().offset_time; 16 | for (unsigned int i = 0; i < livox_msg->point_num; ++i) { 17 | // PointXYZINormal 18 | PointType pt; 19 | pt.x = livox_msg->points[i].x; 20 | pt.y = livox_msg->points[i].y; 21 | pt.z = livox_msg->points[i].z; 22 | if (livox_msg->points[i].tag != 16) 23 | { 24 | // 去除噪点(1. 基于能量判断的噪点 2. 基于空间位置判断的噪点) 25 | continue; 26 | } 27 | // 实现时间归一化 28 | float s = livox_msg->points[i].offset_time / (float)time_end; 29 | // The integer part is line number and the decimal part is timestamp 30 | pt.intensity = livox_msg->points[i].line + s*0.1; 31 | // ROS_INFO("intensity-------- %.6f ",pt.intensity); 32 | pt.curvature = livox_msg->points[i].reflectivity * 0.1; 33 | // ROS_INFO("pt.curvature-------- %.3f ",pt.curvature); 34 | pcl_in.push_back(pt); 35 | // ROS_INFO("pt.tag -----------%d", livox_msg->points[i].tag); 36 | } 37 | } 38 | 39 | /// timebase 5ms ~ 50000000, so 10 ~ 1ns 40 | 41 | unsigned long timebase_ns = livox_data[0]->timebase; 42 | ros::Time timestamp; 43 | timestamp.fromNSec(timebase_ns); 44 | 45 | // ROS_INFO("livox1 republish %u points at time %f buf size %ld", 46 | // pcl_in.size(), 47 | // timestamp.toSec(), livox_data.size()); 48 | 49 | sensor_msgs::PointCloud2 pcl_ros_msg; 50 | pcl::toROSMsg(pcl_in, pcl_ros_msg); 51 | pcl_ros_msg.header.stamp.fromNSec(timebase_ns); 52 | pcl_ros_msg.header.frame_id = "/livox"; 53 | pub_pcl_out1.publish(pcl_ros_msg); 54 | livox_data.clear(); 55 | } 56 | 57 | int main(int argc, char** argv) { 58 | ros::init(argc, argv, "livox_repub"); 59 | ros::NodeHandle nh; 60 | 61 | ROS_INFO("start livox_repub"); 62 | 63 | ros::Subscriber sub_livox_msg1 = nh.subscribe( 64 | "/livox/lidar", 100, LivoxMsgCallback); 65 | pub_pcl_out1 = nh.advertise("/livox_repub", 100); 66 | ros::spin(); 67 | } 68 | 69 | --------------------------------------------------------------------------------