ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PointCloudCluster.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 "ecvPointCloud.h"
9 
10 // CV_CORE_LIB
11 #include <Logging.h>
12 #include <Parallel.h>
13 #include <ScalarField.h>
14 
15 // CV_DB_LIB
16 #include "ecvCone.h"
17 #include "ecvCylinder.h"
18 #include "ecvGenericMesh.h"
19 #include "ecvGenericPointCloud.h"
20 #include "ecvKDTreeFlann.h"
21 #include "ecvMesh.h"
22 #include "ecvPlane.h"
23 #include "ecvSphere.h"
24 #include "ecvTorus.h"
25 
26 #ifdef CV_RANSAC_SUPPORT
27 // PrimitiveShapes/MiscLib
28 #include <ConePrimitiveShape.h>
29 #include <ConePrimitiveShapeConstructor.h>
30 #include <CylinderPrimitiveShape.h>
31 #include <CylinderPrimitiveShapeConstructor.h>
32 #include <PlanePrimitiveShape.h>
33 #include <PlanePrimitiveShapeConstructor.h>
34 #include <RansacShapeDetector.h>
35 #include <SpherePrimitiveShape.h>
36 #include <SpherePrimitiveShapeConstructor.h>
37 #include <TorusPrimitiveShape.h>
38 #include <TorusPrimitiveShapeConstructor.h>
39 #endif
40 
41 #ifdef _OPENMP
42 #include <omp.h>
43 #endif
44 
45 #include <Eigen/Dense>
46 #include <unordered_set>
47 
48 using namespace cloudViewer;
49 
50 std::vector<int> ccPointCloud::ClusterDBSCAN(double eps,
51  size_t min_points,
52  bool print_progress) const {
53  geometry::KDTreeFlann kdtree(*this);
54 
55  // precompute all neighbours
56  utility::LogDebug("Precompute Neighbours");
58  this->size(), "Precompute Neighbours", print_progress);
59  std::vector<std::vector<int>> nbs(this->size());
60 #ifdef _OPENMP
61 #pragma omp parallel for schedule(static) \
62  num_threads(utility::EstimateMaxThreads())
63 #endif
64  for (int idx = 0; idx < int(this->size()); ++idx) {
65  std::vector<double> dists2;
66  kdtree.SearchRadius(getEigenPoint(static_cast<size_t>(idx)), eps,
67  nbs[idx], dists2);
68 
69 #ifdef _OPENMP
70 #pragma omp critical(ClusterDBSCAN)
71 #endif
72  { ++progress_bar; }
73  }
74  utility::LogDebug("Done Precompute Neighbours");
75 
76  // set all labels to undefined (-2)
77  utility::LogDebug("Compute Clusters");
78  progress_bar.reset(this->size(), "Clustering", print_progress);
79  std::vector<int> labels(this->size(), -2);
80  int cluster_label = 0;
81  for (size_t idx = 0; idx < this->size(); ++idx) {
82  if (labels[idx] != -2) { // label is not undefined
83  continue;
84  }
85 
86  // check density
87  if (nbs[idx].size() < min_points) {
88  labels[idx] = -1;
89  continue;
90  }
91 
92  std::unordered_set<int> nbs_next(nbs[idx].begin(), nbs[idx].end());
93  std::unordered_set<int> nbs_visited;
94  nbs_visited.insert(int(idx));
95 
96  labels[idx] = cluster_label;
97  ++progress_bar;
98  while (!nbs_next.empty()) {
99  int nb = *nbs_next.begin();
100  nbs_next.erase(nbs_next.begin());
101  nbs_visited.insert(nb);
102 
103  if (labels[nb] == -1) { // noise label
104  labels[nb] = cluster_label;
105  ++progress_bar;
106  }
107  if (labels[nb] != -2) { // not undefined label
108  continue;
109  }
110  labels[nb] = cluster_label;
111  ++progress_bar;
112 
113  if (nbs[nb].size() >= min_points) {
114  for (int qnb : nbs[nb]) {
115  if (nbs_visited.count(qnb) == 0) {
116  nbs_next.insert(qnb);
117  }
118  }
119  }
120  }
121 
122  cluster_label++;
123  }
124 
125  utility::LogDebug("Done Compute Clusters: {:d}", cluster_label);
126  return labels;
127 }
128 
129 #ifdef CV_RANSAC_SUPPORT
130 
131 std::string geometry::RansacResult::getTypeName() const {
132  if (!this->primitive) {
133  return "";
134  }
135 
136  std::shared_ptr<ccGenericPrimitive> prim =
137  std::static_pointer_cast<ccGenericPrimitive>(this->primitive);
138 
139  if (!prim) {
140  return "";
141  }
142 
143  return prim->getTypeName().toStdString();
144 }
145 
146 unsigned geometry::RansacResult::getDrawingPrecision() const {
147  if (!this->primitive) {
148  return -1;
149  }
150 
151  std::shared_ptr<ccGenericPrimitive> prim =
152  std::static_pointer_cast<ccGenericPrimitive>(this->primitive);
153 
154  if (!prim) {
155  return -1;
156  }
157 
158  return prim->getDrawingPrecision();
159 }
160 
161 bool geometry::RansacResult::setDrawingPrecision(unsigned steps) {
162  if (!this->primitive) {
163  return false;
164  }
165 
166  std::shared_ptr<ccGenericPrimitive> prim =
167  std::static_pointer_cast<ccGenericPrimitive>(this->primitive);
168 
169  if (!prim) {
170  return false;
171  }
172 
173  return prim->setDrawingPrecision(steps);
174 }
175 
176 geometry::RansacResults ccPointCloud::ExecuteRANSAC(
177  const geometry::RansacParams& params,
178  bool print_progress /* = false*/) {
179  geometry::RansacResults group;
180  // consistency check
181  {
182  if (params.primEnabled.size() == 0) {
184  "[ccPointCloud::ExecuteRANSAC] No primitive type "
185  "selected!");
186  return group;
187  }
188  }
189 
190  unsigned count = this->size();
191  bool hasNorms = this->hasNormals();
192  CCVector3 bbMin, bbMax;
193  this->getBoundingBox(bbMin, bbMax);
194  const CCVector3d& globalShift = this->getGlobalShift();
195  double globalScale = this->getGlobalScale();
196  ransac::RansacPointCloud cloud;
197  {
198  try {
199  cloud.reserve(count);
200  } catch (...) {
202  "[ccPointCloud::ExecuteRANSAC] Could not create temporary "
203  "cloud, Not enough memory!");
204  return group;
205  }
206 
207  // default point & normal
208  ransac::RansacPoint Pt;
209  Pt.normal[0] = 0.0;
210  Pt.normal[1] = 0.0;
211  Pt.normal[2] = 0.0;
212  for (unsigned i = 0; i < count; ++i) {
213  const CCVector3* P = this->getPoint(i);
214  Pt.pos[0] = static_cast<float>(P->x);
215  Pt.pos[1] = static_cast<float>(P->y);
216  Pt.pos[2] = static_cast<float>(P->z);
217  if (hasNorms) {
218  const CCVector3& N = this->getPointNormal(i);
219  Pt.normal[0] = static_cast<float>(N.x);
220  Pt.normal[1] = static_cast<float>(N.y);
221  Pt.normal[2] = static_cast<float>(N.z);
222  }
223  Pt.index = i;
224  cloud.push_back(Pt);
225  }
226 
227  // manually set bounding box!
228  Vec3f cbbMin, cbbMax;
229  cbbMin[0] = static_cast<float>(bbMin.x);
230  cbbMin[1] = static_cast<float>(bbMin.y);
231  cbbMin[2] = static_cast<float>(bbMin.z);
232  cbbMax[0] = static_cast<float>(bbMax.x);
233  cbbMax[1] = static_cast<float>(bbMax.y);
234  cbbMax[2] = static_cast<float>(bbMax.z);
235  cloud.setBBox(cbbMin, cbbMax);
236  }
237 
238  RansacShapeDetector::Options ransacOptions;
239  {
240  ransacOptions.m_epsilon = params.epsilon;
241  ransacOptions.m_bitmapEpsilon = params.bitmapEpsilon;
242  ransacOptions.m_normalThresh = static_cast<float>(
243  cos(cloudViewer::DegreesToRadians(params.maxNormalDev_deg)));
244  assert(ransacOptions.m_normalThresh >= 0);
245  ransacOptions.m_probability = params.probability;
246  ransacOptions.m_minSupport = params.supportPoints;
247  }
248  const float scale = cloud.getScale();
249 
250  if (!hasNorms) {
251  cloud.calcNormals(.01f * scale);
252 
253  if (this->reserveTheNormsTable()) {
254  for (unsigned i = 0; i < count; ++i) {
255  Vec3f& Nvi = cloud[i].normal;
257  // normalize the vector in case of
258  Ni.normalize();
259  this->addNorm(Ni);
260  }
261  this->showNormals(true);
262 
263  // currently selected entities appearance may have changed!
264  this->setRedrawFlagRecursive(true);
265  } else {
267  "[ccPointCloud::ExecuteRANSAC] Not enough memory to "
268  "compute normals!");
269  return group;
270  }
271  }
272 
273  RansacShapeDetector detector(ransacOptions); // the detector object
274 
275  const int preserve_detectors_number = 5;
276  bool detectors_enabled[preserve_detectors_number] = {false, false, false,
277  false, false};
278  for (auto object_type : params.primEnabled) {
279  switch (object_type) {
280  case geometry::RansacParams::RPT_PLANE:
281  if (!detectors_enabled[geometry::RansacParams::RPT_PLANE]) {
282  detector.Add(new PlanePrimitiveShapeConstructor());
283  detectors_enabled[geometry::RansacParams::RPT_PLANE] = true;
284  }
285  break;
286  case geometry::RansacParams::RPT_SPHERE:
287  if (!detectors_enabled[geometry::RansacParams::RPT_SPHERE]) {
288  detector.Add(new SpherePrimitiveShapeConstructor());
289  detectors_enabled[geometry::RansacParams::RPT_SPHERE] =
290  true;
291  }
292  break;
293  case geometry::RansacParams::RPT_CYLINDER:
294  if (!detectors_enabled[geometry::RansacParams::RPT_CYLINDER]) {
295  detector.Add(new CylinderPrimitiveShapeConstructor());
296  detectors_enabled[geometry::RansacParams::RPT_CYLINDER] =
297  true;
298  }
299  break;
300  case geometry::RansacParams::RPT_CONE:
301  if (!detectors_enabled[geometry::RansacParams::RPT_CONE]) {
302  detector.Add(new ConePrimitiveShapeConstructor());
303  detectors_enabled[geometry::RansacParams::RPT_CONE] = true;
304  }
305  break;
306  case geometry::RansacParams::RPT_TORUS:
307  if (!detectors_enabled[geometry::RansacParams::RPT_TORUS]) {
308  detector.Add(new TorusPrimitiveShapeConstructor());
309  detectors_enabled[geometry::RansacParams::RPT_TORUS] = true;
310  }
311  break;
312  default:
313  utility::LogWarning("unsupported detector object!");
314  break;
315  }
316  }
317 
318  unsigned remaining = count;
319  typedef std::pair<MiscLib::RefCountPtr<PrimitiveShape>, size_t>
320  DetectedShape;
321  MiscLib::Vector<DetectedShape> shapes; // stores the detected shapes
322 
323  // run detection
324  // returns number of unassigned points
325  // the array shapes is filled with pointers to the detected shapes
326  // the second element per shapes gives the number of points assigned to that
327  // primitive (the support) the points belonging to the first shape
328  // (shapes[0]) have been sorted to the end of pc, i.e. into the range [
329  // pc.size() - shapes[0].second, pc.size() ) the points of shape i are found
330  // in the range [ pc.size() - \sum_{j=0..i} shapes[j].second, pc.size() -
331  // \sum_{j=0..i-1} shapes[j].second )
332  remaining = static_cast<unsigned>(
333  detector.Detect(cloud, 0, cloud.size(), &shapes));
334 
335 #if 0 // def _DEBUG
336  FILE* fp = fopen("RANS_SD_trace.txt", "wt");
337 
338  fprintf(fp, "[Options]\n");
339  fprintf(fp, "epsilon=%f\n", ransacOptions.m_epsilon);
340  fprintf(fp, "bitmap epsilon=%f\n", ransacOptions.m_bitmapEpsilon);
341  fprintf(fp, "normal thresh=%f\n", ransacOptions.m_normalThresh);
342  fprintf(fp, "min support=%i\n", ransacOptions.m_minSupport);
343  fprintf(fp, "probability=%f\n", ransacOptions.m_probability);
344 
345  fprintf(fp, "\n[Statistics]\n");
346  fprintf(fp, "input points=%i\n", count);
347  fprintf(fp, "segmented=%i\n", count - remaining);
348  fprintf(fp, "remaining=%i\n", remaining);
349 
350  if (shapes.size() > 0)
351  {
352  fprintf(fp, "\n[Shapes]\n");
353  for (unsigned i = 0; i < shapes.size(); ++i)
354  {
355  PrimitiveShape* shape = shapes[i].first;
356  size_t shapePointsCount = shapes[i].second;
357 
358  std::string desc;
359  shape->Description(&desc);
360  fprintf(fp, "#%i - %s - %i points\n", i + 1, desc.c_str(), shapePointsCount);
361  }
362  }
363  fclose(fp);
364 #endif
365 
366  if (remaining == count && shapes.size() == 0) {
368  "[ccPointCloud::ExecuteRANSAC] Segmentation failed...");
369  return group;
370  }
371 
372  if (shapes.size() > 0) {
373  unsigned planeCount = 1;
374  unsigned sphereCount = 1;
375  unsigned cylinderCount = 1;
376  unsigned coneCount = 1;
377  unsigned torusCount = 1;
378 
379  for (MiscLib::Vector<DetectedShape>::const_iterator it = shapes.begin();
380  it != shapes.end(); ++it) {
381  const PrimitiveShape* shape = it->first;
382  unsigned shapePointsCount = static_cast<unsigned>(it->second);
383 
384  // too many points?!
385  if (shapePointsCount > count) {
387  "[ccPointCloud::ExecuteRANSAC] Inconsistent result!");
388  break;
389  }
390 
391  if (shapePointsCount < params.supportPoints) {
393  "[ccPointCloud::ExecuteRANSAC] Skipping shape, {:d} "
394  "did not meet minimum point requirement",
395  shapePointsCount);
396  count -= shapePointsCount;
397  continue;
398  }
399 
400  std::string desc;
401  shape->Description(&desc);
402 
403  // points to current shapes last point in cloud
404  const auto shapeCloudIndex = count - 1;
405 
406  // convert detected primitive into a CC primitive type
407  std::shared_ptr<ccGenericPrimitive> prim = nullptr;
408  switch (shape->Identifier()) {
409  case geometry::RansacParams::RPT_PLANE: // plane
410  {
411  const PlanePrimitiveShape* plane =
412  static_cast<const PlanePrimitiveShape*>(shape);
413  Vec3f G = plane->Internal().getPosition();
414  Vec3f N = plane->Internal().getNormal();
415  Vec3f X = plane->getXDim();
416  Vec3f Y = plane->getYDim();
417 
418  // we look for real plane extents
419  float minX, maxX, minY, maxY;
420  for (unsigned j = 0; j < shapePointsCount; ++j) {
421  std::pair<float, float> param;
422  plane->Parameters(cloud[shapeCloudIndex - j].pos,
423  &param);
424  if (j != 0) {
425  if (minX < param.first)
426  minX = param.first;
427  else if (maxX > param.first)
428  maxX = param.first;
429  if (minY < param.second)
430  minY = param.second;
431  else if (maxY > param.second)
432  maxY = param.second;
433  } else {
434  minX = maxX = param.first;
435  minY = maxY = param.second;
436  }
437  }
438 
439  // we recenter plane (as it is not always the case!)
440  float dX = maxX - minX;
441  float dY = maxY - minY;
442  G += X * (minX + dX / 2);
443  G += Y * (minY + dY / 2);
444 
445  // we build matrix from these vectors
446  ccGLMatrix glMat(CCVector3::fromArray(X.getValue()),
447  CCVector3::fromArray(Y.getValue()),
448  CCVector3::fromArray(N.getValue()),
449  CCVector3::fromArray(G.getValue()));
450 
451  // plane primitive
452  prim = std::make_shared<ccPlane>(std::abs(dX), std::abs(dY),
453  &glMat);
454  prim->setSelectionBehavior(ccHObject::SELECTION_FIT_BBOX);
455  prim->enableStippling(true);
456  PointCoordinateType dip = 0.0f;
457  PointCoordinateType dipDir = 0.0f;
459  CCVector3::fromArray(N.getValue()), dip, dipDir);
460  QString dipAndDipDirStr =
462  dip, dipDir);
463  prim->setName("Plane (" + dipAndDipDirStr + ")");
464  planeCount++;
465  } break;
466 
467  case geometry::RansacParams::RPT_SPHERE: // sphere
468  {
469  const SpherePrimitiveShape* sphere =
470  static_cast<const SpherePrimitiveShape*>(shape);
471  float radius = sphere->Internal().Radius();
472  if (radius < params.minRadius ||
473  radius > params.maxRadius) {
474  count -= shapePointsCount;
475  continue;
476  }
477 
478  Vec3f CC = sphere->Internal().Center();
479 
480  // we build matrix from these vectors
481  ccGLMatrix glMat;
482  glMat.setTranslation(CC.getValue());
483  // sphere primitive
484  prim = std::make_shared<ccSphere>(radius, &glMat);
485  prim->setEnabled(true);
486  prim->setName(QString("Sphere (r=%1)").arg(radius, 0, 'f'));
487  sphereCount++;
488  } break;
489 
490  case geometry::RansacParams::RPT_CYLINDER: // cylinder
491  {
492  const CylinderPrimitiveShape* cyl =
493  static_cast<const CylinderPrimitiveShape*>(shape);
494  float radius = cyl->Internal().Radius();
495  if (radius < params.minRadius ||
496  radius > params.maxRadius) {
497  count -= shapePointsCount;
498  continue;
499  }
500 
501  Vec3f G = cyl->Internal().AxisPosition();
502  Vec3f N = cyl->Internal().AxisDirection();
503  Vec3f X = cyl->Internal().AngularDirection();
504  Vec3f Y = N.cross(X);
505 
506  float hMin = cyl->MinHeight();
507  float hMax = cyl->MaxHeight();
508  float h = hMax - hMin;
509  G += N * (hMin + h / 2);
510 
511  // we build matrix from these vectors
512  ccGLMatrix glMat(CCVector3::fromArray(X.getValue()),
513  CCVector3::fromArray(Y.getValue()),
514  CCVector3::fromArray(N.getValue()),
515  CCVector3::fromArray(G.getValue()));
516 
517  // cylinder primitive
518  prim = std::make_shared<ccCylinder>(radius, h, &glMat);
519  prim->setEnabled(true);
520  prim->setName(QString("Cylinder (r=%1/h=%2)")
521  .arg(radius, 0, 'f')
522  .arg(h, 0, 'f'));
523  cylinderCount++;
524  } break;
525 
526  case geometry::RansacParams::RPT_CONE: // cone
527  {
528  const ConePrimitiveShape* cone =
529  static_cast<const ConePrimitiveShape*>(shape);
530  Vec3f CC = cone->Internal().Center();
531  Vec3f CA = cone->Internal().AxisDirection();
532  float alpha = cone->Internal().Angle();
533 
534  // compute max height
535  Vec3f minP, maxP;
536  float minHeight, maxHeight;
537  minP = maxP = cloud[shapeCloudIndex].pos;
538  minHeight = maxHeight =
539  cone->Internal().Height(cloud[shapeCloudIndex].pos);
540  for (size_t j = 1; j < shapePointsCount; ++j) {
541  float h = cone->Internal().Height(
542  cloud[shapeCloudIndex - j].pos);
543  if (h < minHeight) {
544  minHeight = h;
545  minP = cloud[shapeCloudIndex - j].pos;
546  } else if (h > maxHeight) {
547  maxHeight = h;
548  maxP = cloud[shapeCloudIndex - j].pos;
549  }
550  }
551 
552  float minRadius = tan(alpha) * minHeight;
553  float maxRadius = tan(alpha) * maxHeight;
554  if (minRadius < params.minRadius ||
555  maxRadius > params.maxRadius) {
556  count -= shapePointsCount;
557  continue;
558  }
559 
560  // let's build the cone primitive
561  {
562  // the bottom should be the largest part so we inverse
563  // the axis direction
564  CCVector3 Z = -CCVector3::fromArray(CA.getValue());
565  Z.normalize();
566 
567  // the center is halfway between the min and max height
568  float midHeight = (minHeight + maxHeight) / 2;
570  (CC + CA * midHeight).getValue());
571 
572  // radial axis
574  (maxP - (CC + maxHeight * CA)).getValue());
575  X.normalize();
576 
577  // orthogonal radial axis
578  CCVector3 Y = Z * X;
579 
580  // we build the transformation matrix from these vectors
581  ccGLMatrix glMat(X, Y, Z, C);
582 
583  // eventually create the cone primitive
584  prim = std::make_shared<ccCone>(maxRadius, minRadius,
585  maxHeight - minHeight,
586  0, 0, &glMat);
587  prim->setEnabled(true);
588  prim->setName(
589  QString("Cone (alpha=%1/h=%2)")
590  .arg(alpha, 0, 'f')
591  .arg(maxHeight - minHeight, 0, 'f'));
592  coneCount++;
593  }
594 
595  } break;
596 
597  case geometry::RansacParams::RPT_TORUS: // torus
598  {
599  const TorusPrimitiveShape* torus =
600  static_cast<const TorusPrimitiveShape*>(shape);
601  if (torus->Internal().IsAppleShaped()) {
603  "[ccPointCloud::ExecuteRANSAC] Apple-shaped "
604  "torus are not handled by CloudViewer!");
605  } else {
606  Vec3f CC = torus->Internal().Center();
607  Vec3f CA = torus->Internal().AxisDirection();
608  float minRadius = torus->Internal().MinorRadius();
609  float maxRadius = torus->Internal().MajorRadius();
610 
611  if (minRadius < params.minRadius ||
612  maxRadius > params.maxRadius) {
613  count -= shapePointsCount;
614  continue;
615  }
616 
617  CCVector3 Z = CCVector3::fromArray(CA.getValue());
618  CCVector3 C = CCVector3::fromArray(CC.getValue());
619  // construct remaining of base
620  CCVector3 X = Z.orthogonal();
621  CCVector3 Y = Z * X;
622 
623  // we build matrix from these vectors
624  ccGLMatrix glMat(X, Y, Z, C);
625 
626  // torus primitive
627  prim = std::make_shared<ccTorus>(
628  maxRadius - minRadius, maxRadius + minRadius,
629  M_PI * 2.0, false, 0, &glMat);
630  prim->setEnabled(true);
631  prim->setName(QString("Torus (r=%1/R=%2)")
632  .arg(minRadius, 0, 'f')
633  .arg(maxRadius, 0, 'f'));
634  torusCount++;
635  }
636 
637  } break;
638  }
639 
640  // is there a primitive to add to part cloud?
641  if (prim) {
642  geometry::RansacResult result;
643  // for solving phongShader bugs
644  prim->clearTriNormals();
645  prim->applyGLTransformation_recursive();
646  prim->setVisible(true);
647  if (params.randomColor) {
649  prim->setColor(col);
650  prim->showColors(true);
651  }
652  result.primitive = prim;
653 
654  // new cloud for sub-part
655  result.indices.resize(shapePointsCount);
656  {
657  for (unsigned j = 0; j < shapePointsCount; ++j) {
658  result.indices[j] = static_cast<size_t>(
659  cloud[shapeCloudIndex - j].index);
660  }
661  }
662  group.push_back(result);
663  }
664 
665  count -= shapePointsCount;
666  }
667 
668  if (print_progress) {
670  "[ccPointCloud::ExecuteRANSAC] Detect Plane Shape number: "
671  "{:d}",
672  planeCount - 1);
674  "[ccPointCloud::ExecuteRANSAC] Detect Sphere Shape number: "
675  "{:d}",
676  sphereCount - 1);
678  "[ccPointCloud::ExecuteRANSAC] Detect Cylinder Shape "
679  "number: {:d}",
680  cylinderCount - 1);
682  "[ccPointCloud::ExecuteRANSAC] Detect Cone Shape number: "
683  "{:d}",
684  coneCount - 1);
686  "[ccPointCloud::ExecuteRANSAC] Detect Torus Shape number: "
687  "{:d}",
688  torusCount - 1);
689  }
690  }
691 
692  if (print_progress) {
694  "[ccPointCloud::ExecuteRANSAC] Total Shape Detection "
695  "Instances: {:d}",
696  group.size());
697  }
698 
699  return group;
700 }
701 #endif
constexpr double M_PI
Pi.
Definition: CVConst.h:19
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int size
utility::CountingProgressReporter * progress_bar
int count
void * X
Definition: SmallVector.cpp:45
core::Tensor result
Definition: VtkUtils.cpp:76
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Vector3Tpl orthogonal() const
Returns a normalized vector which is orthogonal to this one.
Definition: CVGeom.h:433
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
@ SELECTION_FIT_BBOX
Definition: ecvHObject.h:607
static QString ConvertDipAndDipDirToString(PointCoordinateType dip_deg, PointCoordinateType dipDir_deg)
Converts geological 'dip direction & dip' parameters to a string.
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
std::vector< int > ClusterDBSCAN(double eps, size_t min_points, bool print_progress=false) const
Cluster ccPointCloud using the DBSCAN algorithm Ester et al., "A Density-Based Algorithm for Discover...
KDTree with FLANN for nearest neighbor search.
int SearchRadius(const T &query, double radius, std::vector< int > &indices, std::vector< double > &distance2) const
static Rgb Random(bool lightOnly=true)
Generates a random color.
RGB color structure.
Definition: ecvColorTypes.h:49
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
Generic file read and write utility for python interface.
float DegreesToRadians(int degrees)
Convert degrees to radians.
Definition: CVMath.h:98