使用Python,Open3D对点云散点投影到面上并可视化,使用3种方法计算面的法向量及与平均法向量的夹角

news2024/9/21 22:52:39

使用Python,Open3D对点云散点投影到面上并可视化,使用3种方法计算面的法向量及与平均法向量的夹角

写这篇博客源于博友的提问,他坚定了我继续坚持学习的心,带给了我充实与快乐。
将介绍以下5部分:

  1. 随机生成点云点
  2. 投影点到面(给出了6个面的中心点,离哪个中心点距离近就投影到哪个面)
  3. 对投影到每个面的点云计算法向量点(3种方法 KNN 半径近邻 混合近邻)
  4. 对每个面上的法向量及与平均法向量的夹角
  5. 可视化原始点及法向量点

1. 效果图

1.1 点云点灰色 VS 法向量点绿色 VS 法向量可视化

全量点云点灰色 VS 全量法向量点绿色效果图如下:
投影到每个面的均值点为渲染为红色比较明显。法向量点均值点渲染为黄色这里不太明显,可查看下边每个投影面的效果图;
在这里插入图片描述
全量点云点灰色 VS 全量法向量点绿色 VS 法向量可视化 效果图如下:

在这里插入图片描述

1.2 投影到每个面上的点云点灰色 VS 法向量点绿色 VS 均值点红色,法向量均值点黄色

投影到第一个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

红色,黄色点不明显,旋转下看下一个图;
在这里插入图片描述
投影到第一个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,旋转后红色、黄色点比较明显 如下图

在这里插入图片描述投影到第一个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,同时可视化法向量点 如下图

在这里插入图片描述

投影到第2个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

在这里插入图片描述

投影到第2个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,同时渲染法向量 如下图

在这里插入图片描述

投影到第3个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

在这里插入图片描述
投影到第3个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,同时渲染法向量 如下图

在这里插入图片描述

投影到第4个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

在这里插入图片描述
投影到第4个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,同时渲染法向量 如下图

在这里插入图片描述

投影到第5个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

在这里插入图片描述
投影到第5个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,同时渲染法向量 如下图

在这里插入图片描述

投影到第6个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

在这里插入图片描述
投影到第6个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,同时渲染法向量 如下图

在这里插入图片描述

2. 源码

# 随机生成点
# 投影到面上,(给出了6个面的中心点,离哪个中心点距离近就投影到哪个面)
# 对投影到每个面的点云计算法向量点(3种方法 KNN 半径近邻 混合近邻)
# 对每个面上的法向量求均值及与平均法向量的夹角
# 并可视化原始点灰色,法向量点绿色,均值点红色,均值法向量点黄色
import random

import numpy as np
import open3d as o3d

# 随机种子,以便复现结果
random.seed(123)

# 假设立方体是2*2*2,立方体的质心是笛卡尔坐标的原点,立方体外接球的半径为sqrt(3)

d = {}  # 存储面的中心点及投影到每个面上的点云点,key中心 values投影到该面的点云点


def add_dict(dictionary, loc, centroid):
    # loc is the point's location on sphere,
    # centroid is the center(s) of cube's face(s) that is nearest to the loc
    if tuple(loc) in dictionary.keys():
        dictionary[tuple(loc)].append(list(centroid))
    else:
        dictionary[tuple(loc)] = [list(centroid)]


def point_generator(npoints, r):
    result = []
    # 0 < theta < 2*np.pi
    # 0 < phi < np.pi
    for i in range(npoints):
        theta = random.random() * 2 * np.pi
        phi = random.random() * np.pi
        x = r * np.cos(theta) * np.sin(phi)
        y = r * np.sin(theta) * np.sin(phi)
        z = r * np.cos(phi)
        result.append([x, y, z])
    return result


npoints = 5000
r = np.sqrt(3)
centroids = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, 0, 0], [0, -1, 0], [0, 0, -1]]
points = point_generator(npoints, r)


# print(points)

# 计算俩个点的距离
def distance(p, q):
    if len(p) == len(q):
        result = 0
        for i in range(len(p)):
            result += (p[i] - q[i]) ** 2
        return result ** 0.5
    else:
        print('cannot calculate distance of points from different dimensions')


# 寻找离点最近的面(中心点),并投影到对应的面上
def archive_nearest_pc_pairs(dictionary, centroids, points):
    for p in points:
        dist = []
        for q in centroids:
            dist.append(distance(p, q))
        nearest_centroid = centroids[dist.index(min(dist))]  # 寻找最近的立方体面中心的点距离
        add_dict(dictionary, nearest_centroid, p)


