欢迎点云相关产学研的学者和团体加入我们。
本小节举例说明了如何采用随机采样一致性估计从带有噪声的点云中提取一个圆柱体模型,整个程序处理流程如下:
1、过滤掉远于1.5米的数据点;
2、估计每个点的表面法线;
3、分割出平面模型(在我们的演示数据集中表示桌面)并保存到磁盘中;
4、分割圆出柱体模型(在我们的演示数据集中表示圆杯)并保存到磁盘中。
注意:由于数据中噪音的出现,圆柱体模型并不十分严格。
首先,在PCL(Point Cloud Learning)中国协助发行的书[1]提供光盘的第14章例2文件夹中,打开名为cylinder_segmentation.cpp的代码文件,在同一文件夹下有点云文件table_scene_mug_stereo_textured_plane.pcd。
下面来详细解释打开源代码的关键语句。
pcl::PCDReader reader; //pcd文件读取对象
pcl::PassThrough<PointT> pass; //直通滤波对象
pcl::NormalEstimation<PointT, pcl::Normal> ne; //法线估计对象
pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg; //分割对象
pcl::PCDWriter writer; //pcd文件写入对象
pcl::ExtractIndices<PointT> extract; //点提取对象
pcl::ExtractIndices<pcl::Normal> extract_normals; //点提取对象
pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT> ());
上面代码定义在程序中用到的对象。
pcl::PointCloud<PointT>::Ptr cloud (new pcl::PointCloud<PointT>);
pcl::PointCloud<PointT>::Ptr cloud_filtered (new pcl::PointCloud<PointT>);
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
pcl::PointCloud<PointT>::Ptr cloud_filtered2 (new pcl::PointCloud<PointT>);
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals2 (new pcl::PointCloud<pcl::Normal>);
pcl::ModelCoefficients::Ptr coefficients_plane (new pcl::ModelCoefficients), coefficients_cylinder (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers_plane (new pcl::PointIndices), inliers_cylinder (new pcl::PointIndices);
上面代码定义在程序中用到的数据对象,点类型、法线类型的点云对象,以及模型系数等,以及存储平面上内点的点索引集合对象。
reader.read ("table_scene_mug_stereo_textured.pcd", *cloud);
std::cerr <<"PointCloud has: "<< cloud->points.size () <<" data points."<< std::endl;
上面代码完成对点云数据从文件的读取。
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");
pass.setFilterLimits (0, 1.5);
pass.filter (*cloud_filtered);
std::cerr <<"PointCloud after filtering has: "<< cloud_filtered->points.size () <<" data points."<< std::endl;
上面代码进行直通滤波,将z轴不在(0,1.5)范围内的点过滤掉,将剩余点存储在cloud_filtered对象中后续使用。
ne.setSearchMethod (tree);
ne.setInputCloud (cloud_filtered);
ne.setKSearch (50);
ne.compute (*cloud_normals);
上面代码对过滤后的点云进行法线估计,为后续进行基于法线的分割准备数据。
//设置分割所用的模型类型、方法、相关参数
seg.setOptimizeCoefficients (true);
seg.setModelType (pcl::SACMODEL_NORMAL_PLANE);
seg.setNormalDistanceWeight (0.1);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setMaxIterations (100);
seg.setDistanceThreshold (0.03);
seg.setInputCloud (cloud_filtered);
seg.setInputNormals (cloud_normals);
// 根据上面的输入参数执行分割获取平面模型系数和处在平面上的内点
seg.segment (*inliers_plane, *coefficients_plane);
std::cerr <<"Plane coefficients: "<<*coefficients_plane << std::endl;
// 从点云中抽取分割的处在平面上的点集
extract.setInputCloud (cloud_filtered);
extract.setIndices (inliers_plane);
extract.setNegative (false);
pcl::PointCloud<PointT>::Ptr cloud_plane (new pcl::PointCloud<PointT> ());
extract.filter (*cloud_plane);
//存储分割得到的平面上的点到点云文件
std::cerr <<"PointCloud representing the planar component: "<< cloud_plane->points.size () <<" data points."<< std::endl;
writer.write ("table_scene_mug_stereo_textured_plane.pcd", *cloud_plane, false);
上面代码将过滤后的点云进行分割,并从点云中提取出处在平面上的点,存储到点云文件。
//为圆柱体分割创建分割对象,并设置所有参数
seg.setOptimizeCoefficients (true); //设置对估计的模型系数需要进行优化
seg.setModelType (pcl::SACMODEL_CYLINDER); //设置分割模型为圆柱型
seg.setMethodType (pcl::SAC_RANSAC); //设置采用RANSAC作为算法的参数估计方法
seg.setNormalDistanceWeight (0.1); //设置表面法线权重系数
seg.setMaxIterations (10000); //设置迭代的最大次数10000
seg.setDistanceThreshold (0.05); //设置内点到模型的距离允许最大值
seg.setRadiusLimits (0, 0.1); //设置估计出的圆柱模型的半径范围
如上,我们使用RANSAC随机采样一致性鲁棒估计来获取圆柱体模型系数,并且限制了每个局内点到模型的距离阀值DistanceThreshold,即内点与模型的距离不能大于5厘米,设置表面法线的影响权重NormalDistanceWeight为0.1,并且限制了圆柱体模型的半径RadiusLimits要小于10厘米,估计出圆柱模型系数以及对应的内点后,存储内点到点云文件。
利用光盘提供的CMakeLists.txt文件,在cmake中建立工程文件,并生成相应的可执行文件。在cmd中键入以下命令:
...>pcd_viewer.exetable_scene_mug_stereo_textured_plane.pcd
如图1是原始点云可视化后的结果,三维场景中有平面、杯子以及其他物体。
图1原始点云可视化效果图
在cmd中键入以下命令:
...>cylinder_segmentation.exe
运行之后将看到如图2所示的结果,同时在当前文件夹下生产分割的平面和圆柱点云数据,用点云查看工具,打开后分别如图3和图4所示,分别存储分割得到的圆柱和平面。
图2 圆柱体模型分割提取示例运行结果
图3 分割得到的圆柱(杯子)
图4 分割得到的平面(桌子)
敬请关注PCL(Point Cloud Learning)中国更多的点云库PCL(Point Cloud Library)相关官方教程。
参考文献:
1.朱德海、郭浩、苏伟.点云库PCL学习教程(ISBN 978-7-5124-0954-5)北京航空航天出版社 2012-10