PCL学习四:RANSAC-随机采样一致性

news2024/10/8 11:36:27

参考引用

  • Point Cloud Library
  • 黑马机器人 | PCL-3D点云

1. RANSAC 概念及作用

  • RANSAC(Random Sample Consensus,随机采样一致性)是一种迭代方法,作用:从包含异常值的一组数据中估计数学模型的参数,RANSAC 算法假定要查看的所有数据均由内部值和异常值组成,可以用带有一组特定参数值的模型来解释离群值,而离群值在任何情况下都不适合该模型,其过程可以从数据中估计所选模型的最佳参数

    • 下面的左图和右图显示了 RANSAC 算法在二维数据集上的应用:左图是包含内部值和异常值的数据集的可视表示,右图以红色显示所有异常值,蓝色显示内部值,蓝线是 RANSAC 完成的工作结果
      在这里插入图片描述
  • PCL 中以 RANSAC 算法为核心,实现了五种类似于 RANSAC 的随机参数估计算法,例如:随机采样一致性估计(RANSAC) 、最大似然一致性估计(MLESAC)、最小中值方差一致性估计(LMEDS)等,所有的估计参数算法都符合一致性准则

    • 利用 RANSAC 可实现点云分割,PCL 中支持的几何模型分割有空间平面、直线、二维或三维圆、圆球、锥体
    • RANSAC 的另一应用就是点云配准对的剔除

2. RANSAC 算法简介

(1)RANSAC 从样本中随机抽选出一个样本子集,使用最小方差估计算法对这个子集计算模型参数,然后计算所有样本与该模型的偏差,再使用一个预先设定好的阈值与偏差比较,当偏差小于阈值时,该样本点属于模型内样本点(inliers),或称内部点、局内点或内点,否则为模型外样本点(outliers),或称外部点、局外点或外点,同时记录下当前的 inliers 的个数,然后重复这一过程
(2)每一次重复都记录当前最佳模型参数,所谓最佳即是 inliers 个数最多,对应的 inliers 个数为 best_ninliers
(3)每次迭代的末尾都会根据期望的误差率、best_ninliers、总样本个数、当前迭代次数,计算一个迭代结束评判因子,据此决定是否迭代结束,迭代结束后,最佳模型参数就是最终的模型参数估计值

  • 换而言之,RANSAC 算法是从一组含有 “外点”(outliers) 的数据中正确估计数学模型参数的迭代算法。“外点” 一般指的数据中的噪声,比如说:匹配中的误匹配和估计曲线中的离群点,最多可处理 50% 的外点情况
  • 所以,RANSAC 也是一种 “外点” 检测算法,RANSAC 算法是一种不确定算法,它只能在一种概率下产生结果,并且这个概率会随着迭代次数的增加而加大。对于 RANSAC 算法来说,一个基本的假设就是数据是由 “内点” 和 “外点” 组成的,“内点” 就是组成模型参数的数据,“外点” 就是不适合模型的数据

3. RANSAC 实现流程

3.1 具体流程

  • 1、选定一个 sample 即对于直线模型来说只需随机选取两个点 p 0 = ( x 0 , y 0 ) , p 1 = ( x 1 , y 1 ) \mathrm{p_0}=(x_0,y_0),\mathrm{p_1}=(x_1,y_1) p0=(x0,y0)p1=(x1,y1)

  • 2、求解模型(直线)方程
    x 1 = x 0 + a t y 1 = y 0 + b t \begin{array}{l}x_1=x_0+at\\ y_1=y_0+bt\end{array} x1=x0+aty1=y0+bt

    • p 0 = [ x 0 , y 0 ] T , n = [ a , b ] T \text{p}_0=[x_0,y_0]^T,\text{n}=[a,b]^T p0=[x0,y0]T,n=[a,b]T
    • Δ x = x 1 − x 0 = a t , Δ y = y 1 − y 0 = b t → Δ x Δ y = a b \Delta x=x_1-x_0=at,\Delta y=y_1-y_0=bt\to\dfrac{\Delta x}{\Delta y}=\dfrac{a}{b} Δx=x1x0=atΔy=y1y0=btΔyΔx=ba
      • a = Δ x Δ x 2 + Δ y 2 , b = Δ y Δ x 2 + Δ y 2 a=\frac{\Delta x}{\Delta x^2+\Delta y^2},b=\frac{\Delta y}{\Delta x^2+\Delta y^2} a=Δx2+Δy2Δxb=Δx2+Δy2Δy

