【点云处理教程】02从 Python 中的深度图像估计点云

news2024/9/22 12:13:10

一、说明

        这是“点云处理”教程的第二篇文章。“点云处理”教程对初学者友好,我们将在其中简单地介绍从数据准备到数据分割和分类的点云处理管道。在本教程中,我们将学习如何在不使用 Open3D 库的情况下从深度图像计算点云。我们还将展示如何优化代码以获得更好的性能。
 
  • 第1条:点云处理简介
  • 文章2:在Python中从深度图像估计点云
  • 文章3:了解点云:使用Python实现地面检测
  • 文章4:Python中的点云过滤
  • 文章 5 : Python 中的点云分割

二. 深度图像

        深度图像(也称为深度图)是每个像素提供其相对于传感器坐标系的距离值的图像。深度图像可以通过结构光或飞行时间传感器捕获。为了计算深度数据,结构光传感器(如 Microsoft Kinect V1)会比较投影光和接收光之间的失真。至于像Kinect V2 Microsoft这样的飞行时间传感器,它们投射光线,然后计算投影和接收这些光线之间的时间。

        除了深度图像外,一些传感器还提供相应的RGB图像以形成RGB-D图像。后者使得计算彩色点云成为可能。本教程将使用Microsoft Kinect V1 RGB-D 图像作为示例。

        让我们从导入库开始:

import imageio.v3 as iio
import numpy as np
import matplotlib.pyplot as plt
import open3d as o3d

        现在,我们可以导入深度图像并打印其分辨率和类型:

# Read depth image:
depth_image = iio.imread('data/depth.png')

# print properties:
print(f"Image resolution: {depth_image.shape}")
print(f"Data type: {depth_image.dtype}")
print(f"Min value: {np.min(depth_image)}")
print(f"Max value: {np.max(depth_image)}")

Image resolution: (480, 640)
Data type: int32
Min value: 0
Max value: 2980

        深度图像是大小为 640×480 的矩阵,其中每个像素是一个 32(或 16)位整数,表示以毫米为单位的距离,因此深度图像在打开时显示为黑色(见下图)。最小值 0 表示噪点(没有距离),而最大值 2980 表示最远像素的距离。

        由 Microsoft Kinect V1 生成的深度图像。

为了获得更好的可视化效果,我们计算其灰度图像:

depth_instensity = np.array(256 * depth_image / 0x0fff, dtype=np.uint8)
iio.imwrite('output/grayscale.png', depth_instensity)

        计算灰度图像意味着将深度值缩放到 。现在图像更清晰了:[0, 255]

计算出的灰度图像。黑色像素表示噪点。

请注意,Matplotlib 在可视化深度图像时也会做同样的事情:

# Display depth and grayscale image:
fig, axs = plt.subplots(1, 2)
axs[0].imshow(depth_image, cmap="gray")
axs[0].set_title('Depth image')
axs[1].imshow(depth_grayscale, cmap="gray")
axs[1].set_title('Depth grayscale image')
plt.show()

Matplotlib自动缩放深度图像的像素。

三. 点云

        现在我们已经导入并显示了深度图像,我们如何从中估计点云?第一步是校准深度相机以估计相机矩阵,然后使用它来计算点云。获得的点云也称为2.5D点云,因为它是根据2D投影(深度图像)而不是激光传感器等3D传感器估计的。

3.2 深度相机校准

        校准相机意味着通过查找失真系数和相机矩阵(也称为固有参数)来估计镜头和传感器参数。一般来说,校准相机有三种方法:使用工厂提供的标准参数、使用校准研究中获得的结果或手动校准 Kinect。手动校准相机包括应用一种校准算法,例如棋盘算法[1]。该算法在机器人操作系统(ROS)和OpenCV中实现。校准矩阵 M 是一个 3×3 矩阵:

        其中fx,fy和cx,cy分别是焦距和光学中心。在本教程中,我们将使用获得的纽约大学深度 V2 数据集的结果:

# Depth camera parameters:
FX_DEPTH = 5.8262448167737955e+02
FY_DEPTH = 5.8269103270988637e+02
CX_DEPTH = 3.1304475870804731e+02
CY_DEPTH = 2.3844389626620386e+02

        如果您想自己校准相机,可以参考此OpenCV教程。

