如何基于Python深度圖生成3D點云詳解
前言
廢話不多說,直接開造。這里的話我們有兩個目標,第一個是如何把一個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的空間。
但是這個投影
所以的話,用數(shù)學矩陣的形式表示為:
其中u,v為二維平面上的任意點坐標,1為深度,Zc就是咱們的二維平面上的點。
其中R,T是外參矩陣,并且我們的世界坐標原點就是相機的坐標原點
所以,R,T可以取下面的矩陣,(具體原理的話比較復雜,我也不是很懂為什么會這樣,我回頭再補補數(shù)學)
帶入得到:
最后轉換得到這個公式:
那么接下來咱們就對這個公式進行處理,來生成我們的點云
數(shù)據(jù)準備
前面通過公式我們可以了解到,一個二維圖像,具備了RGB信息,還有對應的那個像素點在二維空間的“投影”。我們需要將這個點重新還原到三維空間。所以這里需要使用到深度信息,而這個是需要一些專業(yè)攝像機才有的,比如iphone的,或者機器人的:
有了這個深度圖片,我們就可以還原坐標了。其實說到這兒,我們其實可以發(fā)現(xiàn),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]
這個要注意的是,這個玩意呢,是不同的設備相機有不同的參數(shù),要根據(jù)自己的設置。
一般來說相機會給這樣的矩陣:
我們對應起來就可以了。
之后是要注意的是這個深度圖是uint16的,所以讀取的時候加個-1
這里還有個東西:
深度圖與比例因子(scale_factor)
在代碼中是:
self.depth_scale = 1000
深度圖對應的尺度因子是深度圖中存儲的值與真實深度(單位為m)的比例
通常情況下,深度值以毫米為單位存儲在16位無符號整數(shù)中(0~65535),因此要獲得以米為單位的z值,深度圖的像素需要除以比例因子1000。不過不同相機的的比例因子可能不同,我這里的話是1000,這個看自己的實際的。
算法實現(xiàn)
之后就是咱們的算法實現(xiàn)了,這個主要是位置換算,得到真實的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 的每一組向量都是 寬×高
生成點云
現(xiàn)在咱們已經(jīng)得到了具體的坐標。那么接下來是要生成點云的,我們要把剩下的色彩信息搞上去。
代碼很簡單:
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 順序存儲點云數(shù)據(jù), 可以是整型或者浮點型。
LAS 是激光雷達數(shù)據(jù)(LiDAR),存儲格式比 pts 復雜,旨在提供一種開放的格式標準,允許不同的硬件和軟件提供商輸出可互操作的統(tǒng)一格式。LAS 格式點云截圖,其中 C:class(所屬類),F(xiàn):flight(航線號),T:time(GPS 時間),I:intensity(回波強度),R:return(第幾次回波),N:number of return(回波次數(shù)),A:scan angle(掃描角),RGB:red green blue(RGB 顏色值)。
等等,我們這里的是ply
全名為多邊形檔案(Polygon File Format)或 史丹佛三角形檔案(Stanford Triangle Format)。. 該格式主要用以儲存立體掃描結果的三維數(shù)值,透過多邊形片面的集合描述三維物體,與其他格式相較之下這是較為簡單的方法。. 它可以儲存的資訊包含顏色、透明度、表面法向量、材質座標與資料可信度,并能對多邊形的正反兩面設定不同的屬性。
格式為
頭部
頂點列表
面片列表
其他元素列表
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點云,實際上有時候我們可能只需一組坐標然后還原,那么這部分的話,要實現(xiàn)的話也不難,不過要重新做一點轉換,公式還是那個公式。這里的話就不能在多說了,而且這里還有一點值得一提的是這個通過這種方式得到的3d點云其實怎么說呢,并不是特別準,所以這邊還是要更加牛批的人工智能算法的,目前有個集成玩意可以玩玩的是middlepipe。這方面的話Google還是得是他。
到此這篇關于如何基于Python深度圖生成3D點云的文章就介紹到這了,更多相關Python深度圖生成3D點云內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
卸載tensorflow-cpu重裝tensorflow-gpu操作
這篇文章主要介紹了卸載tensorflow-cpu重裝tensorflow-gpu操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06