PCL三维重建教程_点云滤波篇之剪裁(直通滤波、条件滤波) C++

讲在前面:

本教程类似于教会你加减乘除(点云基础、分割、滤波、配准),然后自己做一道包含加减乘除的综合题(实践操作)。

此教程用最简单的例程,给大家直观感受。就像做一道物理大题,我们总用理想情况,便于理解学习。因此,所有的示例都围绕斯坦福的小兔子展开。

我不会带你们看官方文档,而是用自己的想法和语言来表达,目的是让一头雾水的人会去应用。毕竟我知道,做这个的大多数人,也只是为了应用,如果去深究原理,必然也不会来到这里。很多东西,我们只会应用就会“高人一等”,原理重要吗?重要!但对于大多数人来说,没有必要。司机并不一定知道汽车内部是什么结构,怎样运行的,他只需要会开车。没错,所以我们只能是司机,但对于我们来说,足够了。在我看来学会了应用,才会去学习更深层次的东西。当你不知原因打印出一个"hello world!",那种神秘和兴奋,会引导你去想弄清楚,这到底是为什么。


前提准备:

首先说明我的环境:win10 +visual studio 2013 +PCL1.8.0
如果你使用其他版本,只要配置好了,也没有什么问题。
如果你还没有配置好环境,请看以下文章。

点我配置环境!

此外需要你有一定的c/c++,或是其他任意一门语言基础。
真的只需要基础就可以,这方面课程也比较多,请先自行去学习。


示例斯坦福兔子下载
ヾ( ̄ー ̄)X

进入正题!

滤波无非就是把数据中的噪声(无用的干扰信息)进行过滤去除。好比在景区拍照,你永远都会拍到别的游客!自拍除外。

其实记住一句话,滤波的核心就是调参。前人已经为我们设计了很多算法,也有人为我们把代码写入库内。 好人一生平安! 我们做的仅仅是简简单单的调参。

这就好像给一个二次函数寻找零点。

f ( x ) = x 2 − 2 f(x) = x^2-2 \quad \quad f(x)=x22
我们目的就是找到使f(x)最接近0的那个值,但前提是我们都是蠢b,只会代数。

其实面对一个真实数据,以我们目前的水平,就是蠢b。我们能做的只能是调参。

在实际问题中,可能各种滤波都不尽人意。而且,我们参数如果设置的不合理,会出现各种奇怪的错误。

本篇可能体会不到,下一篇才有感受!

你能不能处理好你的真实数据,修行就看个人了。

言归正传,在我看来,我们有两种方式去掉它,一种是简单粗暴的剪裁,另一种是筛点。

第一种:剪裁

我们也会看到这种方法叫做离群点去除,群不就是你想拍的东西吗?离群的自然是你不想拍的。

ConditionalRemoval(条件滤波)

//引用头文件,好人一生平安!
#include <iostream>
#include <pcl/point_types.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/conditional_removal.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>

int	main()
{	
    //加载原点云
	pcl::PointCloud<pcl::PointXYZ>::Ptr my_cloud(new pcl::PointCloud<pcl::PointXYZ>);
	pcl::PointCloud<pcl::PointXYZ>::Ptr my_cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

	pcl::PCDReader my_reader;

	my_reader.read<pcl::PointXYZ>("你路径下的rabbit.pcd",*my_cloud);
	
    //创建条件滤波器
	pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());
	//设置过滤范围,根据x y z轴
	//GT大于,GE大于或等于,NE是不等于,EQ是等于,LT小于,LE小于或等于
	//下列说明取值范围  -7≤x<1  0≤y<10
	//那么问题来了,我怎么知道点云的坐标分布?具体看代码之后的文章
	range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new	pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, 0)));
	range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new	pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::LT, 10.0)));
	range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new	pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, -7)));
	range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new	pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, 1)));
	

	//执行点云移除处理,并将剩下的点添加到filtered(过滤后的点云)
	pcl::ConditionalRemoval<pcl::PointXYZ> condrem(range_cond, false);
	condrem.setInputCloud(my_cloud);
	condrem.setKeepOrganized(false);
	condrem.filter(*my_cloud_filtered);

    //打印一下剩下的点云个数而已
	std::cout << "输入的点云个数: " << my_cloud->points.size() << std::endl;
	std::cout << "输出的点云个数: " << my_cloud_filtered->points.size() << std::endl;

	//创建显示窗口并命名3D viewer
	pcl::visualization::PCLVisualizer viewer("3D viewer");

	//这里不要深究,可以同时一个视窗展示2个点
	//并且跟鼠标拖动改变的是同一个视角,方面观察。
	//自己可以根据需求改变点云颜色和背景颜色
	int v1(0);
	//xmin, ymin, xmax, ymax,取值范围0-1
	viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);
	//背景颜色 黑色 
	viewer.setBackgroundColor(0, 0, 0, v1);
	//点云颜色 绿色
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green0(my_cloud, 0, 225, 0);
	viewer.addPointCloud(my_cloud, green0, "cloud", v1);
	
	//同上
	int v2(0);
	viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
	viewer.setBackgroundColor(0.3, 0.3, 0.3, v2);
	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green1(my_cloud_filtered, 0, 225, 0);
	viewer.addPointCloud(my_cloud_filtered, green1, "cloud_filtered", v2);
	
	//添加点云坐标系
	viewer.addCoordinateSystem();
	//循环显示
	while (!viewer.wasStopped()) {
		viewer.spinOnce(100);
	}
	return (0);
}