3.3 点云计算

        这里的计算点云是指将深度像素从深度图像2D坐标系转换为深度相机3D坐标系(x,y和z)。3D 坐标使用以下公式 [2] 计算,其中 depth(i, j) 是行 i 和列 j 处的深度值:

该公式应用于每个像素:

# compute point cloud:
pcd = []
height, width = depth_image.shape
for i in range(height):
   for j in range(width):
       z = depth_image[i][j]
       x = (j - CX_DEPTH) * z / FX_DEPTH
       y = (i - CY_DEPTH) * z / FY_DEPTH
       pcd.append([x, y, z])

让我们使用 Open3D 库显示它:

pcd_o3d = o3d.geometry.PointCloud()  # create point cloud object
pcd_o3d.points = o3d.utility.Vector3dVector(pcd)  # set pcd_np as the point cloud points
# Visualize:
o3d.visualization.draw_geometries([pcd_o3d])

从深度图像计算出的点云。

四. 彩色点云

        如果我们想从RGB-D图像中计算彩色点云怎么办?颜色信息可以增强许多任务(如点云配准)的性能。在这种情况下,如果输入传感器也提供RGB图像,则最好使用它。彩色点云可以定义如下:

        其中 x、y 和 z 是 3D 坐标,r、g 和 b 表示 RGB 系统中的颜色。

        我们首先导入上一个深度图像的相应 RGB 图像:

# Read the rgb image:
rgb_image = iio.imread('../data/rgb.jpg')

# Display depth and grayscale image:
fig, axs = plt.subplots(1, 2)
axs[0].imshow(depth_image, cmap="gray")
axs[0].set_title('Depth image')
axs[1].imshow(rgb_image)
axs[1].set_title('RGB image')
plt.show()

        深度图像及其对应的 RGB 图像

        要查找深度传感器 3D 坐标系中定义的给定点 p(x, y,z) 的颜色,请执行以下操作:

1. 我们将其转换为 RGB 相机坐标系 [2]:

        其中 R 和 T 是两个相机之间的外在参数:分别是旋转矩阵和平移矢量。

        同样,我们使用来自纽约大学深度V2数据集的参数:

# Rotation matrix:
R = -np.array([[9.9997798940829263e-01, 5.0518419386157446e-03, 4.3011152014118693e-03],
                   [-5.0359919480810989e-03, 9.9998051861143999e-01, -3.6879781309514218e-03],
                   [- 4.3196624923060242e-03, 3.6662365748484798e-03, 9.9998394948385538e-01]])
# Translation vector:
T = np.array([2.5031875059141302e-02, -2.9342312935846411e-04, 6.6238747008330102e-04])

        RGB 照相机坐标系中的点计算方法如下:

"""
  Convert the point from depth sensor 3D coordinate system
  to rgb camera coordinate system:
"""
[x_RGB, y_RGB, z_RGB] = np.linalg.inv(R).dot([x, y, z]) - np.linalg.inv(R).dot(T)

2. 使用 RGB 相机的固有参数,我们将其映射到彩色图像坐标系 [2]:

这些是获取颜色像素的索引。

请注意,在前面的公式中,焦距和光学中心是RGB相机参数。同样,我们使用来自纽约大学深度V2数据集的参数:

# RGB camera intrinsic Parameters:
FX_RGB = 5.1885790117450188e+02
FY_RGB = 5.1946961112127485e+02
CX_RGB = 3.2558244941119034e+0
CY_RGB = 2.5373616633400465e+02

        对应像素的指数计算如下:

"""
  Convert from rgb camera coordinate system
  to rgb image coordinate system:
"""
j_rgb = int((x_RGB * FX_RGB) / z_RGB + CX_RGB + width / 2)
i_rgb = int((y_RGB * FY_RGB) / z_RGB + CY_RGB)

        让我们把所有东西放在一起,显示点云:

