三维点云处理-滤波器

前言:

  点云中往往会存在很多噪声,也就是常说的离群点,如下左图中的黑色圈位置,可能会对有效数据的提取分析造成影响,因此在数据分析前通常会考虑采用滤波器(Filter)等手段进行一些预处理的操作。过滤后的点云如下右图所示,这样的点云有助于对其进行更好的数据分析,如平面估计、分类、分割提取等。
image.png

常见的滤波算法

  1. 噪声去除
    1. Radius Outlier Removal 基于半径的异常值去除
    2. Statistical Outlier Removal 统计异常值去除
  2. 下采样
    1. Voxel Grid Downsampling 体素网格下采样
    2. Farthest Point Sampling 最远点采样
    3. Normal Space Sampling 法线空间采样
  3. 上采样/平滑/噪声去除
    1. Bilateral Filter 双边滤波

1. Radius Outlier Removal (ROR) 基于半径的异常值去除

  基本思想:噪声点一般是离群孤立的,而正常数据点其周围会存在足够多的近邻点。
  方法:检查数据点在其指定半径范围内的邻居数量,如果邻居数量少于某个阈值,则视为异常值并去除。
image.png

2. Statistical Outlier Removal (SOR) 统计异常值滤除

  基本思想:基于点云数据的统计分布(如均值和标准差)来识别并移除异常值。
  方法:根据每一点与邻近点距离的分布信息,计算这些点的高斯分布参数,然后使用 3σ 准则进行过滤。image.png

3. Voxel Grid Downsampling (VGD) 体素网格下采样

  基本思想:将三维点云空间划分为一系列的体素(Voxel),然后每个体素内选取少量特征点来表征该体素信息,最终生成降采样后的点云数据。
  那么如何从每个格子中选出一个点?常见的选取方法如:中心点(Centroid)、随机点(Random select);

3.1 VGD - Exact

  主要流程:根据输入的数据和体素尺寸,计算不同维度上可分割的体素数量,之后将数据映射到对应的体素单元得到索引,并根据点云对应的体素索引进行排序,最后从每个体素格子中选取特征点(中心点、随机点)来代表该体素单元,实现点云的降采样。
image.png

3.2 VGD - Approximated

  前面提到的VGD - Extra 需要对所有点云的索引进行排序,C++ 中 sort 使用的是快速排序,平均时间复杂度为 O ( N ∗ l o g N ) O(N*logN) O(NlogN),已经是一种比较优的排序方法了,但是数据量过大时排序效率也会变低,无法满足一些移动计算平台的实时使用。可以采取一些其他的手段来进行加速,这里使用 sort 排序是为了将相同 index 的点放到连续的空间中,也就可以考虑使用 Hash Table来实现这个功能。
  主要流程:根据输入的数据和体素尺寸,计算不同维度上可分割的体素数量,之后将数据映射到对应的体素单元得到索引。接着使用 Hash 函数将点索引映射到 指定数量的容器中,不断迭代直到得到 M 个点。
image.png  当然,如果container_size 小于前面计算得到的 voxel_size时,就有可能出现Hash冲突,即不同区域的数据计算得到的hash值一样。比如container_size 为 100,index-15 和 index-115 通过哈希映射后得到的值均为 15,那么如何处理这种冲突呢?
image.png
  一种处理方法是:如果我们发现了冲突,可以先将原来容器内的点随机选一个点输出,随后清空容器,并将新hash 值对应的点放入容器。

4. Farthest Point Sampling (FPS) 最远点采样

  基本思想:从数据集中选择最远的点作为采样点,确保采样点分布均匀。
  方法:从数据中随机选取一个点作为初始点,然后不断迭代地选择距离已有采样点集合的最远点。
image.png

5. Normal Space Sampling (NSS) 法向空间采样

  基本思想:在法向量空间内均匀随机抽样,以保持地物特征。
  方法:先在法向量空间构建一系列的容器,然后依据平面法向量将所有点放到对应容器中,接着从所有容器中均匀地选点。
image.png

