PyTorch3D

PyTorch3D

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

›

チュートリアル

  • 概要

3D 演算子

  • メッシュの適合
  • バンドル調整

レンダリング

  • テクスチャ付きメッシュのレンダリング
  • DensePose メッシュのレンダリング
  • 色付き点群のレンダリング
  • レンダリングを使用したテクスチャ付きメッシュの適合
  • 微分可能なレンダリングを使用したカメラ位置最適化
  • レイマーチングを使用したボリュームの適合
  • レイマーチングを使用した簡略化された NeRF の適合

データローダー

  • ShapeNetCore と R2N2 のデータローダー

Implicitron

  • Implicitron でカスタムボリューム機能をトレーニング
  • Implicitron の設定システムの詳細調査
Google Colab で実行
チュートリアル Jupyter ノートブックのダウンロード
チュートリアルのソースコードのダウンロード
In [ ]
# Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.

3D ロス関数を使用してターゲットメッシュを作成するために、ソースメッシュを変形¶

このチュートリアルでは、初期の一般的な形状(球など)を変形させて、ターゲット形状に適合させる方法を学びます。

取り上げる内容は次のとおりです。

  • .obj ファイルからメッシュを読み込む方法
  • PyTorch3D Meshes データ構造を使用する方法
  • 4 種類の PyTorch3D メッシュロス関数を使用する方法
  • 最適化ループを設定する方法

球のメッシュから始めて、各最適化ステップで予測されたメッシュがターゲットメッシュに近づくように、メッシュ内の各頂点へのオフセットを学びます。これを実現するため、次を最小化します。

  • 予測(変形)メッシュとターゲットメッシュ間の距離であるchamfer_distanceは、そのサーフェスから微分可能なサンプリングポイントから生成される点群のセット間のチャーンファー距離として定義されます。

ただし、予測メッシュとターゲットメッシュ間のチャーンファー距離のみを最小化すると、非滑らかな形状になります(w_chamfer=1.0 とその他のすべての重みを 0.0 に設定して確認できます)。

目的関数に形状正則化を追加することで滑らかさを強化します。具体的には次を追加します。

  • 予測メッシュ内のエッジの長さを最小化するmesh_edge_length。
  • 隣接する面の法線の一貫性を強制するmesh_normal_consistency。
  • ラプラシアン正則化であるmesh_laplacian_smoothing。

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
from pytorch3d.io import load_obj, save_obj
from pytorch3d.structures import Meshes
from pytorch3d.utils import ico_sphere
from pytorch3d.ops import sample_points_from_meshes
from pytorch3d.loss import (
    chamfer_distance, 
    mesh_edge_loss, 
    mesh_laplacian_smoothing, 
    mesh_normal_consistency,
)
import numpy as np
from tqdm.notebook import tqdm
%matplotlib notebook 
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['savefig.dpi'] = 80
mpl.rcParams['figure.dpi'] = 80

# Set the device
if torch.cuda.is_available():
    device = torch.device("cuda:0")
else:
    device = torch.device("cpu")
    print("WARNING: CPU only, this will be slow!")

1. obj ファイルを読み込み、Meshes オブジェクトを作成¶

イルカのターゲット 3D モデルをダウンロードします。dolphin.obj という名前のファイルとしてローカルに保存されます。

In [ ]
!wget https://dl.fbaipublicfiles.com/pytorch3d/data/dolphin/dolphin.obj
In [ ]
# Load the dolphin mesh.
trg_obj = 'dolphin.obj'
In [ ]
# We read the target 3D model using load_obj
verts, faces, aux = load_obj(trg_obj)

# verts is a FloatTensor of shape (V, 3) where V is the number of vertices in the mesh
# faces is an object which contains the following LongTensors: verts_idx, normals_idx and textures_idx
# For this tutorial, normals and textures are ignored.
faces_idx = faces.verts_idx.to(device)
verts = verts.to(device)

# We scale normalize and center the target mesh to fit in a sphere of radius 1 centered at (0,0,0). 
# (scale, center) will be used to bring the predicted mesh to its original center and scale
# Note that normalizing the target mesh, speeds up the optimization but is not necessary!
center = verts.mean(0)
verts = verts - center
scale = max(verts.abs().max(0)[0])
verts = verts / scale

# We construct a Meshes structure for the target mesh
trg_mesh = Meshes(verts=[verts], faces=[faces_idx])
In [ ]
# We initialize the source shape to be a sphere of radius 1
src_mesh = ico_sphere(4, device)

2. ソースメッシュとターゲットメッシュを視覚化¶

