【前言】
上回我們說到深度圖的主要用途,那接著就讓我們搭配 open3D 該 Library 將深度圖轉為 Point Cloud 的 3D 圖吧。
【重點整理】
- 由於深度圖的特性,遠的背景容易被忽略掉
- 承上列原因,該方法不適合有前景的圖片
- Point Cloud 本身是由像素點在 3D 空間排列,因此不同角度可能有破碎化的結果
【預計內容】
- 將上次的程式碼建立副本
- 安裝 open3D
- 將深度圖轉 Point Cloud
- 成品展示
【主要內容】
1. 建立程式碼副本
若你還保存著上回使用 MiDaS 模型的範例程式碼,可以嘗試建立副本來修改,若沒有的讀者也可以透過下方直接複製程式碼下來。
2. 安裝 o3d
- 在終端機輸入或 ipynb 新增 pip install open3d
3. 將深度圖轉 Point Cloud
import open3d as o3d
import cv2
import torch
import numpy as np
# Load the BGR image
image = cv2.imread('demo.png')
# Turn it into RGB
imageRGB = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Load the MiDaS Model and decide if use GPU or CPU
midas = torch.hub.load("intel-isl/MiDaS", "DPT_Large")
device = torch.device(
"cuda") if torch.cuda.is_available() else torch.device("cpu")
midas.to(device)
midas.eval()
# Transform the image for input to MiDaS
midas_transforms = torch.hub.load("intel-isl/MiDaS", "transforms")
transform = midas_transforms.dpt_transform if hasattr(
midas_transforms, 'dpt_transform') else midas_transforms.default_transform
input_batch = transform(imageRGB).to(device)
# Predict depth map
with torch.no_grad():
prediction = midas(input_batch)
prediction = torch.nn.functional.interpolate(prediction.unsqueeze(
1), size=imageRGB.shape[:2], mode="bicubic", align_corners=False).squeeze()
depth_map = prediction.cpu().numpy()
# Convert depth map to 3D point cloud
# -- Focal Length
fx = 500
fy = 500
# -- Principal Point
cx = imageRGB.shape[1] / 2
cy = imageRGB.shape[0] / 2
# Generate 3D point cloud from depth map
meshX, meshY = np.meshgrid(
np.arange(depth_map.shape[1]), np.arange(depth_map.shape[0]))
x = ((meshX - cx) * depth_map) / fx
y = ((meshY - cy) * depth_map) / fy
z = depth_map
# Create Open3D point cloud with colors from original RGB image
pointCloud = o3d.geometry.PointCloud()
pointCloud.points = o3d.utility.Vector3dVector(
np.dstack((y, x, z)).reshape(-1, 3))
pointCloud.colors = o3d.utility.Vector3dVector(imageRGB.reshape(-1, 3) / 255.0)
# [IF Needed]
# Rotate the point cloud
# * A pi(π) represents 180 degrees
rotation = pointCloud.get_rotation_matrix_from_xyz(
(0, 0, -(np.pi/2)))
pointCloud.rotate(rotation, center=(0, 0, 0))
# Visualize point cloud
o3d.visualization.draw_geometries([pointCloud])
在過程中,我們透過 meshgrid 分出 x 與 y 的座標陣列,與中心點對齊位置,最後再新增代表深度的 z 座標。新增完立體模型後,再利用原圖的顏色進行調色的動作,最終重現立體 RGB 格式的 point cloud。
但在第 50 行,由於 numpy.shape 回傳值格式為(y, x, z),於是我在 dstack() 組合的過程先設定好。那如果你嘗試將這部分給換成(x, y, z),則會顯現水平對稱的結果,可見 4.3 之展示。
而在第 58~60 行,是用來旋轉圖形的 x, y, z 軸,有需要都可以自行調整。那我們知道 pi 代表 180 度,所以該範例中,為了正常顯示我分別轉了(0, 0, -90)度。
4. 成品展示
4.1 純場景
旋轉過程可以看到,所有的像素點幾乎是平滑的形成 3D 模型
4.2 角色前景
相對上方純場景圖,當加入前景後,因為深度圖會將遠方的背景省略,導致 3D 模型呈現的收束情況
4.3 dstack() 順序改變
由於 numpy.shape 的特性,若我們將 dstack() 順序改為(x, y, z)則會產生水平對稱的結果。
【後話】
老實說單就視覺而言,這種圖像呈現方式相對立體許多。至於能否直接圖片轉 3D 建模?實際上是已經有針對人形的機器學習模型,但泛用性少我就先不嘗試了。最後,我們在下一期將會利用深度圖進行去背處理,並講解其中明顯的優缺點。
【參考資料】
[1] open3D 套件官方文件
https://www.open3d.org/docs/release/introduction.html