> 文章列表 > 使用PCL滤波器实现点云裁剪

使用PCL滤波器实现点云裁剪

使用PCL滤波器实现点云裁剪

主要目的就是根据已知的ROI区域,对点云进行裁剪。要么留下点云ROI区域,要么去除。
ROI区域一般都是一个矩形,即(x,y,width,height)。
那么封装的函数形式一般如下:

pcl::PointCloud<pcl::PointXYZ>::Ptr CloudClipper(pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud,double x,double y, double width, double height)
{// 实现点云滤波// 创建滤波后点云pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());// 调用filter方法得到滤波后点云return cloud_filtered;
}

比较简单直接粗暴的方法就是使用直通滤波

PCL库中其实有条件滤波的,感觉听起来确实很像想要用的滤波器,于是就尝试了一下

#include <pcl/filters/conditional_removal.h>
pcl::PointCloud<pcl::PointXYZ>::Ptr ConditionFilter(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, double x, double y, double width, double height){//创建条件限定下的滤波器//pcl::ConditionBasepcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());//创建条件定义对象//为条件定义对象添加比较算子: 使用大于0.0和小于0.8这两个条件用于建立滤波器。range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(newpcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, x)));//添加在x字段上大于0的比较算子  range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(newpcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, x + width)));//添加在x字段上小于0.8的比较算子range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(newpcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, y)));range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(newpcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, y + height)));//创建滤波器并用条件定义对象初始化pcl::ConditionalRemoval<pcl::PointXYZ> condrem;condrem.setCondition(range_cond);condrem.setInputCloud(cloud);           //设置输入点云condrem.setKeepOrganized(false);         //设置保持点云的结构:为true时被剔除的点为NAN//condrem.setUserFilterValue(0.1f);//condrem.getIndicespcl::IndicesConstPtr inliers = condrem.getRemovedIndices();if (inliers != nullptr){std::cout << "indice number: " << inliers->size() << std::endl;}pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());condrem.filter(*cloud_filtered);        //执行条件滤波,存储结果到cloud_filteredstd::cout << "filter size: " << cloud_filtered->points.size() << std::endl;/* pcl::IndicesConstPtr inliers = condrem.getRemovedIndices();pcl::copyPointCloud<pcl::PointXYZ>(*cloud, *inliers, *cloud_filtered);*/return cloud_filtered;}

逐行解释:
1.首先需要创建一个条件定义对象,用于设定条件。其实条件主要有两种pcl::ConditionAndpcl::PointT::Ptr和pcl::ConditionOrpcl::PointT::Ptr,前者是与条件,意思是所有条件都要满足,其实就是每个条件得到的点云求交集,后者是或条件,那么就是每个条件滤波结果求并集。可以根据需要进行使用。

2.设置字段,即range_cond->addComparison(pcl::FieldComparisonpcl::PointXYZ::ConstPtr(new
pcl::FieldComparisonpcl::PointXYZ(“x”, pcl::ComparisonOps::GT, x)));中的“x”其实是指点云的点的x坐标值,如果是“r”可以筛选出RGB中R通道的值,那么点云的数据结构必须是PointXYZRGB而不是PointXYZ,这个得注意。另外对于pcl::ComparisonOps::GT和pcl::ComparisonOps::LT,其实GT就是greater than即大于,LT就是less than即小于。

3.//创建滤波器并用条件定义对象初始化
pcl::ConditionalRemovalpcl::PointXYZ condrem;
condrem.setCondition(range_cond);
condrem.setInputCloud(cloud); //设置输入点云
以上几句无非是实例化滤波器对象后,将上述设置好了的条件和点云都输入进去

4.setKeepOrganized则是用于进行条件移除之后是否保持点云的有序性,但是一般处理的点云都是无序点云,大多数情况下这个地方设置为false,但一定要视实际情况而定。如果无序点云中的点之间不存在明确定义的拓扑关系,例如没有明确的连接关系或者边界关系,那么在移除点云中的一些点后,点云的有序属性也会被破坏。此时,即使设置 setKeepOrganized 为 true,输出点云仍然是无序的。

5.setUserFilterValue:该函数用于设置条件移除的阈值参数。对于某些条件(例如欧式距离),需要指定阈值才能进行移除。setUserFilterValue 函数可以设置这个阈值。该函数需要传递一个模板参数,表示阈值的类型,可以是 float、double、int 等。在使用 setUserFilterValue 函数时,应该根据实际情况设置合适的阈值,避免移除过多或者过少的点。

6.条件滤波器还可以得到
condrem.getIndices();
condrem.getRemovedIndices();即得到点的索引和去除点的索引。有的滤波器中有setNegative方法,设置为true时可以得到滤波器滤掉的点,设置为false时可以得到滤波器留下来的点。但是条件滤波器中没有该方法。于是想通过得到去除点的索引,然后再通过pcl::copyPointCloudpcl::PointXYZ(*cloud, *inliers, *cloud_filtered);提取得到滤波器去掉的点。结果发现滤波器得到的索引中size为0,也就是无索引。可能有以下几个原因:

在执行条件滤波操作之前,没有设置条件对象。条件滤波器必须先设置条件对象,才能根据条件对点云进行筛选。如果没有设置条件对象,则条件滤波器会将输入点云中的所有点都保留下来,因此“已移除索引”列表中就没有任何点。设置的条件不满足任何点。如果设置的条件不满足输入点云中的任何点,则条件滤波器不会移除任何点,因此“已移除索引”列表中也就没有任何点。使用的数据类型不正确。条件滤波器的输入点云和条件对象都必须是相同的数据类型,否则条件滤波器会出现异常,导致“已移除索引”列表为空。例如,如果输入点云是 XYZRGB 类型的,而条件对象是 XYZ 类型的,则条件滤波器会出现异常。

其实上述三个情况都没问题。可是为啥size为0呢,是点云数据结构中本身就没有索引嘛???

而使用condrem.getIndices();返回的更是一个nullptr空指针,原因如下:

1.在执行条件滤波操作之前,没有设置输入点云。条件滤波器必须设置输入点云,才能根据条件对点云进行筛选。如果没有设置输入点云,则 getIndices() 函数返回的指针是 nullptr。

2.条件滤波器没有将任何点移除。如果设置的条件不满足输入点云中的任何点,或者输入点云本身已经满足条件,条件滤波器不会移除任何点,因此 getIndices() 函数返回的指针是 nullptr。

3.没有启用索引输出。条件滤波器默认情况下不会输出被移除点的索引。如果要输出被移除点的索引,需要在条件滤波器上启用索引输出,即pcl::ConditionalRemovalpcl::PointXYZ condrem;
condrem.setKeepOrganized(true); // 启用索引输出
将setKeepOrganized设置为true后,getIndices返回的指针不为空了,但是size为输入点云的数量。而且!!!此时竟然无法滤除点云了。
使用PCL滤波器实现点云裁剪
后面发现其实pcl::IndicesPtr outliers = condrem.getIndices();放在condrem.filter(*cloud_filtered);后面之后,返回得指针也不为空,但是其size还是和输入的点云中点的size一致。

所以,目前通过条件滤波可以得到从点云中剪裁下来的点云,但是无法获取到除了剪裁下来的点之外的点。

使用crop_box进行剪裁

代码如下,可以直接使用

pcl::PointCloud<pcl::PointXYZ>::Ptr cropclipper3D(pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, double x, double y, double width, double height){pcl::PointXYZ min_point(x, y, -100);pcl::PointXYZ max_point(x + width, y + height, 100);Eigen::Vector4f minpt(x, y, -100, 1);Eigen::Vector4f maxpt(x + width, y + height, 100, 1);pcl::CropBox<pcl::PointXYZ> crop_box;crop_box.setMin(minpt);crop_box.setMax(maxpt);// 将点云限制在 3D 盒子内部或者外部,并保存输出点云pcl::PointCloud<pcl::PointXYZ>::Ptr clipped_cloud(new pcl::PointCloud<pcl::PointXYZ>);crop_box.setInputCloud(cloud);crop_box.setNegative(true);crop_box.filter(*clipped_cloud);return clipped_cloud;}

这个滤波器需要先确定box的两个坐标点,这两个点得是对角线上两个点(一个长方体距离最远的两个点)。然后这个滤波器就有我们之前提到的setNegative方法,就可以按照意愿来获取ROI内的点还是区域外的点。效果如下图所示,感觉还行
使用PCL滤波器实现点云裁剪
使用PCL滤波器实现点云裁剪
运行时间大概是44ms,这个速度感觉还好,如果能更快就好了。一块平面点云上如果有很多区域要提取或者裁剪掉,可以考虑多线程。

紧接着,想要研究BoxClipper3D的使用方法,但是找了好久终于在github上找到了,戳这里查看!