6. Learning to Sample[1]

  课程里也介绍了一下利用深度学习降采样的方法。通过几何意义上的限制,使得降采样后的点具有和降采样之前相似的几何状态
  主要流程:输入 -> 神经网络 -> 输出降采样后的点云 -> 输入分类网络。
  核心思路:不直接基于几何关系,而是基于语义检测任务,要求降采样之后的点能够达到语义分析的效果。

image.png
  虽然没有直接基于几何关系进行点云降采样,但是在降采样任务训练的时候,也加入了一些几何约束,如:
image.png

7. Bilateral Filter (BF) 双边滤波

  基本思想:在滤波过程中同时考虑空间邻近度和像素值相似性,以达到保边去噪的效果。
  应用场景:图像处理、点云上采样等;
  以图像为例:通过两个高斯核来对数据进行双边滤波。如对图像使用 距离高斯核 和 颜色高斯核 对图像进行模糊或者平滑。
image.png
  对点云来说,在与图像融合时可以进行上采样填充。
image.png

Voxel Grid Downsampling 练习

1. 代码实现

  前面已经简单介绍了VGD-Exact 体素网格降采样算法的基本原理,这部分主要是进行代码实现及简单测试。
  首先需要先明确一下体素网格降采样算法的输入输入及主要处理流程。

  • 输入部分:原始三维点云
  • 输出部分:降采样后的三维点云
  • 处理步骤:
    1. 根据输入的数据和体素尺寸,计算不同维度上可分割的体素数量;
    2. 将数据映射到对应的体素单元得到索引;
    3. 根据点云对应的体素索引进行排序;
    4. 从每个体素格子中选取特征点(中心点、随机点)来代表该体素单元;
    5. 降采样后的点云输出。
#pargma once

#include <iostream>
#include <vector>
#include <string>
#include <Eigen/Dense>

class VoxelGridDownSampling{
public:
    // 使用枚举来定义体素点选取方式
    enum class SelectPointsMethod{
        CENTROID,
        RANDOM,
    };

    bool set_data(const Eigen::MatrixXd& data);		// 输入点云数据
    bool downsampling(double grid_size, SelectPointsMethod select_pts_method = SelectPointMethod::RANDOM);	// 点云降采样

    const std::vector<std::pair<int, Eigen::Vector3d>>& get_downsample_result(); // 返回降采样数据	

private:
    bool select_centroid(std::vector<std::pair<int, Eigen::Vector3d>>& downsample);	// 中心点降采样
    bool select_random(std::vector<std::pair<int, Eigen::Vector3d>>& downsample);	// 随机点降采样

private:
    Eigen::MatrixXd _pointcloud;
    std::vector<std::pair<int, Eigen::Vector3d>> _downsample;
};
#include "voxel_grid_downsampling.h"

#include <algorithm>
#include <cmath>

bool VoxelGridDownsampling::set_data(const Eigen::MatirxXd& data){
    _pointcloud = data;
    return true;
}

bool VoxelGridDownsampling::downsampling(
    double grid_size, SelectPointsMethod select_pts_method){
    // 1.计算点云的最大最小值
    Eigen::Vector3d max = _pointcloud.rowwise().maxCoeff();
    Eigen::Vector3d min = _pointcloud.rowwise().minCoeff();
    // 计算体素数量
    Eigen::Vector3d dim = (max - min) / grid_size;
    
    // 2.将点云投影到网格中
    for(int i = 0; i < _pointcloud.cols(); ++i){
        Eigen::Vector3d pts = _pointcloud.col(i);
        int h_x = floor((pts(0) - min(0)) / grid_size);
        int h_y = floor((pts(1) - min(1)) / grid_size);
        int h_z = floor((pts(2) - min(2)) / grid_size);

        int h = h_x + dim(0) * h_y + dim(0) * dim(1) * h_z;
        _downsample.emplace_back(std::make_piar(h, pts));
    }
    
    std::cout << "_downsample size: " << _downsample.size() << std::endl;
    
    // 3.索引排序
    std::sort(_downsample.begin(), _downsample.end(), 
        [] (const std::pair<int, Eigen::Vector3d>& p1, const std::pair<int, Eigen::Vector3d>& p2) -> bool {return p1.first < p2.first;} );
    // 4.点云降采样
    switch(select_pts_method) {
        case SelectPointsMethod::CENTROID:
            select_centroid(_downsample);
            break;
        case SelectPointsMethod::RANDOM:
            select_random(_downsample);
            break;
        default:
            std::cerr << "select points method error!" << std::endl;
            return false;
    }
    std::cout << "_downsample size after select: " << _downsample.size() << std::endl;
    return true;
}

