ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ScalableTSDFVolume.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 <Logging.h>
11 #include <ecvPointCloud.h>
12 
13 #include <unordered_set>
14 
17 
18 namespace cloudViewer {
19 namespace pipelines {
20 namespace integration {
21 
22 using namespace cloudViewer;
23 
25  double sdf_trunc,
26  TSDFVolumeColorType color_type,
27  int volume_unit_resolution /* = 16*/,
28  int depth_sampling_stride /* = 4*/)
29  : TSDFVolume(voxel_length, sdf_trunc, color_type),
30  volume_unit_resolution_(volume_unit_resolution),
31  volume_unit_length_(voxel_length * volume_unit_resolution),
32  depth_sampling_stride_(depth_sampling_stride) {}
33 
35 
37 
40  const camera::PinholeCameraIntrinsic &intrinsic,
41  const Eigen::Matrix4d &extrinsic) {
42  if ((image.depth_.num_of_channels_ != 1) ||
43  (image.depth_.bytes_per_channel_ != 4) ||
45  image.color_.num_of_channels_ != 3) ||
47  image.color_.bytes_per_channel_ != 1) ||
49  image.color_.num_of_channels_ != 1) ||
51  image.color_.bytes_per_channel_ != 4)) {
53  "[ScalableTSDFVolume::Integrate] Unsupported image format.");
54  }
55  if ((image.depth_.width_ != intrinsic.width_) ||
56  (image.depth_.height_ != intrinsic.height_)) {
58  "[ScalableTSDFVolume::Integrate] depth image size is ({} x "
59  "{}), "
60  "but got ({} x {}) from intrinsic.",
61  image.depth_.width_, image.depth_.height_, intrinsic.width_,
62  intrinsic.height_);
63  }
65  (image.color_.width_ != intrinsic.width_ ||
66  image.color_.height_ != intrinsic.height_)) {
68  "[ScalableTSDFVolume::Integrate] color image size is ({} x "
69  "{}), "
70  "but got ({} x {}) from intrinsic.",
71  image.color_.width_, image.color_.height_, intrinsic.width_,
72  intrinsic.height_);
73  }
74 
75  auto depth2cameradistance =
77  intrinsic);
78  auto pointcloud = ccPointCloud::CreateFromDepthImage(
79  image.depth_, intrinsic, extrinsic, 1000.0, 1000.0,
81  std::unordered_set<Eigen::Vector3i, utility::hash_eigen<Eigen::Vector3i>>
82  touched_volume_units_;
83  for (const auto &point : pointcloud->getEigenPoints()) {
84  auto min_bound = LocateVolumeUnit(
85  point - Eigen::Vector3d(sdf_trunc_, sdf_trunc_, sdf_trunc_));
86  auto max_bound = LocateVolumeUnit(
87  point + Eigen::Vector3d(sdf_trunc_, sdf_trunc_, sdf_trunc_));
88  for (auto x = min_bound(0); x <= max_bound(0); x++) {
89  for (auto y = min_bound(1); y <= max_bound(1); y++) {
90  for (auto z = min_bound(2); z <= max_bound(2); z++) {
91  auto loc = Eigen::Vector3i(x, y, z);
92  if (touched_volume_units_.find(loc) ==
93  touched_volume_units_.end()) {
94  touched_volume_units_.insert(loc);
95  auto volume = OpenVolumeUnit(Eigen::Vector3i(x, y, z));
96  volume->IntegrateWithDepthToCameraDistanceMultiplier(
97  image, intrinsic, extrinsic,
98  *depth2cameradistance);
99  }
100  }
101  }
102  }
103  }
104 }
105 
106 std::shared_ptr<ccPointCloud> ScalableTSDFVolume::ExtractPointCloud() {
107  auto pointcloud = std::make_shared<ccPointCloud>();
108  double half_voxel_length = voxel_length_ * 0.5;
109  float w0, w1, f0, f1;
110  Eigen::Vector3f c0, c1;
111  for (const auto &unit : volume_units_) {
112  if (unit.second.volume_) {
113  const auto &volume0 = *unit.second.volume_;
114  const auto &index0 = unit.second.index_;
115  for (int x = 0; x < volume0.resolution_; x++) {
116  for (int y = 0; y < volume0.resolution_; y++) {
117  for (int z = 0; z < volume0.resolution_; z++) {
118  Eigen::Vector3i idx0(x, y, z);
119  w0 = volume0.voxels_[volume0.IndexOf(idx0)].weight_;
120  f0 = volume0.voxels_[volume0.IndexOf(idx0)].tsdf_;
122  c0 = volume0.voxels_[volume0.IndexOf(idx0)]
123  .color_.cast<float>();
124  if (w0 != 0.0f && f0 < 0.98f && f0 >= -0.98f) {
125  Eigen::Vector3d p0 =
126  Eigen::Vector3d(half_voxel_length +
127  voxel_length_ * x,
128  half_voxel_length +
129  voxel_length_ * y,
130  half_voxel_length +
131  voxel_length_ * z) +
132  index0.cast<double>() * volume_unit_length_;
133  for (int i = 0; i < 3; i++) {
134  Eigen::Vector3d p1 = p0;
135  Eigen::Vector3i idx1 = idx0;
136  Eigen::Vector3i index1 = index0;
137  p1(i) += voxel_length_;
138  idx1(i) += 1;
139  if (idx1(i) < volume0.resolution_) {
140  w1 = volume0.voxels_[volume0.IndexOf(idx1)]
141  .weight_;
142  f1 = volume0.voxels_[volume0.IndexOf(idx1)]
143  .tsdf_;
144  if (color_type_ !=
146  c1 = volume0.voxels_[volume0.IndexOf(
147  idx1)]
148  .color_.cast<float>();
149  } else {
150  idx1(i) -= volume0.resolution_;
151  index1(i) += 1;
152  auto unit_itr = volume_units_.find(index1);
153  if (unit_itr == volume_units_.end()) {
154  w1 = 0.0f;
155  f1 = 0.0f;
156  } else {
157  const auto &volume1 =
158  *unit_itr->second.volume_;
159  w1 = volume1.voxels_[volume1.IndexOf(
160  idx1)]
161  .weight_;
162  f1 = volume1.voxels_[volume1.IndexOf(
163  idx1)]
164  .tsdf_;
165  if (color_type_ !=
167  c1 = volume1.voxels_
168  [volume1.IndexOf(idx1)]
169  .color_
170  .cast<float>();
171  }
172  }
173  if (w1 != 0.0f && f1 < 0.98f && f1 >= -0.98f &&
174  f0 * f1 < 0) {
175  float r0 = std::fabs(f0);
176  float r1 = std::fabs(f1);
177  Eigen::Vector3d p = p0;
178  p(i) = (p0(i) * r1 + p1(i) * r0) /
179  (r0 + r1);
180 
181  pointcloud->addEigenPoint(p);
182  if (!pointcloud->hasColors())
183  if (!pointcloud->reserveTheRGBTable())
184  assert(false);
185 
186  if (color_type_ ==
188  pointcloud->addEigenColor(
189  ((c0 * r1 + c1 * r0) /
190  (r0 + r1) / 255.0f)
191  .cast<double>());
192  } else if (color_type_ ==
194  pointcloud->addEigenColor(
195  ((c0 * r1 + c1 * r0) /
196  (r0 + r1))
197  .cast<double>());
198  }
199 
200  // has_normal
201  if (!pointcloud->hasNormals() &&
202  !pointcloud->reserveTheNormsTable()) {
203  assert(false);
204  }
205 
206  pointcloud->addEigenNorm(GetNormalAt(p));
207  }
208  }
209  }
210  }
211  }
212  }
213  }
214  }
215  return pointcloud;
216 }
217 
218 std::shared_ptr<ccMesh> ScalableTSDFVolume::ExtractTriangleMesh() {
219  // implementation of marching cubes, based on
220  // http://paulbourke.net/geometry/polygonise/
221  ccPointCloud *baseVertices = new ccPointCloud("vertices");
222  assert(baseVertices);
223  baseVertices->setEnabled(false);
224  // DGM: no need to lock it as it is only used by one mesh!
225  baseVertices->setLocked(false);
226  auto mesh = std::make_shared<ccMesh>(baseVertices);
227  mesh->addChild(baseVertices);
228 
229  double half_voxel_length = voxel_length_ * 0.5;
230  std::unordered_map<
232  std::equal_to<Eigen::Vector4i>,
233  Eigen::aligned_allocator<std::pair<const Eigen::Vector4i, int>>>
234  edgeindex_to_vertexindex;
235 
236  int edge_to_index[12];
237  for (const auto &unit : volume_units_) {
238  if (unit.second.volume_) {
239  const auto &volume0 = *unit.second.volume_;
240  const auto &index0 = unit.second.index_;
241  for (int x = 0; x < volume0.resolution_; x++) {
242  for (int y = 0; y < volume0.resolution_; y++) {
243  for (int z = 0; z < volume0.resolution_; z++) {
244  Eigen::Vector3i idx0(x, y, z);
245  int cube_index = 0;
246  float w[8];
247  float f[8];
248  Eigen::Vector3d c[8];
249  for (int i = 0; i < 8; i++) {
250  Eigen::Vector3i index1 = index0;
251  Eigen::Vector3i idx1 = idx0 + shift[i];
252  if (idx1(0) < volume_unit_resolution_ &&
253  idx1(1) < volume_unit_resolution_ &&
254  idx1(2) < volume_unit_resolution_) {
255  w[i] = volume0.voxels_[volume0.IndexOf(idx1)]
256  .weight_;
257  f[i] = volume0.voxels_[volume0.IndexOf(idx1)]
258  .tsdf_;
260  c[i] = volume0.voxels_[volume0.IndexOf(
261  idx1)]
262  .color_.cast<double>() /
263  255.0;
264  else if (color_type_ ==
266  c[i] = volume0.voxels_[volume0.IndexOf(
267  idx1)]
268  .color_.cast<double>();
269  } else {
270  for (int j = 0; j < 3; j++) {
271  if (idx1(j) >= volume_unit_resolution_) {
272  idx1(j) -= volume_unit_resolution_;
273  index1(j) += 1;
274  }
275  }
276  auto unit_itr1 = volume_units_.find(index1);
277  if (unit_itr1 == volume_units_.end()) {
278  w[i] = 0.0f;
279  f[i] = 0.0f;
280  } else {
281  const auto &volume1 =
282  *unit_itr1->second.volume_;
283  w[i] = volume1.voxels_[volume1.IndexOf(
284  idx1)]
285  .weight_;
286  f[i] = volume1.voxels_[volume1.IndexOf(
287  idx1)]
288  .tsdf_;
289  if (color_type_ ==
291  c[i] = volume1.voxels_[volume1.IndexOf(
292  idx1)]
293  .color_.cast<double>() /
294  255.0;
295  else if (color_type_ ==
297  c[i] = volume1.voxels_[volume1.IndexOf(
298  idx1)]
299  .color_.cast<double>();
300  }
301  }
302  if (w[i] == 0.0f) {
303  cube_index = 0;
304  break;
305  } else {
306  if (f[i] < 0.0f) {
307  cube_index |= (1 << i);
308  }
309  }
310  }
311  if (cube_index == 0 || cube_index == 255) {
312  continue;
313  }
314  for (int i = 0; i < 12; i++) {
315  if (edge_table[cube_index] & (1 << i)) {
316  Eigen::Vector4i edge_index =
317  Eigen::Vector4i(index0(0), index0(1),
318  index0(2), 0) *
320  Eigen::Vector4i(x, y, z, 0) +
321  edge_shift[i];
322  if (edgeindex_to_vertexindex.find(edge_index) ==
323  edgeindex_to_vertexindex.end()) {
324  edge_to_index[i] =
325  (int)baseVertices->size();
326  edgeindex_to_vertexindex[edge_index] =
327  (int)baseVertices->size();
328  Eigen::Vector3d pt(
329  half_voxel_length +
330  voxel_length_ *
331  edge_index(0),
332  half_voxel_length +
333  voxel_length_ *
334  edge_index(1),
335  half_voxel_length +
336  voxel_length_ *
337  edge_index(2));
338  double f0 = std::abs(
339  (double)f[edge_to_vert[i][0]]);
340  double f1 = std::abs(
341  (double)f[edge_to_vert[i][1]]);
342  pt(edge_index(3)) +=
343  f0 * voxel_length_ / (f0 + f1);
344  baseVertices->addEigenPoint(pt);
345  if (color_type_ !=
347  const auto &c0 = c[edge_to_vert[i][0]];
348  const auto &c1 = c[edge_to_vert[i][1]];
349 
350  if (!baseVertices->hasColors())
351  if (!baseVertices
352  ->reserveTheRGBTable())
353  assert(false);
354  baseVertices->addEigenColor(
355  (f1 * c0 + f0 * c1) /
356  (f0 + f1));
357  }
358  } else {
359  edge_to_index[i] = edgeindex_to_vertexindex
360  [edge_index];
361  }
362  }
363  }
364  for (int i = 0; tri_table[cube_index][i] != -1;
365  i += 3) {
366  mesh->addTriangle(Eigen::Vector3i(
367  edge_to_index[tri_table[cube_index][i]],
368  edge_to_index[tri_table[cube_index][i + 2]],
369  edge_to_index[tri_table[cube_index]
370  [i + 1]]));
371  }
372  }
373  }
374  }
375  }
376  }
377 
378  // do some cleaning
379  {
380  baseVertices->shrinkToFit();
381  mesh->shrinkToFit();
382  NormsIndexesTableType *normals = mesh->getTriNormsTable();
383  if (normals) {
384  normals->shrink_to_fit();
385  }
386  }
387  return mesh;
388 }
389 
390 std::shared_ptr<ccPointCloud> ScalableTSDFVolume::ExtractVoxelPointCloud() {
391  auto voxel = std::make_shared<ccPointCloud>();
392  for (auto &unit : volume_units_) {
393  if (unit.second.volume_) {
394  auto v = unit.second.volume_->ExtractVoxelPointCloud();
395  *voxel += *v;
396  }
397  }
398  return voxel;
399 }
400 
401 std::shared_ptr<UniformTSDFVolume> ScalableTSDFVolume::OpenVolumeUnit(
402  const Eigen::Vector3i &index) {
403  auto &unit = volume_units_[index];
404  if (!unit.volume_) {
405  unit.volume_.reset(new UniformTSDFVolume(
407  color_type_, index.cast<double>() * volume_unit_length_));
408  unit.index_ = index;
409  }
410  return unit.volume_;
411 }
412 
413 Eigen::Vector3d ScalableTSDFVolume::GetNormalAt(const Eigen::Vector3d &p) {
414  Eigen::Vector3d n;
415  const double half_gap = 0.99 * voxel_length_;
416  for (int i = 0; i < 3; i++) {
417  Eigen::Vector3d p0 = p;
418  p0(i) -= half_gap;
419  Eigen::Vector3d p1 = p;
420  p1(i) += half_gap;
421  n(i) = GetTSDFAt(p1) - GetTSDFAt(p0);
422  }
423  return n.normalized();
424 }
425 
426 double ScalableTSDFVolume::GetTSDFAt(const Eigen::Vector3d &p) {
427  Eigen::Vector3d p_locate =
428  p - Eigen::Vector3d(0.5, 0.5, 0.5) * voxel_length_;
429  Eigen::Vector3i index0 = LocateVolumeUnit(p_locate);
430  auto unit_itr = volume_units_.find(index0);
431  if (unit_itr == volume_units_.end()) {
432  return 0.0;
433  }
434  const auto &volume0 = *unit_itr->second.volume_;
435  Eigen::Vector3i idx0;
436  Eigen::Vector3d p_grid =
437  (p_locate - index0.cast<double>() * volume_unit_length_) /
439  for (int i = 0; i < 3; i++) {
440  idx0(i) = (int)std::floor(p_grid(i));
441  if (idx0(i) < 0) idx0(i) = 0;
442  if (idx0(i) >= volume_unit_resolution_)
443  idx0(i) = volume_unit_resolution_ - 1;
444  }
445  Eigen::Vector3d r = p_grid - idx0.cast<double>();
446  float f[8];
447  for (int i = 0; i < 8; i++) {
448  Eigen::Vector3i index1 = index0;
449  Eigen::Vector3i idx1 = idx0 + shift[i];
450  if (idx1(0) < volume_unit_resolution_ &&
451  idx1(1) < volume_unit_resolution_ &&
452  idx1(2) < volume_unit_resolution_) {
453  f[i] = volume0.voxels_[volume0.IndexOf(idx1)].tsdf_;
454  } else {
455  for (int j = 0; j < 3; j++) {
456  if (idx1(j) >= volume_unit_resolution_) {
457  idx1(j) -= volume_unit_resolution_;
458  index1(j) += 1;
459  }
460  }
461  auto unit_itr1 = volume_units_.find(index1);
462  if (unit_itr1 == volume_units_.end()) {
463  f[i] = 0.0f;
464  } else {
465  const auto &volume1 = *unit_itr1->second.volume_;
466  f[i] = volume1.voxels_[volume1.IndexOf(idx1)].tsdf_;
467  }
468  }
469  }
470  return (1 - r(0)) * ((1 - r(1)) * ((1 - r(2)) * f[0] + r(2) * f[4]) +
471  r(1) * ((1 - r(2)) * f[3] + r(2) * f[7])) +
472  r(0) * ((1 - r(1)) * ((1 - r(2)) * f[1] + r(2) * f[5]) +
473  r(1) * ((1 - r(2)) * f[2] + r(2) * f[6]));
474 }
475 
476 } // namespace integration
477 } // namespace pipelines
478 
479 } // namespace cloudViewer
std::shared_ptr< core::Tensor > image
Array of compressed 3D normals (single index)
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.)
static std::shared_ptr< ccPointCloud > CreateFromDepthImage(const cloudViewer::geometry::Image &depth, const cloudViewer::camera::PinholeCameraIntrinsic &intrinsic, const Eigen::Matrix4d &extrinsic=Eigen::Matrix4d::Identity(), double depth_scale=1000.0, double depth_trunc=1000.0, int stride=1, bool project_valid_depth_only=true)
Factory function to create a pointcloud from a depth image and a camera model.
void addEigenColor(const Eigen::Vector3d &color)
bool hasColors() const override
Returns whether colors are enabled or not.
void shrinkToFit()
Removes unused capacity.
void addEigenPoint(const Eigen::Vector3d &point)
unsigned size() const override
Definition: PointCloudTpl.h:38
Contains the pinhole camera intrinsic parameters.
static std::shared_ptr< Image > CreateDepthToCameraDistanceMultiplierFloatImage(const camera::PinholeCameraIntrinsic &intrinsic)
RGBDImage is for a pair of registered color and depth images,.
Definition: RGBDImage.h:27
void Integrate(const geometry::RGBDImage &image, const camera::PinholeCameraIntrinsic &intrinsic, const Eigen::Matrix4d &extrinsic) override
Function to integrate an RGB-D image into the volume.
std::shared_ptr< ccPointCloud > ExtractVoxelPointCloud()
Debug function to extract the voxel data into a point cloud.
std::shared_ptr< ccMesh > ExtractTriangleMesh() override
Function to extract a triangle mesh, using the marching cubes algorithm. (https://en....
ScalableTSDFVolume(double voxel_length, double sdf_trunc, TSDFVolumeColorType color_type, int volume_unit_resolution=16, int depth_sampling_stride=4)
void Reset() override
Function to reset the TSDFVolume.
std::shared_ptr< ccPointCloud > ExtractPointCloud() override
Function to extract a point cloud with normals.
std::unordered_map< Eigen::Vector3i, VolumeUnit, utility::hash_eigen< Eigen::Vector3i > > volume_units_
Base class of the Truncated Signed Distance Function (TSDF) volume.
Definition: TSDFVolume.h:43
double sdf_trunc_
Truncation value for signed distance function (SDF).
Definition: TSDFVolume.h:78
double voxel_length_
Length of the voxel in meters.
Definition: TSDFVolume.h:76
TSDFVolumeColorType color_type_
Color type of the TSDF volume.
Definition: TSDFVolume.h:80
UniformTSDFVolume implements the classic TSDF volume with uniform voxel grid (Curless and Levoy 1996)...
double normals[3]
#define LogError(...)
Definition: Logging.h:60
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
Eigen::Matrix< Index, 4, 1 > Vector4i
Definition: knncpp.h:31
Definition: lsd.c:149