26 #ifdef CV_RANSAC_SUPPORT
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>
45 #include <Eigen/Dense>
46 #include <unordered_set>
52 bool print_progress)
const {
58 this->
size(),
"Precompute Neighbours", print_progress);
59 std::vector<std::vector<int>> nbs(this->
size());
61 #pragma omp parallel for schedule(static) \
62 num_threads(utility::EstimateMaxThreads())
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,
70 #pragma omp critical(ClusterDBSCAN)
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) {
87 if (nbs[idx].
size() < min_points) {
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));
96 labels[idx] = cluster_label;
98 while (!nbs_next.empty()) {
99 int nb = *nbs_next.begin();
100 nbs_next.erase(nbs_next.begin());
101 nbs_visited.insert(nb);
103 if (labels[nb] == -1) {
104 labels[nb] = cluster_label;
107 if (labels[nb] != -2) {
110 labels[nb] = cluster_label;
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);
129 #ifdef CV_RANSAC_SUPPORT
131 std::string geometry::RansacResult::getTypeName()
const {
132 if (!this->primitive) {
136 std::shared_ptr<ccGenericPrimitive> prim =
137 std::static_pointer_cast<ccGenericPrimitive>(this->primitive);
143 return prim->getTypeName().toStdString();
146 unsigned geometry::RansacResult::getDrawingPrecision()
const {
147 if (!this->primitive) {
151 std::shared_ptr<ccGenericPrimitive> prim =
152 std::static_pointer_cast<ccGenericPrimitive>(this->primitive);
158 return prim->getDrawingPrecision();
161 bool geometry::RansacResult::setDrawingPrecision(
unsigned steps) {
162 if (!this->primitive) {
166 std::shared_ptr<ccGenericPrimitive> prim =
167 std::static_pointer_cast<ccGenericPrimitive>(this->primitive);
173 return prim->setDrawingPrecision(steps);
176 geometry::RansacResults ccPointCloud::ExecuteRANSAC(
177 const geometry::RansacParams& params,
178 bool print_progress ) {
179 geometry::RansacResults group;
182 if (params.primEnabled.size() == 0) {
184 "[ccPointCloud::ExecuteRANSAC] No primitive type "
191 bool hasNorms = this->hasNormals();
193 this->getBoundingBox(bbMin, bbMax);
194 const CCVector3d& globalShift = this->getGlobalShift();
195 double globalScale = this->getGlobalScale();
196 ransac::RansacPointCloud cloud;
199 cloud.reserve(
count);
202 "[ccPointCloud::ExecuteRANSAC] Could not create temporary "
203 "cloud, Not enough memory!");
208 ransac::RansacPoint Pt;
212 for (
unsigned i = 0; i <
count; ++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);
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);
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);
238 RansacShapeDetector::Options ransacOptions;
240 ransacOptions.m_epsilon = params.epsilon;
241 ransacOptions.m_bitmapEpsilon = params.bitmapEpsilon;
242 ransacOptions.m_normalThresh =
static_cast<float>(
244 assert(ransacOptions.m_normalThresh >= 0);
245 ransacOptions.m_probability = params.probability;
246 ransacOptions.m_minSupport = params.supportPoints;
248 const float scale = cloud.getScale();
251 cloud.calcNormals(.01f * scale);
253 if (this->reserveTheNormsTable()) {
254 for (
unsigned i = 0; i <
count; ++i) {
255 Vec3f& Nvi = cloud[i].normal;
261 this->showNormals(
true);
264 this->setRedrawFlagRecursive(
true);
267 "[ccPointCloud::ExecuteRANSAC] Not enough memory to "
273 RansacShapeDetector detector(ransacOptions);
275 const int preserve_detectors_number = 5;
276 bool detectors_enabled[preserve_detectors_number] = {
false,
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;
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] =
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] =
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;
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;
318 unsigned remaining =
count;
319 typedef std::pair<MiscLib::RefCountPtr<PrimitiveShape>,
size_t>
321 MiscLib::Vector<DetectedShape> shapes;
332 remaining =
static_cast<unsigned>(
333 detector.Detect(cloud, 0, cloud.size(), &shapes));
336 FILE* fp = fopen(
"RANS_SD_trace.txt",
"wt");
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);
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);
350 if (shapes.size() > 0)
352 fprintf(fp,
"\n[Shapes]\n");
353 for (
unsigned i = 0; i < shapes.size(); ++i)
355 PrimitiveShape* shape = shapes[i].first;
356 size_t shapePointsCount = shapes[i].second;
359 shape->Description(&desc);
360 fprintf(fp,
"#%i - %s - %i points\n", i + 1, desc.c_str(), shapePointsCount);
366 if (remaining ==
count && shapes.size() == 0) {
368 "[ccPointCloud::ExecuteRANSAC] Segmentation failed...");
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;
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);
385 if (shapePointsCount >
count) {
387 "[ccPointCloud::ExecuteRANSAC] Inconsistent result!");
391 if (shapePointsCount < params.supportPoints) {
393 "[ccPointCloud::ExecuteRANSAC] Skipping shape, {:d} "
394 "did not meet minimum point requirement",
396 count -= shapePointsCount;
401 shape->Description(&desc);
404 const auto shapeCloudIndex =
count - 1;
407 std::shared_ptr<ccGenericPrimitive> prim =
nullptr;
408 switch (shape->Identifier()) {
409 case geometry::RansacParams::RPT_PLANE:
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();
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,
425 if (minX < param.first)
427 else if (maxX > param.first)
429 if (minY < param.second)
431 else if (maxY > param.second)
434 minX = maxX = param.first;
435 minY = maxY = param.second;
440 float dX = maxX - minX;
441 float dY = maxY - minY;
442 G +=
X * (minX + dX / 2);
443 G += Y * (minY + dY / 2);
452 prim = std::make_shared<ccPlane>(std::abs(dX), std::abs(dY),
455 prim->enableStippling(
true);
460 QString dipAndDipDirStr =
463 prim->setName(
"Plane (" + dipAndDipDirStr +
")");
467 case geometry::RansacParams::RPT_SPHERE:
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;
478 Vec3f CC = sphere->Internal().Center();
484 prim = std::make_shared<ccSphere>(radius, &glMat);
485 prim->setEnabled(
true);
486 prim->setName(QString(
"Sphere (r=%1)").arg(radius, 0,
'f'));
490 case geometry::RansacParams::RPT_CYLINDER:
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;
501 Vec3f G = cyl->Internal().AxisPosition();
502 Vec3f N = cyl->Internal().AxisDirection();
503 Vec3f
X = cyl->Internal().AngularDirection();
504 Vec3f Y = N.cross(
X);
506 float hMin = cyl->MinHeight();
507 float hMax = cyl->MaxHeight();
508 float h = hMax - hMin;
509 G += N * (hMin + h / 2);
518 prim = std::make_shared<ccCylinder>(radius, h, &glMat);
519 prim->setEnabled(
true);
520 prim->setName(QString(
"Cylinder (r=%1/h=%2)")
526 case geometry::RansacParams::RPT_CONE:
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();
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);
545 minP = cloud[shapeCloudIndex - j].pos;
546 }
else if (h > maxHeight) {
548 maxP = cloud[shapeCloudIndex - j].pos;
552 float minRadius = tan(alpha) * minHeight;
553 float maxRadius = tan(alpha) * maxHeight;
554 if (minRadius < params.minRadius ||
555 maxRadius > params.maxRadius) {
556 count -= shapePointsCount;
568 float midHeight = (minHeight + maxHeight) / 2;
570 (CC + CA * midHeight).getValue());
574 (maxP - (CC + maxHeight * CA)).getValue());
584 prim = std::make_shared<ccCone>(maxRadius, minRadius,
585 maxHeight - minHeight,
587 prim->setEnabled(
true);
589 QString(
"Cone (alpha=%1/h=%2)")
591 .arg(maxHeight - minHeight, 0,
'f'));
597 case geometry::RansacParams::RPT_TORUS:
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!");
606 Vec3f CC = torus->Internal().Center();
607 Vec3f CA = torus->Internal().AxisDirection();
608 float minRadius = torus->Internal().MinorRadius();
609 float maxRadius = torus->Internal().MajorRadius();
611 if (minRadius < params.minRadius ||
612 maxRadius > params.maxRadius) {
613 count -= shapePointsCount;
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'));
642 geometry::RansacResult
result;
644 prim->clearTriNormals();
645 prim->applyGLTransformation_recursive();
646 prim->setVisible(
true);
647 if (params.randomColor) {
650 prim->showColors(
true);
655 result.indices.resize(shapePointsCount);
657 for (
unsigned j = 0; j < shapePointsCount; ++j) {
658 result.indices[j] =
static_cast<size_t>(
659 cloud[shapeCloudIndex - j].index);
665 count -= shapePointsCount;
668 if (print_progress) {
670 "[ccPointCloud::ExecuteRANSAC] Detect Plane Shape number: "
674 "[ccPointCloud::ExecuteRANSAC] Detect Sphere Shape number: "
678 "[ccPointCloud::ExecuteRANSAC] Detect Cylinder Shape "
682 "[ccPointCloud::ExecuteRANSAC] Detect Cone Shape number: "
686 "[ccPointCloud::ExecuteRANSAC] Detect Torus Shape number: "
692 if (print_progress) {
694 "[ccPointCloud::ExecuteRANSAC] Total Shape Detection "
float PointCoordinateType
Type of the coordinates of a (N-D) point.
utility::CountingProgressReporter * progress_bar
void normalize()
Sets vector norm to unity.
Vector3Tpl orthogonal() const
Returns a normalized vector which is orthogonal to this one.
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
Float version of ccGLMatrixTpl.
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.
Generic file read and write utility for python interface.
float DegreesToRadians(int degrees)
Convert degrees to radians.