[Python] 圖像處理 – Depthmap 002 – 深度圖轉 Point Cloud

【前言】

上回我們說到深度圖的主要用途,那接著就讓我們搭配 open3D 該 Library 將深度圖轉為 Point Cloud 的 3D 圖吧。

【重點整理】

  1. 由於深度圖的特性,遠的背景容易被忽略掉
  2. 承上列原因,該方法不適合有前景的圖片
  3. Point Cloud 本身是由像素點在 3D 空間排列,因此不同角度可能有破碎化的結果

【預計內容】

  1. 將上次的程式碼建立副本
  2. 安裝 open3D
  3. 將深度圖轉 Point Cloud
  4. 成品展示

【主要內容】

1. 建立程式碼副本

若你還保存著上回使用 MiDaS 模型的範例程式碼,可以嘗試建立副本來修改,若沒有的讀者也可以透過下方直接複製程式碼下來。

2. 安裝 o3d

  1. 在終端機輸入或 ipynb 新增 pip install open3d

3. 將深度圖轉 Point Cloud

Python
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 模型呈現的收束情況

來源:[Pixiv] SHATTERED CONNEXION by SWAV
https://www.pixiv.net/artworks/76423975
4.3 dstack() 順序改變

由於 numpy.shape 的特性,若我們將 dstack() 順序改為(x, y, z)則會產生水平對稱的結果。

【後話】

老實說單就視覺而言,這種圖像呈現方式相對立體許多。至於能否直接圖片轉 3D 建模?實際上是已經有針對人形的機器學習模型,但泛用性少我就先不嘗試了。最後,我們在下一期將會利用深度圖進行去背處理,並講解其中明顯的優缺點。

【參考資料】

[1] open3D 套件官方文件
https://www.open3d.org/docs/release/introduction.html

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.