在本教程中,我們將學習如何在不使用 Open3D 庫的情況下從深度圖像計算點云。我們還將展示如何優化代碼以獲得更好的性能。
1. 深度圖像
深度圖像(也稱為深度圖)是一種圖像,其中每個像素提供相對于傳感器坐標系的距離值。深度圖像可以通過結構光或飛行時間傳感器捕獲。為了計算深度數據,結構光傳感器(例如 Microsoft Kinect V1)會比較投射光和接收光之間的變化。至于像微軟Kinect V2這樣的飛行時間傳感器,它們投射光線,然后計算從投射到隨后接收光線的時間間隔。
除了深度圖像外,一些傳感器還提供其對應的RGB圖像以形成rgb- D圖像。這使得計算彩色點云成為可能。本教程將以微軟Kinect V1 RGB-D圖像為例。
讓我們從導入Python/ target=_blank class=infotextkey>Python庫開始:
import imageio.v3 as iioimport numpy as npimport Matplotlib.pyplot as pltimport 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: int32Min value: 0Max 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.點云
現在我們已經導入并顯示了深度圖像,我們如何根據它估計點云呢?首先對深度相機進行標定,估計相機矩陣,然后用它來計算點云。得到的點云也被稱為2.5D點云,因為它是從 2D 投影(深度圖像)而不是 3D 傳感器(如激光傳感器)估計的。
2.1 深度相機標定
標定相機意味著通過尋找畸變系數和相機矩陣來估計鏡頭和傳感器參數。一般來說,標定相機有三種方法:使用工廠提供的標準參數,使用標定研究中獲得的結果或手動標定Kinect。手動標定包括標定算法,如棋盤格標定法。標定矩陣M是一個3×3矩陣:
其中fx、fy和cx、cy分別為焦距和光心。對于本教程,我們將使用NYU Depth V2數據集獲得的結果:
# Depth camera parameters:FX_DEPTH = 5.8262448167737955e+02FY_DEPTH = 5.8269103270988637e+02CX_DEPTH = 3.1304475870804731e+02CY_DEPTH = 2.3844389626620386e+02
2.2 點云計算
這里計算點云意味著將深度像素從深度圖像2D坐標系轉換到深度相機3D坐標系(x, y和z)。3D坐標使用以下公式計算,其中depth(i, j)為第i行和第j列處的深度值:
該公式適用于每個像素:
# compute point cloud:pcd = []height, width = depth_image.shapefor i in range(height):for j in range(width):z = depth_image[i][j]x = (j - CX_DEPTH) * z / FX_DEPTHy = (i - CY_DEPTH) * z / FY_DEPTHpcd.Append([x, y, z])
讓我們使用 Open3D 庫顯示它:
pcd_o3d = o3d.geometry.PointCloud() # create point cloud objectpcd_o3d.points = o3d.utility.Vector3dVector(pcd) # set pcd_np as the point cloud points# Visualize:o3d.visualization.draw_geometries([pcd_o3d])
從深度圖像計算的點云
3.彩色點云
如果我們想從RGB-D圖像中計算彩色點云怎么辦呢?顏色信息可以提高點云配準等許多任務的性能。彩色點云的定義如下:
其中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為兩個相機之間的外部參數:分別為旋轉矩陣和平移向量。
類似地,我們使用NYU Depth 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 systemto 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相機的固有參數,將其映射到彩色圖像坐標系
這些是獲取顏色像素的索引
注意,在前面的公式中,焦距和光心是RGB相機的參數。類似地,我們使用NYU Depth V2數據集的參數:
# RGB camera intrinsic Parameters:FX_RGB = 5.1885790117450188e+02FY_RGB = 5.1946961112127485e+02CX_RGB = 3.2558244941119034e+0CY_RGB = 2.5373616633400465e+02
對應像素的索引計算如下:
Convert from rgb camera coordinate systemto 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 systemto depth sensor 3D coordinate systemz = depth_image[i][j]x = (j - CX_DEPTH) * z / FX_DEPTHy = (i - CY_DEPTH) * z / FY_DEPTHConvert the point from depth sensor 3D coordinate systemto 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 systemto 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 objectpcd_o3d.points = o3d.utility.Vector3dVector(pcd)pcd_o3d.colors = o3d.utility.Vector3dVector(colors)# Visualize:o3d.visualization.draw_geometries([pcd_o3d])
從 RGB-D 圖像計算的彩色點云
4.代碼優化
在本節中,我們將解釋如何優化代碼,使其更高效,更適合實時應用程序。
4.1 點云
使用嵌套循環計算點云非常耗時。對于分辨率為480×640的深度圖像,在一臺擁有8GB RAM和i7-4500 CPU的機器上,計算點云大約需要2.154秒。
為了減少計算時間,可以用向量化操作取代嵌套循環,計算時間可減少至約0.024秒:
# get depth resolution:height, width = depth_im.shapelength = height * width# compute indices:jj = np.tile(range(width), height)ii = np.repeat(range(height), width)# rechape depth imagez = 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_DEPTHyy = (ii - CY_DEPTH) / FY_DEPTH# transform depth image to vector of z:length = height * widthz = depth_image.reshape(height * width)# compute point cloudpcd = 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_DEPTHyy = (ii - CY_DEPTH) / FY_DEPTH# transform depth image to vector of z:length = height * widthz = depth_image.reshape(length)# compute point cloudpcd = 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]
5. 結論
在本教程中,我們學習了如何從 RGB-D 數據來計算點云。