草庐IT

基于Python深度图生成3D点云

Huterox 2023-04-13 原文

文章目录

前言

废话不多说,直接开造。这里的话我们有两个目标,第一个是如何把一个2维图片上的点映射到3维空间。第二就是如何生成3D点云。当然实际上这是一个大问题,因为只要解决了第一个问题,第二个问题就是“送分”

二维RGB图像

在说到3D点云之前我们就不得不说到RGB图像,也就是一些二维图像。

图像以像RGB三个通道的形式进行存储。也就是这样:

平时我们看到的就是左边的2D图像,实际上是以右边的形式存储的。

那么从我们的矩阵角度来看的话,大概是这个样子的:

[
[[r,g,b],[r,g,b],[r,g,b],[r,g,b]],
[[r,g,b],[r,g,b],[r,g,b],[r,g,b]],
...

]

这里面存在了两组信息,
第一组就是我们最容易忽略的位置信息,也就是像素点的位置。
第二组就是我们的色彩信息。

显然如果我们想要生成3D点云的话,我们的色彩信息是必要的。

而位置信息,在二维平面上,是在3为空间上的映射

那么要想得到3D点云,那么我们就必须得到完整的位置坐标,也就是x,y,z

成像原理

那么说到这里的话,我们就不得不说到,摄像机是如何把咱们的影像映射出来的了。

大概他是这样成像的:

这里咱们有三个东西需要注意一下。

首先是我们的摄像机位置
之后是相片的位置也就是中间那个
最后是我们物品实际位置

所以我们实际上的一个二维图像,就是一个投影,在实际上的话,我们可以理解为深度为1的空间。
但是这个投影

所以的话,用数学矩阵的形式表示为:


其中u,v为二维平面上的任意点坐标,1为深度,Zc就是咱们的二维平面上的点。

其中R,T是外参矩阵,并且我们的世界坐标原点就是相机的坐标原点
所以,R,T可以取下面的矩阵,(具体原理的话比较复杂,我也不是很懂为什么会这样,我回头再补补数学)

带入得到:

最后转换得到这个公式:

那么接下来咱们就对这个公式进行处理,来生成我们的点云

数据准备

前面通过公式我们可以了解到,一个二维图像,具备了RGB信息,还有对应的那个像素点在二维空间的“投影”。我们需要将这个点重新还原到三维空间。所以这里需要使用到深度信息,而这个是需要一些专业摄像机才有的,比如iphone的,或者机器人的:

有了这个深度图片,我们就可以还原坐标了。其实说到这儿,我们其实可以发现,RGB图片的话其实只是提供了一下色彩如果要还原为3D点云的话,因为深度图和对应的rgb的矩阵的位置是一一对应的。

图片加载

说到这里,我们就可以开始我们的正式编码了。

首先是咱们的图片加载阶段,这里的话主要是我们先需要一些信息,和读取图片。


    def __init__(self, rgb_file, depth_file, save_ply, camera_intrinsics=[784.0, 779.0, 649.0, 405.0]):
        self.rgb_file = rgb_file
        self.depth_file = depth_file
        self.save_ply = save_ply

        self.rgb = cv2.imread(rgb_file)
        self.depth = cv2.imread(self.depth_file, -1)

        print("your depth image shape is:",self.depth.shape)

        self.width = self.rgb.shape[1]
        self.height = self.rgb.shape[0]

        self.camera_intrinsics = camera_intrinsics
        self.depth_scale = 1000

下面的这个东西,就是咱们公式当中的u0,v0,dx,dy

camera_intrinsics=[784.0, 779.0, 649.0, 405.0]

这个要注意的是,这个玩意呢,是不同的设备相机有不同的参数,要根据自己的设置。

一般来说相机会给这样的矩阵:

我们对应起来就可以了。

之后是要注意的是这个深度图是uint16的,所以读取的时候加个-1

这里还有个东西:
深度图与比例因子(scale_factor)
在代码中是:

self.depth_scale = 1000

深度图对应的尺度因子是深度图中存储的值与真实深度(单位为m)的比例
通常情况下,深度值以毫米为单位存储在16位无符号整数中(0~65535),因此要获得以米为单位的z值,深度图的像素需要除以比例因子1000。不过不同相机的的比例因子可能不同,我这里的话是1000,这个看自己的实际的。

