123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- // SPDX-License-Identifier: BSD-2-Clause
- #include <memory>
- #include <iostream>
- #include <boost/optional.hpp>
- #include <ros/ros.h>
- #include <ros/time.h>
- #include <pcl_ros/point_cloud.h>
- #include <std_msgs/Time.h>
- #include <sensor_msgs/PointCloud2.h>
- #include <hdl_graph_slam/FloorCoeffs.h>
- #include <nodelet/nodelet.h>
- #include <pluginlib/class_list_macros.h>
- #include <pcl/common/transforms.h>
- #include <pcl/features/normal_3d.h>
- #include <pcl/search/impl/search.hpp>
- #include <pcl/filters/impl/plane_clipper3D.hpp>
- #include <pcl/filters/extract_indices.h>
- #include <pcl/sample_consensus/ransac.h>
- #include <pcl/sample_consensus/sac_model_plane.h>
- namespace hdl_graph_slam {
- class FloorDetectionNodelet : public nodelet::Nodelet {
- public:
- typedef pcl::PointXYZI PointT;
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW
- FloorDetectionNodelet() {}
- virtual ~FloorDetectionNodelet() {}
- virtual void onInit() {
- NODELET_DEBUG("initializing floor_detection_nodelet...");
- nh = getNodeHandle();
- private_nh = getPrivateNodeHandle();
- initialize_params();
- points_sub = nh.subscribe("/filtered_points", 256, &FloorDetectionNodelet::cloud_callback, this);
- floor_pub = nh.advertise<hdl_graph_slam::FloorCoeffs>("/floor_detection/floor_coeffs", 32);
- read_until_pub = nh.advertise<std_msgs::Header>("/floor_detection/read_until", 32);
- floor_filtered_pub = nh.advertise<sensor_msgs::PointCloud2>("/floor_detection/floor_filtered_points", 32);
- floor_points_pub = nh.advertise<sensor_msgs::PointCloud2>("/floor_detection/floor_points", 32);
- }
- private:
- /**
- * @brief initialize parameters
- */
- void initialize_params() {
- tilt_deg = private_nh.param<double>("tilt_deg", 0.0); // approximate sensor tilt angle [deg]
- sensor_height = private_nh.param<double>("sensor_height", 2.0); // approximate sensor height [m]
- height_clip_range = private_nh.param<double>("height_clip_range", 1.0); // points with heights in [sensor_height - height_clip_range, sensor_height + height_clip_range] will be used for floor detection
- floor_pts_thresh = private_nh.param<int>("floor_pts_thresh", 512); // minimum number of support points of RANSAC to accept a detected floor plane
- floor_normal_thresh = private_nh.param<double>("floor_normal_thresh", 10.0); // verticality check thresold for the detected floor plane [deg]
- use_normal_filtering = private_nh.param<bool>("use_normal_filtering", true); // if true, points with "non-"vertical normals will be filtered before RANSAC
- normal_filter_thresh = private_nh.param<double>("normal_filter_thresh", 20.0); // "non-"verticality check threshold [deg]
- points_topic = private_nh.param<std::string>("points_topic", "/velodyne_points");
- }
- /**
- * @brief callback for point clouds
- * @param cloud_msg point cloud msg
- */
- void cloud_callback(const sensor_msgs::PointCloud2ConstPtr& cloud_msg) { // 处理点云数据的回调函数
- pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>());
- pcl::fromROSMsg(*cloud_msg, *cloud);
- if(cloud->empty()) {
- return;
- }
- // floor detection
- boost::optional<Eigen::Vector4f> floor = detect(cloud);
- // publish the detected floor coefficients
- hdl_graph_slam::FloorCoeffs coeffs;
- coeffs.header = cloud_msg->header;
- if(floor) {
- coeffs.coeffs.resize(4);
- for(int i = 0; i < 4; i++) {
- coeffs.coeffs[i] = (*floor)[i];
- }
- }
- floor_pub.publish(coeffs);
- // for offline estimation
- std_msgs::HeaderPtr read_until(new std_msgs::Header());
- read_until->frame_id = points_topic;
- read_until->stamp = cloud_msg->header.stamp + ros::Duration(1, 0);
- read_until_pub.publish(read_until);
- read_until->frame_id = "/filtered_points";
- read_until_pub.publish(read_until);
- }
- /**
- * @brief detect the floor plane from a point cloud
- * @param cloud input cloud
- * @return detected floor plane coefficients
- */
- boost::optional<Eigen::Vector4f> detect(const pcl::PointCloud<PointT>::Ptr& cloud) const { // 主要的检测函数,用 RANSAC 算法你和一个平面,并进行垂直性检查
- // compensate the tilt rotation
- Eigen::Matrix4f tilt_matrix = Eigen::Matrix4f::Identity();
- tilt_matrix.topLeftCorner(3, 3) = Eigen::AngleAxisf(tilt_deg * M_PI / 180.0f, Eigen::Vector3f::UnitY()).toRotationMatrix();
- // filtering before RANSAC (height and normal filtering)
- pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>);
- pcl::transformPointCloud(*cloud, *filtered, tilt_matrix);
- filtered = plane_clip(filtered, Eigen::Vector4f(0.0f, 0.0f, 1.0f, sensor_height + height_clip_range), false);
- filtered = plane_clip(filtered, Eigen::Vector4f(0.0f, 0.0f, 1.0f, sensor_height - height_clip_range), true);
- if(use_normal_filtering) {
- filtered = normal_filtering(filtered);
- }
- pcl::transformPointCloud(*filtered, *filtered, static_cast<Eigen::Matrix4f>(tilt_matrix.inverse()));
- if(floor_filtered_pub.getNumSubscribers()) {
- filtered->header = cloud->header;
- floor_filtered_pub.publish(*filtered);
- }
- // too few points for RANSAC
- if(filtered->size() < floor_pts_thresh) {
- return boost::none;
- }
- // RANSAC
- pcl::SampleConsensusModelPlane<PointT>::Ptr model_p(new pcl::SampleConsensusModelPlane<PointT>(filtered));
- pcl::RandomSampleConsensus<PointT> ransac(model_p);
- ransac.setDistanceThreshold(0.1);
- ransac.computeModel();
- pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
- ransac.getInliers(inliers->indices);
- // too few inliers
- if(inliers->indices.size() < floor_pts_thresh) {
- return boost::none;
- }
- // verticality check of the detected floor's normal
- Eigen::Vector4f reference = tilt_matrix.inverse() * Eigen::Vector4f::UnitZ();
- Eigen::VectorXf coeffs;
- ransac.getModelCoefficients(coeffs);
- double dot = coeffs.head<3>().dot(reference.head<3>());
- if(std::abs(dot) < std::cos(floor_normal_thresh * M_PI / 180.0)) {
- // the normal is not vertical
- return boost::none;
- }
- // make the normal upward
- if(coeffs.head<3>().dot(Eigen::Vector3f::UnitZ()) < 0.0f) {
- coeffs *= -1.0f;
- }
- if(floor_points_pub.getNumSubscribers()) {
- pcl::PointCloud<PointT>::Ptr inlier_cloud(new pcl::PointCloud<PointT>);
- pcl::ExtractIndices<PointT> extract;
- extract.setInputCloud(filtered);
- extract.setIndices(inliers);
- extract.filter(*inlier_cloud);
- inlier_cloud->header = cloud->header;
- floor_points_pub.publish(*inlier_cloud);
- }
- return Eigen::Vector4f(coeffs);
- }
- /**
- * @brief plane_clip
- * @param src_cloud
- * @param plane
- * @param negative
- * @return
- */
- pcl::PointCloud<PointT>::Ptr plane_clip(const pcl::PointCloud<PointT>::Ptr& src_cloud, const Eigen::Vector4f& plane, bool negative) const {
- pcl::PlaneClipper3D<PointT> clipper(plane);
- pcl::PointIndices::Ptr indices(new pcl::PointIndices);
- clipper.clipPointCloud3D(*src_cloud, indices->indices);
- pcl::PointCloud<PointT>::Ptr dst_cloud(new pcl::PointCloud<PointT>);
- pcl::ExtractIndices<PointT> extract;
- extract.setInputCloud(src_cloud);
- extract.setIndices(indices);
- extract.setNegative(negative);
- extract.filter(*dst_cloud);
- return dst_cloud;
- }
- /**
- * @brief filter points with non-vertical normals
- * @param cloud input cloud
- * @return filtered cloud
- */
- pcl::PointCloud<PointT>::Ptr normal_filtering(const pcl::PointCloud<PointT>::Ptr& cloud) const {
- pcl::NormalEstimation<PointT, pcl::Normal> ne;
- ne.setInputCloud(cloud);
- pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
- ne.setSearchMethod(tree);
- pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
- ne.setKSearch(10);
- ne.setViewPoint(0.0f, 0.0f, sensor_height);
- ne.compute(*normals);
- pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>);
- filtered->reserve(cloud->size());
- for(int i = 0; i < cloud->size(); i++) {
- float dot = normals->at(i).getNormalVector3fMap().normalized().dot(Eigen::Vector3f::UnitZ());
- if(std::abs(dot) > std::cos(normal_filter_thresh * M_PI / 180.0)) {
- filtered->push_back(cloud->at(i));
- }
- }
- filtered->width = filtered->size();
- filtered->height = 1;
- filtered->is_dense = false;
- return filtered;
- }
- private:
- ros::NodeHandle nh;
- ros::NodeHandle private_nh;
- // ROS topics
- ros::Subscriber points_sub;
- ros::Publisher floor_pub;
- ros::Publisher floor_points_pub;
- ros::Publisher floor_filtered_pub;
- std::string points_topic;
- ros::Publisher read_until_pub;
- // floor detection parameters
- // see initialize_params() for the details
- double tilt_deg;
- double sensor_height;
- double height_clip_range;
- int floor_pts_thresh;
- double floor_normal_thresh;
- bool use_normal_filtering;
- double normal_filter_thresh;
- };
- } // namespace hdl_graph_slam
- PLUGINLIB_EXPORT_CLASS(hdl_graph_slam::FloorDetectionNodelet, nodelet::Nodelet)
|