《点云处理》 提取点云内点和外点

news2024/11/29 4:45:09

前言

关于内点(inliers)和外点(outliers)在点云处理方向上是个非常常见的名词。有时候,内点也会被称之为有效点,而外点会被称之为无效点。所谓有效和无效都是相对而言的,无效不一定是真的没有意义,并不等价于噪点,而有效也并不是绝对是想要的。有时候,可能既要内点,也要外点。之所以这么称呼,是想要从一整个大块的点云中将其分开来。通常,内点和外点具备不同的特征或者属性,根据这个属性,总能找到相应的算法将其分离。

举个例子,使用RANSAC进行平面拟合时,通常会设置一个距离阈值distance。RANSAC每次迭代都会从点云中任意取3个点,3个点在空间中确定了唯一的平面,可以得到平面方程。随即,遍历点云,计算每个点到该平面的欧氏距离,若距离大于设定的阈值distance,那么该点则是外点,这个点被认为是距离平面比较“远”的点,而点到平面的距离小于设定的阈值,则该点是内点,这个点被认为是距离平面比较“近”的点,或者干脆说在误差允许的范围内,该点在拟合的平面上。通常运行RANSAC算法后,会得到一个std::vector inliers动态数组,该数组中存放的一般就是内点的索引。

当然,除了拟合平面外,还有很多这样的例子,如欧式聚类。接下来就汇总一下,得到inliers之后,如何提取内点点云和外点点云。

方法一:

/// <summary>
/// 根据索引提取点云中的内点或者外点
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="inliers">存放内点索引的数组</param>
/// <param name="cloud_out">输出点云</param>
/// <param name="is_true">输入true则提取内点点云,输入false则提取外点点云</param>
/// <returns>return true则表示提取成功,return false则表示提取失败</returns>
bool GetCloudByIndex(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, const std::vector<int>& inliers, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_out, const bool& is_true)
{
    if (cloud == nullptr) return false;            // 判断输入点云是否为空
    if (cloud->points.size() < 10) return false;   // 判断输入点云的尺寸
    if (cloud_out == nullptr) cloud_out.reset(new pcl::PointCloud<pcl::PointXYZ>);  // 判断输出点云是否为空,如果为空则需要为其new出一个对象

    int PointSize = cloud->points.size();
    if (is_true)
    {
        pcl::copyPointCloud(*cloud, inliers, *cloud_out);
    }
    else
    {
        std::vector<int> indices(PointSize);
        std::vector<int> outliers;
        std::iota(indices.begin(), indices.end(), 0);
        std::set_difference(indices.begin(), indices.end(), inliers.begin(), inliers.end(), std::back_inserter(outliers));
        pcl::copyPointCloud(*cloud, outliers, *cloud_out);
    }

    return true;
}

上述代码中,直接使用了pcl::copyPointCloud(*cloud, inliers, *cloud_out);提取点云,传入的第二个参数可以是inliers,也可以是outliers。输入点云cloud中所有点的索引或者说是下标肯定是0~n-1,0是第一个点的索引,n-1为最后一个点的索引,n是cloud包含的点数。

通常,想查看点云cloud中某个点的坐标,一般都会写成cloud->points[index],这个index的范围就是0到n-1。std::iota(indices.begin(), indices.end(), 0);这句代码运行之后,就会得到一个cloud点云索引的容器,存放着0,1,2,3,4,5 … n-3,n-2,n-1。而inliers则是存放输内点索引的容器,std::set_difference(indices.begin(), indices.end(), inliers.begin(), inliers.end(), std::back_inserter(outliers));这句代码就是将点云cloud的全部索引indices与内点索引inliers进行比较,其中indices中有但是inliers中没有的索引值则存放进outliers。最后,用pcl::copyPointCloud(*cloud, outliers, *cloud_out);就提取到了外点点云。

运行时间对比如下图所示,可以发现内点运行时间还长一点,而外点提取时间还短一点。这个其实跟点的数量有关系,如果内点的数量多,当然就更耗时。但是总的来说,无论是提取内点还是外点,使用上述方法都是比较快的,而且很方便。
在这里插入图片描述

方法二:
除了上述方法之外,PCL库中特意集成有相应的方法提取点云的内点或者外点。
代码:

/// <summary>
/// 使用PCL库中pcl::ExtractIndices方法进行内点和外点的提取
/// </summary>
/// <param name="cloud">输入点云</param>
/// <param name="inliers">内点的索引数组</param>
/// <param name="cloudout">输出点云</param>
/// <param name="is_in">输入true则提取内点点云,输入false则提取外点点云</param>
/// <returns>return true则表示提取成功,return false则表示提取失败</returns>
bool ExtractCloudByIndices(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud, const std::vector<int>& inliers, pcl::PointCloud<pcl::PointXYZ>::Ptr& cloudout, const bool& is_in)
{
    if (cloud == nullptr) return false;
    if (cloud->points.size() < 10) return false;
    if (cloudout == nullptr) cloudout.reset(new pcl::PointCloud<pcl::PointXYZ>);

    pcl::PointIndices::Ptr pinliers(new pcl::PointIndices());
    pinliers->indices.assign(inliers.begin(), inliers.end());
    pcl::ExtractIndices<pcl::PointXYZ> extractor;
    extractor.setInputCloud(cloud);
    extractor.setIndices(pinliers);
    extractor.setNegative(!is_in); //如果设为true,可以提取指定index之外的点云
    extractor.filter(*cloudout);

    return true;
}

