├── README.md ├── package.xml ├── CMakeLists.txt └── src └── curb_detec.cpp /README.md: -------------------------------------------------------------------------------- 1 | # 3DLidar_curb_detection 2 | 1- download the rosbag for the VELODYNE 3D lidar 3 | [click here](http://db3.ertl.jp/autoware/sample_data/sample_moriyama_150324.tar.gz) 4 | 5 | ```Bash 6 | $ cd 7 | $ cp ~/Downloads/sample_moriyama_* . 8 | $ tar zxfv sample_moriyama_150324.tar.gz 9 | 10 | ``` 11 | 12 | 2- clone the repo into your workspace 13 | 14 | ```Bash 15 | $ cd 16 | $ cd catkin_ws/src/ 17 | $ git clone https://github.com/SohaibAl-emara/3D_Lidar_Curb_Detection.git 18 | ``` 19 | 20 | 3- build the code 21 | ```Bash 22 | $ cd 23 | $ cd catkin_ws/ 24 | $ catkin_make 25 | ``` 26 | 4- run rosmaster 27 | ```BASH 28 | $ roscore 29 | ``` 30 | 31 | 5- in a different terminal, play the rosbag 32 | ```BASH 33 | $ cd 34 | $ rosbag play sample_moriyama_150324.bag 35 | ``` 36 | 37 | 6- in a different terminal, open rviz 38 | ``` 39 | $ rosrun rviz rviz -f velodyne 40 | ``` 41 | 42 | 7- in a different terminal, run the curb detection node 43 | ```Bash 44 | $ rosrun curb_detec_cpp curb_detec 45 | ``` 46 | 47 | 8- setup rviz to see the pointcloud2 data 48 | 49 | ![alt text](https://imgshare.io/images/2019/12/25/Selection_001.png) 50 | 51 | this will show you the curbs detection only, 52 | 53 | if you want to see the lidar data, do the same thing again add a new pointcloud2 data, the topic name is /points_raw 54 | keep the size of the points smaller and than the curb detection points, and make them different color. This way you can see the different between the point clouds easily. 55 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | curb_detec_cpp 4 | 0.0.0 5 | The curb_detec_cpp package 6 | 7 | 8 | 9 | 10 | xh 11 | 12 | 13 | 14 | 15 | 16 | TODO 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | roscpp 53 | rospy 54 | std_msgs 55 | libpcl-all-dev 56 | libpcl-all 57 | roscpp 58 | rospy 59 | std_msgs 60 | roscpp 61 | rospy 62 | std_msgs 63 | libpcl-all-dev 64 | libpcl-all 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(curb_detec_cpp) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED COMPONENTS 11 | roscpp 12 | rospy 13 | std_msgs 14 | pcl_conversions 15 | pcl_ros 16 | ) 17 | ## System dependencies are found with CMake's conventions 18 | # find_package(Boost REQUIRED COMPONENTS system) 19 | 20 | 21 | ## Uncomment this if the package has a setup.py. This macro ensures 22 | ## modules and global scripts declared therein get installed 23 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 24 | # catkin_python_setup() 25 | 26 | ################################################ 27 | ## Declare ROS messages, services and actions ## 28 | ################################################ 29 | 30 | ## To declare and build messages, services or actions from within this 31 | ## package, follow these steps: 32 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 33 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 34 | ## * In the file package.xml: 35 | ## * add a build_depend tag for "message_generation" 36 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET 37 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 38 | ## but can be declared for certainty nonetheless: 39 | ## * add a exec_depend tag for "message_runtime" 40 | ## * In this file (CMakeLists.txt): 41 | ## * add "message_generation" and every package in MSG_DEP_SET to 42 | ## find_package(catkin REQUIRED COMPONENTS ...) 43 | ## * add "message_runtime" and every package in MSG_DEP_SET to 44 | ## catkin_package(CATKIN_DEPENDS ...) 45 | ## * uncomment the add_*_files sections below as needed 46 | ## and list every .msg/.srv/.action file to be processed 47 | ## * uncomment the generate_messages entry below 48 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 49 | 50 | ## Generate messages in the 'msg' folder 51 | # add_message_files( 52 | # FILES 53 | # Message1.msg 54 | # Message2.msg 55 | # ) 56 | 57 | ## Generate services in the 'srv' folder 58 | # add_service_files( 59 | # FILES 60 | # Service1.srv 61 | # Service2.srv 62 | # ) 63 | 64 | ## Generate actions in the 'action' folder 65 | # add_action_files( 66 | # FILES 67 | # Action1.action 68 | # Action2.action 69 | # ) 70 | 71 | ## Generate added messages and services with any dependencies listed here 72 | # generate_messages( 73 | # DEPENDENCIES 74 | # std_msgs 75 | # ) 76 | 77 | ################################################ 78 | ## Declare ROS dynamic reconfigure parameters ## 79 | ################################################ 80 | 81 | ## To declare and build dynamic reconfigure parameters within this 82 | ## package, follow these steps: 83 | ## * In the file package.xml: 84 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" 85 | ## * In this file (CMakeLists.txt): 86 | ## * add "dynamic_reconfigure" to 87 | ## find_package(catkin REQUIRED COMPONENTS ...) 88 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 89 | ## and list every .cfg file to be processed 90 | 91 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 92 | # generate_dynamic_reconfigure_options( 93 | # cfg/DynReconf1.cfg 94 | # cfg/DynReconf2.cfg 95 | # ) 96 | 97 | ################################### 98 | ## catkin specific configuration ## 99 | ################################### 100 | ## The catkin_package macro generates cmake config files for your package 101 | ## Declare things to be passed to dependent projects 102 | ## INCLUDE_DIRS: uncomment this if your package contains header files 103 | ## LIBRARIES: libraries you create in this project that dependent projects also need 104 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 105 | ## DEPENDS: system dependencies of this project that dependent projects also need 106 | catkin_package( 107 | # INCLUDE_DIRS include 108 | # LIBRARIES curb_detec_cpp 109 | # CATKIN_DEPENDS roscpp rospy std_msgs 110 | # DEPENDS system_lib 111 | ) 112 | 113 | ########### 114 | ## Build ## 115 | ########### 116 | 117 | ## Specify additional locations of header files 118 | ## Your package locations should be listed before other locations 119 | include_directories(include ${catkin_INCLUDE_DIRS}) 120 | 121 | ## Declare a C++ library 122 | # add_library(${PROJECT_NAME} 123 | # src/${PROJECT_NAME}/curb_detec_cpp.cpp 124 | # ) 125 | 126 | ## Add cmake target dependencies of the library 127 | ## as an example, code may need to be generated before libraries 128 | ## either from message generation or dynamic reconfigure 129 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 130 | 131 | ## Declare a C++ executable 132 | ## With catkin_make all packages are built within a single CMake context 133 | ## The recommended prefix ensures that target names across packages don't collide 134 | add_executable(curb_detec src/curb_detec.cpp) 135 | 136 | ## Rename C++ executable without prefix 137 | ## The above recommended prefix causes long target names, the following renames the 138 | ## target back to the shorter version for ease of user use 139 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 140 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 141 | 142 | ## Add cmake target dependencies of the executable 143 | ## same as for the library above 144 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 145 | 146 | ## Specify libraries to link a library or executable target against 147 | target_link_libraries(curb_detec ${catkin_LIBRARIES}) 148 | 149 | ############# 150 | ## Install ## 151 | ############# 152 | 153 | # all install targets should use catkin DESTINATION variables 154 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 155 | 156 | ## Mark executable scripts (Python etc.) for installation 157 | ## in contrast to setup.py, you can choose the destination 158 | # install(PROGRAMS 159 | # scripts/my_python_script 160 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 161 | # ) 162 | 163 | ## Mark executables and/or libraries for installation 164 | # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node 165 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 166 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 167 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 168 | # ) 169 | 170 | ## Mark cpp header files for installation 171 | # install(DIRECTORY include/${PROJECT_NAME}/ 172 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 173 | # FILES_MATCHING PATTERN "*.h" 174 | # PATTERN ".svn" EXCLUDE 175 | # ) 176 | 177 | ## Mark other files for installation (e.g. launch and bag files, etc.) 178 | # install(FILES 179 | # # myfile1 180 | # # myfile2 181 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 182 | # ) 183 | 184 | ############# 185 | ## Testing ## 186 | ############# 187 | 188 | ## Add gtest based cpp test target and link libraries 189 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_curb_detec_cpp.cpp) 190 | # if(TARGET ${PROJECT_NAME}-test) 191 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 192 | # endif() 193 | 194 | ## Add folders to be run by python nosetests 195 | # catkin_add_nosetests(test) 196 | -------------------------------------------------------------------------------- /src/curb_detec.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ros/ros.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | using namespace std; 10 | 11 | 12 | // Help sort point clouds. 13 | // Ascending order. (For points where y is greater than 0) 14 | 15 | bool comp_up(const pcl::PointXYZ &a, const pcl::PointXYZ &b) 16 | { 17 | return a.y < b.y; 18 | } 19 | 20 | // Descending. (For points where y is less than 0) 21 | bool comp_down(const pcl::PointXYZ &a, const pcl::PointXYZ &b) 22 | { 23 | return a.y > b.y; 24 | } 25 | 26 | // This class is used to detect curb. 27 | // Input a point cloud and return a point cloud consisting of detected curb points. 28 | // The main execution function is detection. 29 | class curbDetector 30 | { 31 | public: 32 | curbDetector(){} 33 | 34 | pcl::PointCloud detector(const std::vector > input) 35 | { 36 | pc_in = input; 37 | pcl::PointCloud look_test; 38 | 39 | 40 | // Process and detect each layer. Since we took 10 layers of 64 lidar here, the number of layers here is 10. 41 | for (int i = 0; i < 10; i++) 42 | { 43 | pcl::PointCloud pointsInTheRing = pc_in[i]; // Save the points on this line. 44 | pcl::PointCloud pc_left; // Store the point where y is greater than 0 (the left point). 45 | pcl::PointCloud pc_right; // Store the point where y is less than 0 (the right point). 46 | 47 | pcl::PointCloud cleaned_pc_left; 48 | pcl::PointCloud cleaned_pc_right; 49 | 50 | pcl::PointXYZ point; 51 | size_t numOfPointsInTheRing = pointsInTheRing.size(); 52 | 53 | // Separate the left and right points and save them to the corresponding point cloud. 54 | for (int idx = 0; idx < numOfPointsInTheRing; idx++) 55 | { 56 | point = pointsInTheRing[idx]; 57 | if (point.y >= 0) 58 | {pc_left.push_back(point);} 59 | else 60 | {pc_right.push_back(point);} 61 | } 62 | 63 | // Sort. (In ascending order of absolute value) 64 | sort(pc_left.begin(), pc_left.end(), comp_up); 65 | sort(pc_right.begin(), pc_right.end(), comp_down); 66 | 67 | 68 | slideForGettingPoints(pc_left, true); 69 | slideForGettingPoints(pc_right, false); 70 | 71 | } 72 | return curb_left + curb_right; 73 | 74 | } 75 | 76 | int slideForGettingPoints(pcl::PointCloud points, bool isLeftLine) 77 | { 78 | int w_0 = 10; 79 | int w_d = 30; 80 | int i = 0; 81 | 82 | // some important parameters influence the final performance. 83 | float xy_thresh = 0.1; 84 | float z_thresh = 0.06; 85 | 86 | int points_num = points.size(); 87 | 88 | while((i + w_d) < points_num) 89 | { 90 | float z_max = points[i].z; 91 | float z_min = points[i].z; 92 | 93 | int idx_ = 0; 94 | float z_dis = 0; 95 | 96 | for (int i_ = 0; i_ < w_d; i_++) 97 | { 98 | float dis = fabs(points[i+i_].z - points[i+i_+1].z); 99 | if (dis > z_dis) {z_dis = dis; idx_ = i+i_;} 100 | if (points[i+i_].z < z_min){z_min = points[i+i_].z;} 101 | if (points[i+i_].z > z_max){z_max = points[i+i_].z;} 102 | } 103 | 104 | if (fabs(z_max - z_min) >= z_thresh) 105 | { 106 | for (int i_ = 0; i_ < (w_d - 1); i_++) 107 | { 108 | float p_dist = sqrt(((points[i + i_].y - points[i + 1 + i_].y) * (points[i + i_].y - points[i + 1 + i_].y)) 109 | + ((points[i + i_].x - points[i + 1 + i_].x) *(points[i + i_].x - points[i + 1 + i_].x))); 110 | if (p_dist >= xy_thresh) 111 | { 112 | if (isLeftLine) {curb_left.push_back(points[i_ + i]);return 0;} 113 | else {curb_right.push_back(points[i_ + i]);return 0;} 114 | } 115 | } 116 | if (isLeftLine) {curb_left.push_back(points[idx_]);return 0;} 117 | else {curb_right.push_back(points[idx_]);return 0;} 118 | } 119 | i += w_0; 120 | } 121 | } 122 | 123 | private: 124 | std::vector > pc_in; 125 | pcl::PointCloud curb_left; 126 | pcl::PointCloud curb_right; 127 | }; 128 | 129 | 130 | class rosTransPoints 131 | { 132 | public: 133 | rosTransPoints() 134 | { 135 | //Set the parameters of the area of interest. The four parameters set the rectangle of this region of interest. 136 | // X is used to define the distance of the detection infront of the car. 137 | // Y is used to define the distance from the car to the curb. 138 | // x_up means infront of the car. x_down behid the car. 139 | // y_up means how far from the middle of the car to the left 140 | // y_down means how far from the middle of the car to the right 141 | // z, i am not very sure about it, but it is most likely the hight of the curb 142 | // ring_idx is the layer id. The lidar that they are using has 64 layers, and they selected 10 layers for detection. 143 | x_range_up = 30; 144 | x_range_down = 0; 145 | y_range_up = 20; 146 | y_range_down = -10; 147 | z_range_down = -1.5; 148 | int ring_idx_[10] = {25, 27, 30, 32, 35, 38, 40, 41, 42, 43}; 149 | memcpy(ring_idx, ring_idx_, sizeof(ring_idx_)); 150 | 151 | // define the publisher and the subscriber 152 | pub = nh.advertise("/curb_detection_result", 1); 153 | sub = nh.subscribe("/points_raw",3,&rosTransPoints::call_back,this); 154 | } 155 | 156 | void call_back(const boost::shared_ptr& input) 157 | { 158 | // this fuction is the callback for the subscriber, it gets excuted everytime a point cloud data is obtained through topic /points_raw 159 | clock_t startTime,endTime; 160 | startTime = ros::Time::now().toNSec(); 161 | curbDetector cd; 162 | 163 | pcl::fromROSMsg(*input, cloud_in); 164 | 165 | // Here to add the code for processing the pc(pointcloud). 166 | cloud_cleaned = cleanPoints(cloud_in); 167 | cloud_out = cd.detector(cloud_cleaned); 168 | 169 | pcl::toROSMsg(cloud_out, cloud_final); 170 | cloud_final.header.stamp = ros::Time::now(); 171 | cloud_final.header.frame_id = "velodyne"; 172 | pub.publish (cloud_final); 173 | 174 | endTime = ros::Time::now().toNSec(); 175 | cout << "The run time is:" << (double)(endTime - startTime) / 10e6 << "ms" << endl; 176 | } 177 | 178 | int hiPoints_WhereAreYouFrom(pcl::PointXYZ p) 179 | { 180 | // find which to which layer the point belongs 181 | // refer to this code to understand it:https://github.com/luhongquan66/loam_velodyne/blob/master/src/lib/MultiScanRegistration.cpp 182 | double angle; 183 | int scanID; 184 | angle = atan(p.z / sqrt(p.x * p.x + p.y * p.y)); 185 | // double test = sqrt(p.x * p.x + p.y * p.y); 186 | // cout << test << endl; 187 | scanID = (int)(angle * 134.18714161056457 + 58.81598513011153); 188 | 189 | return scanID; 190 | } 191 | 192 | std::vector > cleanPoints(pcl::PointCloud pc) 193 | { 194 | // this function cleans the points, this way you can have only the 10 layers we want 195 | // 1. gets rid of unwanted layers 196 | // 2. takes the points according to the layer 197 | // 3. save the new layers in a new pointcloud mesage and return them 198 | size_t cloudSize = pc.size(); 199 | // size_t ringSize; 200 | pcl::PointXYZ point; 201 | int scanID_; 202 | // pcl::PointCloud _laserCloud; 203 | std::vector > laserCloudScans(10); 204 | 205 | for (int i = 0; i < cloudSize; i++) 206 | { 207 | point.x = pc[i].x; 208 | point.y = pc[i].y; 209 | point.z = pc[i].z; 210 | 211 | if (!pcl_isfinite(point.x) || 212 | !pcl_isfinite(point.y) || 213 | !pcl_isfinite(point.z)) 214 | {continue;} 215 | if ((point.x < x_range_down) || (point.x > x_range_up) || (point.y < y_range_down) || (point.y > y_range_up) || (point.z > z_range_down)) 216 | {continue;} 217 | 218 | scanID_ = hiPoints_WhereAreYouFrom(point); 219 | 220 | for (int ring_num = 0;ring_num < 10; ring_num++) 221 | { 222 | if (scanID_ == ring_idx[ring_num]) 223 | {laserCloudScans[ring_num].push_back(point);} 224 | } 225 | } 226 | 227 | return laserCloudScans; 228 | } 229 | 230 | private: 231 | ros::NodeHandle nh; 232 | ros::Publisher pub; 233 | ros::Subscriber sub; 234 | pcl::PointCloud cloud_in; 235 | pcl::PointCloud cloud_out; 236 | std::vector > cloud_cleaned; 237 | sensor_msgs::PointCloud2 cloud_final; 238 | 239 | // parameters help to select the detection scope. 240 | int x_range_up; 241 | int x_range_down; 242 | int y_range_up; 243 | int y_range_down; 244 | float z_range_down; 245 | int ring_idx[10]; 246 | 247 | }; 248 | 249 | 250 | int main(int argc, char **argv) 251 | { 252 | ros::init(argc, argv, "curb_detector"); 253 | rosTransPoints start_detec; 254 | ros::spin(); 255 | } 256 | 257 | --------------------------------------------------------------------------------