archive_nearest_pc_pairs(d, centroids, points)
print(centroids)


# 3种方法计算法向量
def compute_normals(pcd, flag=1):
    # 混合搜索  KNN搜索  半径搜索
    if (flag == 1):
        pcd.estimate_normals(
            search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=20))  # 计算法线,搜索半径1cm,只考虑邻域内的20个点
    elif (flag == 2):
        pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamKNN(knn=3))  # 计算法线,只考虑邻域内的20个点
    else:
        pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamRadius(radius=0.01))  # 计算法线,搜索半径1cm,只考虑邻域内的20个点


# 计算俩个法向量的夹角可参考 http://mp.ofweek.com/it/a456714148137
# x为第一个法向量,y为第二个法向量
def cal_angle(x, y):
    # 分别计算两个向量的模:
    l_x = np.sqrt(x.dot(x))
    l_y = np.sqrt(y.dot(y))
    # print('向量的模=', l_x, l_y)

    # 计算两个向量的点积
    dian = x.dot(y)
    # print('向量的点积=', dian)

    # 计算夹角的cos值:
    cos_ = dian / (l_x * l_y)
    # print('夹角的cos值=', cos_)

    # 求得夹角(弧度制):
    angle_hu = np.arccos(cos_)
    # print('夹角(弧度制)=', angle_hu)

    # 转换为角度值:
    angle_d = angle_hu * 180 / np.pi
    # print('夹角=%f°' % angle_d)
    return angle_d


# 初始化法向量dict,key中心点 values法向量点
normals_dict = {}
pcds = []
for i, (key, value) in enumerate(d.items()):
    # print(value)
    val = np.array(value)

    # 计算点云均值点,并插入到原点云点的第一个点
    avg_point = np.mean(val, axis=0)  # axis=0,计算每一列的均值
    origin_points = np.row_stack((avg_point, val))

    # 构造点云数据
    pcd = o3d.geometry.PointCloud()
    points = o3d.utility.Vector3dVector(origin_points)
    pcd.points = points

    # 计算法向量,可选择3种方法计算法向量,传值1/2/其他
    compute_normals(pcd, 2)
    normals = o3d.np.asarray(pcd.normals)
    # print('pcd-normals: ', normals)
    normals_dict[key] = normals
    print('第', str(i + 1), '个面, center:', key, len(pcd.normals), '个点')
    # print(np.sum(normals) / len(normals))

    # 均值点法向量点
    avg_normal = normals[0]

    # 遍历计算平均向量与点云向量的夹角(由于第一个点是均值点,所以去除)
    for j, (point, point_normal) in enumerate(zip(origin_points[1:], normals[1:])):
        # 计算均值点法向量 与 点云点法向量的夹角
        print('\t第 %s 个点, angle: %s' % (
            str(j + 1),
            cal_angle(np.array(avg_point) - np.array(avg_normal), np.array(point) - np.array(point_normal))))
    print("--------------------------------------------------------")

    # 可视化法向量点和原始点
    pcd.paint_uniform_color([0.5, 0.5, 0.5])  # 把原始点渲染为灰色
    pcd.colors[0] = [1, 0, 0]  # 原始点云均值点渲染为红色
    normal_point = o3d.utility.Vector3dVector(pcd.normals)
    normals = o3d.geometry.PointCloud()
    normals.points = normal_point
    normals.paint_uniform_color((0, 1, 0))  # 点云法向量的点都以绿色显示
    normals.colors[0] = [1, 1, 0]  # 均值点法向量点渲染为黄色
    o3d.visualization.draw_geometries([pcd, normals], "Open3D origin " + str(i + 1) + " VS normals points True",
                                      width=800,
                                      height=600, left=50,
                                      top=50,
                                      point_show_normal=True, mesh_show_wireframe=False,
                                      mesh_show_back_face=False)
    o3d.visualization.draw_geometries([pcd, normals], "Open3D origin " + str(i + 1) + " VS normals points False",
                                      width=800,
                                      height=600, left=50,
                                      top=50,
                                      point_show_normal=False, mesh_show_wireframe=False,
                                      mesh_show_back_face=False)

    # 加入list以便全量渲染
    pcds.append(pcd)
    pcds.append(normals)

print(pcds[0], pcds[1], pcds[2], pcds[3], pcds[4], pcds[5],
      pcds[6], pcds[7], pcds[8], pcds[9], pcds[10], pcds[11],
      len(pcds))
