点云梯度下采样

news2025/2/25 12:37:13

点云下采样又称点云精简。

均匀网格下采样

均匀网格下采样法是建立在空间包围盒精简算法之上对散乱点云快速简化的一种算法,其基本思想为:根据点云数据的密度确定最小三维网格(体素)的边长为 a ∗ b ∗ c a*b*c abc,计算最小三维网格的重心,通过邻域搜索保留离重心点最近的点并删除其余的点。每个三维网格重心依据式下式进行计算。
X = ∑ i = 1 n x i n , Y = ∑ i = 1 n y i n , Z = ∑ i = 1 n z i n X=\frac{\sum_{i=1}^n x_i}{n}, \quad Y=\frac{\sum_{i=1}^n y_i}{n}, \quad Z=\frac{\sum_{i=1}^n z_i}{n} X=ni=1nxi,Y=ni=1nyi,Z=ni=1nzi
其中 n n n 表示最小三维网格中的点云数据量 [ 1 ] ^{[1]} [1]

原理类似与点云降采样(DownSampling这篇中所提到的体素网格下采样。

曲率下采样

对于上述的均匀下采样(体素下采样),随着体素尺寸的增大,采样后得到的点云将会丢失细节特征,如曲率较大处,相反,如果在曲率变化不大的地方采样过多点,会显得冗余。所以,在这种情况下基于曲率特征的点云采样方法更加合适。

采样思路如下:

Step1:使用局部曲面拟合法计算出每个待采样点 p i p_i pi 的曲率 H i H_i Hi,并计算点云整体的平均曲率作为曲率阈值 H t H_t Ht

Step2:比较 H i H_i Hi 与曲率阈值 H t H_t Ht 的大小,如果小于曲率阈值 H t H_t Ht,则把采样点 p i p_i pi 划分到平缓区域,反之划分到陡峭区域。
Step3:采用均匀网格法对两个区域的点云进行精简(下采样),陡峭区域和平缓区域的边长阈值分别设置为 A A A B B B ,并且 A < B A<B A<B [ 1 ] ^{[1]} [1]

梯度下采样

通过类比法,我们用有限元体网格节点梯度替代表面曲率,设计出一种基于梯度特征的节点点云下采样方法。

采样思路如下:

Step1:计算出每个待采样点 p i p_i pi 的梯度 G i G_i Gi,并计算节点点云整体的平均梯度作为梯度阈值 G t G_t Gt

Step2:比较 G i G_i Gi 与梯度阈值 G t G_t Gt 大小,如果小于梯度阈值 G t G_t Gt,则把采样点 p i p_i pi 划分到节点属性变化剧烈区域,反之划分到节点属性变化缓慢区域。
Step3:采用均匀网格法对两个区域的节点点云进行精简(下采样),剧烈区域和缓慢区域的边长阈值分别设置为 A A A B B B ,并且 A < B A<B A<B

代码实现

均匀网格下采样

均匀网格下采样,也就是体素下采样的python代码实现如下:

def voxel_filter(origin_points, leaf_size):
    """体素下采样"""
    filtered_points = []
    # 计算边界点
    x_min, y_min, z_min = np.amin(origin_points, axis = 0)  # 计算x y z 三个维度的最值
    x_max, y_max, z_max = np.amax(origin_points, axis = 0)

    # 计算 voxel grid维度
    Dx = (x_max - x_min) // leaf_size + 1
    Dy = (y_max - y_min) // leaf_size + 1
    Dz = (z_max - z_min) // leaf_size + 1
    # print("Dx x Dy x Dz is {} x {} x {}".format(Dx, Dy, Dz))

    # 计算每个点的voxel索引,即确定每个点所被划分到的voxel
    h = []  # h 为保存索引的列表
    for i in range(len(origin_points)):
        hx = (origin_points[i][0] - x_min) // leaf_size
        hy = (origin_points[i][1] - y_min) // leaf_size
        hz = (origin_points[i][2] - z_min) // leaf_size
        h.append(hx + hy * Dx + hz * Dx * Dy)  # voxel索引填充顺序x-y-z
    h = np.array(h)

    # 筛选点
    h_indice = np.argsort(h)  # 返回h里面的元素按从小到大排序的索引
    h_sorted = h[h_indice]
    begin = 0
    for i in range(len(h_sorted)):
        if i == len(h_sorted) - 1:  # 到最后一个体素的最后一个点
            point_idx = h_indice[begin: i + 1]
            filtered_points.append(np.mean(origin_points[point_idx], axis = 0))  # 计算最后一个体素的采样点
            continue
        if h_sorted[i] == h_sorted[i + 1]:
            continue
        else:
            point_idx = h_indice[begin: i + 1]
            filtered_points.append(np.mean(origin_points[point_idx], axis = 0))
            begin = i + 1

    # 把点云格式改成array,并对外返回
    filtered_points = np.array(filtered_points, dtype = np.float64)
    return filtered_points

上面代码参考这篇文章【点云学习】Python实现点云体素下采样(Voxel Filter)中的代码。原文中的代码在迭代采样体素过程中,存在无法在最后一个体素中采样的问题。所以,我在迭代中添加了如下语句,进行完善。

    if i == len(h_sorted) - 1:  # 到最后一个体素的最后一个点
        point_idx = h_indice[begin: i + 1]
        filtered_points.append(np.mean(origin_points[point_idx], axis = 0))  # 计算最后一个体素的采样点
        continue

梯度下采样

结合上述的算法流程,梯度下采样python代码实现如下。

def gradient_downsampling(origin_points, G, a, b):
    """gradient downsampling

    :param origin_points: 源点云
    :param G: 点云梯度值
    :param a: 梯度变化剧烈区域采样体素尺寸
    :param b: 梯度变化缓慢区域采样体素尺寸
    :return: 采样点云
    """
    filtered_points, a_points, b_points = [], [], []
    # 将点与其对应梯度绑定
    # origin_points = np.hstack((origin_points, G.reshape(-1, 1)))
    # Step1: 将平均梯度作为梯度阈值
    G_t = np.mean(G)
    # Step2: 根据梯度划分点云为a_points和b_points两个区域
    for i, G_i in enumerate(G):
        if G_i > G_t:
            a_points.append(origin_points[i])
        else:
            b_points.append(origin_points[i])

    # Step3: 采样体素下采样对a_points和b_points两个区域进行采样
    a_filtered = voxel_filter(np.array(a_points), a, near = True)
    b_filtered = voxel_filter(np.array(b_points), b, near = True)
    filtered_points = np.vstack((a_filtered, b_filtered))

    return filtered_points

为了满足采样点为源点云中的点,即采样距离体素重心最近的点,改写voxel_filter函数如下。

def voxel_filter(origin_points, leaf_size, near = False):
    """体素下采样"""
    filtered_points = []
    if near:
        # 构建KD-Tree寻找最近点
        from scipy import spatial
        tree = spatial.KDTree(data = origin_points[:, :3])

    # 计算边界点
    x_min, y_min, z_min = np.amin(origin_points[:, :3], axis = 0)  # 计算x y z 三个维度的最值
    x_max, y_max, z_max = np.amax(origin_points[:, :3], axis = 0)

    # 计算 voxel grid维度
    Dx = (x_max - x_min) // leaf_size + 1
    Dy = (y_max - y_min) // leaf_size + 1
    Dz = (z_max - z_min) // leaf_size + 1
    # print("Dx x Dy x Dz is {} x {} x {}".format(Dx, Dy, Dz))

    # 计算每个点的voxel索引,即确定每个点所被划分到的voxel
    h = []  # h 为保存索引的列表
    for i in range(len(origin_points)):
        hx = (origin_points[i][0] - x_min) // leaf_size
        hy = (origin_points[i][1] - y_min) // leaf_size
        hz = (origin_points[i][2] - z_min) // leaf_size
        h.append(hx + hy * Dx + hz * Dx * Dy)  # voxel索引填充顺序x-y-z
    h = np.array(h)

    # 筛选点
    h_indice = np.argsort(h)  # 返回h里面的元素按从小到大排序的索引
    h_sorted = h[h_indice]
    begin = 0
    for i in range(len(h_sorted)):
        point_idx = h_indice[begin: i + 1]
        if i == len(h_sorted) - 1:  # 到最后一个体素的最后一个点
            if near:
                query_point = np.mean(origin_points[point_idx], axis = 0)[:3]
                dist, ind = tree.query(query_point, k = 1)
                filtered_points.append(origin_points[ind])
            else:
                filtered_points.append(np.mean(origin_points[point_idx], axis = 0))  # 计算最后一个体素的采样点
            continue
        if h_sorted[i] == h_sorted[i + 1]:
            continue
        else:
            if near:
                query_point = np.mean(origin_points[point_idx], axis = 0)[:3]
                dist, ind = tree.query(query_point, k = 1)
                filtered_points.append(origin_points[ind])
            else:
                filtered_points.append(np.mean(origin_points[point_idx], axis = 0))
            begin = i + 1

    # 把点云格式改成array,并对外返回
    filtered_points = np.array(filtered_points, dtype = np.float64)
    return filtered_points

通过构建待采样点云的KD-Tree实现最邻近搜索。

测试结果

我们对比测试同一待采样点云的体素下采样和梯度下采样,测试代码如下:

input_points = np.hstack((points, gradient))
drawPointCloud(input_points, color = True)
# 体素下采样
Vsample_points = voxel_filter(input_points, 5)
drawPointCloud(Vsample_points, color = True)
# 梯度下采样
Gsample_points = gradient_downsampling(input_points, gradient, 2, 5)
drawPointCloud(Gsample_points, color = True)

这里我们通过使用open3d库进行点云可视化。

def drawPointCloud(points, color = False):
    import open3d as o3d
    cloud = o3d.geometry.PointCloud()
    cloud.points = o3d.utility.Vector3dVector(points[:, :3])
    print(len(cloud.points))
    if not color:
        # 所有点统一颜色
        cloud.paint_uniform_color([241 / 255, 135 / 255, 184 / 255])
    else:
        # 颜色映射
        colors = np.zeros([points.shape[0], 3])
        color_max = np.max(points[:, 3])
        color_min = np.min(points[:, 3])
        delta_c = abs(color_max - color_min) / (255 * 2)
        for j in range(points.shape[0]):
            color_n = (points[:, 3][j] - color_min) / delta_c
            if color_n <= 255:
                colors[j, :] = [0, 1 - color_n / 255, 1]
            else:
                colors[j, :] = [(color_n - 255) / 255, 0, 1]

        cloud.colors = o3d.utility.Vector3dVector(colors)
    o3d.visualization.draw_geometries([cloud])

测试结果如下图所示。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

PinkCAx中测试结果如下。
在这里插入图片描述

参考

[1] 李国远,梁周雁,石信肖,等. 基于曲率特征约束的激光点云精简方法研究[J]. 计算机与数字工程,2020,48(8):2034-2037,2063. DOI:10.3969/j.issn.1672-9722.2020.08.042.
[2] 【点云学习】Python实现点云体素下采样(Voxel Filter)
[3] 采用Open3d绘制高度颜色点云图

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

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

相关文章

含电热联合系统的微电网运行优化matlab程序(yalmip+cplex)(yalmip+gurobi)

含电热联合系统的微电网运行优化matlab程序&#xff08;yalmipcplex&#xff09;&#xff08;yalmipgurobi&#xff09; 参考文献&#xff1a;含电热联合系统的微电网运行优化 在当前能源互联网迅速发展及电热联系日渐紧密的环境下&#xff0c;提出基于电热联合调度的区域并网…

个人电影网站web网页设计制作—— 影视公司5页 DIV+CSS制作 浮动布局

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 精彩专栏推荐&#x1f4…

【Vim】更改Vim编辑器的字体大小、改变字号;永久改变字号;改变字体颜色、字体显示样式

一、问题背景 初次使用Vim&#xff0c;由于电脑分辨率较高&#xff0c;在编辑器上显示的文本字号较小&#xff0c;不甚看清。 我使用的是Gvim for Windows。 二、网上的已有方法小结 2.1 快捷键ctrl 和ctrl - 标题中提到的号&#xff0c;需要按下shift才能输入&#xff0c…

用DIV+CSS技术设计的公益主题网站——防止电信诈骗(web前端网页制作课作业)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

狂神说MybatisPlus学习笔记

MyBatis-Plus 学习MyBatis-Plus之前要先学MyBatis–>Spring—>SpringMVC 为什么要学它?MyBatisPlus可以节省我们大量的时间,所有CRUD代码都可以自动完成 JPA, tk-mapper ,MyBatisPlus 偷懒用的! 1.简介 是什么? 2.特性 无侵入&#xff1a;只做增强不做改变&am…

C语言第十二课(上):操作符详解【算数、移位、位、赋值操作符】

目录 前言&#xff1a; 一、操作符分类&#xff1a; 二、操作符详解&#xff1a; 1.算术操作符、-、*、/、%&#xff1a; 2.移位操作符>>、<<&#xff1a; 1.原码、反码与补码&#xff1a; 2.左移操作符&#xff1a; 3.右移操作符&#xff1a; 4.警告⚠&#xf…

Qt5开发从入门到精通——第十二篇三节(Qt5 事件处理及实例——多线程应用、服务器端编程、客户端编程)

提示&#xff1a;欢迎小伙伴的点评✨✨&#xff0c;相互学习c/c应用开发。&#x1f373;&#x1f373;&#x1f373; 博主&#x1f9d1;&#x1f9d1; 本着开源的精神交流Qt开发的经验、将持续更新续章&#xff0c;为社区贡献博主自身的开源精神&#x1f469;‍&#x1f680; 文…

用DIV+CSS技术设计的抗击疫情网页与实现制作(web前端网页制作课作业)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

基于C++实现的游客信息管理系统

目 录 一、 项目技术路线说明 1 二、 项目需求分析 2 2.1 项目介绍 2 2.2 功能需求 2 三、 系统分析与设计 3 3.1 本程序需解决的关键技术问题 3 3.2 程序流程 3 3.2.1 注册或登陆流程图 3 3.2.2 信息日期判断流程图 4 3.2.3 操作功能选择模块 5 3.3 功能模块 6 3.3.1 增删改查…

让Unity打包AssetBundle更轻松

AssetBundle作用1、AssetBundle是一个压缩包包含模型、贴图、预制体、声音、甚至整个场景&#xff0c;可以在游戏运行的时候被加载&#xff1b; 2、AssetBundle自身保存着互相的依赖关系&#xff1b; 3、压缩包可以使用LZMA和LZ4压缩算法&#xff0c;减少包大小&#xff0c;更快…

HTML小游戏13 —— 仿《神庙逃亡》3D风格跑酷游戏《墓地逃亡》(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且实用的计…

BGP服务器

BGP服务器被称为“边界网关协议”(BGP)&#xff0c;是一种用于在不同主机网关、 Internet或自治系统之间传输数据和信息的路由协议。 BGP是一种路径矢量协议(PVP)&#xff0c;它维护不同主机、网络和网关的路由器的路径&#xff0c;并根据 BGP做出路由决定。把电信、联通、联通…

算法day32|122,55,45

122.买卖股票的最佳时机II class Solution:def maxProfit(self, prices: List[int]) -> int:profit 0for i in range(len(prices)-1):diff prices[i1]-prices[i]if diff > 0:profit diffelse:profit 0return profit 简单到我不敢相信。 本题解法很巧妙&#xff0c;大…

Redis实战——缓存

目录 1 前言 1.1什么是缓存&#xff1f; 1.2 缓存的作用及成本 1.3 Redis缓存模型 2 给商户信息添加缓存 3 缓存更新策略 3.1 更新策略介绍 3.2 主动更新策略 3.3 主动更新策略练习 4 缓存穿透及其解决方案 4.1 缓存穿透的概念 4.2 解决方案及实现 5 缓存雪崩的…

C++:STL::String模拟实现

前言&#xff1a; 浅拷贝和深拷贝 实现string需要知道深浅拷贝问题。观察如下自命名空间中实现的string&#xff0c;不自写string的string类型参数的构造函数&#xff0c;编译器会默认生成&#xff0c;做浅拷贝。对于自定义类型使用自定义类型的构造函数&#xff0c;如果是默认…

基于遗传算法的PID控制器增益的实现(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

【信息量判别块:语义监督:GAN:IVIF】

Semantic-supervised Infrared and Visible Image Fusion via a Dual-discriminator Generative Adversarial Network &#xff08;通过双重鉴别器生成对抗网络进行语义监督的红外和可见光图像融合&#xff09; 我们提出了一种新的端到端模型&#xff0c;以在红外和可见光图像…

java序列化,看这篇就够了

面试官&#xff1a;兄弟&#xff0c;说说你对transient的理解和感悟 哪吒&#xff1a;what&#xff1f;还有感悟&#xff1f; 先说结论&#xff0c;在序列化、反序列化时&#xff0c;被transient关键字修饰的成员属性变量不会被序列化。 面试官&#xff1a;这就完了&#xf…

Flutter高仿微信-第51篇-群聊-修改群名

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 实现代码&#xff1a; //修改群名 void _updateGroupName(){bool isOwner fals…

Multi-Interest Network with Dynamic Routing forRecommendation at Tmall 论文阅读笔记

1. ABSTRACT 1.1 Industrial recommender systems &#xff08;1&#xff09;工业推荐系统通常由匹配阶段和排名阶段组成&#xff1b; &#xff08;2&#xff09;匹配阶段&#xff1a;检索与用户兴趣相关的候选项&#xff1b; &#xff08;3&#xff09;排名阶段&#xff1a;…