PyTorch3D

PyTorch3D

  • ドキュメント
  • チュートリアル
  • API
  • GitHub

›

チュートリアル

  • 概要

3D演算子

  • メッシュのフィッティング
  • バンドル調整

レンダリング

  • テクスチャ付きメッシュのレンダリング
  • DensePoseメッシュのレンダリング
  • カラー付き点群のレンダリング
  • レンダリングによるテクスチャ付きメッシュのフィッティング
  • 微分可能なレンダリングによるカメラ位置の最適化
  • レイマーチングによる体積のフィッティング
  • レイマーチングによる簡略化されたNeRFのフィッティング

データローダー

  • ShapeNetCoreおよびR2N2用のデータローダー

Implicitron

  • implicitronを用いたカスタム体積関数のトレーニング
  • Implicitron構成システムの詳細
Google Colabで実行
チュートリアルJupyter Notebookをダウンロード
チュートリアルソースコードをダウンロード
In [ ]
# Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.

微分可能なレンダリングを用いたカメラ位置の最適化¶

このチュートリアルでは、微分可能なレンダリングを使用して、基準画像からカメラの[x, y, z]位置を学習します。

まず、カメラの開始位置でレンダラーを初期化します。次に、これを使用して画像を生成し、基準画像との損失を計算し、最後にパイプライン全体をバックプロパゲートしてカメラの位置を更新します。

このチュートリアルでは、次の方法を学びます。

  • .objファイルからメッシュをロードする
  • Camera、Shader、Rendererを初期化する
  • メッシュをレンダリングする
  • 損失関数とオプティマイザーを使用して最適化ループを設定する

0. モジュールのインストールとインポート¶

torchとtorchvisionがインストールされていることを確認してください。pytorch3dがインストールされていない場合は、次のセルを使用してインストールしてください。

In [ ]
import os
import sys
import torch
need_pytorch3d=False
try:
    import pytorch3d
except ModuleNotFoundError:
    need_pytorch3d=True
if need_pytorch3d:
    if torch.__version__.startswith("2.2.") and sys.platform.startswith("linux"):
        # We try to install PyTorch3D via a released wheel.
        pyt_version_str=torch.__version__.split("+")[0].replace(".", "")
        version_str="".join([
            f"py3{sys.version_info.minor}_cu",
            torch.version.cuda.replace(".",""),
            f"_pyt{pyt_version_str}"
        ])
        !pip install fvcore iopath
        !pip install --no-index --no-cache-dir pytorch3d -f https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/{version_str}/download.html
    else:
        # We try to install PyTorch3D from source.
        !pip install 'git+https://github.com/facebookresearch/pytorch3d.git@stable'
In [ ]
import os
import torch
import numpy as np
from tqdm.notebook import tqdm
import imageio
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
from skimage import img_as_ubyte

# io utils
from pytorch3d.io import load_obj

# datastructures
from pytorch3d.structures import Meshes

# 3D transformations functions
from pytorch3d.transforms import Rotate, Translate

# rendering components
from pytorch3d.renderer import (
    FoVPerspectiveCameras, look_at_view_transform, look_at_rotation, 
    RasterizationSettings, MeshRenderer, MeshRasterizer, BlendParams,
    SoftSilhouetteShader, HardPhongShader, PointLights, TexturesVertex,
)

1. Objのロード¶

objファイルをロードし、Meshesオブジェクトを作成します。Meshesは、異なるサイズのメッシュのバッチを処理するためにPyTorch3Dで提供される独自のデータ構造です。レンダリングパイプラインで使用されるいくつかの便利なクラスメソッドがあります。

PyTorch3Dリポジトリをクローンした後、このノートブックをローカルで実行している場合、メッシュはすでに利用可能です。Google Colabを使用している場合は、メッシュを取得してdata/パスに保存してください。

In [ ]
!mkdir -p data
!wget -P data https://dl.fbaipublicfiles.com/pytorch3d/data/teapot/teapot.obj
In [ ]
# Set the cuda device 
if torch.cuda.is_available():
    device = torch.device("cuda:0")
    torch.cuda.set_device(device)
else:
    device = torch.device("cpu")

# Load the obj and ignore the textures and materials.
verts, faces_idx, _ = load_obj("./data/teapot.obj")
faces = faces_idx.verts_idx