算法实现

之后就是咱们的算法实现了,这个主要是位置换算,得到真实的x,y,z


        depth = np.asarray(self.depth, dtype=np.uint16).T
        # depth[depth==65535]=0
        self.Z = depth / self.depth_scale
        fx, fy, cx, cy = self.camera_intrinsics

        X = np.zeros((self.width, self.height))
        Y = np.zeros((self.width, self.height))
        for i in range(self.width):
            X[i, :] = np.full(X.shape[1], i)

        self.X = ((X - cx / 2) * self.Z) / fx
        for i in range(self.height):
            Y[:, i] = np.full(Y.shape[0], i)
        self.Y = ((Y - cy / 2) * self.Z) / fy

之后,x,y,z就算好了。

注意的的是我们计算完成后得到的x,y , x 的每一组向量都是 宽×高

生成点云

现在咱们已经得到了具体的坐标。那么接下来是要生成点云的,我们要把剩下的色彩信息搞上去。
代码很简单:

    data_ply = np.zeros((6, self.width * self.height))
        data_ply[0] = self.X.T.reshape(-1)
        data_ply[1] = -self.Y.T.reshape(-1)
        data_ply[2] = -self.Z.T.reshape(-1)
        img = np.array(self.rgb, dtype=np.uint8)
        data_ply[3] = img[:, :, 0:1].reshape(-1)
        data_ply[4] = img[:, :, 1:2].reshape(-1)
        data_ply[5] = img[:, :, 2:3].reshape(-1)
        self.data_ply = data_ply
        t2 = time.time()
        print('calcualte 3d point cloud Done.', t2 - t1)

之后就是保存文件了。
目前点云的主要存储格式包括:pts、LAS、PCD、.xyz 和. pcap 等

例如:
pts 点云文件格式是最简便的点云格式,直接按 XYZ 顺序存储点云数据, 可以是整型或者浮点型。
LAS 是激光雷达数据(LiDAR),存储格式比 pts 复杂,旨在提供一种开放的格式标准,允许不同的硬件和软件提供商输出可互操作的统一格式。LAS 格式点云截图,其中 C:class(所属类),F:flight(航线号),T:time(GPS 时间),I:intensity(回波强度),R:return(第几次回波),N:number of return(回波次数),A:scan angle(扫描角),RGB:red green blue(RGB 颜色值)。

等等,我们这里的是ply
全名为多边形档案(Polygon File Format)或 史丹佛三角形档案(Stanford Triangle Format)。. 该格式主要用以储存立体扫描结果的三维数值,透过多边形片面的集合描述三维物体,与其他格式相较之下这是较为简单的方法。. 它可以储存的资讯包含颜色、透明度、表面法向量、材质座标与资料可信度,并能对多边形的正反两面设定不同的属性。

格式为

头部

顶点列表

面片列表

其他元素列表

       	start = time.time()
        float_formatter = lambda x: "%.4f" % x
        points = []
        for i in self.data_ply.T:
            points.append("{} {} {} {} {} {} 0\n".format
                          (float_formatter(i[0]), float_formatter(i[1]), float_formatter(i[2]),
                           int(i[3]), int(i[4]), int(i[5])))

        file = open(self.save_ply, "w")
        file.write('''ply
        format ascii 1.0
        element vertex %d
        property float x
        property float y
        property float z
        property uchar red
        property uchar green
        property uchar blue
        property uchar alpha
        end_header
        %s
        ''' % (len(points), "".join(points)))
        file.close()

        end = time.time()
        print("Write into .ply file Done.", end - start)

点云显示

这个的话可以使用软件

也可以使用open3d模块

完整代码

import cv2
import numpy as np
import open3d as o3d

import time