上述代码中extractor.setNegative(!is_in);就是设置提取内点还是外点的成员函数。如果extractor.setNegative(true);即输入的是true,则提取除了inliers以外的,也就是外点的点云,而extractor.setNegative(false);才是提取inliers索引的点云,与方法一中的习惯相反,为了调整成一致,所以传入的是!is_in,而不是is_in。这个时候就与方法一保持一样的习惯了。

此外,还有一点不同的是pcl::ExtractIndices所需要的传入的索引不是std::vector 而必须是pcl::PointIndices::Ptr。其实这两种数据结构是可以互相转化的,所以用 pinliers->indices.assign(inliers.begin(), inliers.end());这句代码进行了一次转化。当然,在使用PCL库中算法时,有时候得到的不一定是std::vector,而是pcl::PointIndices::Ptr。这个就看每个人的习惯和喜好去封装函数了,也可以封装成一个传参为pcl::PointIndices::Ptr数据的函数。

运行结果如下:
在这里插入图片描述
可以从结果看出,方法二的耗时要比方法一长“很多”。具体使用哪种方法进行点云内点或者外点的提取则是要看个人习惯了。

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

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

相关文章

【数据结构—队列的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 一、队列 1.1队列的概念及结构 二、队列的实现 2.1头文件的实现—Queue.h 2.2源文件的实现—Queue.c 2.3源文件的测试—test.c 三、测试队列实际数据的展示 3.…

第一个程序(STM32F103点灯)

点亮LED 看原理图确定控制LED的引脚看主芯片手册确定如何设置/控制引脚写程序 LED有很多种&#xff0c;像插脚的&#xff0c;贴片的。 它们长得完全不一样&#xff0c;因此我们在原理图中将它抽象出来。 嵌入式系统中&#xff0c;一个LED的电阻非常低&#xff0c;I U/R&…

RabbitMQ搭建集群环境、配置镜像集群、负载均衡

RabbitMQ集群搭建 Linux安装RabbitMQ下载安装基本操作命令开启管理界面及配置 RabbitMQ集群搭建确定rabbitmq安装目录启动第一个节点启动第二个节点停止命令创建集群查看集群集群管理 RabbitMQ镜像集群配置启用HA策略创建一个镜像队列测试镜像队列 负载均衡-HAProxy安装HAProxy…

GoWin FPGA, GPIO--- startup1

一个Bank只能用一个电压&#xff0c;假如同一个Bank&#xff0c;在引脚里设置不同的电压&#xff0c;编译不过。 解释说明 2. 错误引脚限制 以上编译设置会导致编译错误。

低阶的人机交互和高阶的人机交互

低阶的人机交互和高阶的人机交互是指在人与机器之间进行信息交流和操作时的不同层次和方式。低阶的人机交互通常是指简单直接的交互方式&#xff0c;主要依赖于人类用户对界面或设备的直接操控。以下是几个低阶的人机交互示例&#xff1a; 键盘和鼠标&#xff1a;使用键盘输入文…

线程安全说明

线程安全性的定义 线程安全性是指当多个线程同时访问某个类时&#xff0c;这个类的行为仍然是正确的。在不同的线程交叉执行的情况下&#xff0c;程序仍能够保持一致的状态。 示例&#xff1a;线程安全的计数器 考虑一个简单的计数器类&#xff0c;它需要保证在多线程环境下…

HashMap构造函数解析与应用场景

目录 1. HashMap简介 2. HashMap的构造函数 2.1 默认构造函数 2.2 指定初始容量和加载因子的构造函数 3. 构造函数参数的影响 3.1 初始容量的选择 3.2 加载因子的选择 4. 构造函数的应用场景 4.1 默认构造函数的应用场景 4.2 指定初始容量和加载因子的构造函数的应用…

记一次挖矿病毒的溯源

ps&#xff1a;因为项目保密的原因部分的截图是自己在本地的环境复现。 1. 起因 客户打电话过来说&#xff0c;公司web服务异常卡顿。起初以为是web服务缓存过多导致&#xff0c;重启几次无果后觉得可能是受到了攻击。起初以为是ddos攻击&#xff0c;然后去查看web服务器管理…

java --- 异常