# Initialize each vertex to be white in color.
verts_rgb = torch.ones_like(verts)[None]  # (1, V, 3)
textures = TexturesVertex(verts_features=verts_rgb.to(device))

# Create a Meshes object for the teapot. Here we have only one mesh in the batch.
teapot_mesh = Meshes(
    verts=[verts.to(device)],   
    faces=[faces.to(device)], 
    textures=textures
)

2. 最適化の設定¶

レンダラーの作成¶

PyTorch3Dのレンダラーは、ラスタライザーとシェーダーで構成されており、それぞれにカメラ(正射影/透視)などの多くのサブコンポーネントがあります。ここでは、これらのコンポーネントの一部を初期化し、残りのコンポーネントにはデフォルト値を使用します。

カメラ位置を最適化するために、オブジェクトのシルエットのみを生成し、ライティングやシェーディングを適用しないレンダラーを使用します。また、完全なPhongシェーディングを適用する別のレンダラーを初期化し、これを可視化に使用します。

In [ ]
# Initialize a perspective camera.
cameras = FoVPerspectiveCameras(device=device)

# To blend the 100 faces we set a few parameters which control the opacity and the sharpness of 
# edges. Refer to blending.py for more details. 
blend_params = BlendParams(sigma=1e-4, gamma=1e-4)

# Define the settings for rasterization and shading. Here we set the output image to be of size
# 256x256. To form the blended image we use 100 faces for each pixel. We also set bin_size and max_faces_per_bin to None which ensure that 
# the faster coarse-to-fine rasterization method is used. Refer to rasterize_meshes.py for 
# explanations of these parameters. Refer to docs/notes/renderer.md for an explanation of 
# the difference between naive and coarse-to-fine rasterization. 
raster_settings = RasterizationSettings(
    image_size=256, 
    blur_radius=np.log(1. / 1e-4 - 1.) * blend_params.sigma, 
    faces_per_pixel=100, 
)

# Create a silhouette mesh renderer by composing a rasterizer and a shader. 
silhouette_renderer = MeshRenderer(
    rasterizer=MeshRasterizer(
        cameras=cameras, 
        raster_settings=raster_settings
    ),
    shader=SoftSilhouetteShader(blend_params=blend_params)
)


# We will also create a Phong renderer. This is simpler and only needs to render one face per pixel.
raster_settings = RasterizationSettings(
    image_size=256, 
    blur_radius=0.0, 
    faces_per_pixel=1, 
)
# We can add a point light in front of the object. 
lights = PointLights(device=device, location=((2.0, 2.0, -2.0),))
phong_renderer = MeshRenderer(
    rasterizer=MeshRasterizer(
        cameras=cameras, 
        raster_settings=raster_settings
    ),
    shader=HardPhongShader(device=device, cameras=cameras, lights=lights)
)

基準画像の作成¶

まず、ティーポットを配置して画像を生成します。ヘルパー関数を使用して、ティーポットを目的の視点に回転させます。次に、レンダラーを使用して画像を生成できます。ここでは、両方のレンダラーを使用して、シルエットと完全なシェーディング画像を可視化します。

ワールド座標系は、+Yが上、+Xが左、+Zが奥として定義されています。ワールド座標のティーポットは、注ぎ口が左を向いています。

正のz軸に配置されたカメラを定義したため、注ぎ口が右に見えます。

In [ ]
# Select the viewpoint using spherical angles  
distance = 3   # distance from camera to the object
elevation = 50.0   # angle of elevation in degrees
azimuth = 0.0  # No rotation so the camera is positioned on the +Z axis. 

# Get the position of the camera based on the spherical angles
R, T = look_at_view_transform(distance, elevation, azimuth, device=device)

# Render the teapot providing the values of R and T. 
silhouette = silhouette_renderer(meshes_world=teapot_mesh, R=R, T=T)
image_ref = phong_renderer(meshes_world=teapot_mesh, R=R, T=T)

silhouette = silhouette.cpu().numpy()
image_ref = image_ref.cpu().numpy()

plt.figure(figsize=(10, 10))
plt.subplot(1, 2, 1)
plt.imshow(silhouette.squeeze()[..., 3])  # only plot the alpha channel of the RGBA image
plt.grid(False)
plt.subplot(1, 2, 2)
plt.imshow(image_ref.squeeze())
plt.grid(False)