在这里插入图片描述

  • 3、计算每个点 p i = ( x i , y i ) p_i=(x_i,y_i) pi=(xi,yi) 的误差函数
    d i = n T ( p i − p 0 ) ∥ n ∥ 2 d_i=\dfrac{n^T(\mathrm p_i-\mathrm p_0)}{\|n\|_2} di=n2nT(pip0)
    在这里插入图片描述

  • 4、统计与模型(直线)一致的点,即小于设定阈值 τ \tau τ 的内点 d i d_i di(inliers)
    在这里插入图片描述

  • 5、重复上述 4 个步骤并迭代 N 次,选择拥有最多内点(inliers)的模型(直线)
    在这里插入图片描述

3.2 两个参数选定

  • 距离阈值 τ \tau τ
    • 通常根据经验来选定(一般不用卡方分布试验来选,因为实际情况下模型标准差未知)
  • 迭代次数 N N N
    • e e e:采样点是外点的概率(见下表)
    • s s s:采样点的数量(直线拟合中为 2)
    • N N N:采样次数(RANSAC 迭代次数)
    • p p p:至少得到一个好样本(没有外点)的置信度(人为给定,比如 0.9)

( 1 − ( 1 − e ) s ) N = 1 − p \left(1-(1-e)^s\right)^N=1-p (1(1e)s)N=1p

N = l o g ( 1 − p ) l o g ( 1 − ( 1 − e ) s ) N=\dfrac{log(1-p)}{log(1-(1-e)^s)} N=log(1(1e)s)log(1p)

在这里插入图片描述

3.3 两个实践技巧

  • 找到一条达到预期的模型/直线(内点的概率达到期望值)后就可以提前终止迭代,节省计算时间
    T = ( 1 − e ) ⋅ ( t o t a l   n u m   o f   d a t a   p o i n t s ) T=(1-e)·(total num of data points) T=(1e)(total num of data points)

  • 当最终的模型/直线选出来之后,使用 LSQ(最小二乘)法重新优化一下,所得数据更加准确

4. RANSAC 存在的问题

  • RANSAC 理论上可剔除 outliers 的影响,并得到全局最优的参数估计,但存在两个问题
    • 首先,在每次迭代中都要区分 inliers 和 outlieres,因此需要事先设定阈值,当模型具有明显的物理意义时,这个阈值还比较容易设定,但是若模型比较抽象时,阈值就不那么容易设定了,而且固定阈值不适用于样本动态变化的应用
    • 其次,RANSAC 的迭代次数是运行期决定的,不能预知迭代的确切次数(当然迭代次数的范围是可以预测的)。除此之外,RANSAC 只能从一个特定数据集中估计一个模型,当两个(或者更多个)模型存在时,RANSAC 不能找到别的模型