目录 一、异常体系介绍 二、异常的作用 三、异常处理方式 3.1 捕获异常 2.1 灵魂一问&#xff1a; 如果try中没有遇到问题&#xff0c;如何执行&#xff1f; 2.2 灵魂二问&#xff1a;如果try中可能会遇到多个问题&#xff0c;怎么执行&#xff1f; 2.3 灵魂三问&#x…

眼镜店验光配镜处方单打印管理系统软件教程

一、前言 1、眼镜店原始的手写处方单逐步被电脑打印单取代 2、使用电脑开单&#xff0c;记录可以保存可以查询&#xff0c;而且同一个人配镜可以对比之前的信息 软件下载或技术支持可以点击最下方官网卡片 如上图&#xff0c;该软件有顾客信息模块&#xff0c;旧镜检查模块…

飞天使-docker知识点8-docker的资源限制

文章目录 容器资源限制示例OOM 优先级机制内存限制参数swap 限制 容器资源限制 Docker提供了多种资源限制的方式&#xff0c;可以根据应用程序的需求和系统资源的可用性进行选择。以下是一些常见的Docker资源限制及其使用情况&#xff1a;CPU限制&#xff1a;通过设置CPU的配额…

AUTOSAR ComM模块配置以及代码

ComM模块配置以及代码执行流程 1、基本的一个通道的配置列表 ComMNmVariant 概念的个人理解&#xff1a; FULL&#xff1a; 完全按照AUTOSAR NM方式进行调用 LIGHT &#xff1a;设置一个超时时间&#xff0c;在请求停止通信的时候开始计时&#xff0c;超时之后才会进入FULLCOM…

processon使用及流程图和泳道图的绘画(登录界面流程图,门诊流程图绘制门诊泳道图,住院泳道图,OA会议泳道图),Axure自定义元件

目录 一.processon图形的使用场景介绍 二.流程图绘画 三.泳道图的绘画 1.绘制门诊流程图绘制门诊泳道图 2. 绘制住院泳道图​编辑 3.绘制药库采购入库流程图 4.绘制OA会议泳道图 四.Axure自定义元件 1.Axure载入元件库 一.processon图形的使用场景介绍 二.流程图绘画 示例&…

【案例】注册表简介,新建一个右键菜单打开方式选项

这里写目录标题 来源注册表的介绍注册表编辑器VScode的打开方式菜单![image-20231217201730121](https://img-blog.csdnimg.cn/img_convert/56c02643df9e8ec3afb4f3ac5cc0cdd5.png)如何自定义一个右键菜单备份注册表新建一个菜单选项”右键用记事本打开“ DWORDQWORD可扩充字符…

MySQL数据库,表的增量备份与恢复

1. 从物理与逻辑的角度 数据库备份可以分为物理备份和逻辑备份。物理备份是对数据库操作系统的物理文件&#xff08;如数据 文件&#xff0c;日志文件等&#xff09;的备份。这种类型的备份适用于在出现问题时需要快速恢复的大型重要数据库。 物理备份又可以分为冷备份&#xf…

WPF——命令commond的实现方法

命令commond的实现方法 属性通知的方式 鼠标监听绑定事件 行为&#xff1a;可以传递界面控件的参数 第一种&#xff1a; 第二种&#xff1a; 附加属性 propa&#xff1a;附加属性快捷方式

I/O流的相关内容

首先我们了解一下什么是文件&#xff1a; 文件其实就是让我们用来保存数据的地方&#xff0c;它可以用来保存信息&#xff0c;图片&#xff0c;以及音频等各类数据。 文件流&#xff1a; 那我们是如何通过我们的程序来进行对文件的操作呢&#xff1f;这里我们就要提出一个概…

记录 | gpu docker启动报错libnvidia-ml.so.1: file exists: unknown

困扰了两天的问题&#xff0c;记录一下 问题出在启动一个本身已经安装 cuda 的镜像上&#xff0c;具体来说&#xff0c;我是启动地平线天工开物工具链镜像的时候出现的问题&#xff0c;具体报错如下&#xff1a; docker: Error response from daemon: failed to create task …

【SpringMVC】SpringMVC简介、过程分析、bean的加载和控制

文章目录 1. SpringMVC简介2. SpringMVC入门案例文件结构第一步&#xff1a;坐标导入第二步&#xff1a;创建SpringMVC容器的控制器类第三步&#xff1a;初始化SpringMVC环境&#xff0c;设定Spring加载对应的bean第四步&#xff1a;初始化Servlet容器&#xff0c;加载SpringMV…

腾讯技术工程总结-如何写好技术文章?

文章参考&#xff1a;腾讯技术工程《写好一篇高质量的技术文章》 如何写好技术文章&#xff1f; 大家可以先思考一下&#xff0c;为什么要写技术文章&#xff1f; 我们写技术文章的目的是什么呢&#xff1f;面向哪些人呢&#xff1f; 面向人群 写技术文章的目的毫无疑问是为…