colors = []
pcd = []
for i in range(height):
    for j in range(width):
        """
            Convert the pixel from depth coordinate system
            to depth sensor 3D coordinate system
        """
        z = depth_image[i][j]
        x = (j - CX_DEPTH) * z / FX_DEPTH
        y = (i - CY_DEPTH) * z / FY_DEPTH

        """
            Convert the point from depth sensor 3D coordinate system
            to rgb camera coordinate system:
        """
        [x_RGB, y_RGB, z_RGB] = np.linalg.inv(R).dot([x, y, z]) - np.linalg.inv(R).dot(T)

        """
            Convert from rgb camera coordinates system
            to rgb image coordinates system:
        """
        j_rgb = int((x_RGB * FX_RGB) / z_RGB + CX_RGB + width / 2)
        i_rgb = int((y_RGB * FY_RGB) / z_RGB + CY_RGB)

        # Add point to point cloud:
        pcd.append([x, y, z])

        # Add the color of the pixel if it exists:
        if 0 <= j_rgb < width and 0 <= i_rgb < height:
            colors.append(rgb_image[i_rgb][j_rgb] / 255)
        else:
            colors.append([0., 0., 0.])
            
# Convert to Open3D.PointCLoud:
pcd_o3d = o3d.geometry.PointCloud()  # create a point cloud object
pcd_o3d.points = o3d.utility.Vector3dVector(pcd)
pcd_o3d.colors = o3d.utility.Vector3dVector(colors)
# Visualize:
o3d.visualization.draw_geometries([pcd_o3d])

从RGB-D图像计算出的彩色点云

五、代码优化

在本节中,我们将介绍如何优化代码,使其更高效并适合实时应用程序。

5.1 点云

使用嵌套循环计算点云非常耗时。对于分辨率为 480×640 的深度图像,在具有 8GB RAM 和 i7-4500 CPU 的机器上,计算点云大约需要 2.154 秒

为了减少计算时间,嵌套循环可以用矢量化操作代替,计算时间可以减少到0.024秒左右:

# get depth resolution:
height, width = depth_im.shape
length = height * width
# compute indices:
jj = np.tile(range(width), height)
ii = np.repeat(range(height), width)
# rechape depth image
z = depth_im.reshape(length)
# compute pcd:
pcd = np.dstack([(ii - CX_DEPTH) * z / FX_DEPTH,
                 (jj - CY_DEPTH) * z / FY_DEPTH,
                 z]).reshape((length, 3))

        我们还可以通过在开始时计算一次常数来将计算时间减少到大约 0.015 秒

# compute indices:
jj = np.tile(range(width), height)
ii = np.repeat(range(height), width)
# Compute constants:
xx = (jj - CX_DEPTH) / FX_DEPTH
yy = (ii - CY_DEPTH) / FY_DEPTH
# transform depth image to vector of z:
length = height * width
z = depth_image.reshape(height * width)
# compute point cloud
pcd = np.dstack((xx * z, yy * z, z)).reshape((length, 3))

4.2 彩色点云

        至于彩色点云,在同一台机器上,执行前面的示例大约需要 36.263 秒。通过应用矢量化,运行时间减少到 0.722 秒

# compute indices:
jj = np.tile(range(width), height)
ii = np.repeat(range(height), width)

# Compute constants:
xx = (jj - CX_DEPTH) / FX_DEPTH
yy = (ii - CY_DEPTH) / FY_DEPTH

# transform depth image to vector of z:
length = height * width
z = depth_image.reshape(length)

# compute point cloud
pcd = np.dstack((xx * z, yy * z, z)).reshape((length, 3))
cam_RGB = np.apply_along_axis(np.linalg.inv(R).dot, 1, pcd) - np.linalg.inv(R).dot(T)
xx_rgb = ((cam_RGB[:, 0] * FX_RGB) / cam_RGB[:, 2] + CX_RGB + width / 2).astype(int).clip(0, width - 1)
yy_rgb = ((cam_RGB[:, 1] * FY_RGB) / cam_RGB[:, 2] + CY_RGB).astype(int).clip(0, height - 1)
colors = rgb_image[yy_rgb, xx_rgb]/255

六. 结论

        在本教程中,我们学习了如何从 RGB-D 数据计算点云。在下一个教程中,我们将以一个简单的地面检测为例,仔细分析点云。

谢谢,我希望你喜欢阅读这篇文章。您可以在我的 GitHub 存储库中找到示例。