# 同时可视化法向量
o3d.visualization.draw_geometries([pcds[0], pcds[1], pcds[2], pcds[3], pcds[4],
                                   pcds[5], pcds[6], pcds[7], pcds[8], pcds[9], pcds[10], pcds[11]
                                   ], "Open3D originAll VS normals points True",
                                  width=800,
                                  height=600, left=50,
                                  top=50,
                                  point_show_normal=True, mesh_show_wireframe=False,
                                  mesh_show_back_face=False)

# 不可视化法向量
o3d.visualization.draw_geometries([pcds[0], pcds[1], pcds[2], pcds[3], pcds[4],
                                   pcds[5], pcds[6], pcds[7], pcds[8], pcds[9],
                                   pcds[10], pcds[11]], "Open3D originAll VS normals points False",
                                  width=800,
                                  height=600, left=50,
                                  top=50,
                                  point_show_normal=False, mesh_show_wireframe=False,
                                  mesh_show_back_face=False)

参考

  • 计算俩个法向量的夹角可参考 http://mp.ofweek.com/it/a456714148137

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

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

相关文章

LaTeX学习笔记

LaTeX学习笔记 文章目录LaTeX学习笔记1. 开始的尝试2.文档类与宏包3.标题与章节4.标注5.列表6.对齐7.插入代码块8.绘制表格9.插入图片10.数学公式10.1.基础公式10.2.复杂公式10.3 常用符号11.参考文献冲鸭&#xff01;&#xff01;&#xff01; 1. 开始的尝试 先开始试一下一个…

MySQL数据库索引和事务详解

目录 前言&#xff1a; 索引 查看索引 创建索引 删除索引 索引使用 底层数据结构分析 事务 事务引出 MySQL设计事务 事务四大特性 小结&#xff1a; 前言&#xff1a; 数据库索引和事务的存在&#xff0c;对于数据库的一些性能有了显著提升。我们需掌握其底层的实现…

NUMA那些事儿

NUMA——Non Uniform Memory Access&#xff0c;中文为非统一内存访问&#xff0c;在NUMA出现之前&#xff0c;内存的控制器是包含在北桥芯片中的&#xff0c;所有内存由北桥统一管理&#xff0c;因此可以保证访问内存的一致性。随着CPU架构的不断迭代和演进&#xff0c;核数越…

Elasticsearch与Kibana安装

现有环境 windows docker ubuntu Elasticsearch安装 安装包下载 ES不同平台、版本下载路径&#xff1a;Download Elasticsearch | Elastic 本文演示用linux # 启动ubuntu环境&#xff0c;开放端口9200、9300、5601 docker run -name es -p 9200:9200 -p 9300:9300 -p 5…

指夹式血氧饱和检测仪方案分析

指夹式心率血氧饱和度方案的测量原理是根据血红蛋白(Hb)和氧合血红蛋白 (HbO2)在红光和近红光区域的吸收光谱特性为依据&#xff0c;运用Lambert Beer定律建立数据处理经验公式&#xff0c;采用光电血氧检测技术结合光电容积脉搏波描记&#xff08;PPG&#xff09;技术&#xf…

化工制造行业数字化升级案例—基于HK-Domo商业智能分析工具

案例背景导读 世伟洛克&#xff08;Swagelok&#xff09;是全球领先的流体系统解决方案的开发商和制造商&#xff0c;为包括科研、仪表、制药、油气、电力、石化、代用燃料和半导体等在内的各个行业提供产品、组装和服务。世伟洛克通过独立的销售和服务中心网站进行运营&#x…

使用 Typescript 封装 Axios

对 axios 二次封装,更加的可配置化、扩展性更加强大灵活 通过 class 类实现&#xff0c;class 具备更强封装性(封装、继承、多态)&#xff0c;通过实例化类传入自定义的配置 创建 class 严格要求实例化时传入的配置&#xff0c;拥有更好的代码提示 /*** param {AxiosInstance…

C语言习题练习8--二进制操作符

IO型--从main函数开始写&#xff0c;要写输入、计算、输出 接口型--不需要写主函数&#xff0c;默认主函数是存在的&#xff0c;你只需要完成函数就行 一、二进制中1的个数 (12条消息) C语言丨关键字signed和unsigned 的使用与区别详解_Emily-C的博客-CSDN博客_signed unsi…

【笔记】samba shell 脚本 离线安装 - Ubuntu 20.04