基本的なモデルの設定¶

ここでは、単純なモデルクラスを作成し、カメラ位置のパラメーターを初期化します。

In [ ]
class Model(nn.Module):
    def __init__(self, meshes, renderer, image_ref):
        super().__init__()
        self.meshes = meshes
        self.device = meshes.device
        self.renderer = renderer
        
        # Get the silhouette of the reference RGB image by finding all non-white pixel values. 
        image_ref = torch.from_numpy((image_ref[..., :3].max(-1) != 1).astype(np.float32))
        self.register_buffer('image_ref', image_ref)
        
        # Create an optimizable parameter for the x, y, z position of the camera. 
        self.camera_position = nn.Parameter(
            torch.from_numpy(np.array([3.0,  6.9, +2.5], dtype=np.float32)).to(meshes.device))

    def forward(self):
        
        # Render the image using the updated camera position. Based on the new position of the 
        # camera we calculate the rotation and translation matrices
        R = look_at_rotation(self.camera_position[None, :], device=self.device)  # (1, 3, 3)
        T = -torch.bmm(R.transpose(1, 2), self.camera_position[None, :, None])[:, :, 0]   # (1, 3)
        
        image = self.renderer(meshes_world=self.meshes.clone(), R=R, T=T)
        
        # Calculate the silhouette loss
        loss = torch.sum((image[..., 3] - self.image_ref) ** 2)
        return loss, image
  

3. モデルとオプティマイザーの初期化¶

ここで、上記のモデルのインスタンスを作成し、カメラ位置パラメーターのオプティマイザーを設定できます。

In [ ]
# We will save images periodically and compose them into a GIF.
filename_output = "./teapot_optimization_demo.gif"
writer = imageio.get_writer(filename_output, mode='I', duration=0.3)

# Initialize a model using the renderer, mesh and reference image
model = Model(meshes=teapot_mesh, renderer=silhouette_renderer, image_ref=image_ref).to(device)

# Create an optimizer. Here we are using Adam and we pass in the parameters of the model
optimizer = torch.optim.Adam(model.parameters(), lr=0.05)

開始位置と基準位置の可視化¶

In [ ]
plt.figure(figsize=(10, 10))

_, image_init = model()
plt.subplot(1, 2, 1)
plt.imshow(image_init.detach().squeeze().cpu().numpy()[..., 3])
plt.grid(False)
plt.title("Starting position")

plt.subplot(1, 2, 2)
plt.imshow(model.image_ref.cpu().numpy().squeeze())
plt.grid(False)
plt.title("Reference silhouette");

4. 最適化の実行¶

フォワードパスとバックワードパスを数回繰り返して実行し、10回繰り返すごとに出力を保存します。完了したら、最適化プロセスのクールなgifである./teapot_optimization_demo.gifを見てください。

In [ ]
loop = tqdm(range(200))
for i in loop:
    optimizer.zero_grad()
    loss, _ = model()
    loss.backward()
    optimizer.step()
    
    loop.set_description('Optimizing (loss %.4f)' % loss.data)
    
    if loss.item() < 200:
        break
    
    # Save outputs to create a GIF. 
    if i % 10 == 0:
        R = look_at_rotation(model.camera_position[None, :], device=model.device)
        T = -torch.bmm(R.transpose(1, 2), model.camera_position[None, :, None])[:, :, 0]   # (1, 3)
        image = phong_renderer(meshes_world=model.meshes.clone(), R=R, T=T)
        image = image[0, ..., :3].detach().squeeze().cpu().numpy()
        image = img_as_ubyte(image)
        writer.append_data(image)
        
        plt.figure()
        plt.imshow(image[..., :3])
        plt.title("iter: %d, loss: %0.2f" % (i, loss.data))
        plt.axis("off")
    
writer.close()

5. 結論¶

このチュートリアルでは、objファイルからメッシュをロードする方法、Meshesと呼ばれるPyTorch3Dデータ構造を初期化する方法、ラスタライザーとシェーダーで構成されるレンダラーを設定する方法、モデルと損失関数を含む最適化ループを設定する方法、そして最適化を実行する方法を学びました。

pytorch3d
Facebook Open Source
Copyright © 2024 Meta Platforms, Inc
リーガル:プライバシー利用規約