In [ ]
def plot_pointcloud(mesh, title=""):
    # Sample points uniformly from the surface of the mesh.
    points = sample_points_from_meshes(mesh, 5000)
    x, y, z = points.clone().detach().cpu().squeeze().unbind(1)    
    fig = plt.figure(figsize=(5, 5))
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter3D(x, z, -y)
    ax.set_xlabel('x')
    ax.set_ylabel('z')
    ax.set_zlabel('y')
    ax.set_title(title)
    ax.view_init(190, 30)
    plt.show()
In [ ]
# %matplotlib notebook
plot_pointcloud(trg_mesh, "Target mesh")
plot_pointcloud(src_mesh, "Source mesh")

3. 最適化ループ¶

In [ ]
# We will learn to deform the source mesh by offsetting its vertices
# The shape of the deform parameters is equal to the total number of vertices in src_mesh
deform_verts = torch.full(src_mesh.verts_packed().shape, 0.0, device=device, requires_grad=True)
In [ ]
# The optimizer
optimizer = torch.optim.SGD([deform_verts], lr=1.0, momentum=0.9)
In [ ]
# Number of optimization steps
Niter = 2000
# Weight for the chamfer loss
w_chamfer = 1.0 
# Weight for mesh edge loss
w_edge = 1.0 
# Weight for mesh normal consistency
w_normal = 0.01 
# Weight for mesh laplacian smoothing
w_laplacian = 0.1 
# Plot period for the losses
plot_period = 250
loop = tqdm(range(Niter))

chamfer_losses = []
laplacian_losses = []
edge_losses = []
normal_losses = []

%matplotlib inline

for i in loop:
    # Initialize optimizer
    optimizer.zero_grad()
    
    # Deform the mesh
    new_src_mesh = src_mesh.offset_verts(deform_verts)
    
    # We sample 5k points from the surface of each mesh 
    sample_trg = sample_points_from_meshes(trg_mesh, 5000)
    sample_src = sample_points_from_meshes(new_src_mesh, 5000)
    
    # We compare the two sets of pointclouds by computing (a) the chamfer loss
    loss_chamfer, _ = chamfer_distance(sample_trg, sample_src)
    
    # and (b) the edge length of the predicted mesh
    loss_edge = mesh_edge_loss(new_src_mesh)
    
    # mesh normal consistency
    loss_normal = mesh_normal_consistency(new_src_mesh)
    
    # mesh laplacian smoothing
    loss_laplacian = mesh_laplacian_smoothing(new_src_mesh, method="uniform")
    
    # Weighted sum of the losses
    loss = loss_chamfer * w_chamfer + loss_edge * w_edge + loss_normal * w_normal + loss_laplacian * w_laplacian
    
    # Print the losses
    loop.set_description('total_loss = %.6f' % loss)
    
    # Save the losses for plotting
    chamfer_losses.append(float(loss_chamfer.detach().cpu()))
    edge_losses.append(float(loss_edge.detach().cpu()))
    normal_losses.append(float(loss_normal.detach().cpu()))
    laplacian_losses.append(float(loss_laplacian.detach().cpu()))
    
    # Plot mesh
    if i % plot_period == 0:
        plot_pointcloud(new_src_mesh, title="iter: %d" % i)
        
    # Optimization step
    loss.backward()
    optimizer.step()

4. 損失を視覚化¶

In [ ]
fig = plt.figure(figsize=(13, 5))
ax = fig.gca()
ax.plot(chamfer_losses, label="chamfer loss")
ax.plot(edge_losses, label="edge loss")
ax.plot(normal_losses, label="normal loss")
ax.plot(laplacian_losses, label="laplacian loss")
ax.legend(fontsize="16")
ax.set_xlabel("Iteration", fontsize="16")
ax.set_ylabel("Loss", fontsize="16")
ax.set_title("Loss vs iterations", fontsize="16");

5. 予測メッシュを保存¶

In [ ]
# Fetch the verts and faces of the final predicted mesh
final_verts, final_faces = new_src_mesh.get_mesh_verts_faces(0)

# Scale normalize back to the original target size
final_verts = final_verts * scale + center

# Store the predicted mesh using save_obj
final_obj = 'final_model.obj'
save_obj(final_obj, final_verts, final_faces)

6. 結論¶

このチュートリアルでは、obj ファイルからメッシュを読み込み、Meshes と呼ばれる PyTorch3D データ構造を初期化し、最適化ループを設定し、4 つの異なる PyTorch3D メッシュロス関数を使用する方法を学びました。

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