const std::vector<std::pair<int, Eigen::Vector3d>>& VoxelGridDownsampling::get_downsample_result(){
    return _downsample;
}

bool VoxelGridDownsampling::select_centroid(std::vector<std::pair<int, Eigen::Vector3d>>& downsample){
    // 定义存储网格点云和降采样后点云的变量
    std::vector<std::pair<int, Eigen::Vector3d>> grid_pts;
    std::vector<std::pair<int, Eigen::Vector3d>> res;
    // 依次对每个点云进行处理
    for(size_t i = 0; i < downsample.size(); ++i){
        // grid_pts 为空
        if(grid_pts.size() == 0){
            grid_pts.emplace_back(downsample[i]);
        }else if(grid_pts[grid_pts.size() - 1].first != downsample[i].first){
            // grid_pts点云与downsample[i]网格索引不一致
            // 计算中心点
            double cx = 0, cy = 0, cz = 0;
            for(size_t j = 0; j < grid_pts.size(); ++j){
                cx += grid_pts[j].second(0);
                cy += grid_pts[j].second(1);
                cz += grid_pts[j].second(2);
            }
            cx /= grid_pts.size();
            cy /= grid_pts.size();
            cz /= grid_pts.size();
            res.emplace_back(std::make_pair(grid_pts[0].first, Eigen::Vector3d(cx, cy, cz)));

            // 清空当前网格点云,并添加新点云
            grid_pts.clear();
            grid_pts.emplace_back(downsample[i]);
        }else{
            // downsample[i]与grid_pts点云索引一致
            grid_pts.emplace_back(downsample[i]);
        }     
    }
    // 更新降采样点云
    downsample.swap(res);
    return true;
}

bool VoxelGridDownSampling::select_random(std::vector<std::pair<int, Eigen::Vector3d>>& downsample){
    // 定义存储网格点云和结果点云的变量
    std::vector<std::pair<int, Eigen::Vector3d>> grid_pts;
    std::vector<std::pair<int, Eigen::Vector3d>> res;
    // 处理每个点云并选取random点
    for(size_t i = 0; i < downsample.size(); ++i){
        // 网格内无点云,直接添加
        if(grid_pts.size() == 0){
            grid_pts.emplace_back(downsample[i]);
        }else if(grid_pts[grid_pts.size() - 1].first != downsample[i].first){
            // 网格点云和当前点云归属不同网格,计算均值点
            int rand_idx = rand() % grid_pts.size();
            Eigen::Vector3d pts;
            pts << grid_pts[rand_idx].second(0),
                   grid_pts[rand_idx].second(1),
                   grid_pts[rand_idx].second(2);

            res.emplace_back(std::make_pair(grid_pts[0].first, pts));
            // 清空网格内点云并添加新点云
            grid_pts.clear();
            grid_pts.emplace_back(downsample[i]);
        }else{
            // 正常添加网格点云
            grid_pts.emplace_back(downsample[i]);
        }
    }

    // 更新下采样点云结果
    downsample.swap(res);
    return true;    
}

2. 可视化效果

  这里使用 ModelNet40 数据集进行点云降采样测试,降采样网格大小为0.1m,体素网格点云分别为中心点和随机点方式表示,测试效果如下:

在这里插入图片描述
  上图从左到右分别是:原始点云图、VGD降采样点云(中心点法)、VGD降采样点云(随机点法)。

  声明:以上公式和图片来自课程上的PPT部分,小部分参考借鉴了其他博主,仅作为学习、交流使用。