请先读完代码注释!那就没什么好说的了。

所以重点是我们要知道点云的XYZ坐标分布
在这里插入图片描述
我们可以使用CloudCompare软件来查看,本人并不怎么会使用,如果有大神还请指教修改。

仔细看左下角写明了点云总体所占坐标体积,好吧,我只会凭感觉来,还没专门学过这个软件。
在这里插入图片描述

红绿蓝RGB三色的三维指示坐标轴XYZ

颜色坐标轴
红色RX
绿色GY
蓝色BZ

右手坐标系

拇指x 食指y 中指z 自己比划一下就知道了
在这里插入图片描述

好吧,其实,在cloudcompare里我们完全可以手动切割。
在这里插入图片描述
兔子:好像身体被掏空

这是不是说明我没有必要用代码了?

难道拍照有自带美颜功能,你就不给自己再P图了吗?

好像是,也好像不是。如果你有一堆点云数据需要处理呢,这种手动效果很好,但却很慢。

我从头到尾都会说:“自己要根据所要解决的问题进行选择”。

下面是代码例程效果:给大家切个兔子头
在这里插入图片描述

PassThrough(直通滤波)

#include <iostream>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/passthrough.h>

using namespace std;

 int main() {
	  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
	  pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
	  
	  //加载读取点云文件
	  if (pcl::io::loadPCDFile("E:\\Desktop\\code\\点云滤波\\rabbit.pcd", *cloud) == -1)
	  {
		  cout << "COULD NOT READ FILE \n";
		  system("pause");
		  return (-1);
	  }
	  cout << "points size is:" << cloud->size() << endl;

	  // 定义滤波器
	  pcl::PassThrough<pcl::PointXYZ> pass;
	  pass.setInputCloud(cloud);
	  pass.setFilterFieldName("z");
	  pass.setFilterLimits(0.0, 1.0);
	  //pass.setFilterLimitsNegative (true); 为true则取反 不写默认为false
	  pass.filter(*cloud_filtered);
	  
	  //显示窗口
	  pcl::visualization::PCLVisualizer viewer("3D viewer");

	  int v1(0);
	  viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);//xmin, ymin, xmax, ymax,取值范围0-1
	  viewer.setBackgroundColor(0, 0, 0, v1);
	  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green0(cloud, 0, 225, 0);
	  viewer.addPointCloud(cloud, green0, "cloud", v1);

	  int v2(0);
	  viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);
	  viewer.setBackgroundColor(0.3, 0.3, 0.3, v2);
	  pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> green1(cloud_filtered, 0, 225, 0);
	  viewer.addPointCloud(cloud_filtered, green1, "cloud_filtered", v2);

	  cout << "filtered points size is:" << cloud_filtered->size() << endl;
	  while (!viewer.wasStopped()) {
		  viewer.spinOnce(100);
	  }
	  
	  return 0;
  }


有什么区别?

直通滤波器在切割上貌似只能在一个轴上,除非再多创建几个滤波器。

至于跟条件滤波有什么区别?鄙人拙见,没什么区别。如果你只想过滤一个轴上的点,想更简洁更快,必然选择直通滤波。反之,还是条件滤波。

网上一般过滤z轴(我愿称之为深度轴),也就是一些远近点的过滤。

但如果我们三维重建一个物体,毫无疑问选择条件滤波,因为在x、y轴(我愿称之为平面轴),也有很多我们不需要的点,具体项目具体分析,适合自己解决问题的就是最好的。

例程效果如下:
在这里插入图片描述

pass.setFilterLimitsNegative (false);

修改该参数是一种取反的效果。
在这里插入图片描述

其实,这里重点我不是想介绍直通滤波。

我想说的是,你有没有发现两种滤波代码有什么不同,又有什么不同。

你会发现,两种代码的不同之处,只有头文件定义滤波器部分罢了。

自然而然,我们用什么方法滤波,就要引用别人写好的头文件。

这就像更换天文望远镜的目镜一样,其他的代码根本不用动哈。

你甚至可以再定义多个滤波器,想怎么滤,就怎么滤。

	
	pcl::方法<pcl::PointXYZ> 方法对象名;
	//传入原来的点云cloud
	方法对象名.setInputCloud(cloud);

	...
	...
	...此处省略不同方法的参数的设置
	
	//生成过滤后的点云cloud_filtered
	方法对象名.filter(*cloud_filtered);

	然后套娃就好,
	你可以在下面把cloud_filtered传入另一种过滤方法对象,
	传出时命名cloud_filtered_again 怎么命名随你啦

还是那句话,怎么过滤好,要根据自己的情况来。

什么,你还发现了读取点云的代码不一样?无非是两种方法罢了,都是可以替换的,是不是这种更简单?要学会变通!

文章开头我说过,滤波就是调参,我们改动的也不过只是滤波器内的参数,像这种剪裁方式的滤波还是比较好控制的。

另一种,我称之为点云滤波之筛点,比如:高斯滤波、半径滤波、体素滤波等等,下一篇再介绍。

根据点赞来决定下一篇何时发布。

  • 58
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值