ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
TriangleMeshDeformation.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include <Logging.h>
9 #include <Parallel.h>
10 
11 #include <Eigen/Dense>
12 #include <Eigen/Sparse>
13 #include <algorithm>
14 
15 #include "ecvHObjectCaster.h"
16 #include "ecvMesh.h"
17 #include "ecvPointCloud.h"
18 
19 using namespace cloudViewer;
20 
21 std::shared_ptr<ccMesh> ccMesh::DeformAsRigidAsPossible(
22  const std::vector<int>& constraint_vertex_indices,
23  const std::vector<Eigen::Vector3d>& constraint_vertex_positions,
24  size_t max_iter,
25  DeformAsRigidAsPossibleEnergy energy_model,
26  double smoothed_alpha) const {
27  ccPointCloud* baseVertices = new ccPointCloud("vertices");
28  assert(baseVertices);
29  baseVertices->setEnabled(false);
30  // DGM: no need to lock it as it is only used by one mesh!
31  baseVertices->setLocked(false);
32  auto prime = std::make_shared<ccMesh>(baseVertices);
33  prime->addChild(baseVertices);
34  baseVertices->addPoints(this->getVertices());
35  prime->addTriangles(this->getTriangles());
36 
37  utility::LogDebug("[DeformAsRigidAsPossible] setting up S'");
38  prime->ComputeAdjacencyList();
39  auto edges_to_vertices = prime->GetEdgeToVerticesMap();
40  auto edge_weights =
41  prime->ComputeEdgeWeightsCot(edges_to_vertices, /*min_weight=*/0);
42  utility::LogDebug("[DeformAsRigidAsPossible] done setting up S'");
43 
44  std::unordered_map<int, Eigen::Vector3d> constraints;
45  for (size_t idx = 0; idx < constraint_vertex_indices.size() &&
46  idx < constraint_vertex_positions.size();
47  ++idx) {
48  constraints[constraint_vertex_indices[idx]] =
49  constraint_vertex_positions[idx];
50  }
51 
52  double surface_area = -1;
53  // std::vector<Eigen::Matrix3d> Rs(vertices_.size(),
54  // Eigen::Matrix3d::Identity());
55  std::vector<Eigen::Matrix3d> Rs(getVerticeSize());
56  std::vector<Eigen::Matrix3d> Rs_old;
57  if (energy_model == DeformAsRigidAsPossibleEnergy::Smoothed) {
58  surface_area = prime->GetSurfaceArea();
59  Rs_old.resize(getVerticeSize());
60  }
61 
62  // Build system matrix L and its solver
63  utility::LogDebug("[DeformAsRigidAsPossible] setting up system matrix L");
64  std::vector<Eigen::Triplet<double>> triplets;
65  for (int i = 0; i < int(getVerticeSize()); ++i) {
66  if (constraints.count(i) > 0) {
67  triplets.push_back(Eigen::Triplet<double>(i, i, 1));
68  } else {
69  double W = 0;
70  for (int j : prime->adjacency_list_[i]) {
71  double w = edge_weights[GetOrderedEdge(i, j)];
72  triplets.push_back(Eigen::Triplet<double>(i, j, -w));
73  W += w;
74  }
75  if (W > 0) {
76  triplets.push_back(Eigen::Triplet<double>(i, i, W));
77  }
78  }
79  }
80  Eigen::SparseMatrix<double> L(getVerticeSize(), getVerticeSize());
81  L.setFromTriplets(triplets.begin(), triplets.end());
83  "[DeformAsRigidAsPossible] done setting up system matrix L");
84 
85  utility::LogDebug("[DeformAsRigidAsPossible] setting up sparse solver");
86  Eigen::SparseLU<Eigen::SparseMatrix<double>> solver;
87  solver.analyzePattern(L);
88  solver.factorize(L);
89  if (solver.info() != Eigen::Success) {
91  "[DeformAsRigidAsPossible] Failed to build solver (factorize)");
92  } else {
94  "[DeformAsRigidAsPossible] done setting up sparse solver");
95  }
96 
97  std::vector<Eigen::VectorXd> b = {Eigen::VectorXd(getVerticeSize()),
98  Eigen::VectorXd(getVerticeSize()),
99  Eigen::VectorXd(getVerticeSize())};
100 
101  for (size_t iter = 0; iter < max_iter; ++iter) {
102  if (energy_model == DeformAsRigidAsPossibleEnergy::Smoothed) {
103  std::swap(Rs, Rs_old);
104  }
105 #ifdef _OPENMP
106 #pragma omp parallel for schedule(static) \
107  num_threads(utility::EstimateMaxThreads())
108 #endif
109  for (int i = 0; i < int(getVerticeSize()); ++i) {
110  // Update rotations
111  Eigen::Matrix3d S = Eigen::Matrix3d::Zero();
112  Eigen::Matrix3d R = Eigen::Matrix3d::Zero();
113  int n_nbs = 0;
114  for (int j : prime->adjacency_list_[i]) {
115  Eigen::Vector3d e0 = getVertice(static_cast<size_t>(i)) -
116  getVertice(static_cast<size_t>(j));
117  Eigen::Vector3d e1 = prime->getVertice(static_cast<size_t>(i)) -
118  prime->getVertice(static_cast<size_t>(j));
119  double w = edge_weights[GetOrderedEdge(i, j)];
120  S += w * (e0 * e1.transpose());
121  if (energy_model == DeformAsRigidAsPossibleEnergy::Smoothed) {
122  R += Rs_old[j];
123  }
124  n_nbs++;
125  }
126  if (energy_model == DeformAsRigidAsPossibleEnergy::Smoothed &&
127  iter > 0 && n_nbs > 0) {
128  S = 2 * S +
129  (4 * smoothed_alpha * surface_area / n_nbs) * R.transpose();
130  }
131  Eigen::JacobiSVD<Eigen::Matrix3d> svd(
132  S, Eigen::ComputeFullU | Eigen::ComputeFullV);
133  Eigen::Matrix3d U = svd.matrixU();
134  Eigen::Matrix3d V = svd.matrixV();
135  Eigen::Vector3d D(1, 1, (V * U.transpose()).determinant());
136  // ensure rotation:
137  // http://graphics.stanford.edu/~smr/ICP/comparison/eggert_comparison_mva97.pdf
138  Rs[i] = V * D.asDiagonal() * U.transpose();
139  if (Rs[i].determinant() <= 0) {
141  "[DeformAsRigidAsPossible] something went wrong with "
142  "updateing R");
143  }
144  }
145 
146 #ifdef _OPENMP
147 #pragma omp parallel for schedule(static) \
148  num_threads(utility::EstimateMaxThreads())
149 #endif
150  for (int i = 0; i < int(getVerticeSize()); ++i) {
151  // Update Positions
152  Eigen::Vector3d bi(0, 0, 0);
153  if (constraints.count(i) > 0) {
154  bi = constraints[i];
155  } else {
156  for (int j : prime->adjacency_list_[i]) {
157  double w = edge_weights[GetOrderedEdge(i, j)];
158  bi += w / 2 *
159  ((Rs[i] + Rs[j]) *
160  (getVertice(static_cast<size_t>(i)) -
161  getVertice(static_cast<size_t>(j))));
162  }
163  }
164  b[0](i) = bi(0);
165  b[1](i) = bi(1);
166  b[2](i) = bi(2);
167  }
168 #ifdef _OPENMP
169 #pragma omp parallel for schedule(static) \
170  num_threads(utility::EstimateMaxThreads())
171 #endif
172  for (int comp = 0; comp < 3; ++comp) {
173  Eigen::VectorXd p_prime = solver.solve(b[comp]);
174  if (solver.info() != Eigen::Success) {
176  "[DeformAsRigidAsPossible] Cholesky solve failed");
177  }
178  for (int i = 0; i < int(getVerticeSize()); ++i) {
179  baseVertices->getPointPtr(static_cast<size_t>(i))->u[comp] =
180  static_cast<PointCoordinateType>(p_prime(i));
181  }
182  }
183 
184  // Compute energy and log
185  double energy = 0;
186  double reg = 0;
187  for (int i = 0; i < int(getVerticeSize()); ++i) {
188  for (int j : prime->adjacency_list_[i]) {
189  double w = edge_weights[GetOrderedEdge(i, j)];
190  Eigen::Vector3d e0 = getVertice(static_cast<size_t>(i)) -
191  getVertice(static_cast<size_t>(j));
192  Eigen::Vector3d e1 = prime->getVertice(static_cast<size_t>(i)) -
193  prime->getVertice(static_cast<size_t>(j));
194  Eigen::Vector3d diff = e1 - Rs[i] * e0;
195  energy += w * diff.squaredNorm();
196  if (energy_model == DeformAsRigidAsPossibleEnergy::Smoothed) {
197  reg += (Rs[i] - Rs[j]).squaredNorm();
198  }
199  }
200  }
201  if (energy_model == DeformAsRigidAsPossibleEnergy::Smoothed) {
202  energy = energy + smoothed_alpha * surface_area * reg;
203  }
204  utility::LogDebug("[DeformAsRigidAsPossible] iter={}, energy={:e}",
205  iter, energy);
206  }
207 
208  // do some cleaning
209  {
210  baseVertices->shrinkToFit();
211  prime->shrinkToFit();
212  NormsIndexesTableType* normals = prime->getTriNormsTable();
213  if (normals) {
214  normals->shrink_to_fit();
215  }
216  }
217  return prime;
218 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
Array of compressed 3D normals (single index)
std::shared_ptr< ccMesh > DeformAsRigidAsPossible(const std::vector< int > &constraint_vertex_indices, const std::vector< Eigen::Vector3d > &constraint_vertex_positions, size_t max_iter, DeformAsRigidAsPossibleEnergy energy=DeformAsRigidAsPossibleEnergy::Spokes, double smoothed_alpha=0.01) const
This function deforms the mesh using the method by Sorkine and Alexa, "As-Rigid-As-Possible Surface M...
virtual void setLocked(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:117
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void shrinkToFit()
Removes unused capacity.
CCVector3 * getPointPtr(size_t index)
void addPoints(const std::vector< CCVector3 > &points)
double normals[3]
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
static Edge< T > GetOrderedEdge(T vidx0, T vidx1)
brief Helper function to get an edge with ordered vertex indices.
Generic file read and write utility for python interface.
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370