参考链接:
  1. Learning to Sample
  2. 《三维点云处理》学习笔记(2):滤波器
  3. 三维点云处理:5滤波:降采样_点云滤波-CSDN博客
  4. 三维点云处理-深蓝学院

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

东北大学工程训练CNC加工中心(坤图)

东北大学加工中心&#xff08;CNC&#xff09;采用的系统为FANUC系统。 要求学生自主设计图样&#xff0c;编写GCODE文件&#xff0c;操作电脑使机床按设计路径铣出图案。 本人设计的图样为坤坤图 图为用CAD设计绘制的图样。 计算坐标&#xff0c;设计铣刀轨迹&#xff0c;得…

解析社交电商:从私域流量到移动突破口

亲爱的朋友们&#xff0c;我是微三云的周丽&#xff0c;一名专注于私域电商模式创新的探索者。 随着互联网的迅速发展和科技的不断进步&#xff0c;社交电商作为新型商业模式不断崛起。在这个时代&#xff0c;私域流量、社群电商、社区电商以及移动电商等概念层出不穷&#xf…

成功密码期刊投稿简介

《成功密码》综合版是由国家新闻出版总署批准&#xff0c;江西省教育厅主管的正规期刊&#xff0c;"以培养担当民族复兴大任的时代新人为着眼点&#xff0c;强化教育引导、实践养成、制度保障"&#xff0c;倡导教育研究的学术水准&#xff0c;注重理论与实践的有机结…

Linux消息队列信号量(了解)

消息队列 要实现进程间通信我们必须得让不同的进程看到同一份资源&#xff0c; 根据这个资源的不同&#xff08;文件缓冲区&#xff0c; 内存块&#xff0c; 队列&#xff09; 我们将通信方式分为管道&#xff0c;共享内存&#xff0c;以及我们接下来要讲的消息队列。 消息队…

【学习笔记二十七】EWM存储类型控制

一、EWM存储类型控制概述 Storage control 是用来决定仓库产品移动时所需要的流程步骤。它的目的是用来处理基于仓库物理布局及仓库流程所要求的复杂的上架和下架流程步骤。 仓库里常见的操作步骤有:Picking、Packing、Staging、Loading、Putaway、Unloading、Counting、Quali…

【C语言】联合体详解

目录 1.联合体的声明 2.联合体的特点 3.相同成员的结构体和联合体对比 4.联合体大小的计算 1.联合体的声明 像结构体一样&#xff0c;联合体也是由一个或者多个成员构成&#xff0c;这些成员可以不同的类型。但是编译器只为最大的成员分配足够的内存空间。 联合体的特点是所…

操作系统:进程间通信 | System V IPC

目录 前言&#xff1a; 1.共享内存 1.1.什么是共享内存 1.2.共享内存使用接口 shmget函数 shmat函数 shmdt函数 shmctl函数 2.共享内存实现通信 2.1.代码实现 comm.hpp server,cpp client.cpp 2.2.共享内存的缺点 2.3.实现通信的同步化 2.4共享内存通信的优势 3.…

Vitis HLS 学习笔记--HLS入门示例集合-目录

目录 1. 示例集合概述 2. Interface 接口 2.1 Aggregation_Disaggregation 聚合与解聚 2.1.1 aggregation_of_m_axi_ports 2.1.2 aggregation_of_nested_structs 2.1.3 aggregation_of_struct 2.1.4 auto_disaggregation_of_struct 2.1.5 disaggregation_of_axis_port …

游戏工作室为什么要使用海外住宅IP防封?

当谈到游戏工作室时&#xff0c;它们通常以多开游戏账号来获取收益为主要目标。这种商业模式在游戏产业中已经成为一个独特而且颇具潜力的领域。然而&#xff0c;随之而来的是防封问题&#xff0c;特别是当游戏工作室试图通过多开账号来赚取更多收益时。因此&#xff0c;我们有…

Navicat连接SQLSever报错:[08001] MicrosoftTCP Provider 远程主机强迫关闭了一个现有的连接