引用

[1] Zhang, S., & Huang, P. S. (2006).结构光系统校准的新方法。光学工程, 45(8), 083601.

[2] 周旭, (2012).Microsoft Kinect 校准的研究。费尔法克斯乔治梅森大学计算机科学系

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

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

相关文章

调整数组顺序使奇数位于偶数前面——剑指 Offer 21

文章目录 题目描述法一 两次遍历法二 双指针一次遍历法三 原地交换 题目描述 法一 两次遍历 class Solution{ public:vectro<int> exchange(vector<int>& nums){vector<int> res;for(auto & num : nums){if(num%21){res.push_back(num);}}for(auto &…

Java常用API:StringBuilder、StringBuffer、StringJoiner

StringBuilder的几个常用的API StringBuilder s new StringBuilder("111");//111 //1.拼接内容 s.append(12); s.append("11"); s.append(true);//2.支持链式编程 s.append(12).append("11").append(true);//输出是1211true //3.反转操作 s.reve…

c++[左值+右值](25)

左值 对左值取别名 左值举例 int main() {//左值&#xff1a;可以取地址int a 10;const int b 20;int* p &a; //a&#xff0c;b&#xff0c;p均是左值*p 100; //*p也是左值return 0; }左值引用举例 int main() {//以下 p、b、c、*p都是…

《向量数据库指南》:向量数据库Pinecone如何集成Elasticsearch

目录 上传嵌入模型 上传数据集 创建嵌入 将Elasticsearch索引移动到Pinecone 概要 是一个强大的开源搜索引擎和分析平台,广泛用作基于关键字的文本搜索的文档存储。 Pinecone是一个广泛用于生产应用程序的向量数据库,例如语义搜索、推荐系统和威胁检测,需要在数亿甚…

【多模态】20、OVR-CNN | 使用 caption 来实现开放词汇目标检测

文章目录 一、背景二、方法2.1 学习 视觉-语义 空间2.2 学习开放词汇目标检测 三、效果 论文&#xff1a;Open-Vocabulary Object Detection Using Captions 代码&#xff1a;https://github.com/alirezazareian/ovr-cnn 出处&#xff1a;CVPR2021 Oral 一、背景 目标检测数…

关于rtthread的I/O设备模型

分层 从官方给的架构可以看出&#xff0c;官方是将IO设备的使用分成了三层。 IO设备管理层&#xff1a;也就是正常用户接触的一层&#xff0c;用户直接调用该层接口实现IO设备的操作。例如代码中的device.c文件 设备驱动框架层&#xff1a;这一层可以称为中间层了&#xff0c…

Spring之BeanDefinition(三)

Spring之BeanDefinition&#xff08;三&#xff09; 文章目录 Spring之BeanDefinition&#xff08;三&#xff09;一、Spring的启动类三行代码研究二、Spring创建工厂类型和属性三、Spring中内置的BeanDefinition四、注册配置类五、BeanDefinition总结 一、Spring的启动类三行代…

Few Shot Classification小知识——数据集的加载

概述 Few-shot classification&#xff08;小样本分类&#xff09;是机器学习和人工智能的一个子领域&#xff0c;解决的问题是在训练数据非常有限的情况下&#xff0c;学习对新样本进行分类。在传统的监督学习中&#xff0c;模型需要在包含大量标记样本的数据集上进行训练&am…

Linux安装wget

1.第一步登录wget官网下载地址&#xff0c;下载最新的wget的rpm安装包到本地 官网地址&#xff1a;http://mirrors.163.com/centos/7/os/x86_64/Packages/ 2.将下载好的wget的rpm安装包通过Xftp工具上传到Linux服务器对应目录下。 3.cd命令进入到这个wget目录下&#xff0c;再…

【文献分享】动态环境下竟然能实现实时语义RGB-D SLAM??

论文题目&#xff1a;Towards Real-time Semantic RGB-D SLAM in Dynamic Environments 中文题目&#xff1a;动态环境下实时语义RGB-D SLAM研究 作者&#xff1a;Tete Ji, Chen Wang, and Lihua Xie 作者机构&#xff1a;新加坡南洋理工大学电气与电子工程学院 卡内基梅隆大…

用于WINDOWS的HACKRF ONE扫频分析仪

https://github.com/pavsa/hackrf-spectrum-analyzer GitHub - mutability/rtl-sdr: RTL-SDR *very* experimental branch - its probably broken! https://github.com/greatscottgadgets/hackrf hackrf_sweep 用于WINDOWS的HACKRF ONE扫频分析仪 几个星期前&#xff0c;Ha…

Java动态代理(全网最详细,没有之一)

首先你要明白为什么要创建代理&#xff1f;&#xff1f;&#xff1f; 例如&#xff1a;我们看下面这张图我们发现&#xff0c;有很多重复的代码&#xff0c;我们就可以创建代理&#xff0c;让代理帮我们干这些事情。 1.想要创建代理&#xff0c;我们就要为这个类写一个接口 pu…

无涯教程-jQuery - Menu组件函数

小部件菜单功能可与JqueryUI中的小部件一起使用。一个简单的菜单显示项目列表。 Menu - 语法 $( "#menu" ).menu(); Menu - 示例 以下是显示菜单用法的简单示例- <!doctype html> <html lang"en"><head><meta charset"utf-…

基于Linux操作系统中的MySQL数据库备份(三十三)

目录 一、概述 二、数据备份的重要性 三、造成数据丢失的原因 1、程序错误 2、人为错误 3、运算失败 4、磁盘故障 5、灾难&#xff08;如火灾、地震&#xff09;和盗窃 四、备份类型 &#xff08;一&#xff09;物理与逻辑角度 1、物理备份 1.1、冷备份 1.2、热备…

人工智能-Dlib+Python实现人脸识别(人脸识别篇)

人脸识别流程 人脸检测,人脸数据提取:首先是检测到人脸保存人脸数据:可以保存到mysql数据库中mysql数据库连接mysql数据库安装mysql数据库操作设置人脸数据标签:(人脸名字),保存到数据库打开摄像头,检测到人脸,提取人脸数据:人脸数据与数据库中的数据对比,1、人脸检…

子组件未抛出事件 父组件如何通过$refs监听子组件中数据的变化

我们平时开发项目会使用一些比较成熟的组件库, 但是在极小的情况下,可能会出现我们需要监听某个属性的变化,使我们的页面根据这个属性发生一些改变,但是偏偏组件库没有把这个属性抛出来,当我们使用watch通过refs监听时,由于生命周期的原因还不能拿到,这时候我们可以这样做,以下…

03-高阶导数_导数判断单调性_导数与极值

高阶导数 前面学的是一阶导数&#xff0c;对导数再次求导就是高阶导数&#xff0c;二阶和二阶以上的导数统称为高阶导数。 导数与函数单调性的关系 极值定理 导数为我们寻找极值提供依据&#xff0c;对于可导函数而言&#xff0c;因为在极值位置必然有函数的导数等于 0。 …

深入篇【C++】手搓模拟实现list类(详细剖析底层实现原理)模拟实现正反向迭代器【容器适配器模式】

深入篇【C】手搓模拟实现list类(详细剖析底层实现原理&#xff09;&& 模拟实现正反向迭代器【容器适配器模式】 Ⅰ.迭代器实现1.一个模板参数2.两个模板参数3.三个模板参数 Ⅱ.反向迭代器实现1.容器适配器模式 Ⅲ.list模拟实现1.定义结点2.封装结点3.构造/拷贝4.迭代器…

【Python】Web学习笔记_flask(1)——模拟登录

安装flask pip3 install flask 第一部分内容&#xff1a; 1、主页面输出hello world 2、根据不同用户名参数输出用户信息 3、模拟登录 from flask import Flask,url_for,redirectappFlask(__name__)app.route(/) def index():return hello worldapp.route(/user/<uname…

linux_进程状态

目录 一. 概念铺设 状态是什么&#xff1f; 传统操作系统的状态转换图 二. 传统操作系统状态 1. 运行 2. 阻塞 3. 挂起 三. linux 中的进程状态 1. 总体介绍 2. R 3. S 4. D kill -9 D vs S 5. T kill T vs S 6. Z 什么是僵尸状态&#xff1f; 僵尸进程的危害 …