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

news2025/4/14 10:56:15

主要目的就是根据已知的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::ConditionBase
        pcl::ConditionAnd<pcl::PointXYZ>::Ptr range_cond(new pcl::ConditionAnd<pcl::PointXYZ>());
        //创建条件定义对象
        //为条件定义对象添加比较算子: 使用大于0.0和小于0.8这两个条件用于建立滤波器。
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
            pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::GT, x)));
        //添加在x字段上大于0的比较算子  
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
            pcl::FieldComparison<pcl::PointXYZ>("x", pcl::ComparisonOps::LT, x + width)));
        //添加在x字段上小于0.8的比较算子
        
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
            pcl::FieldComparison<pcl::PointXYZ>("y", pcl::ComparisonOps::GT, y)));
        range_cond->addComparison(pcl::FieldComparison<pcl::PointXYZ>::ConstPtr(new
            pcl::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.getIndices
        pcl::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_filtered

        std::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::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内的点还是区域外的点。效果如下图所示,感觉还行
在这里插入图片描述
在这里插入图片描述
运行时间大概是44ms,这个速度感觉还好,如果能更快就好了。一块平面点云上如果有很多区域要提取或者裁剪掉,可以考虑多线程。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/453704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《死锁》与《CAS ABA》问题

文章目录 什么是死锁常见死锁情况❗️死锁的必要条件❗️如何避免死锁呢&#xff1f;CASCAS中ABA问题解决ABA问题 什么是死锁 死锁是指两个或两个以上的进程在执行过程中&#xff0c;由于竞争资源或者由于彼此通信而造成的一种阻塞的现象 。 常见死锁情况❗️ 1.一个线程一把…

Java-static那些事儿

static作为java中基础常用的关键字&#xff0c;通常用于修饰内部类&#xff0c;方法和变量和代码段&#xff0c;且具有以下特性&#xff1a; static修饰内部类时&#xff0c;该类属于静态内部类&#xff0c;其只能访问外部的静态变量和方法static修饰方法时&#xff0c;该方法…

ROS学习第三十七节——机器人运动控制以及里程计信息显示

https://download.csdn.net/download/qq_45685327/87719766 https://download.csdn.net/download/qq_45685327/87719873 gazebo 中已经可以正常显示机器人模型了&#xff0c;那么如何像在 rviz 中一样控制机器人运动呢&#xff1f;在此&#xff0c;需要涉及到ros中的组件: ros…

【mapbox+turf.js】WebGIS空间分析系列(1)

最近在想&#xff0c;自己一直使用webgis做的都是可视化的内容&#xff0c;缺少空间分析的功能。 所以吧&#xff0c;最近理一下使用mapbox turf来做一些基础的空间分析功能。 大概的思路是&#xff0c;获取目标图层&#xff08;多个图层&#xff09;&#xff0c;然后选择空…

servlet(2)—javaEE

1.获取请求数据 1.1开发前端发请求 ajax封装代码 // 参数 args 是一个 JS 对象, 里面包含了以下属性 // method: 请求方法 // url: 请求路径 // body: 请求的正文数据 // contentType: 请求正文的格式 // callback: 处理响应的回调函数, 有两个参数, 响应正文和响应的状态码…

qt中使用 ui 文件进行界面设计

目录 1、创建 Qt 应用 ​2、项目创建成功 3、直接点击打开 mainwindow.ui 文件 4、随便从左边侧边栏拖拽一个空间到 界面设计区域 5、在右侧边栏右键点击 pushButton 控件&#xff0c;点击转到槽 6、根据实际需要选择对应的信号&#xff0c;我这里方便演示选择 clicked&a…

linux 信号量semget/semop/semctl

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 概述 原理机制 接口说明 代码演示 结尾 前言 本专栏主要分享linux下并发编…

基于 多态 的职工管理系统(Staff Management System)

目录 一、管理系统需求 作用&#xff1a;管理公司内所有员工的信息 分类&#xff1a;要显示每位员工的编号、姓名、岗位与职责 具体实现的功能&#xff1a; 二、创建管理 类 三、各个接口函数 1、菜单展示功能 2、 选择功能 3、创建员工功能 ①普通员工employee ②经理…

【Web3.0大势所趋】我看到了互联网未来的模样

前言 Web3.0 是一个越来越受到关注的话题&#xff0c;它被认为将会带来天翻地覆的变化。本文我们一起来谈谈 Web3.0 的概念、特点和优势&#xff0c;并探讨它为什么如此重要和具有革命性的。 文章目录 前言Web3.0是什么Web3.0的技术Web3.0的优势总结 Web3.0是什么 Web3.0: 是下…

尚硅谷Kafka

Kafka 1.Kafka概述1.1 定义1.2 消息队列1.2.1 传统消息队列的应用场景1.2.2 消息队列的两种模式 1.3 kafka基础架构 2.快速入门2.1 kafka环境安装2.2 kafka命令行操作参数2.2.1 主题命令行操作 2.2.2 生产者命令行操作2.2.3 消费者命令行操作 3.Kafka 生产者3.1 生产者消息发送…

Vue3+Vite神器:按需引入自定义组件unplugin-vue-components

前言 我们做项目时&#xff0c;会封装大量的公共组件&#xff0c;如果我们每一个都去在maints里面引入&#xff0c;非常麻烦不说&#xff0c;代码也不优雅。所以更好的方法就是自动注册全局组件&#xff0c;在组件中直接使用就好。 一种方法是自己在components文件夹下新建in…

QML控件--MenuBar

文章目录 一、控件基本信息二、控件使用三、属性成员四、成员函数 一、控件基本信息 Import Statement&#xff1a;import QtQuick.Controls 1.4 Since&#xff1a;Qt 5.1 二、控件使用 MenuBar&#xff1a;是菜单栏&#xff0c;通常&#xff0c;菜单静态声明为菜单栏的子项&…

redis入门必会知识

Redis基础知识目录 5、sortedSet 文章目录 系列文章目录前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 一、redis是什么&#xff1f; Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务 ! 是一个开源的使用ANSI C语言编写…

【C++】——- 模板初阶介绍

前言&#xff1a; 在之前的学习中&#xff0c;我们已经把 C前期所需要用到的知识都给大家介绍了一遍。接下来&#xff0c;我们要学习的就是关于在C 中模板的基本知识&#xff0c;今天我带给大家的内容便是关于 模板初阶的介绍。 目录 &#xff08;一&#xff09; 泛型编程 &…

【Python_Opencv图像处理框架】图像形态学操作

写在前面 本篇文章是opencv学习的第三篇文章&#xff0c;主要讲解了图像的形态学有关操作&#xff0c;作为初学者&#xff0c;我尽己所能&#xff0c;但仍会存在疏漏的地方&#xff0c;希望各位看官不吝指正❤️ 写在中间 读完这篇文章后&#xff0c;相信您便能信手拈来下面图…

给照片换底色(python+opencv)

给照片换底色&#xff08;pythonopencv&#xff09; 本篇目录&#xff1a; &#x1f984; 一、分析照片基本信息 &#x1f984; 二、方法一&#xff08;遍历图像&#xff0c;将像素值点替换修改为指定颜色&#xff09; &#x1f984; 三、修改图片颜色方法二&#xff08;先转…

MySQL数据库索引

目录 0.知识回顾 1.数据库约束 一.索引 1.什么是索引 2.为什么要使用索引(作用) 3.索引的使用场景 4.如何使用索引 1.查看索引 2.创建索引 3.修改索引 4.删除索引 5.索引的分类 1.使用场景不同 2.按列区分 3.按数据组织方式 二.索引的数据结构 1.HASH 2.二叉搜…

如何设计自动化测试框架?阿里P7工程师耗时一个月总结而成

目录 一、什么是自动化测试框架 二、自动化测试框架的架构设计 三、自动化测试框架的最佳实践 四、自动化测试框架的组成部分 五、自动化测试框架的设计原则 六、如何选择自动化测试框架 七、自动化测试框架实例 八、结论 一、什么是自动化测试框架 自动化测试框架是一…

UE4/5多人游戏详解(八、游戏模式和游戏状态里的函数重写,插件内地图的地址做变量,做变量让按钮出现不同状态,插件内的所有代码)

目录 这里不写在插件里面&#xff0c;而是在游戏模式&#xff1a; 头文件&#xff1a; Cpp文件&#xff1a; 更改ini文件 进入地图设置模式&#xff1a; 写插件里面&#xff0c;做一个变量&#xff1a; 写变量 然后更改函数MenuSet&#xff1a; 在子系统中做变量&…

FPGA 20个例程篇:20.USB2.0/RS232/LAN控制并行DAC输出任意频率正弦波、梯形波、三角波、方波(四)

接着同样地我们也需要完成对千兆网口ETH模块和USB2.0模块的编写&#xff0c;实际上和UART串口模块的设计思想大同小异&#xff0c;也同样地需要完成两项关键功能即识别并解析报文、接收并发送数据&#xff0c;千兆网口ETH和USB2.0的底层驱动在前面的例程中也详细说明了&#xf…