ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Neighbourhood.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 <Neighbourhood.h>
9 
10 // local
11 #include <CVMath.h>
12 #include <CVPointCloud.h>
13 #include <ConjugateGradient.h>
14 #include <Delaunay2dMesh.h>
16 #include <SimpleMesh.h>
17 
18 // System
19 #include <algorithm>
20 
21 // Eigenvalues decomposition
22 // #define USE_EIGEN
23 #ifdef USE_EIGEN
24 #include <Eigen/Eigenvalues>
25 #endif
26 
27 #include <Jacobi.h>
28 
29 using namespace cloudViewer;
30 
31 #ifdef USE_EIGEN
32 Eigen::MatrixXd ToEigen(const SquareMatrixd& M) {
33  unsigned sz = M.size();
34 
35  Eigen::MatrixXd A(sz, sz);
36  for (unsigned c = 0; c < sz; ++c) {
37  for (unsigned r = 0; r < sz; ++r) {
38  A(r, c) = M.getValue(r, c);
39  }
40  }
41 
42  return A;
43 }
44 #endif
45 
47  : m_quadricEquationDirections(0, 1, 2),
48  m_structuresValidity(FLAG_DEPRECATED),
49  m_associatedCloud(associatedCloud) {
50  memset(m_quadricEquation, 0, sizeof(PointCoordinateType) * 6);
51  memset(m_lsPlaneEquation, 0, sizeof(PointCoordinateType) * 4);
52 
53  assert(m_associatedCloud);
54 }
55 
57 
61  : nullptr);
62 }
63 
65  m_gravityCenter = G;
67 }
68 
73  : nullptr);
74 }
75 
77  const CCVector3& X,
78  const CCVector3& Y,
79  const CCVector3& N) {
80  memcpy(m_lsPlaneEquation, eq, sizeof(PointCoordinateType) * 4);
81  m_lsPlaneVectors[0] = X;
82  m_lsPlaneVectors[1] = Y;
83  m_lsPlaneVectors[2] = N;
84 
86 }
87 
92  : nullptr);
93 }
94 
99  : nullptr);
100 }
101 
106  : nullptr);
107 }
108 
111  computeQuadric();
112  }
113 
114  if (dims) {
116  }
117 
119  : nullptr);
120 }
121 
123  // invalidate previous centroid (if any)
125 
126  assert(m_associatedCloud);
127  unsigned count = (m_associatedCloud ? m_associatedCloud->size() : 0);
128  if (!count) return;
129 
130  // sum
131  CCVector3d Psum(0, 0, 0);
132  for (unsigned i = 0; i < count; ++i) {
133  const CCVector3* P = m_associatedCloud->getPoint(i);
134  Psum.x += static_cast<double>(P->x);
135  Psum.y += static_cast<double>(P->y);
136  Psum.z += static_cast<double>(P->z);
137  }
138 
139  setGravityCenter({static_cast<PointCoordinateType>(Psum.x / count),
140  static_cast<PointCoordinateType>(Psum.y / count),
141  static_cast<PointCoordinateType>(Psum.z / count)});
142 }
143 
145  assert(m_associatedCloud);
146  unsigned count = (m_associatedCloud ? m_associatedCloud->size() : 0);
147  if (!count) return cloudViewer::SquareMatrixd();
148 
149  // we get centroid
150  const CCVector3* G = getGravityCenter();
151  assert(G);
152 
153  // we build up covariance matrix
154  double mXX = 0.0;
155  double mYY = 0.0;
156  double mZZ = 0.0;
157  double mXY = 0.0;
158  double mXZ = 0.0;
159  double mYZ = 0.0;
160 
161  for (unsigned i = 0; i < count; ++i) {
162  const CCVector3 P = *m_associatedCloud->getPoint(i) - *G;
163 
164  mXX += static_cast<double>(P.x) * static_cast<double>(P.x);
165  mYY += static_cast<double>(P.y) * static_cast<double>(P.y);
166  mZZ += static_cast<double>(P.z) * static_cast<double>(P.z);
167  mXY += static_cast<double>(P.x) * static_cast<double>(P.y);
168  mXZ += static_cast<double>(P.x) * static_cast<double>(P.z);
169  mYZ += static_cast<double>(P.y) * static_cast<double>(P.z);
170  }
171 
172  // symmetry
173  cloudViewer::SquareMatrixd covMat(3);
174  covMat.m_values[0][0] = mXX / count;
175  covMat.m_values[1][1] = mYY / count;
176  covMat.m_values[2][2] = mZZ / count;
177  covMat.m_values[1][0] = covMat.m_values[0][1] = mXY / count;
178  covMat.m_values[2][0] = covMat.m_values[0][2] = mXZ / count;
179  covMat.m_values[2][1] = covMat.m_values[1][2] = mYZ / count;
180 
181  return covMat;
182 }
183 
185  assert(m_associatedCloud);
186  unsigned pointCount = (m_associatedCloud ? m_associatedCloud->size() : 0);
187  if (pointCount < 2) return 0;
188 
189  // get the centroid
190  const CCVector3* G = getGravityCenter();
191  if (!G) {
192  assert(false);
193  return PC_NAN;
194  }
195 
196  double maxSquareDist = 0;
197  for (unsigned i = 0; i < pointCount; ++i) {
198  const CCVector3* P = m_associatedCloud->getPoint(i);
199  const double d2 = static_cast<double>((*P - *G).norm2());
200  if (d2 > maxSquareDist) maxSquareDist = d2;
201  }
202 
203  return static_cast<PointCoordinateType>(sqrt(maxSquareDist));
204 }
205 
207  // invalidate previous LS plane (if any)
209 
210  assert(m_associatedCloud);
211  unsigned pointCount = (m_associatedCloud ? m_associatedCloud->size() : 0);
212 
213  // we need at least 3 points to compute a plane
214  static_assert(CV_LOCAL_MODEL_MIN_SIZE[LS] >= 3,
215  "Invalid CV_LOCAL_MODEL_MIN_SIZE size");
216  if (pointCount < CV_LOCAL_MODEL_MIN_SIZE[LS]) {
217  // not enough points!
218  return false;
219  }
220 
221  CCVector3 G(0, 0, 0);
222  if (pointCount > 3) {
224 
225  // we determine plane normal by computing the smallest eigen value of M
226  // = 1/n * S[(p-µ)*(p-µ)']
227  SquareMatrixd eigVectors;
228  std::vector<double> eigValues;
229  if (!Jacobi<double>::ComputeEigenValuesAndVectors(covMat, eigVectors,
230  eigValues, true)) {
231  // failed to compute the eigen values!
232  return false;
233  }
234 
235  // get normal
236  {
237  CCVector3d vec(0, 0, 1);
238  double minEigValue = 0;
239  // the smallest eigen vector corresponds to the "least square best
240  // fitting plane" normal
241  Jacobi<double>::GetMinEigenValueAndVector(eigVectors, eigValues,
242  minEigValue, vec.u);
244  }
245 
246  // get also X (Y will be deduced by cross product, see below
247  {
248  CCVector3d vec;
249  double maxEigValue = 0;
250  Jacobi<double>::GetMaxEigenValueAndVector(eigVectors, eigValues,
251  maxEigValue, vec.u);
253  }
254 
255  // get the centroid (should already be up-to-date - see
256  // computeCovarianceMatrix)
257  G = *getGravityCenter();
258  } else {
259  // we simply compute the normal of the 3 points by cross product!
260  const CCVector3* A = m_associatedCloud->getPoint(0);
261  const CCVector3* B = m_associatedCloud->getPoint(1);
262  const CCVector3* C = m_associatedCloud->getPoint(2);
263 
264  // get X (AB by default) and Y (AC - will be updated later) and deduce N
265  // = X ^ Y
266  m_lsPlaneVectors[0] = (*B - *A);
267  m_lsPlaneVectors[1] = (*C - *A);
269 
270  // the plane passes through any of the 3 points
271  G = *A;
272  }
273 
274  // make sure all vectors are unit!
275  if (LessThanEpsilon(m_lsPlaneVectors[2].norm2())) {
276  // this means that the points are colinear!
277  // m_lsPlaneVectors[2] = CCVector3(0,0,1); //any normal will do
278  return false;
279  } else {
281  }
282  // normalize X as well
284  // and update Y
286 
287  // deduce the proper equation
291 
292  // eventually we just have to compute the 'constant' coefficient a3
293  // we use the fact that the plane pass through G --> GM.N = 0 (scalar prod)
294  // i.e. a0*G[0]+a1*G[1]+a2*G[2]=a3
296 
298 
299  return true;
300 }
301 
303  // invalidate previous quadric (if any)
305 
306  assert(m_associatedCloud);
307  if (!m_associatedCloud) return false;
308 
309  unsigned count = m_associatedCloud->size();
310 
311  assert(CV_LOCAL_MODEL_MIN_SIZE[QUADRIC] >= 5);
312  if (count < CV_LOCAL_MODEL_MIN_SIZE[QUADRIC]) return false;
313 
314  const PointCoordinateType* lsPlane = getLSPlane();
315  if (!lsPlane) return false;
316 
317  // we get centroid (should already be up-to-date - see
318  // computeCovarianceMatrix)
319  const CCVector3* G = getGravityCenter();
320  assert(G);
321 
322  // get the best projection axis
323  Tuple3ub idx(0 /*x*/, 1 /*y*/,
324  2 /*z*/); // default configuration: z is the "normal"
325  // direction, we use (x,y) as the base plane
326  const PointCoordinateType nxx = lsPlane[0] * lsPlane[0];
327  const PointCoordinateType nyy = lsPlane[1] * lsPlane[1];
328  const PointCoordinateType nzz = lsPlane[2] * lsPlane[2];
329  if (nxx > nyy) {
330  if (nxx > nzz) {
331  // as x is the "normal" direction, we use (y,z) as the base plane
332  idx.x = 1 /*y*/;
333  idx.y = 2 /*z*/;
334  idx.z = 0 /*x*/;
335  }
336  } else {
337  if (nyy > nzz) {
338  // as y is the "normal" direction, we use (z,x) as the base plane
339  idx.x = 2 /*z*/;
340  idx.y = 0 /*x*/;
341  idx.z = 1 /*y*/;
342  }
343  }
344 
345  // compute the A matrix and b vector
346  std::vector<float> A, b;
347  try {
348  A.resize(6 * count, 0);
349  b.resize(count, 0);
350  } catch (const std::bad_alloc&) {
351  // not enough memory
352  return false;
353  }
354 
355  float lmax2 = 0; // max (squared) dimension
356 
357  // for all points
358  {
359  float* _A = A.data();
360  float* _b = b.data();
361  for (unsigned i = 0; i < count; ++i) {
362  CCVector3 P = *m_associatedCloud->getPoint(i) - *G;
363 
364  float lX = static_cast<float>(P.u[idx.x]);
365  float lY = static_cast<float>(P.u[idx.y]);
366  float lZ = static_cast<float>(P.u[idx.z]);
367 
368  *_A++ = 1.0f;
369  *_A++ = lX;
370  *_A++ = lY;
371  *_A = lX * lX;
372  // by the way, we track the max 'X' squared dimension
373  if (*_A > lmax2) lmax2 = *_A;
374  ++_A;
375  *_A++ = lX * lY;
376  *_A = lY * lY;
377  // by the way, we track the max 'Y' squared dimension
378  if (*_A > lmax2) lmax2 = *_A;
379  ++_A;
380 
381  *_b++ = lZ;
382  lZ *= lZ;
383  // and don't forget to track the max 'Z' squared dimension as well
384  if (lZ > lmax2) lmax2 = lZ;
385  }
386  }
387 
388  // conjugate gradient initialization
389  // we solve tA.A.X=tA.b
391  const cloudViewer::SquareMatrixd& tAA = cg.A();
392  double* tAb = cg.b();
393 
394  // compute tA.A and tA.b
395  {
396  for (unsigned i = 0; i < 6; ++i) {
397  // tA.A part
398  for (unsigned j = i; j < 6; ++j) {
399  double tmp = 0;
400  float* _Ai = &(A[i]);
401  float* _Aj = &(A[j]);
402  for (unsigned k = 0; k < count; ++k, _Ai += 6, _Aj += 6) {
403  // tmp += A[(6*k)+i] * A[(6*k)+j];
404  tmp += static_cast<double>(*_Ai) *
405  static_cast<double>(*_Aj);
406  }
407  tAA.m_values[j][i] = tAA.m_values[i][j] = tmp;
408  }
409 
410  // tA.b part
411  {
412  double tmp = 0;
413  float* _Ai = &(A[i]);
414  for (unsigned k = 0; k < count; ++k, _Ai += 6) {
415  // tmp += A[(6*k)+i]*b[k];
416  tmp += static_cast<double>(*_Ai) *
417  static_cast<double>(b[k]);
418  }
419  tAb[i] = tmp;
420  }
421  }
422 
423 #if 0
424  //trace tA.A and tA.b to a file
425  FILE* f = nullptr;
426  fopen_s(&f, "CG_trace.txt", "wt");
427  if (f)
428  {
429  fprintf_s(f, "lmax2 = %3.12f\n", lmax2);
430 
431  {
432  float Amin = 0, Amax = 0;
433  Amin = Amax = A[0];
434  for (unsigned i = 1; i < 6 * count; ++i)
435  {
436  Amin = std::min(A[i], Amin);
437  Amax = std::max(A[i], Amax);
438  }
439  fprintf_s(f, "A in [%3.12f ; %3.12f]\n", Amin, Amax);
440  }
441  {
442  float bmin = 0, bmax = 0;
443  bmin = bmax = b[0];
444  for (unsigned i = 1; i < count; ++i)
445  {
446  bmin = std::min(b[i], bmin);
447  bmax = std::max(b[i], bmax);
448  }
449  fprintf_s(f, "b in [%3.12f ; %3.12f]\n", bmin, bmax);
450  }
451 
452  fprintf_s(f, "tA.A\n");
453  for (unsigned i = 0; i < 6; ++i)
454  {
455  for (unsigned j = 0; j < 6; ++j)
456  {
457  fprintf_s(f, "%3.12f ", tAA.m_values[i][j]);
458  }
459  fprintf_s(f, "\n");
460  }
461 
462  //tA.b part
463  fprintf_s(f, "tA.b\n");
464  for (unsigned i = 0; i < 6; ++i)
465  {
466  fprintf_s(f, "%3.12f ", tAb[i]);
467  }
468  fprintf_s(f, "\n");
469 
470  fclose(f);
471  }
472 #endif
473  }
474 
475  // first guess for X: plane equation (a0.x+a1.y+a2.z=a3 --> z = a3/a2 -
476  // a0/a2.x - a1/a2.y)
477  double X0[6] = {
478  static_cast<double>(
479  /*lsPlane[3]/lsPlane[idx.z]*/ 0), // DGM: warning, points
480  // have already been
481  // recentred around the
482  // gravity center! So
483  // forget about a3
484  static_cast<double>(-lsPlane[idx.x] / lsPlane[idx.z]),
485  static_cast<double>(-lsPlane[idx.y] / lsPlane[idx.z]), 0, 0, 0};
486 
487  // special case: a0 = a1 = a2 = 0! //happens for perfectly flat surfaces!
488  if (X0[1] == 0 && X0[2] == 0) {
489  X0[0] = 1.0;
490  }
491 
492  // init. conjugate gradient
493  cg.initConjugateGradient(X0);
494 
495  // conjugate gradient iterations
496  {
497  const double convergenceThreshold =
498  static_cast<double>(lmax2) *
499  1.0e-8; // max. error for convergence = 1e-8 of largest cloud
500  // dimension (empirical!)
501  for (unsigned i = 0; i < 1500; ++i) {
502  double lastError = cg.iterConjugateGradient(X0);
503  if (lastError < convergenceThreshold) // converged
504  break;
505  }
506  }
507  // fprintf(fp,"X%i=(%f,%f,%f,%f,%f,%f)\n",i,X0[0],X0[1],X0[2],X0[3],X0[4],X0[5]);
508  // fprintf(fp,"lastError=%E/%E\n",lastError,convergenceThreshold);
509  // fclose(fp);
510 
511  // output
512  {
513  for (unsigned i = 0; i < 6; ++i) {
514  m_quadricEquation[i] = static_cast<PointCoordinateType>(X0[i]);
515  }
517 
519  }
520 
521  return true;
522 }
523 
524 bool Neighbourhood::compute3DQuadric(double quadricEquation[10]) {
525  if (!m_associatedCloud || !quadricEquation) {
526  // invalid (input) parameters
527  assert(false);
528  return false;
529  }
530 
531  // computes a 3D quadric of the form ax2 +by2 +cz2 + 2exy + 2fyz + 2gzx +
532  // 2lx + 2my + 2nz + d = 0 "THREE-DIMENSIONAL SURFACE CURVATURE ESTIMATION
533  // USING QUADRIC SURFACE PATCHES", I. Douros & B. Buxton, University College
534  // London
535 
536  // we get centroid
537  const CCVector3* G = getGravityCenter();
538  assert(G);
539 
540  // we look for the eigen vector associated to the minimum eigen value of a
541  // matrix A where A=transpose(D)*D, and D=[xi^2 yi^2 zi^2 xiyi yizi xizi xi
542  // yi zi 1] (i=1..N)
543 
544  unsigned count = m_associatedCloud->size();
545 
546  // we compute M = [x2 y2 z2 xy yz xz x y z 1] for all points
547  std::vector<PointCoordinateType> M;
548  {
549  try {
550  M.resize(count * 10);
551  } catch (const std::bad_alloc&) {
552  return false;
553  }
554 
555  PointCoordinateType* _M = M.data();
556  for (unsigned i = 0; i < count; ++i) {
557  const CCVector3 P = *m_associatedCloud->getPoint(i) - *G;
558 
559  // we fill the ith line
560  (*_M++) = P.x * P.x;
561  (*_M++) = P.y * P.y;
562  (*_M++) = P.z * P.z;
563  (*_M++) = P.x * P.y;
564  (*_M++) = P.y * P.z;
565  (*_M++) = P.x * P.z;
566  (*_M++) = P.x;
567  (*_M++) = P.y;
568  (*_M++) = P.z;
569  (*_M++) = 1;
570  }
571  }
572 
573  // D = tM.M
574  SquareMatrixd D(10);
575  for (unsigned l = 0; l < 10; ++l) {
576  for (unsigned c = 0; c < 10; ++c) {
577  double sum = 0;
578  const PointCoordinateType* _M = M.data();
579  for (unsigned i = 0; i < count; ++i, _M += 10)
580  sum += static_cast<double>(_M[l] * _M[c]);
581 
582  D.m_values[l][c] = sum;
583  }
584  }
585 
586  // we don't need M anymore
587  M.resize(0);
588 
589  // now we compute eigen values and vectors of D
590 #ifdef USE_EIGEN
591  Eigen::MatrixXd A = ToEigen(D);
592  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es;
593  es.compute(A);
594 
595  // eigen values (and vectors) are sorted in ascending order
596  //(we get the eigen vector corresponding to the minimum eigen value)
597  const auto& minEigenVec = es.eigenvectors().col(0);
598 
599  for (unsigned i = 0; i < D.size(); ++i) {
600  quadricEquation[i] = minEigenVec[i];
601  }
602 #else
603  cloudViewer::SquareMatrixd eigVectors;
604  std::vector<double> eigValues;
605  if (!Jacobi<double>::ComputeEigenValuesAndVectors(D, eigVectors, eigValues,
606  true)) {
607  // failure
608  return false;
609  }
610 
611  // we get the eigen vector corresponding to the minimum eigen value
612  double minEigValue = 0;
613  Jacobi<double>::GetMinEigenValueAndVector(eigVectors, eigValues,
614  minEigValue, quadricEquation);
615 #endif
616 
617  return true;
618 }
619 
621  bool duplicateVertices,
622  PointCoordinateType maxEdgeLength,
623  std::string& outputErrorStr) {
625  // can't compute LSF plane with less than 3 points!
626  outputErrorStr = "Not enough points";
627  return nullptr;
628  }
629 
630  // safety check: Triangle lib will crash if the points are all the same!
632  return nullptr;
633  }
634 
635  // project the points on this plane
636  GenericIndexedMesh* mesh = nullptr;
637  std::vector<CCVector2> points2D;
638 
639  if (projectPointsOn2DPlane<CCVector2>(points2D)) {
640  Delaunay2dMesh* dm = new Delaunay2dMesh();
641 
642  // triangulate the projected points
643  if (!dm->buildMesh(points2D, Delaunay2dMesh::USE_ALL_POINTS,
644  outputErrorStr)) {
645  delete dm;
646  return nullptr;
647  }
648 
649  // change the default mesh's reference
650  if (duplicateVertices) {
651  PointCloud* cloud = new PointCloud();
652  const unsigned count = m_associatedCloud->size();
653  if (!cloud->reserve(count)) {
654  outputErrorStr = "Not enough memory";
655  delete dm;
656  delete cloud;
657  return nullptr;
658  }
659  for (unsigned i = 0; i < count; ++i)
660  cloud->addPoint(*m_associatedCloud->getPoint(i));
661  dm->linkMeshWith(cloud, true);
662  } else {
663  dm->linkMeshWith(m_associatedCloud, false);
664  }
665 
666  // remove triangles with too long edges
667  if (maxEdgeLength > 0) {
668  dm->removeTrianglesWithEdgesLongerThan(maxEdgeLength);
669  if (dm->size() == 0) {
670  // no more triangles?
671  outputErrorStr = "Not triangle left after pruning";
672  delete dm;
673  dm = nullptr;
674  }
675  }
676  mesh = static_cast<GenericIndexedMesh*>(dm);
677  }
678 
679  return mesh;
680 }
681 
683  unsigned nStepY) {
684  if (nStepX < 2 || nStepY < 2) return nullptr;
685 
686  // qaudric fit
687  const PointCoordinateType* Q =
688  getQuadric(); // Q: Z = a + b.X + c.Y + d.X^2 + e.X.Y + f.Y^2
689  if (!Q) return nullptr;
690 
691  const PointCoordinateType& a = Q[0];
692  const PointCoordinateType& b = Q[1];
693  const PointCoordinateType& c = Q[2];
694  const PointCoordinateType& d = Q[3];
695  const PointCoordinateType& e = Q[4];
696  const PointCoordinateType& f = Q[5];
697 
698  const unsigned char X = m_quadricEquationDirections.x;
699  const unsigned char Y = m_quadricEquationDirections.y;
700  const unsigned char Z = m_quadricEquationDirections.z;
701 
702  // gravity center (should be ok if the quadric is ok)
703  const CCVector3* G = getGravityCenter();
704  assert(G);
705 
706  // bounding box
707  CCVector3 bbMin, bbMax;
708  m_associatedCloud->getBoundingBox(bbMin, bbMax);
709  CCVector3 bboxDiag = bbMax - bbMin;
710 
711  // Sample points on Quadric and triangulate them!
712  const PointCoordinateType spanX = bboxDiag.u[X];
713  const PointCoordinateType spanY = bboxDiag.u[Y];
714  const PointCoordinateType stepX = spanX / (nStepX - 1);
715  const PointCoordinateType stepY = spanY / (nStepY - 1);
716 
717  PointCloud* vertices = new PointCloud();
718  if (!vertices->reserve(nStepX * nStepY)) {
719  delete vertices;
720  return nullptr;
721  }
722 
723  SimpleMesh* quadMesh = new SimpleMesh(vertices, true);
724  if (!quadMesh->reserve((nStepX - 1) * (nStepY - 1) * 2)) {
725  delete quadMesh;
726  return nullptr;
727  }
728 
729  for (unsigned x = 0; x < nStepX; ++x) {
730  CCVector3 P;
731  P.x = bbMin[X] + stepX * x - G->u[X];
732  for (unsigned y = 0; y < nStepY; ++y) {
733  P.y = bbMin[Y] + stepY * y - G->u[Y];
734  P.z = a + b * P.x + c * P.y + d * P.x * P.x + e * P.x * P.y +
735  f * P.y * P.y;
736 
737  CCVector3 Pc;
738  Pc.u[X] = P.x;
739  Pc.u[Y] = P.y;
740  Pc.u[Z] = P.z;
741  Pc += *G;
742 
743  vertices->addPoint(Pc);
744 
745  if (x > 0 && y > 0) {
746  const unsigned iA = (x - 1) * nStepY + y - 1;
747  const unsigned iB = iA + 1;
748  const unsigned iC = iA + nStepY;
749  const unsigned iD = iB + nStepY;
750 
751  quadMesh->addTriangle(iA, iC, iB);
752  quadMesh->addTriangle(iB, iC, iD);
753  }
754  }
755  }
756 
757  return quadMesh;
758 }
759 
761  if (!m_associatedCloud || m_associatedCloud->size() < 3) {
762  // not enough points
763  return NAN_VALUE;
764  }
765 
766  SquareMatrixd eigVectors;
767  std::vector<double> eigValues;
769  computeCovarianceMatrix(), eigVectors, eigValues, true)) {
770  // failed to compute the eigen values
771  return NAN_VALUE;
772  }
773 
775  eigVectors, eigValues); // sort the eigenvectors in decreasing
776  // order of their associated eigenvalues
777 
778  double m1 = 0.0, m2 = 0.0;
779  CCVector3d e2;
780  Jacobi<double>::GetEigenVector(eigVectors, 1, e2.u);
781 
782  for (unsigned i = 0; i < m_associatedCloud->size(); ++i) {
783  double dotProd =
785  .dot(e2);
786  m1 += dotProd;
787  m2 += dotProd * dotProd;
788  }
789 
790  // see "Contour detection in unstructured 3D point clouds", Asher 2016
791  return (m2 < std::numeric_limits<double>::epsilon()
792  ? NAN_VALUE
793  : static_cast<ScalarType>((m1 * m1) / m2));
794 }
795 
797  if (!m_associatedCloud || m_associatedCloud->size() < 3) {
798  // not enough points
799  return std::numeric_limits<double>::quiet_NaN();
800  }
801 
802  SquareMatrixd eigVectors;
803  std::vector<double> eigValues;
805  computeCovarianceMatrix(), eigVectors, eigValues, true)) {
806  // failed to compute the eigen values
807  return std::numeric_limits<double>::quiet_NaN();
808  }
809 
811  eigVectors, eigValues); // sort the eigenvectors in decreasing
812  // order of their associated eigenvalues
813 
814  // shortcuts
815  const double& l1 = eigValues[0];
816  const double& l2 = eigValues[1];
817  const double& l3 = eigValues[2];
818 
819  double value = std::numeric_limits<double>::quiet_NaN();
820 
821  switch (feature) {
822  case EigenValuesSum:
823  value = l1 + l2 + l3;
824  break;
825  case Omnivariance:
826  value = pow(l1 * l2 * l3, 1.0 / 3.0);
827  break;
828  case EigenEntropy:
829  value = -(l1 * log(l1) + l2 * log(l2) + l3 * log(l3));
830  break;
831  case Anisotropy:
832  if (std::abs(l1) > std::numeric_limits<double>::epsilon())
833  value = (l1 - l3) / l1;
834  break;
835  case Planarity:
836  if (std::abs(l1) > std::numeric_limits<double>::epsilon())
837  value = (l2 - l3) / l1;
838  break;
839  case Linearity:
840  if (std::abs(l1) > std::numeric_limits<double>::epsilon())
841  value = (l1 - l2) / l1;
842  break;
843  case PCA1: {
844  double sum = l1 + l2 + l3;
845  if (std::abs(sum) > std::numeric_limits<double>::epsilon())
846  value = l1 / sum;
847  } break;
848  case PCA2: {
849  double sum = l1 + l2 + l3;
850  if (std::abs(sum) > std::numeric_limits<double>::epsilon())
851  value = l2 / sum;
852  } break;
853  case SurfaceVariation: {
854  double sum = l1 + l2 + l3;
855  if (std::abs(sum) > std::numeric_limits<double>::epsilon())
856  value = l3 / sum;
857  } break;
858  case Sphericity:
859  if (std::abs(l1) > std::numeric_limits<double>::epsilon())
860  value = l3 / l1;
861  break;
862  case Verticality: {
863  CCVector3d Z(0, 0, 1);
864  CCVector3d e3(Z);
865  Jacobi<double>::GetEigenVector(eigVectors, 2, e3.u);
866 
867  value = 1.0 - std::abs(Z.dot(e3));
868  } break;
869  case EigenValue1:
870  value = l1;
871  break;
872  case EigenValue2:
873  value = l2;
874  break;
875  case EigenValue3:
876  value = l3;
877  break;
878  default:
879  assert(false);
880  break;
881  }
882 
883  return value;
884 }
885 
887  const CCVector3& P, const CCVector3* roughnessUpDir /*=nullptr*/) {
888  const PointCoordinateType* lsPlane = getLSPlane();
889  if (lsPlane) {
890  ScalarType distToPlane =
892  lsPlane);
893  if (roughnessUpDir) {
894  if (CCVector3::vdot(lsPlane, roughnessUpDir->u) < 0) {
895  distToPlane = -distToPlane;
896  }
897  } else {
898  distToPlane = std::abs(distToPlane);
899  }
900  return distToPlane;
901  } else {
902  return NAN_VALUE;
903  }
904 }
905 
907  CurvatureType cType) {
908  switch (cType) {
909  case GAUSSIAN_CURV:
910  case MEAN_CURV: {
911  // we get 2D1/2 quadric parameters
912  const PointCoordinateType* H = getQuadric();
913  if (!H) return NAN_VALUE;
914 
915  // compute centroid
916  const CCVector3* G = getGravityCenter();
917 
918  // we compute curvature at the input neighbour position + we
919  // recenter it by the way
920  const CCVector3 Q(P - *G);
921 
922  const unsigned char X = m_quadricEquationDirections.x;
923  const unsigned char Y = m_quadricEquationDirections.y;
924 
925  // z = a+b.x+c.y+d.x^2+e.x.y+f.y^2
926  // const PointCoordinateType& a = H[0];
927  const PointCoordinateType& b = H[1];
928  const PointCoordinateType& c = H[2];
929  const PointCoordinateType& d = H[3];
930  const PointCoordinateType& e = H[4];
931  const PointCoordinateType& f = H[5];
932 
933  // See "CURVATURE OF CURVES AND SURFACES – A PARABOLIC APPROACH" by
934  // ZVI HAR’EL
935  const PointCoordinateType fx =
936  b + (d * 2) * Q.u[X] + (e)*Q.u[Y]; // b+2d*X+eY
937  const PointCoordinateType fy =
938  c + (e)*Q.u[X] + (f * 2) * Q.u[Y]; // c+2f*Y+eX
939  const PointCoordinateType fxx = d * 2; // 2d
940  const PointCoordinateType fyy = f * 2; // 2f
941  const PointCoordinateType& fxy = e; // e
942 
943  const PointCoordinateType fx2 = fx * fx;
944  const PointCoordinateType fy2 = fy * fy;
945  const PointCoordinateType q = (1 + fx2 + fy2);
946 
947  switch (cType) {
948  case GAUSSIAN_CURV: {
949  // to sign the curvature, we need a normal!
950  const PointCoordinateType K =
951  std::abs(fxx * fyy - fxy * fxy) / (q * q);
952  return static_cast<ScalarType>(K);
953  }
954 
955  case MEAN_CURV: {
956  // to sign the curvature, we need a normal!
957  const PointCoordinateType H2 =
958  std::abs(((1 + fx2) * fyy - 2 * fx * fy * fxy +
959  (1 + fy2) * fxx)) /
960  (2 * sqrt(q) * q);
961  return static_cast<ScalarType>(H2);
962  }
963 
964  default:
965  assert(false);
966  break;
967  }
968  } break;
969 
970  case NORMAL_CHANGE_RATE: {
971  assert(m_associatedCloud);
972  unsigned pointCount =
974 
975  // we need at least 4 points
976  if (pointCount < 4) {
977  // not enough points!
978  return pointCount == 3 ? 0 : NAN_VALUE;
979  }
980 
981  // we determine plane normal by computing the smallest eigen value
982  // of M = 1/n * S[(p-µ)*(p-µ)']
984  CCVector3d e(0, 0, 0);
985 
986  SquareMatrixd eigVectors;
987  std::vector<double> eigValues;
989  covMat, eigVectors, eigValues, true)) {
990  // failure
991  return NAN_VALUE;
992  }
993 
994  // compute curvature as the rate of change of the surface
995  e.x = eigValues[0];
996  e.y = eigValues[1];
997  e.z = eigValues[2];
998 
999  const double sum = e.x + e.y + e.z; // we work with absolute values
1000  if (LessThanEpsilon(sum)) {
1001  return NAN_VALUE;
1002  }
1003 
1004  const double eMin = std::min(std::min(e.x, e.y), e.z);
1005  return static_cast<ScalarType>(eMin / sum);
1006  } break;
1007 
1008  default:
1009  assert(false);
1010  }
1011 
1012  return NAN_VALUE;
1013 }
constexpr unsigned CV_LOCAL_MODEL_MIN_SIZE[]
Min number of points to compute local models (see CV_LOCAL_MODEL_TYPES)
Definition: CVConst.h:129
@ LS
Definition: CVConst.h:123
@ TRI
Definition: CVConst.h:124
@ QUADRIC
Definition: CVConst.h:125
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
constexpr PointCoordinateType PC_NAN
'NaN' as a PointCoordinateType value
Definition: CVConst.h:71
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int count
void * X
Definition: SmallVector.cpp:45
Jacobi eigen vectors/values decomposition.
Definition: Jacobi.h:23
static bool GetEigenVector(const SquareMatrix &eigenVectors, unsigned index, Scalar eigenVector[])
Returns the given eigenvector.
Definition: Jacobi.h:364
static bool GetMinEigenValueAndVector(const SquareMatrix &eigenVectors, const EigenValues &eigenValues, Scalar &minEigenValue, Scalar minEigenVector[])
Returns the smallest eigenvalue and its associated eigenvector.
Definition: Jacobi.h:410
static bool SortEigenValuesAndVectors(SquareMatrix &eigenVectors, EigenValues &eigenValues)
Definition: Jacobi.h:333
static bool GetMaxEigenValueAndVector(const SquareMatrix &eigenVectors, const EigenValues &eigenValues, Scalar &maxEigenValue, Scalar maxEigenVector[])
Returns the biggest eigenvalue and its associated eigenvector.
Definition: Jacobi.h:385
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
static PointCoordinateType vdot(const PointCoordinateType p[], const PointCoordinateType q[])
Definition: CVGeom.h:520
A class to perform a conjugate gradient optimization.
Scalar iterConjugateGradient(Scalar *Xn)
Iterates the conjugate gradient.
cloudViewer::SquareMatrixTpl< Scalar > & A()
Returns A matrix.
void initConjugateGradient(const Scalar *X0)
Initializes the conjugate gradient.
Scalar * b()
Returns b vector.
A class to compute and handle a Delaunay 2D mesh on a subset of points.
virtual unsigned size() const override
Returns the number of triangles.
bool removeTrianglesWithEdgesLongerThan(PointCoordinateType maxEdgeLength)
Filters out the triangles based on their edge length.
virtual bool buildMesh(const std::vector< CCVector2 > &points2D, std::size_t pointCountToUse, std::string &outputErrorStr)
Build the Delaunay mesh on top a set of 2D points.
virtual void linkMeshWith(GenericIndexedCloud *aCloud, bool passOwnership=false)
Associate this mesh to a point cloud.
static constexpr int USE_ALL_POINTS
static ScalarType computePoint2PlaneDistance(const CCVector3 *P, const PointCoordinateType *planeEquation)
Computes the (signed) distance between a point and a plane.
virtual void getBoundingBox(CCVector3 &bbMin, CCVector3 &bbMax)=0
Returns the cloud bounding box.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
A generic mesh with index-based vertex access.
ScalarType computeCurvature(const CCVector3 &P, CurvatureType cType)
Computes the curvature of a set of point (by fitting a 2.5D quadric)
cloudViewer::SquareMatrixd computeCovarianceMatrix()
Computes the covariance matrix.
PointCoordinateType computeLargestRadius()
const PointCoordinateType * getQuadric(Tuple3ub *dims=nullptr)
Returns the best interpolating 2.5D quadric.
GenericIndexedMesh * triangulateFromQuadric(unsigned stepsX, unsigned stepsY)
const CCVector3 * getLSPlaneY()
Returns best interpolating plane (Least-square) 'Y' base vector.
const PointCoordinateType * getLSPlane()
Returns best interpolating plane equation (Least-square)
ScalarType computeRoughness(const CCVector3 &P, const CCVector3 *roughnessUpDir=nullptr)
CCVector3 m_lsPlaneVectors[3]
Least-square best fitting plane base vectors.
const CCVector3 * getGravityCenter()
Returns gravity center.
PointCoordinateType m_quadricEquation[6]
2.5D Quadric equation
void setLSPlane(const PointCoordinateType eq[4], const CCVector3 &X, const CCVector3 &Y, const CCVector3 &N)
Sets the best interpolating plane equation (Least-square)
bool computeQuadric()
Computes best fitting 2.5D quadric.
virtual void reset()
Resets structure (depreactes all associated geometrical fetaures)
Neighbourhood(GenericIndexedCloudPersist *associatedCloud)
Default constructor.
GeomFeature
Geometric feature computed from eigen values/vectors.
bool compute3DQuadric(double quadricEquation[10])
Computes the best interpolating quadric (Least-square)
CCVector3 m_gravityCenter
Gravity center.
const CCVector3 * getLSPlaneX()
Returns best interpolating plane (Least-square) 'X' base vector.
void computeGravityCenter()
Computes the gravity center.
void setGravityCenter(const CCVector3 &G)
Sets gravity center.
GenericIndexedMesh * triangulateOnPlane(bool duplicateVertices, PointCoordinateType maxEdgeLength, std::string &outputErrorStr)
Applies 2D Delaunay triangulation.
CurvatureType
Curvature type.
Definition: Neighbourhood.h:42
GenericIndexedCloudPersist * m_associatedCloud
Associated cloud.
unsigned char m_structuresValidity
Geometrical elements validity (flags)
const CCVector3 * getLSPlaneNormal()
Returns best interpolating plane (Least-square) normal vector.
Tuple3ub m_quadricEquationDirections
2.5D Quadric equation dimensions
double computeFeature(GeomFeature feature)
Computes the given feature on a set of point.
ScalarType computeMomentOrder1(const CCVector3 &P)
PointCoordinateType m_lsPlaneEquation[4]
Least-square best fitting plane parameters.
bool computeLeastSquareBestFittingPlane()
Computes the least-square best fitting plane.
virtual bool reserve(unsigned newCapacity)
Reserves memory for the point database.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
A simple mesh structure, with index-based vertex access.
Definition: SimpleMesh.h:26
virtual void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
Definition: SimpleMesh.cpp:103
virtual bool reserve(unsigned n)
Reserves the memory to store the triangles (as 3 indexes each)
Definition: SimpleMesh.cpp:109
Scalar ** m_values
The matrix rows.
Definition: SquareMatrix.h:157
unsigned size() const
Returns matrix size.
Definition: SquareMatrix.h:133
Scalar getValue(unsigned row, unsigned column) const
Returns a particular matrix value.
Definition: SquareMatrix.h:168
int min(int a, int b)
Definition: cutil_math.h:53
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
int max(int a, int b)
Definition: cutil_math.h:48
::ccPointCloud PointCloud
Definition: PointCloud.h:19
Generic file read and write utility for python interface.
SquareMatrixTpl< double > SquareMatrixd
Double square matrix type.
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23