点云裁剪是根据提取划分或者说标注出来的点云区域(ROI区域),对点云进行区域分离(点云裁剪和点云分割还是有区别的,所以这里用分离而不是分割)。根据已知的ROI区域,对点云进行裁剪。要么留下点云ROI区域,要么去除。
ROI区域一般都是一个矩形,即(x,y,width,height),也可以是多边形。
那么封装的函数形式一般如下:
比较简单直接粗暴的方法就是使用直通滤波,直通滤波只需要遍历点云一次,且只比较一个坐标值的大小,所以速度通常比较快。
除了直通滤波之外还可以使用条件滤波,可以比较多个坐标值的大小,对点云进行裁剪。
逐行解释:
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即小于。
3https://blog.csdn.net/dyk4ever/article/details//创建滤波器并用条件定义对象初始化
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,也就是无索引。可能有以下几个原因:
其实上述三个情况都没问题。可是为啥size为0呢,是点云数据结构中本身就没有索引嘛???
而使用condrem.getIndices();返回的更是一个nullptr空指针,原因如下:
1.在执行条件滤波操作之前,没有设置输入点云。条件滤波器必须设置输入点云,才能根据条件对点云进行筛选。如果没有设置输入点云,则 getIndices() 函数返回的指针是 nullptr。
2.条件滤波器没有将任何点移除。如果设置的条件不满足输入点云中的任何点,或者输入点云本身已经满足条件,条件滤波器不会移除任何点,因此 getIndices() 函数返回的指针是 nullptr。
所以,目前通过条件滤波可以得到从点云中剪裁下来的点云,但是无法获取到除了剪裁下来的点之外的点。
直通滤波是比较了单个坐标值,而cropbox其实是比较了x,y,z。但是也是遍历一遍点云,速度也挺快的。
代码如下:
紧接着,想要研究BoxClipper3D的使用方法,但是找了好久终于在github上找到了,戳这里查看!这应该是PCL的官方测试代码。
这段官方测试代码中,EXPECT_EQ
EXPECT_EQ是Google Test中的一个宏,用于比较两个值是否相等。当两个值不相等时,它会产生一个失败的测试结果。EXPECT_EQ宏的使用格式如下:
其中,expected_value表示期望值,actual_value表示实际值。如果expected_value和actual_value的值相等,测试就会通过,否则测试就会失败,并输出expected_value和actual_value的值。
EXPECT_EQ宏适用于比较整型、浮点型、字符型、字符串等基本数据类型。如果要比较自定义类型的值,可以通过重载operator==运算符来实现。
可以按照这个功能,自定义一个宏MY_ASSERT_EQ,效果差不多。
这个BoxClipper3D类主要是实现使用一个Box(空间中的正方体,想象成一个小盒子)去裁剪点云。落在这个盒子里的点被留下来,其余的给去掉。这个盒子不需要我们自己去添加,但是我们可以按照需求去改变它。盒子的质心在不人为去改变的情况下处于坐标系的原点,各条棱长2,那其实这八个顶点,可以写出来:
如果想象不出来可以在纸上画一画,就出来了。
既然盒子是给定的,那我们怎么用它灵活且按照我们的想法去剪裁点云呢。首先,需要定义一个仿射变换矩阵,是3x3的,在PCL里面直接通过Eigen::Affine3f T;来声明。BoxClipper3D 类中有一个成员函数void setTransformation (const Eigen::Affine3f &transformation) 可以将我们给定的仿射变换矩阵设置进去。那么这个盒子就会经过我们设定的仿射变换矩阵变换。最后,点云落在这个盒子内的点就是保留下来。我们都知道仿射变换矩阵可以进行缩放、平移、旋转等变换,也就是说通过一个仿射变换矩阵,可以把盒子缩放、平移、旋转等操作,盒子就会被我们变换到我们想要裁剪的地方。
一般来说,我们都是使用上述的clipPointCloud3D 进行剪裁,第一个参数传入待裁剪的点云,第二个参数直接声明一个pcl::Indices indices;传入进去即可,最终这个算法会将落在盒子内的点的索引输入到indices中。
可以使用下属代码提取出点云:
在使用过程中我发现这玩意很玄学,不知道是不是我个人理解问题,我尝试着将这个Box按照想要的方式进行仿射变换,我先根据我的想法创建一个仿射变换矩阵,代码如下
得到的仿射变换矩阵也没毛病
t : 0.5 0 0 16
0 1 0 -58
0 0 1 -11.7
0 0 0 1
但是神奇的是,竟然裁剪不到任何点。于是我创建了8个点,分别代表这个盒子的8个顶点,然后对这个点云进行仿射变换,得到的结果再进行可视化,发现盒子其实在点云上的。而对于能够剪裁到的盒子,我也可视化了一下,发现这个盒子反而距离点云很远。