ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
NonRigidOptimizer.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 
9 
10 #include <FileSystem.h>
11 #include <ImageIO.h>
12 #include <Parallel.h>
13 #include <PinholeCameraTrajectoryIO.h>
14 
15 #include <memory>
16 #include <vector>
17 
18 #include "io/ImageWarpingFieldIO.h"
19 #include "io/TriangleMeshIO.h"
22 
23 namespace Eigen {
24 
25 typedef Eigen::Matrix<double, 14, 1> Vector14d;
26 typedef Eigen::Matrix<int, 14, 1> Vector14i;
27 
28 } // namespace Eigen
29 
30 namespace cloudViewer {
31 namespace pipelines {
32 namespace color_map {
33 
34 static std::vector<ImageWarpingField> CreateWarpingFields(
35  const std::vector<geometry::Image>& images,
36  int number_of_vertical_anchors) {
37  std::vector<ImageWarpingField> fields;
38  for (size_t i = 0; i < images.size(); i++) {
39  int width = images[i].width_;
40  int height = images[i].height_;
41  fields.push_back(
42  ImageWarpingField(width, height, number_of_vertical_anchors));
43  }
44  return fields;
45 }
46 
53 template <typename VecInTypeDouble,
54  typename VecInTypeInt,
55  typename MatOutType,
56  typename VecOutType>
57 static std::tuple<MatOutType, VecOutType, double> ComputeJTJandJTrNonRigid(
58  std::function<void(int, VecInTypeDouble&, double&, VecInTypeInt&)> f,
59  int iteration_num,
60  int nonrigidval,
61  bool verbose /*=true*/) {
62  MatOutType JTJ(6 + nonrigidval, 6 + nonrigidval);
63  VecOutType JTr(6 + nonrigidval);
64  double r2_sum = 0.0;
65  JTJ.setZero();
66  JTr.setZero();
67 #pragma omp parallel
68  {
69  MatOutType JTJ_private(6 + nonrigidval, 6 + nonrigidval);
70  VecOutType JTr_private(6 + nonrigidval);
71  double r2_sum_private = 0.0;
72  JTJ_private.setZero();
73  JTr_private.setZero();
74  VecInTypeDouble J_r;
75  VecInTypeInt pattern;
76  double r;
77 #pragma omp for nowait
78  for (int i = 0; i < iteration_num; i++) {
79  f(i, J_r, r, pattern);
80  for (auto x = 0; x < J_r.size(); x++) {
81  for (auto y = 0; y < J_r.size(); y++) {
82  JTJ_private(pattern(x), pattern(y)) += J_r(x) * J_r(y);
83  }
84  }
85  for (auto x = 0; x < J_r.size(); x++) {
86  JTr_private(pattern(x)) += r * J_r(x);
87  }
88  r2_sum_private += r * r;
89  }
90 #pragma omp critical(ComputeJTJandJTrNonRigid)
91  {
92  JTJ += JTJ_private;
93  JTr += JTr_private;
94  r2_sum += r2_sum_private;
95  }
96  }
97  if (verbose) {
98  utility::LogDebug("Residual : {:.2e} (# of elements : {:d})",
99  r2_sum / (double)iteration_num, iteration_num);
100  }
101  return std::make_tuple(std::move(JTJ), std::move(JTr), r2_sum);
102 }
103 
105  int row,
106  Eigen::Vector14d& J_r,
107  double& r,
108  Eigen::Vector14i& pattern,
109  const ccMesh& mesh,
110  const std::vector<double>& proxy_intensity,
111  const geometry::Image& images_gray,
112  const geometry::Image& images_dx,
113  const geometry::Image& images_dy,
114  const ImageWarpingField& warping_fields,
115  const Eigen::Matrix4d& intrinsic,
116  const Eigen::Matrix4d& extrinsic,
117  const std::vector<int>& visibility_image_to_vertex,
118  const int image_boundary_margin) {
119  J_r.setZero();
120  pattern.setZero();
121  r = 0;
122  int anchor_w = warping_fields.anchor_w_;
123  double anchor_step = warping_fields.anchor_step_;
124  int vid = visibility_image_to_vertex[row];
125  Eigen::Vector3d V = mesh.getVertice(vid);
126  Eigen::Vector4d G = extrinsic * Eigen::Vector4d(V(0), V(1), V(2), 1);
127  Eigen::Vector4d L = intrinsic * G;
128  double u = L(0) / L(2);
129  double v = L(1) / L(2);
130  if (!images_gray.TestImageBoundary(u, v, image_boundary_margin)) {
131  return;
132  }
133  int ii = (int)(u / anchor_step);
134  int jj = (int)(v / anchor_step);
135  if (ii >= warping_fields.anchor_w_ - 1 ||
136  jj >= warping_fields.anchor_h_ - 1) {
137  return;
138  }
139  double p = (u - ii * anchor_step) / anchor_step;
140  double q = (v - jj * anchor_step) / anchor_step;
141  Eigen::Vector2d grids[4] = {
142  warping_fields.QueryFlow(ii, jj),
143  warping_fields.QueryFlow(ii, jj + 1),
144  warping_fields.QueryFlow(ii + 1, jj),
145  warping_fields.QueryFlow(ii + 1, jj + 1),
146  };
147  Eigen::Vector2d uuvv = (1 - p) * (1 - q) * grids[0] +
148  (1 - p) * (q)*grids[1] + (p) * (1 - q) * grids[2] +
149  (p) * (q)*grids[3];
150  double uu = uuvv(0);
151  double vv = uuvv(1);
152  if (!images_gray.TestImageBoundary(uu, vv, image_boundary_margin)) {
153  return;
154  }
155  bool valid;
156  double gray, dIdfx, dIdfy;
157  std::tie(valid, gray) = images_gray.FloatValueAt(uu, vv);
158  std::tie(valid, dIdfx) = images_dx.FloatValueAt(uu, vv);
159  std::tie(valid, dIdfy) = images_dy.FloatValueAt(uu, vv);
160  Eigen::Vector2d dIdf(dIdfx, dIdfy);
161  Eigen::Vector2d dfdx =
162  ((grids[2] - grids[0]) * (1 - q) + (grids[3] - grids[1]) * q) /
163  anchor_step;
164  Eigen::Vector2d dfdy =
165  ((grids[1] - grids[0]) * (1 - p) + (grids[3] - grids[2]) * p) /
166  anchor_step;
167  double dIdx = dIdf.dot(dfdx);
168  double dIdy = dIdf.dot(dfdy);
169  double invz = 1. / G(2);
170  double v0 = dIdx * intrinsic(0, 0) * invz;
171  double v1 = dIdy * intrinsic(1, 1) * invz;
172  double v2 = -(v0 * G(0) + v1 * G(1)) * invz;
173  J_r(0) = -G(2) * v1 + G(1) * v2;
174  J_r(1) = G(2) * v0 - G(0) * v2;
175  J_r(2) = -G(1) * v0 + G(0) * v1;
176  J_r(3) = v0;
177  J_r(4) = v1;
178  J_r(5) = v2;
179  J_r(6) = dIdf(0) * (1 - p) * (1 - q);
180  J_r(7) = dIdf(1) * (1 - p) * (1 - q);
181  J_r(8) = dIdf(0) * (1 - p) * (q);
182  J_r(9) = dIdf(1) * (1 - p) * (q);
183  J_r(10) = dIdf(0) * (p) * (1 - q);
184  J_r(11) = dIdf(1) * (p) * (1 - q);
185  J_r(12) = dIdf(0) * (p) * (q);
186  J_r(13) = dIdf(1) * (p) * (q);
187  pattern(0) = 0;
188  pattern(1) = 1;
189  pattern(2) = 2;
190  pattern(3) = 3;
191  pattern(4) = 4;
192  pattern(5) = 5;
193  pattern(6) = 6 + (ii + jj * anchor_w) * 2;
194  pattern(7) = 6 + (ii + jj * anchor_w) * 2 + 1;
195  pattern(8) = 6 + (ii + (jj + 1) * anchor_w) * 2;
196  pattern(9) = 6 + (ii + (jj + 1) * anchor_w) * 2 + 1;
197  pattern(10) = 6 + ((ii + 1) + jj * anchor_w) * 2;
198  pattern(11) = 6 + ((ii + 1) + jj * anchor_w) * 2 + 1;
199  pattern(12) = 6 + ((ii + 1) + (jj + 1) * anchor_w) * 2;
200  pattern(13) = 6 + ((ii + 1) + (jj + 1) * anchor_w) * 2 + 1;
201  r = (gray - proxy_intensity[vid]);
202 }
203 
204 std::pair<ccMesh, camera::PinholeCameraTrajectory> RunNonRigidOptimizer(
205  const ccMesh& mesh,
206  const std::vector<geometry::RGBDImage>& images_rgbd,
207  const camera::PinholeCameraTrajectory& camera_trajectory,
208  const NonRigidOptimizerOption& option) {
209  // The following properties will change during optimization.
210  ccMesh opt_mesh = mesh;
211  camera::PinholeCameraTrajectory opt_camera_trajectory = camera_trajectory;
212  std::vector<ImageWarpingField> warping_fields;
213 
214  // The following properties remain unchanged during optimization.
215  std::vector<geometry::Image> images_gray;
216  std::vector<geometry::Image> images_dx;
217  std::vector<geometry::Image> images_dy;
218  std::vector<geometry::Image> images_color;
219  std::vector<geometry::Image> images_depth;
220  std::vector<geometry::Image> images_mask;
221  std::vector<std::vector<int>> visibility_vertex_to_image;
222  std::vector<std::vector<int>> visibility_image_to_vertex;
223  std::vector<ImageWarpingField> warping_fields_init;
224 
225  // Create all debugging directories. We don't delete any existing files but
226  // will overwrite them if the names are the same.
227  if (!option.debug_output_dir_.empty()) {
228  std::vector<std::string> dirs{
229  option.debug_output_dir_,
230  option.debug_output_dir_ + "/non_rigid",
231  option.debug_output_dir_ + "/non_rigid/images_mask",
232  option.debug_output_dir_ + "/non_rigid/opt_mesh",
233  option.debug_output_dir_ + "/non_rigid/opt_camera_trajectory",
234  option.debug_output_dir_ + "/non_rigid/warping_fields"};
235  for (const std::string& dir : dirs) {
237  utility::LogInfo("Directory exists: {}.", dir);
238  } else {
240  utility::LogInfo("Directory created: {}.", dir);
241  } else {
242  utility::LogError("Making directory failed: {}.", dir);
243  }
244  }
245  }
246  }
247 
248  utility::LogDebug("[ColorMapOptimization] CreateUtilImagesFromRGBD");
249  std::tie(images_gray, images_dx, images_dy, images_color, images_depth) =
250  CreateUtilImagesFromRGBD(images_rgbd);
251 
252  utility::LogDebug("[ColorMapOptimization] CreateDepthBoundaryMasks");
253  images_mask = CreateDepthBoundaryMasks(
254  images_depth, option.depth_threshold_for_discontinuity_check_,
256  if (!option.debug_output_dir_.empty()) {
257  for (size_t i = 0; i < images_mask.size(); ++i) {
258  std::string file_name = fmt::format(
259  "{}/{}.png",
260  option.debug_output_dir_ + "/non_rigid/images_mask", i);
261  io::WriteImage(file_name, images_mask[i]);
262  }
263  }
264 
265  utility::LogDebug("[ColorMapOptimization] CreateVertexAndImageVisibility");
266  std::tie(visibility_vertex_to_image, visibility_image_to_vertex) =
268  opt_mesh, images_depth, images_mask, opt_camera_trajectory,
271 
272  utility::LogDebug("[ColorMapOptimization] Non-Rigid Optimization");
273  warping_fields = CreateWarpingFields(images_gray,
275  warping_fields_init = CreateWarpingFields(
276  images_gray, option.number_of_vertical_anchors_);
277  std::vector<double> proxy_intensity;
278  size_t n_vertex = opt_mesh.getVerticeSize();
279  int n_camera = int(opt_camera_trajectory.parameters_.size());
280  SetProxyIntensityForVertex(opt_mesh, images_gray, warping_fields,
281  opt_camera_trajectory,
282  visibility_vertex_to_image, proxy_intensity,
283  option.image_boundary_margin_);
284  for (int itr = 0; itr < option.maximum_iteration_; itr++) {
285  utility::LogDebug("[Iteration {:04d}] ", itr + 1);
286  double residual = 0.0;
287  double residual_reg = 0.0;
288 #pragma omp parallel for schedule(static) \
289  num_threads(utility::EstimateMaxThreads())
290  for (int c = 0; c < n_camera; c++) {
291  int nonrigidval = warping_fields[c].anchor_w_ *
292  warping_fields[c].anchor_h_ * 2;
293  double rr_reg = 0.0;
294 
295  Eigen::Matrix4d pose;
296  pose = opt_camera_trajectory.parameters_[c].extrinsic_;
297 
298  auto intrinsic = opt_camera_trajectory.parameters_[c]
299  .intrinsic_.intrinsic_matrix_;
300  auto extrinsic = opt_camera_trajectory.parameters_[c].extrinsic_;
301  Eigen::Matrix4d intr = Eigen::Matrix4d::Zero();
302  intr.block<3, 3>(0, 0) = intrinsic;
303  intr(3, 3) = 1.0;
304 
305  auto f_lambda = [&](int i, Eigen::Vector14d& J_r, double& r,
306  Eigen::Vector14i& pattern) {
308  i, J_r, r, pattern, opt_mesh, proxy_intensity,
309  images_gray[c], images_dx[c], images_dy[c],
310  warping_fields[c], intr, extrinsic,
311  visibility_image_to_vertex[c],
312  option.image_boundary_margin_);
313  };
314  Eigen::MatrixXd JTJ;
315  Eigen::VectorXd JTr;
316  double r2;
317  std::tie(JTJ, JTr, r2) =
319  Eigen::MatrixXd, Eigen::VectorXd>(
320  f_lambda, int(visibility_image_to_vertex[c].size()),
321  nonrigidval, false);
322 
323  double weight = option.non_rigid_anchor_point_weight_ *
324  visibility_image_to_vertex[c].size() / n_vertex;
325  for (int j = 0; j < nonrigidval; j++) {
326  double r = weight * (warping_fields[c].flow_(j) -
327  warping_fields_init[c].flow_(j));
328  JTJ(6 + j, 6 + j) += weight * weight;
329  JTr(6 + j) += weight * r;
330  rr_reg += r * r;
331  }
332 
333  bool success;
334  Eigen::VectorXd result;
335  std::tie(success, result) = utility::SolveLinearSystemPSD(
336  JTJ, -JTr, /*prefer_sparse=*/false,
337  /*check_symmetric=*/false,
338  /*check_det=*/false, /*check_psd=*/false);
339  Eigen::Vector6d result_pose;
340  result_pose << result.block(0, 0, 6, 1);
341  auto delta = utility::TransformVector6dToMatrix4d(result_pose);
342  pose = delta * pose;
343 
344  for (int j = 0; j < nonrigidval; j++) {
345  warping_fields[c].flow_(j) += result(6 + j);
346  }
347  opt_camera_trajectory.parameters_[c].extrinsic_ = pose;
348 
349 #pragma omp critical(RunNonRigidOptimizer)
350  {
351  residual += r2;
352  residual_reg += rr_reg;
353  }
354  }
355  utility::LogDebug("Residual error : {:.6f}, reg : {:.6f}", residual,
356  residual_reg);
357  SetProxyIntensityForVertex(opt_mesh, images_gray, warping_fields,
358  opt_camera_trajectory,
359  visibility_vertex_to_image, proxy_intensity,
360  option.image_boundary_margin_);
361 
362  if (!option.debug_output_dir_.empty()) {
363  // Save opt_mesh.
364  SetGeometryColorAverage(opt_mesh, images_color, warping_fields,
365  opt_camera_trajectory,
366  visibility_vertex_to_image,
367  option.image_boundary_margin_,
369  std::string file_name = fmt::format(
370  "{}/iter_{}.ply",
371  option.debug_output_dir_ + "/non_rigid/opt_mesh", itr);
372  io::WriteTriangleMesh(file_name, opt_mesh);
373 
374  // Save opt_camera_trajectory.
375  file_name = fmt::format("{}/iter_{}.json",
376  option.debug_output_dir_ +
377  "/non_rigid/opt_camera_trajectory",
378  itr);
379  io::WritePinholeCameraTrajectory(file_name, opt_camera_trajectory);
380 
381  // Save warping_fields.
382  for (size_t i = 0; i < warping_fields.size(); ++i) {
383  file_name = fmt::format(
384  "{}/iter_{}_camera_{}.json",
385  option.debug_output_dir_ + "/non_rigid/warping_fields",
386  itr, i);
387  io::WriteImageWarpingField(file_name, warping_fields[i]);
388  }
389  }
390  }
391 
392  utility::LogDebug("[ColorMapOptimization] Set Mesh Color");
393  SetGeometryColorAverage(opt_mesh, images_color, warping_fields,
394  opt_camera_trajectory, visibility_vertex_to_image,
395  option.image_boundary_margin_,
397 
398  return std::make_pair(opt_mesh, opt_camera_trajectory);
399 }
400 
401 } // namespace color_map
402 } // namespace pipelines
403 } // namespace cloudViewer
filament::Texture::InternalFormat format
int width
int size
std::vector< PCLPointField > fields
int height
core::Tensor result
Definition: VtkUtils.cpp:76
Triangular mesh.
Definition: ecvMesh.h:35
Eigen::Vector3d getVertice(size_t index) const
unsigned int getVerticeSize() const
std::vector< PinholeCameraParameters > parameters_
List of PinholeCameraParameters objects.
The Image class stores image with customizable width, height, num of channels and bytes per channel.
Definition: Image.h:33
bool TestImageBoundary(double u, double v, double inner_margin=0.0) const
Test if coordinate (u, v) is located in the inner_marge of the image.
std::pair< bool, double > FloatValueAt(double u, double v) const
#define LogInfo(...)
Definition: Logging.h:81
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
Definition: Eigen.h:103
Eigen::Matrix< double, 6, 1 > Vector6d
Definition: Eigen.h:107
Eigen::Matrix< double, 14, 1 > Vector14d
Eigen::Matrix< int, 14, 1 > Vector14i
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
bool WriteTriangleMesh(const std::string &filename, const ccMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress)
bool WriteImage(const std::string &filename, const geometry::Image &image, int quality=kCloudViewerImageIODefaultQuality)
bool WriteImageWarpingField(const std::string &filename, const pipelines::color_map::ImageWarpingField &trajectory)
bool WritePinholeCameraTrajectory(const std::string &filename, const camera::PinholeCameraTrajectory &trajectory)
void SetProxyIntensityForVertex(const ccMesh &mesh, const std::vector< geometry::Image > &images_gray, const utility::optional< std::vector< ImageWarpingField >> &warping_fields, const camera::PinholeCameraTrajectory &camera_trajectory, const std::vector< std::vector< int >> &visibility_vertex_to_image, std::vector< double > &proxy_intensity, int image_boundary_margin)
std::pair< ccMesh, camera::PinholeCameraTrajectory > RunNonRigidOptimizer(const ccMesh &mesh, const std::vector< geometry::RGBDImage > &images_rgbd, const camera::PinholeCameraTrajectory &camera_trajectory, const NonRigidOptimizerOption &option)
std::tuple< std::vector< std::vector< int > >, std::vector< std::vector< int > > > CreateVertexAndImageVisibility(const ccMesh &mesh, const std::vector< geometry::Image > &images_depth, const std::vector< geometry::Image > &images_mask, const camera::PinholeCameraTrajectory &camera_trajectory, double maximum_allowable_depth, double depth_threshold_for_visibility_check)
static std::tuple< MatOutType, VecOutType, double > ComputeJTJandJTrNonRigid(std::function< void(int, VecInTypeDouble &, double &, VecInTypeInt &)> f, int iteration_num, int nonrigidval, bool verbose)
std::tuple< std::vector< geometry::Image >, std::vector< geometry::Image >, std::vector< geometry::Image >, std::vector< geometry::Image >, std::vector< geometry::Image > > CreateUtilImagesFromRGBD(const std::vector< geometry::RGBDImage > &images_rgbd)
std::vector< geometry::Image > CreateDepthBoundaryMasks(const std::vector< geometry::Image > &images_depth, double depth_threshold_for_discontinuity_check, int half_dilation_kernel_size_for_discontinuity_map)
void SetGeometryColorAverage(ccMesh &mesh, const std::vector< geometry::Image > &images_color, const utility::optional< std::vector< ImageWarpingField >> &warping_fields, const camera::PinholeCameraTrajectory &camera_trajectory, const std::vector< std::vector< int >> &visibility_vertex_to_image, int image_boundary_margin, int invisible_vertex_color_knn)
static std::vector< ImageWarpingField > CreateWarpingFields(const std::vector< geometry::Image > &images, int number_of_vertical_anchors)
static void ComputeJacobianAndResidualNonRigid(int row, Eigen::Vector14d &J_r, double &r, Eigen::Vector14i &pattern, const ccMesh &mesh, const std::vector< double > &proxy_intensity, const geometry::Image &images_gray, const geometry::Image &images_dx, const geometry::Image &images_dy, const ImageWarpingField &warping_fields, const Eigen::Matrix4d &intrinsic, const Eigen::Matrix4d &extrinsic, const std::vector< int > &visibility_image_to_vertex, const int image_boundary_margin)
bool MakeDirectoryHierarchy(const std::string &directory)
Definition: FileSystem.cpp:499
bool DirectoryExists(const std::string &directory)
Definition: FileSystem.cpp:473
std::tuple< bool, Eigen::VectorXd > SolveLinearSystemPSD(const Eigen::MatrixXd &A, const Eigen::VectorXd &b, bool prefer_sparse=false, bool check_symmetric=false, bool check_det=false, bool check_psd=false)
Function to solve Ax=b.
Definition: Eigen.cpp:21
Eigen::Matrix4d TransformVector6dToMatrix4d(const Eigen::Vector6d &input)
Definition: Eigen.cpp:76
Generic file read and write utility for python interface.
int maximum_iteration_
Number of iterations for optimization steps.