5. RANSAC 代码实现(C++)

  • random_sample_consensus.cpp

    #include <iostream>
    #include <thread>
    
    #include <pcl/console/parse.h>
    #include <pcl/point_cloud.h>
    #include <pcl/common/io.h>
    #include <pcl/point_types.h>
    #include <pcl/sample_consensus/ransac.h>
    #include <pcl/sample_consensus/sac_model_plane.h>
    #include <pcl/sample_consensus/sac_model_sphere.h>
    #include <pcl/visualization/pcl_visualizer.h>
    
    // chrono_literals 是一个时间单位后缀,它可以让我们在编写 C++ 代码时更方便地定义时间常量
    // 通过使用这个后缀,我们可以直接在数字后面添加特定的后缀表示不同的时间单位
        // 例如:ns(纳秒)、us(微秒)、ms(毫秒)、s(秒)、min(分钟)、h(小时)、d(天)等
    using namespace std::chrono_literals; 
    
    // final 参数是可选的,如果它被提供,则将其作为另一个点云添加到可视化器中
    pcl::visualization::PCLVisualizer::Ptr simpleVis(pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud,
                                                     pcl::PointCloud<pcl::PointXYZ>::ConstPtr final = nullptr) {
        // 3D Viewer 表示可视化窗口的标题
        pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer ("3D Viewer"));
        viewer->addPointCloud<pcl::PointXYZ> (cloud, "sample cloud"); // 将点云数据添加到可视化器中,并命名为 "sample cloud"
        // 设置点云的大小为 2
        viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");
    
        // 将点云 final 显示为红色的点,点的大小为 4,以便进行可视化和调试
        if (final != nullptr) {
            viewer->addPointCloud<pcl::PointXYZ> (final, "final"); // 将第二个点云数据添加到可视化器中,并命名为 "final"
            viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "final"); // 设置点云的颜色为红色
            viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 4, "final"); // 设置点云的大小为 4
        }
    
        viewer->addCoordinateSystem (1.0, "global");
        viewer->initCameraParameters ();
        return (viewer);
    }
    
    int main(int argc, char *argv[]) {
        // 定义并实例化两个共享的 Point Cloud 结构
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr final(new pcl::PointCloud<pcl::PointXYZ>);
    
        // 用点填充上面其中一个 Point Cloud
        cloud->width = 500;
        cloud->height = 1;
        cloud->is_dense = false;
        cloud->points.resize(cloud->width * cloud->height);
        for (std::size_t i = 0; i < cloud->points.size(); ++i) {
            // 检查程序启动时是否有 -s 或 -sf 参数传入(分别得到不同结果,平面或球体)
            if (pcl::console::find_argument(argc, argv, "-s") >= 0 
                || pcl::console::find_argument(argc, argv, "-sf") >= 0) {
                cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0);
                cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0);
                if (i % 5 == 0) {    // 可能会散落在球体外
                    cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0);
                } else if (i % 2 == 0) {    // 在球体正方向内
                    cloud->points[i].z = sqrt(1 - (cloud->points[i].x * cloud->points[i].x) - 
                                                  (cloud->points[i].y * cloud->points[i].y));
                } else {    // 在球体负方向内
                    cloud->points[i].z = -sqrt(1 - (cloud->points[i].x * cloud->points[i].x) - 
                                                   (cloud->points[i].y * cloud->points[i].y));
                } 
            } else {
                cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0);
                cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0);
                if (i % 2 == 0) {
                    cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0);
                } else {
                    cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y);
                }
            }
        }
    
        std::vector<int> inliers;
    
        // 使用 RANSAC 算法估计平面(-f)或球体(-sf)模型,并得到内点集合(inliers)
        pcl::SampleConsensusModelSphere<pcl::PointXYZ>::Ptr model_s(new pcl::SampleConsensusModelSphere<pcl::PointXYZ>(cloud));
        pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptr model_p(new pcl::SampleConsensusModelPlane<pcl::PointXYZ>(cloud));
        if (pcl::console::find_argument(argc, argv, "-f") >= 0) {
            // 创建一个随机采样一致性对象,并将上面创建的模型对象作为参数传入
            pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_p);
            // 这个距离阈值 0.01 是用来判断一个点是否属于模型的,如果某个点与模型的
                // 距离小于等于该阈值,则被认为是内点(inlier),否则被认为是外点(outlier)
            ransac.setDistanceThreshold(.01);
            // 执行随机采样一致性算法,并得到内点的索引集合(inliers)
            ransac.computeModel();
            ransac.getInliers(inliers);
        } else if (pcl::console::find_argument(argc, argv, "-sf") >= 0) {
            pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_s);
            ransac.setDistanceThreshold(.01);
            ransac.computeModel();
            ransac.getInliers(inliers);
        }
    
        /*    
        作用:将提取原始点云中索引列表中列出的点,并将它们复制到指向 final 的目标点云中
              结果点云仅包含所选的点子集,而不是整个原始点云
            1.*cloud:指向包含所有点的原始点云的指针
            2.inliers:一组索引,表示要从原始点云中提取的点的子集
                        这些索引通常来自某种过滤操作,例如:平面分割或异常值去除
            3.*final:指向目标点云的指针,提取的点的子集将存储在其中
        */
        pcl::copyPointCloud(*cloud, inliers, *final);
    
        pcl::visualization::PCLVisualizer::Ptr viewer;
        if (pcl::console::find_argument(argc, argv, "-f") >= 0 ||
            pcl::console::find_argument(argc, argv, "-sf") >= 0) {
            // 同时显示原始点云和计算后的点云
            viewer = simpleVis(cloud, final);
        } else {
            viewer = simpleVis(cloud); // 只显示原始点云
        }
        while (!viewer->wasStopped()) {
            viewer->spinOnce(100);
            // C++11 引入的线程库函数,其作用是使当前线程休眠一段时间
                // 接受一个时间参数,可以是任何表示时间段的类型,比如
                // std::chrono::milliseconds 表示毫秒,因此 100ms 表示休眠 100 毫秒
            std::this_thread::sleep_for(100ms); // 调用时,当前线程将被挂起,直到过了指定的时间后再继续执行
        }
    
        return 0;
    }
    
  • 配置文件 CMakeLists.txt

    cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    
    project(random_sample_consensus)
    
    set(CMAKE_CXX_STANDARD 14) # std::chrono_literals 为 C++14 标准
    
    find_package(PCL 1.2 REQUIRED)
    
    include_directories(${PCL_INCLUDE_DIRS})
    link_directories(${PCL_LIBRARY_DIRS})
    add_definitions(${PCL_DEFINITIONS})
    
    add_executable (random_sample_consensus random_sample_consensus.cpp)
    target_link_libraries (random_sample_consensus ${PCL_LIBRARIES})
    
  • 编译并执行

    $ mkdir build
    $ cd build
    $ cmake ..
    $ make
    
    $ ./random_sample_consensus -f     # 下图一:创建包含外部点的平面,并计算平面内部点
    $ ./random_sample_consensus -sf    # 下图二:创建包含外部点的球体,并计算球体内部点
    

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

