镭神16线激光雷达利用ROS系统进行平面分割测试

试验目的

笔者手里有一个镭神16线激光雷达,用此激光雷达做一个测试试验,测试同等级的激光雷达的点云数据相比谁的更稳定,数据量谁的更多一些,后来考虑:不需要很麻烦的测试步骤,仅仅需要两步就可以了,第一步:把雷达驱动安装,在RVIZ中进行可视化;第二步:利用平面分割算法将地面影响消除,然后再将过滤地面的点云数据传输到欧式聚类算法中进行聚类。这样可以有效的观察到激光雷达点云数量的多少。这一切操作都在ROS系统中(不介绍ROS的安装了)。

雷达驱动安装

找到驱动的方法有很多,可以在Github网站上寻找代码,也可以在官方网站中找到驱动包。这里需要的步骤有 :

  1. 硬件安装;
  2. 网线连接笔记本和雷达,网线地址一般是192.168.1.102
  3. 配置yaml文件,需要设置相对应的型号的雷达;
  4. 编译ROS工作空间,catkin_make ,然后source一下
  5. 最后roslaunch开启驱动
// 开启驱动命令
roslaunch rslidar_sdk start.launch
//这里的rslidar_sdk是launch文件所在的文件夹,start.launch是开启文件

平面分割算法

对于雷达点云数据,将地面分割出去,便于后续的数据处理。
1. ROS初始化;

int main(int argc, char **argv)
{
  ros::init(argc, argv, "pcl_test");//初始化ROS节点

  ros::NodeHandle nh;	//该句柄是roscpp的“万能接口类”,常用于创建topic server等,可以指定命名空间(全局、普通和私有)

  PclTestCore core(nh);	//整个程序定义的类,赋予core这个对象
  // core.Spin();
  return 0;
}

2. ROS设定发布者和订阅者,使用该算法,仅仅需要改一下订阅者,改成接收自己的激光雷达topic,

PclTestCore::PclTestCore(ros::NodeHandle &nh)
{
 sub_point_cloud_ = nh.subscribe("/rslidar_points", 10, &PclTestCore::point_cb, this);	//订阅来自激光雷达的点云数据

 pub_ground_ = nh.advertise<sensor_msgs::PointCloud2>("/filtered_points_ground", 10);	//发布只含地面的点云数据
 pub_no_ground_ = nh.advertise<sensor_msgs::PointCloud2>("/filtered_points_no_ground", 10);	//发布不含地面的点云数据

 ros::spin();	//循环等待,一直订阅发布的作用
}

3. 点云裁剪,要分割地面和非地面,那么过高的区域不需要考虑,对点云高度进行裁剪,lidar的高度是1.78米,因此裁掉1.28米以上的部分。

void PclTestCore::clip_above(double clip_height, const pcl::PointCloud<pcl::PointXYZI>::Ptr in,
                          const pcl::PointCloud<pcl::PointXYZI>::Ptr out)
{
 pcl::ExtractIndices<pcl::PointXYZI> cliper;

 cliper.setInputCloud(in);
 pcl::PointIndices indices;
#pragma omp for //该语法OpenMP的并行化语法,即希望通过OpenMP并行化执行这条语句的for循环,从而起到加速的效果
 for (size_t i = 0; i < in->points.size(); i++)
 {
     if (in->points[i].z > clip_height)
     {
         indices.indices.push_back(i);
     }
 }
 cliper.setIndices(boost::make_shared<pcl::PointIndices>(indices));
 cliper.setNegative(true); //ture to remove the indices
 cliper.filter(*out);
}

4. 消除车身自身对雷达反射的影响,我们对近距离的点云进行过滤

void PclTestCore::remove_close_pt(double min_distance, const pcl::PointCloud<pcl::PointXYZI>::Ptr in,
                               const pcl::PointCloud<pcl::PointXYZI>::Ptr out)
{
 pcl::ExtractIndices<pcl::PointXYZI> cliper;

 cliper.setInputCloud(in);
 pcl::PointIndices indices;
#pragma omp for
 for (size_t i = 0; i < in->points.size(); i++)
 {
     double distance = sqrt(in->points[i].x * in->points[i].x + in->points[i].y * in->points[i].y);

     if (distance < min_distance)
     {
         indices.indices.push_back(i);
     }
 }
 cliper.setIndices(boost::make_shared<pcl::PointIndices>(indices));
 cliper.setNegative(true); //ture to remove the indices
 cliper.filter(*out);
}

5. 这两个坡度阈值的单位为度(degree)我们通过这两个坡度阈值以及当前点的半径,求得高度阈值,通过判断当前点的高度是否在加减高度阈值范围内,来判断是否地面。
如下
该算法的核心:如上图把激光雷达纵截面画了出来,这是一个数学问题(工程类问题最终就是数学问题!)点云投射到地面,通过点与激光雷达的夹角点和激光雷达的直线距离计算出来相邻的点的高度差,如果在阈值范围内,那么判断为地面。