class point_cloud_generator():

    def __init__(self, rgb_file, depth_file, save_ply, camera_intrinsics=[784.0, 779.0, 649.0, 405.0]):
        self.rgb_file = rgb_file
        self.depth_file = depth_file
        self.save_ply = save_ply

        self.rgb = cv2.imread(rgb_file)
        self.depth = cv2.imread(self.depth_file, -1)

        print("your depth image shape is:",self.depth.shape)

        self.width = self.rgb.shape[1]
        self.height = self.rgb.shape[0]

        self.camera_intrinsics = camera_intrinsics
        self.depth_scale = 1000

    def compute(self):
        t1 = time.time()

        depth = np.asarray(self.depth, dtype=np.uint16).T
        # depth[depth==65535]=0
        self.Z = depth / self.depth_scale
        fx, fy, cx, cy = self.camera_intrinsics

        X = np.zeros((self.width, self.height))
        Y = np.zeros((self.width, self.height))
        for i in range(self.width):
            X[i, :] = np.full(X.shape[1], i)

        self.X = ((X - cx / 2) * self.Z) / fx
        for i in range(self.height):
            Y[:, i] = np.full(Y.shape[0], i)
        self.Y = ((Y - cy / 2) * self.Z) / fy

        data_ply = np.zeros((6, self.width * self.height))
        data_ply[0] = self.X.T.reshape(-1)
        data_ply[1] = -self.Y.T.reshape(-1)
        data_ply[2] = -self.Z.T.reshape(-1)
        img = np.array(self.rgb, dtype=np.uint8)
        data_ply[3] = img[:, :, 0:1].reshape(-1)
        data_ply[4] = img[:, :, 1:2].reshape(-1)
        data_ply[5] = img[:, :, 2:3].reshape(-1)
        self.data_ply = data_ply
        t2 = time.time()
        print('calcualte 3d point cloud Done.', t2 - t1)

    def write_ply(self):
        start = time.time()
        float_formatter = lambda x: "%.4f" % x
        points = []
        for i in self.data_ply.T:
            points.append("{} {} {} {} {} {} 0\n".format
                          (float_formatter(i[0]), float_formatter(i[1]), float_formatter(i[2]),
                           int(i[3]), int(i[4]), int(i[5])))

        file = open(self.save_ply, "w")
        file.write('''ply
        format ascii 1.0
        element vertex %d
        property float x
        property float y
        property float z
        property uchar red
        property uchar green
        property uchar blue
        property uchar alpha
        end_header
        %s
        ''' % (len(points), "".join(points)))
        file.close()

        end = time.time()
        print("Write into .ply file Done.", end - start)

    def show_point_cloud(self):
        pcd = o3d.io.read_point_cloud(self.save_ply)
        o3d.visualization.draw([pcd])


if __name__ == '__main__':
    camera_intrinsics = [378.998657, 378.639862, 321.935120, 240.766663]
    rgb_file = "data/1.jpg"
    depth_file = "data/1.png"
    save_ply = "data.ply"
    a = point_cloud_generator(rgb_file=rgb_file,
                              depth_file=depth_file,
                              save_ply=save_ply,
                              camera_intrinsics=camera_intrinsics
                              )
    a.compute()
    a.write_ply()
    a.show_point_cloud()
  


效果如下:

总结

这里的话其实还是从一张深度图到3d点云,实际上有时候我们可能只需一组坐标然后还原,那么这部分的话,要实现的话也不难,不过要重新做一点转换,公式还是那个公式。这里的话就不能在多说了,而且这里还有一点值得一提的是这个通过这种方式得到的3d点云其实怎么说呢,并不是特别准,所以这边还是要更加牛批的人工智能算法的,目前有个集成玩意可以玩玩的是middlepipe。这方面的话Google还是得是他。

有关基于Python深度图生成3D点云的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  4. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  5. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

    我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

  6. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  7. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  8. ruby-on-rails - 如何在 Rails 3 中创建自定义脚手架生成器? - 2

    有这些railscast。http://railscasts.com/episodes/218-making-generators-in-rails-3有了这个,你就会知道如何创建样式表和脚手架生成器。http://railscasts.com/episodes/216-generators-in-rails-3通过这个,您可以了解如何添加一些文件来修改脚手架View。我想把两者结合起来。我想创建一个生成器,它也可以创建脚手架View。有点像RyanBates漂亮的生成器或web_app_themegem(https://github.com/pilu/web-app-theme)。我

  9. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  10. 报告回顾丨模型进化狂飙,DetectGPT能否识别最新模型生成结果? - 2

    导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri

随机推荐