mirror of
https://github.com/facebookresearch/pytorch3d.git
synced 2026-02-26 08:06:00 +08:00
Reviewed By: bottler Differential Revision: D93709347 fbshipit-source-id: 69710e6082a0785126a121e26f1d96a571360f1d
136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the BSD-style license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
# pyre-unsafe
|
|
|
|
|
|
import torch
|
|
from pytorch3d.ops import cot_laplacian
|
|
|
|
|
|
def mesh_laplacian_smoothing(meshes, method: str = "uniform"):
|
|
r"""
|
|
Computes the laplacian smoothing objective for a batch of meshes.
|
|
This function supports three variants of Laplacian smoothing,
|
|
namely with uniform weights("uniform"), with cotangent weights ("cot"),
|
|
and cotangent curvature ("cotcurv").For more details read [1, 2].
|
|
|
|
Args:
|
|
meshes: Meshes object with a batch of meshes.
|
|
method: str specifying the method for the laplacian.
|
|
Returns:
|
|
loss: Average laplacian smoothing loss across the batch.
|
|
Returns 0 if meshes contains no meshes or all empty meshes.
|
|
|
|
Consider a mesh M = (V, F), with verts of shape Nx3 and faces of shape Mx3.
|
|
The Laplacian matrix L is a NxN tensor such that LV gives a tensor of vectors:
|
|
for a uniform Laplacian, LuV[i] points to the centroid of its neighboring
|
|
vertices, a cotangent Laplacian LcV[i] is known to be an approximation of
|
|
the surface normal, while the curvature variant LckV[i] scales the normals
|
|
by the discrete mean curvature. For vertex i, assume S[i] is the set of
|
|
neighboring vertices to i, a_ij and b_ij are the "outside" angles in the
|
|
two triangles connecting vertex v_i and its neighboring vertex v_j
|
|
for j in S[i], as seen in the diagram below.
|
|
|
|
.. code-block:: python
|
|
|
|
a_ij
|
|
/\
|
|
/ \
|
|
/ \
|
|
/ \
|
|
v_i /________\ v_j
|
|
\ /
|
|
\ /
|
|
\ /
|
|
\ /
|
|
\/
|
|
b_ij
|
|
|
|
The definition of the Laplacian is LV[i] = sum_j w_ij (v_j - v_i)
|
|
For the uniform variant, w_ij = 1 / |S[i]|
|
|
For the cotangent variant,
|
|
w_ij = (cot a_ij + cot b_ij) / (sum_k cot a_ik + cot b_ik)
|
|
For the cotangent curvature, w_ij = (cot a_ij + cot b_ij) / (4 A[i])
|
|
where A[i] is the sum of the areas of all triangles containing vertex v_i.
|
|
|
|
There is a nice trigonometry identity to compute cotangents. Consider a triangle
|
|
with side lengths A, B, C and angles a, b, c.
|
|
|
|
.. code-block:: python
|
|
|
|
c
|
|
/|\
|
|
/ | \
|
|
/ | \
|
|
B / H| \ A
|
|
/ | \
|
|
/ | \
|
|
/a_____|_____b\
|
|
C
|
|
|
|
Then cot a = (B^2 + C^2 - A^2) / 4 * area
|
|
We know that area = CH/2, and by the law of cosines we have
|
|
|
|
A^2 = B^2 + C^2 - 2BC cos a => B^2 + C^2 - A^2 = 2BC cos a
|
|
|
|
Putting these together, we get:
|
|
|
|
B^2 + C^2 - A^2 2BC cos a
|
|
_______________ = _________ = (B/H) cos a = cos a / sin a = cot a
|
|
4 * area 2CH
|
|
|
|
|
|
[1] Desbrun et al, "Implicit fairing of irregular meshes using diffusion
|
|
and curvature flow", SIGGRAPH 1999.
|
|
|
|
[2] Nealan et al, "Laplacian Mesh Optimization", Graphite 2006.
|
|
"""
|
|
|
|
if meshes.isempty():
|
|
return torch.tensor(
|
|
[0.0], dtype=torch.float32, device=meshes.device, requires_grad=True
|
|
)
|
|
|
|
N = len(meshes)
|
|
verts_packed = meshes.verts_packed() # (sum(V_n), 3)
|
|
faces_packed = meshes.faces_packed() # (sum(F_n), 3)
|
|
num_verts_per_mesh = meshes.num_verts_per_mesh() # (N,)
|
|
verts_packed_idx = meshes.verts_packed_to_mesh_idx() # (sum(V_n),)
|
|
weights = num_verts_per_mesh.gather(0, verts_packed_idx) # (sum(V_n),)
|
|
weights = 1.0 / weights.float()
|
|
|
|
# We don't want to backprop through the computation of the Laplacian;
|
|
# just treat it as a magic constant matrix that is used to transform
|
|
# verts into normals
|
|
with torch.no_grad():
|
|
if method == "uniform":
|
|
L = meshes.laplacian_packed()
|
|
elif method in ["cot", "cotcurv"]:
|
|
L, inv_areas = cot_laplacian(verts_packed, faces_packed)
|
|
if method == "cot":
|
|
norm_w = torch.sparse.sum(L, dim=1).to_dense().view(-1, 1)
|
|
idx = norm_w > 0
|
|
norm_w[idx] = torch.reciprocal(norm_w[idx])
|
|
else:
|
|
L_sum = torch.sparse.sum(L, dim=1).to_dense().view(-1, 1)
|
|
norm_w = 0.25 * inv_areas
|
|
else:
|
|
raise ValueError("Method should be one of {uniform, cot, cotcurv}")
|
|
|
|
if method == "uniform":
|
|
loss = L.mm(verts_packed)
|
|
elif method == "cot":
|
|
# pyre-fixme[61]: `norm_w` is undefined, or not always defined.
|
|
loss = L.mm(verts_packed) * norm_w - verts_packed
|
|
elif method == "cotcurv":
|
|
# pyre-fixme[61]: `norm_w` may not be initialized here.
|
|
loss = (L.mm(verts_packed) - L_sum * verts_packed) * norm_w
|
|
loss = loss.norm(dim=1)
|
|
|
|
loss = loss * weights
|
|
return loss.sum() / N
|