AIGC:【LLM(一)】——LoRA微调加速技术

文章目录 一.微调方法1.1 Instruct微调1.2 LoRA微调 二.LoRA原理三.LoRA使用 一.微调方法 Instruct微调和LoRA微调是两种不同的技术。 1.1 Instruct微调 Instruct微调是指在深度神经网络训练过程中调整模型参数的过程&#xff0c;以优化模型的性能。在微调过程中&#xff0c…

Flutter——最详细(TextField)使用教程

TextField简介 文本输入框&#xff0c;拥有复杂的属性。可指定控制器、文字样式、装饰线、行数限制、游标样式等。监听输入框变动事件。 使用场景&#xff1a; 搜索框&#xff0c;输入账号密码等 属性作用controller输入框监听器decoration输入框装饰属性textAlign内容对齐方式…

UE5.1.1 C++ 从0开始 (1.人物移动)

开个天坑&#xff0c;UE5.1.1的移动代码做了一个大更新&#xff0c;对于我这种万年蓝图然后正在转C的人来说可以说是个挑战也可以说是个更方便我去工作的一个点。同时斯坦福大学的那个教程的开头几个章节就不适用了&#xff0c;对于学习UE5.1.1的同学来说。所以我这里会尽量每天…

[230506] 2021年托福阅读真题第6篇|Water and Life on Mars|15:30~16:30|16:30~19:19

正确率&#xff1a;6/10 ​​​​​​​ Water and Life on Mars Paragraph 1: The question of life on Mars depends heavily on the characteristics of its air and water. Mars has a relatively thin and dry atmosphere, with a high percentage of carbon dioxide com…

想转行大数据,需要学习什么?

Python近段时间一直涨势迅猛&#xff0c;在各大编程排行榜中崭露头角&#xff0c;得益于它多功能性和简单易上手的特性&#xff0c;让它可以在很多不同的工作中发挥重大作用。 正因如此&#xff0c;目前几乎所有大中型互联网企业都在使用 Python 完成各种各样的工作&#xff0…

Spark大数据处理讲课笔记3.7 Spark任务调度

文章目录 零、本节学习目标一、有向无环图&#xff08;一&#xff09;DAG概念&#xff08;二&#xff09;实例讲解 二、Stage划分依据&#xff08;一&#xff09;两阶段案例&#xff08;二&#xff09;三阶段案例 三、RDD在Spark中的运行流程&#xff08;一&#xff09;RDD Obj…

buuctf7

目录 Crypto MD5 Url编码 看我回旋踢 web [极客大挑战 2019]BuyFlag​ [BJDCTF2020]Easy MD5 Crypto MD5 1.下载文件 2.md5在线解密 3.外包flag Url编码 使用url在线解码 看我回旋踢 下载&#xff0c;得到这串字符&#xff0c;搜一下synt编码 看到使用凯撒密码&#x…

2023-05-04:用go语言重写ffmpeg的scaling_video.c示例,用于实现视频缩放(Scaling)功能。

2023-05-04&#xff1a;用go语言重写ffmpeg的scaling_video.c示例&#xff0c;用于实现视频缩放&#xff08;Scaling&#xff09;功能。 答案2023-05-04&#xff1a; 这段代码实现了使用 libswscale 库进行视频缩放的功能。下面是程序的主要流程&#xff1a; 1.获取命令行参…

唐书计组第三章总线部分课后习题和解答

我自己的一些总结 总线周期分为哪四个阶段 申请分配阶段寻址阶段存数阶段结束阶段 总线分为哪四种通信方式 同步通信异步通信半同步通信分离式通信 总线有哪几种判优方式 链式查询 计数器定时查询 独立请求方式 计算数据传输率 3.14设总线的时钟频率为8MHz,一个总线周期…