前言 按照官网调试代码、网上各种步骤来走&#xff08;还收费&#xff09;都不行 结果发现是防火墙问题 公司服务器安装的ufw使用失效&#xff0c;导致端口号放行添加失败 换用firewall-cmd成功 现在免费放下代码&#xff0c;气死他们收费的 目录 ├── home│ ├── k…

linux备份mysql8.0数据库脚本

文章目录环境要求步骤1、创建一个.sh文件编写shell脚本2、添加定时任务环境要求 linux系统&#xff0c;安装了mysql8.0 步骤 1、创建一个.sh文件编写shell脚本 创建文件的命令&#xff1a; vim ***.shshell文件文件参考自文章 链接 export LANGen_US.UTF-8 #注意&#xf…

测试开发技术:Python测试框架Pytest的基础入门

Pytest简介 Pytest is a mature full-featured Python testing tool that helps you write better programs.The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. 通过官方网站介绍…

十五、Lua 协同程序(coroutine)的学习

Lua 协同程序(coroutine) 什么是协同(coroutine)&#xff1f; Lua 协同程序(coroutine)与线程比较类似&#xff1a;拥有独立的堆栈&#xff0c;独立的局部变量&#xff0c;独立的指令指针&#xff0c;同时又与其它协同程序共享全局变量和其它大部分东西。 协同是非常强大的功…

2646-61-9, 脯氨酰内肽酶(PEP)底物: Z-GPLGP-OH

编号: 160473中文名称: 脯氨酰内肽酶&#xff08;PEP&#xff09;底物&#xff1a;Z-Gly-Pro-Leu-Gly-ProCAS号: 2646-61-9单字母: Z-GPLGP-OH三字母: Cbz-Gly-Pro-Leu-Gly-Pro-COOH氨基酸个数: 5分子式: C28H39O8N5平均分子量: 573.64精确分子量: 573.28等电点(PI): -pH7.0时的…

Arduino程序设计(三) 光照采集 + 温度采集

光照采集 温度采集前言一、光敏电阻检测环境光二、DS18B20检测环境温度总结参考文献前言 本文主要介绍两种常见的传感器采集环境参数&#xff0c;即光照传感器和温度传感器。光照传感器采用光敏电阻GL3516&#xff08;5-10K&#xff09;检测环境光。温度传感器采用DS18B20检测…

2022Q3家电行业高增长细分市场分析(含热门品类数据)

2022年&#xff0c;在大环境的影响下&#xff0c;大众消费偏好更趋于理性化、追求高性价比&#xff0c;不少行业增速有所放缓&#xff0c;在此背景下&#xff0c;2022年Q3季度中&#xff0c;消费市场中仍有一些高增长概念涌现。 在家电行业中&#xff0c;我们发现了3个高增长品…

【重识云原生】第六章容器基础6.4.9.5节——端点切片(Endpoint Slices)

1 EndpointSlice特性 Kubernetes v1.21 [stable] 端点切片&#xff08;EndpointSlices&#xff09; 是一个新 API&#xff0c;它提供了 Endpoint API 可伸缩和可拓展的替代方案。EndpointSlice 会跟踪 Service Pod 的 IP 地址、端口、readiness 和拓扑信息。 在 Kubernetes v…

一文看懂页面置换算法

页面置换算法分为两类 1、局部页面置换算法 最优页面置换算法&#xff08;OPT、optimal&#xff09;先进先出算法&#xff08;FIFO&#xff09;最近最久未使用算法&#xff08;LRU,Least Recently Used&#xff09;时钟页面置换算法&#xff08;Clock&#xff09;最不常用算法…

【算法 | 实验18】在字符矩阵中查找给定字符串的所有匹配项

文章目录题目描述思路分析bug记录&#xff1a;"error: >> should be > > within a nested template argument list"代码题目描述 题目 在字符矩阵中查找给定字符串的所有匹配项 给定一个MN字符矩阵&#xff0c;以及一个字符串S&#xff0c;找到在矩阵中所…

给Git仓库添加.gitignore:清理、删除、排除被Git误添加的临时文件

文章目录一、前言二、发现提交的临时文件三、去掉临时文件的方法3.1 添加.gitignore3.2 删除临时文件缓存3.3 添加后的效果一、前言 最近维护代码过程中&#xff0c;发现某APP代码库里被提交了许多临时文件&#xff0c;而这些临时文件每次都会变化&#xff0c;所以导致每次修改…

为什么要写单测

一、什么是单元测试 “在计算机编程中&#xff0c;单元测试又称为模块测试&#xff0c;是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中&#xff0c;一个单元就是单个程序、函数、过程等&#xff1b;对于面向对象编程&#xff0c;最…