void PclTestCore::classify_pc(std::vector<PointCloudXYZIRTColor> &in_radial_ordered_clouds,
                             pcl::PointIndices &out_ground_indices,
                             pcl::PointIndices &out_no_ground_indices)
{
   out_ground_indices.indices.clear();
   out_no_ground_indices.indices.clear();
#pragma omp for
   for (size_t i = 0; i < in_radial_ordered_clouds.size(); i++) //sweep through each radial division 遍历每一根射线
   {
       float prev_radius = 0.f;
       float prev_height = -SENSOR_HEIGHT;
       bool prev_ground = false;
       bool current_ground = false;
       for (size_t j = 0; j < in_radial_ordered_clouds[i].size(); j++) //loop through each point in the radial div
       {
           float points_distance = in_radial_ordered_clouds[i][j].radius - prev_radius;
           float height_threshold = tan(DEG2RAD(local_max_slope_)) * points_distance;
           float current_height = in_radial_ordered_clouds[i][j].point.z;
           float general_height_threshold = tan(DEG2RAD(general_max_slope_)) * in_radial_ordered_clouds[i][j].radius;

           //for points which are very close causing the height threshold to be tiny, set a minimum value
           if (points_distance > concentric_divider_distance_ && height_threshold < min_height_threshold_)
           {
               height_threshold = min_height_threshold_;
           }

           //check current point height against the LOCAL threshold (previous point)
           if (current_height <= (prev_height + height_threshold) && current_height >= (prev_height - height_threshold))
           {
               //Check again using general geometry (radius from origin) if previous points wasn't ground
               if (!prev_ground)
               {
                   if (current_height <= (-SENSOR_HEIGHT + general_height_threshold) && current_height >= (-SENSOR_HEIGHT - general_height_threshold))
                   {
                       current_ground = true;
                   }
                   else
                   {
                       current_ground = false;
                   }
               }
               else
               {
                   current_ground = true;
               }
           }
           else
           {
               //check if previous point is too far from previous one, if so classify again
               if (points_distance > reclass_distance_threshold_ &&
                   (current_height <= (-SENSOR_HEIGHT + height_threshold) && current_height >= (-SENSOR_HEIGHT - height_threshold)))
               {
                   current_ground = true;
               }
               else
               {
                   current_ground = false;
               }
           }

           if (current_ground)
           {
               out_ground_indices.indices.push_back(in_radial_ordered_clouds[i][j].original_index);
               prev_ground = true;
           }
           else
           {
               out_no_ground_indices.indices.push_back(in_radial_ordered_clouds[i][j].original_index);
               prev_ground = false;
           }

           prev_radius = in_radial_ordered_clouds[i][j].radius;
           prev_height = in_radial_ordered_clouds[i][j].point.z;
       }
   }
}

附录全部代码:https://download.csdn.net/download/xiaobaiwsc/86398602

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是一个简单的Lua脚本范本,用于在CoppeliaSim中模拟Velodyne 16线激光雷达并发布ROS话题: ```lua function sysCall_init() -- 获取Velodyne模型的句柄 velodyneHandle = sim.getObjectHandle('Velodyne') -- 设置ROS节点名称和话题名称 nodeName = 'velodyne_sim' topicName = '/velodyne_points' -- 导入ROS插件并初始化节点 simExtROS.init(nodeName) -- 创建Velodyne 16线激光雷达的发布者 publisher = simExtROS.advertise(topicName, 'sensor_msgs/PointCloud2') -- 设置Velodyne的参数 rotationSpeed = 10 -- 激光雷达旋转速度,单位是rad/s scanAngle = 2*math.pi -- 激光雷达扫描角度,单位是rad scanResolution = 0.1 -- 激光雷达分辨率,单位是m -- 设置定时器,用于控制激光雷达的运行 timerInterval = 0.1 -- 定时器间隔时间,单位是s timerHandle = sim.addTimer(timerInterval, 'publishPointCloud') end function publishPointCloud() -- 获取当前仿真时间 currentTime = sim.getSimulationTime() -- 计算当前激光雷达的角度 angle = (currentTime * rotationSpeed) % (2*math.pi) -- 创建PointCloud2消息对象 msg = simROS.createPointCloud2Message('header', 1, simROS.pointCloud2_fields_xyzrgb) -- 设置PointCloud2消息的元数据 msg.header.stamp = simROS.getTime() msg.header.frame_id = 'velodyne_link' msg.height = 1 msg.width = 16 msg.is_bigendian = false msg.point_step = 32 msg.row_step = 512 msg.is_dense = true -- 填充PointCloud2消息的数据 pointData = {} for i=1,16 do -- 计算当前激光线的角度 laserAngle = angle + (i-1) * scanAngle / 15 -- 计算当前激光线的坐标 x = math.cos(laserAngle) y = math.sin(laserAngle) z = 0 -- 将激光点添加到PointCloud2消息中 pointData[(i-1)*4+1] = x pointData[(i-1)*4+2] = y pointData[(i-1)*4+3] = z pointData[(i-1)*4+4] = 255 * (i-1) / 15 end msg.data = sim.packFloats(pointData) -- 发布PointCloud2消息 simExtROS.publish(publisher, msg) end function sysCall_cleanup() -- 停止定时器 sim.removeTimer(timerHandle) -- 关闭ROS节点 simExtROS.shutdown() end ``` 这个范本使用Lua脚本语言,在CoppeliaSim中模拟Velodyne 16线激光雷达的工作,并发布ROS话题`/velodyne_points`。你可以根据自己的需求进行修改和扩展。在使用前,需要将该脚本添加到CoppeliaSim场景中,然后启动ROS节点和仿真。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值