(3)信号槽

目录 1.信号槽的概念 2.信号槽的连接 2.1自带信号 → 自带槽 2.2 自带信号 → 自定义槽 2.3 自定义信号 1.信号槽的概念 信号槽指的是信号函数与槽函数的连接&#xff0c;可以使用不同的对象通过信号槽连接在一起&#xff0c;从而实现对象之间的通信。 可以把信号槽的连接…

数字化经营3.0阶段,云徙科技如何定义“为增长而生”?

作者&#xff1a;Lucky 新时代风云变幻中&#xff0c;通过数字化转型&#xff0c;驱动业务增长、提升运营效率是企业升级的必由之路。如今&#xff0c;数字化经营也已经进入3.0阶段&#xff0c;企业对“人、货、场”三位一体的前端数字化的要求更高&#xff0c;行业也需要更有效…

Java设计模式-建造者模式

简介 建造者模式是一种创建型设计模式&#xff0c;用于将复杂对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。建造者模式通过将复杂对象的构建过程分解为多个简单的步骤来实现。 与其他创建型模式不同&#xff0c;建造者模式强调的是将构建过…

QML路径视图(The PathView)

路径视图&#xff08;PathView&#xff09;非常强大&#xff0c;但也非常复杂&#xff0c;这个视图由QtQuick提供。它创建了一个可以让子项沿着任意路径移动的视图。沿着相同的路径&#xff0c;使用缩放&#xff08;scale&#xff09;&#xff0c;透明&#xff08;opacity&…

nssctf (1)

[NISACTF 2022]popchains Happy New Year~ MAKE A WISH <?phpecho Happy New Year~ MAKE A WISH<br>;if(isset($_GET[wish])){ #通过get获取wish的值 并判断是不是空@unserialize($_GET[wish]); #反序列化wish } else{$a=new Road_is_Long; #实例化Road_is…

YOLOv5:添加SE、CBAM、CoordAtt、ECA注意力机制

YOLOv5&#xff1a;添加SE、CBAM、CoordAtt、ECA注意力机制 前言前提条件相关介绍注意力机制SE添加SE注意力机制到YOLOv5 CBAM添加CBAM注意力机制到YOLOv5 CoordAtt添加CoordAtt注意力机制到YOLOv5 ECA添加ECA注意力机制到YOLOv5 参考 前言 记录在YOLOv5添加注意力机制&#xf…

原神3.2真端完整版架设教程

想必在座的各位都玩过这款游戏吧、开放世界的玩法、折磨人的剧情、做不完的任务、话多且烦人的派蒙、没眼看的伤害、贵到爆的抽卡、打不动的深渊、树脂刷空也刷不到想要的圣遗物、打不动的BOSS、这怎么受得了呀!反正我是受不了。废话不多说、教程开始。 准备工具: 一台16H 3…

【经典面试题】请使用C语言编程实现对IPV4地址的合法性判断

C语言编程实现对IPV4地址的合法性判断 有了解过我的朋友&#xff0c;可能有点印象&#xff0c;我在N年前的博客中&#xff0c;就写了这个主题&#xff0c;当时确实是工作中遇到了这个问题。本想着等工作搞完之后&#xff0c;就把这个问题的解决代码补上&#xff0c;结果一鸽&am…

MATLAB实现工业PCB电路板缺陷识别和检测

PCB&#xff08;PrintedCircuitBoard印刷电路板&#xff09;是电子产品中众多电子元器件的承载体&#xff0c;它为各电子元器件的秩序连接提供了可能&#xff0c;PCB已成为现代电子产品的核心部分。随着现代电子工业迅猛发展&#xff0c;电子技术不断革新&#xff0c;PCB密集度…

K8S常见异常事件与解决方案

集群相关 Coredns容器或local-dns容器重启 集群中的coredns组件发生重启(重新创建)&#xff0c;一般是由于coredns组件压力较大导致oom&#xff0c;请检查业务是否异常&#xff0c;是否存在应用容器无法解析域名的异常。 如果是local-dns重启&#xff0c;说明local-dns的性能…

fastai2 实现SSD

https://github.com/search?qfastaissd 有几个值得参考的代码&#xff0c;好好学习。 GitHub - Samjoel3101/SSD-Object-Detection: I am working on a SSD Object Detector using fastai and pytorch fastai2实现的SSD&#xff0c;终于找到了code。https://github.com/sidrav…