Navicat连接SQLSever报错&#xff1a;[08001] [Microsoft][SQL Server Native Client 10.0]TCP Provider: 远程主机强迫关闭了一个现有的连接 问题分析 旧版的MSSQL 如果不是最新版的&#xff0c;可以去这安装以下即可。 最新版的MSSQL 如果是安装最新版的MSSQL连接不上很正…

Kubernetes 的未来:通过生成式 AI 实现的潜在改进

Kubernetes 是一个用于自动化部署、扩展和管理容器化应用程序的开源平台&#xff0c;它彻底改变了 IT 行业。然而&#xff0c;与所有创新技术一样&#xff0c;它不断寻求改进以提高效率、可用性和功能。生成式人工智能&#xff08;Generative AI&#xff09;是一个有望取得改进…

C++:匿名对象

在C中&#xff0c;匿名对象是指在不分配给定变量名称的情况下创建的临时对象。这些对象通常用于传递参数给函数、作为函数的返回值或者在表达式中使用。 创建匿名对象 在C中&#xff0c;您可以使用类的构造函数来创建匿名对象。例如&#xff1a; MyClass(); // 创建一个匿名…

终于有人把“Linux云计算路线”整理出来了,收藏起来,随时查看

一&#xff0c;计算机硬件 二&#xff0c;计算机网络 三&#xff0c;Linux系统管理 四&#xff0c;构建Linux服务系统&#xff0c;数据库&#xff0c;程序及Web服务 五&#xff0c;消息队列&#xff0c;web集群&#xff0c;系统及软件优化&#xff0c;智能化监控&#xff0c;海…

CSS画一条虚线,并且灵活设置虚线的宽度和虚线之间的间隔和虚线的颜色

CSS画一条虚线,并且灵活设置虚线的宽度和虚线之间的间隔和虚线的颜色。 先看效果图&#xff1a; 在CSS中&#xff0c;你可以使用border属性或者background属性来画一条虚线。以下是两种常见的方法&#xff1a; 方法一&#xff1a;使用border属性 你可以设置一个元素的border…

4.25日学习记录

[HZNUCTF 2023 preliminary]ppppop 对于php反序列化&#xff0c;在之前的学习中有过了解&#xff0c;但是对于序列化字符串的格式不是很了解&#xff0c;刚好接触这题&#xff0c;可以了解一下 序列化字符串的格式&#xff1a; 布尔型&#xff08;bool&#xff09;b&#xf…

036——完善编译框架和注释并选择开源协议

目录 小总结 编译框架完善 代码风格 开源协议选择 小总结 经过两个月的努力现在已经写了457MB的代码了 . ├── board │ ├── Linux │ │ └── 4_9_88 │ │ └── ARM32 │ │ └── 100ask │ │ └── imx6ull_mi…

10.JAVAEE之网络编程

1.网络编程 通过网络,让两个主机之间能够进行通信 >基于这样的通信来完成一定的功能进行网络编程的时候,需要操作系统给咱们提供一组 AP1, 通过这些 API才能完成编程&#xff08;API 可以认为是 应用层 和 传输层 之间交互的路径&#xff09;&#xff08;API:Socket API相当…

简单案例验证说明 双亲委派机制

双亲委派介绍 双亲委派机制&#xff08;Parent Delegation Mechanism&#xff09;是Java中的一种类加载机制。在Java中&#xff0c;类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时&#xff0c;它会先将该请求委派给它的父…

aysnc-await的用法

aysnc-await是promise的一种特殊语法&#xff0c;它可以更简洁的得到promise aysnc function aysnc 放在函数前定义函数&#xff0c;它规定了这个函数的返回值一定为promise&#xff0c; // 通过new新建一个promise(旧) // let p new Promise(function(resolve,reject){ //…

可搜索加密:保护隐私的搜索技术

在信息化、数字化快速发展的今天&#xff0c;数据的安全性和隐私性已成为公众关注的焦点。随着云计算、大数据等技术的广泛应用&#xff0c;数据共享与协同工作日益普遍&#xff0c;但如何在确保数据安全性的前提下&#xff0c;实现数据的快速、高效检索&#xff0c;成为了一个…