ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvRegistrationTools.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 "ecvRegistrationTools.h"
9 
10 // CV_CORE_LIB
11 #include <CVLog.h>
12 #include <CVPointCloud.h>
13 #include <CloudSamplingTools.h>
15 #include <Garbage.h>
17 #include <MeshSamplingTools.h>
18 #include <ParallelSort.h>
19 #include <RegistrationTools.h>
20 
21 // CV_DB_LIB
22 #include <ecvGenericMesh.h>
23 #include <ecvHObjectCaster.h>
24 #include <ecvPointCloud.h>
25 #include <ecvProgressDialog.h>
26 #include <ecvScalarField.h>
27 
28 // system
29 #include <set>
30 
32 static const unsigned s_defaultSampledPointsOnDataMesh = 50000;
34 static const char REGISTRATION_DISTS_SF[] = "RegistrationDistances";
35 
37  ccHObject* data,
38  ccHObject* model,
39  ccGLMatrix& transMat,
40  double& finalScale,
41  double& finalRMS,
42  unsigned& finalPointCount,
43  const cloudViewer::ICPRegistrationTools::Parameters& inputParameters,
44  bool useDataSFAsWeights /*=false*/,
45  bool useModelSFAsWeights /*=false*/,
46  QWidget* parent /*=nullptr*/) {
47  bool restoreColorState = false;
48  bool restoreSFState = false;
50 
51  // progress bar
52  QScopedPointer<ecvProgressDialog> progressDlg;
53  if (parent) {
54  progressDlg.reset(new ecvProgressDialog(false, parent));
55  }
56 
58 
59  // if the 'model' entity is a mesh, we need to sample points on it
60  cloudViewer::GenericIndexedCloudPersist* modelCloud = nullptr;
61  ccGenericMesh* modelMesh = nullptr;
62  if (model->isKindOf(CV_TYPES::MESH)) {
63  modelMesh = ccHObjectCaster::ToGenericMesh(model);
64  modelCloud = modelMesh->getAssociatedCloud();
65  } else {
66  modelCloud = ccHObjectCaster::ToGenericPointCloud(model);
67  }
68 
69  // if the 'data' entity is a mesh, we need to sample points on it
70  cloudViewer::GenericIndexedCloudPersist* dataCloud = nullptr;
71  if (data->isKindOf(CV_TYPES::MESH)) {
74  s_defaultSampledPointsOnDataMesh, progressDlg.data());
75  if (!dataCloud) {
76  CVLog::Error("[ICP] Failed to sample points on 'data' mesh!");
77  return false;
78  }
79  cloudGarbage.add(dataCloud);
80  } else {
81  dataCloud = ccHObjectCaster::ToGenericPointCloud(data);
82  }
83 
84  // we activate a temporary scalar field for registration distances
85  // computation
86  cloudViewer::ScalarField* dataDisplayedSF = nullptr;
87  int oldDataSfIdx = -1;
88  int dataSfIdx = -1;
89 
90  // if the 'data' entity is a real ccPointCloud, we can even create a proper
91  // temporary SF for registration distances
92  if (data->isA(CV_TYPES::POINT_CLOUD)) {
93  ccPointCloud* pc = static_cast<ccPointCloud*>(data);
94  restoreColorState = pc->colorsShown();
95  restoreSFState = pc->sfShown();
96  dataDisplayedSF = pc->getCurrentDisplayedScalarField();
97  oldDataSfIdx = pc->getCurrentInScalarFieldIndex();
99  if (dataSfIdx < 0)
100  dataSfIdx = pc->addScalarField(REGISTRATION_DISTS_SF);
101  if (dataSfIdx >= 0)
102  pc->setCurrentScalarField(dataSfIdx);
103  else {
104  CVLog::Error(
105  "[ICP] Couldn't create temporary scalar field! Not enough "
106  "memory?");
107  return false;
108  }
109  } else {
110  if (!dataCloud->enableScalarField()) {
111  CVLog::Error(
112  "[ICP] Couldn't create temporary scalar field! Not enough "
113  "memory?");
114  return false;
115  }
116  }
117 
118  // add a 'safety' margin to input ratio
119  static double s_overlapMarginRatio = 0.2;
120  params.finalOverlapRatio =
121  std::max(params.finalOverlapRatio, 0.01); // 1% minimum
122  // do we need to reduce the input point cloud (so as to be close
123  // to the theoretical number of overlapping points - but not too
124  // low so as we are not registered yet ;)
125  if (params.finalOverlapRatio < 1.0 - s_overlapMarginRatio) {
126  // DGM we can now use 'approximate' distances as SAITO algorithm is
127  // exact (but with a coarse resolution) level = 7 if < 1.000.000 level =
128  // 8 if < 10.000.000 level = 9 if > 10.000.000
129  int gridLevel =
130  static_cast<int>(floor(log10(static_cast<double>(
131  std::max(dataCloud->size(), modelCloud->size()))))) +
132  2;
133  gridLevel = std::min(std::max(gridLevel, 7), 9);
134  int result = -1;
135  if (modelMesh) {
136  cloudViewer::DistanceComputationTools::
137  Cloud2MeshDistancesComputationParams c2mParams;
138  c2mParams.octreeLevel = gridLevel;
139  c2mParams.maxSearchDist = 0;
140  c2mParams.useDistanceMap = true;
141  c2mParams.signedDistances = false;
142  c2mParams.flipNormals = false;
143  c2mParams.multiThread = false;
145  computeCloud2MeshDistances(dataCloud, modelMesh, c2mParams,
146  progressDlg.data());
147  } else {
149  computeApproxCloud2CloudDistance(dataCloud, modelCloud,
150  gridLevel, -1,
151  progressDlg.data());
152  }
153 
154  if (result < 0) {
155  CVLog::Error(
156  "Failed to determine the max (overlap) distance (not "
157  "enough memory?)");
158  return false;
159  }
160 
161  // determine the max distance that (roughly) corresponds to the input
162  // overlap ratio
163  ScalarType maxSearchDist = 0;
164  {
165  unsigned count = dataCloud->size();
166  std::vector<ScalarType> distances;
167  try {
168  distances.resize(count);
169  } catch (const std::bad_alloc&) {
170  CVLog::Error("Not enough memory!");
171  return false;
172  }
173  for (unsigned i = 0; i < count; ++i) {
174  distances[i] = dataCloud->getPointScalarValue(i);
175  }
176 
177  ParallelSort(distances.begin(), distances.end());
178 
179  // now look for the max value at 'finalOverlapRatio + margin'
180  // percent
181  maxSearchDist =
182  distances[static_cast<size_t>(std::max(
183  1.0, count * (params.finalOverlapRatio +
184  s_overlapMarginRatio))) -
185  1];
186  }
187 
188  // evntually select the points with distance below 'maxSearchDist'
189  //(should roughly correspond to 'finalOverlapRatio + margin' percent)
190  {
191  cloudViewer::ReferenceCloud* refCloud =
192  new cloudViewer::ReferenceCloud(dataCloud);
193  cloudGarbage.add(refCloud);
194  unsigned countBefore = dataCloud->size();
195  unsigned baseIncrement = static_cast<unsigned>(std::max(
196  100.0, countBefore * params.finalOverlapRatio * 0.05));
197  for (unsigned i = 0; i < countBefore; ++i) {
198  if (dataCloud->getPointScalarValue(i) <= maxSearchDist) {
199  if (refCloud->size() == refCloud->capacity() &&
200  !refCloud->reserve(refCloud->size() + baseIncrement)) {
201  CVLog::Error("Not enough memory!");
202  return false;
203  }
204  refCloud->addPointIndex(i);
205  }
206  }
207  refCloud->resize(refCloud->size());
208  dataCloud = refCloud;
209 
210  unsigned countAfter = dataCloud->size();
211  double keptRatio = static_cast<double>(countAfter) / countBefore;
212  CVLog::Print(QString("[ICP][Partial overlap] Selecting %1 points "
213  "out of %2 (%3%) for registration")
214  .arg(countAfter)
215  .arg(countBefore)
216  .arg(static_cast<int>(100 * keptRatio)));
217 
218  // update the relative 'final overlap' ratio
219  params.finalOverlapRatio /= keptRatio;
220  }
221  }
222 
223  // weights
224  params.modelWeights = nullptr;
225  params.dataWeights = nullptr;
226  {
227  if (!modelMesh && useModelSFAsWeights) {
228  if (modelCloud ==
230  model) &&
231  model->isA(CV_TYPES::POINT_CLOUD)) {
232  ccPointCloud* pc = static_cast<ccPointCloud*>(model);
233  params.modelWeights = pc->getCurrentDisplayedScalarField();
234  if (!params.modelWeights)
236  "[ICP] 'useDataSFAsWeights' is true but model has "
237  "no displayed scalar field!");
238  } else {
240  "[ICP] 'useDataSFAsWeights' is true but only point "
241  "cloud scalar fields can be used as weights!");
242  }
243  }
244 
245  if (useDataSFAsWeights) {
246  if (!dataDisplayedSF) {
247  if (dataCloud == ccHObjectCaster::ToPointCloud(data))
249  "[ICP] 'useDataSFAsWeights' is true but data has "
250  "no displayed scalar field!");
251  else
253  "[ICP] 'useDataSFAsWeights' is true but only point "
254  "cloud scalar fields can be used as weights!");
255  } else {
256  params.dataWeights = dataDisplayedSF;
257  }
258  }
259  }
260 
263 
265  modelCloud, modelMesh, dataCloud, params, transform, finalRMS,
266  finalPointCount,
268  progressDlg.data()));
269 
271  CVLog::Error("Registration failed: an error occurred (code %i)",
272  result);
274  transMat = FromCCLibMatrix<double, float>(transform.R, transform.T,
275  transform.s);
276  finalScale = transform.s;
277  }
278 
279  // remove temporary SF (if any)
280  if (dataSfIdx >= 0) {
281  assert(data->isA(CV_TYPES::POINT_CLOUD));
282  ccPointCloud* pc = dynamic_cast<ccPointCloud*>(data);
283  pc->setCurrentScalarField(oldDataSfIdx);
284  pc->deleteScalarField(dataSfIdx);
285  pc->showColors(restoreColorState);
286  pc->showSF(restoreSFState);
287  }
288 
290 }
int count
#define ParallelSort
Definition: ParallelSort.h:33
cmdLineReadable * params[]
core::Tensor result
Definition: VtkUtils.cpp:76
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Garbage container (automatically deletes pointers when destroyed)
Definition: Garbage.h:15
void add(C *item)
Puts an item in the trash.
Definition: Garbage.h:18
virtual bool colorsShown() const
Returns whether colors are shown or not.
virtual bool sfShown() const
Returns whether active scalar field is visible.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Generic mesh interface.
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
static ccGenericMesh * ToGenericMesh(ccHObject *obj)
Converts current object to ccGenericMesh (if possible)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
void deleteScalarField(int index) override
Deletes a specific scalar field.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
static bool ICP(ccHObject *data, ccHObject *model, ccGLMatrix &transMat, double &finalScale, double &finalRMS, unsigned &finalPointCount, const cloudViewer::ICPRegistrationTools::Parameters &inputParameters, bool useDataSFAsWeights=false, bool useModelSFAsWeights=false, QWidget *parent=nullptr)
Applies ICP registration on two entities.
static int computeApproxCloud2CloudDistance(GenericIndexedCloudPersist *comparedCloud, GenericIndexedCloudPersist *referenceCloud, unsigned char octreeLevel, PointCoordinateType maxSearchDist=0, GenericProgressCallback *progressCb=nullptr, DgmOctree *compOctree=nullptr, DgmOctree *refOctree=nullptr)
Computes approximate distances between two point clouds.
static int computeCloud2MeshDistances(GenericIndexedCloudPersist *pointCloud, GenericIndexedMesh *mesh, Cloud2MeshDistancesComputationParams &params, GenericProgressCallback *progressCb=nullptr, DgmOctree *cloudOctree=nullptr)
Computes the distance between a point cloud and a mesh.
virtual bool enableScalarField()=0
Enables the scalar field associated to the cloud.
virtual unsigned size() const =0
Returns the number of points.
virtual ScalarType getPointScalarValue(unsigned pointIndex) const =0
Returns the ith point associated scalar value.
A generic 3D point cloud with index-based and presistent access to points.
static RESULT_TYPE Register(GenericIndexedCloudPersist *modelCloud, GenericIndexedMesh *modelMesh, GenericIndexedCloudPersist *dataCloud, const Parameters &params, ScaledTransformation &totalTrans, double &finalRMS, unsigned &finalPointCount, GenericProgressCallback *progressCb=nullptr)
Registers two clouds or a cloud and a mesh.
static PointCloud * samplePointsOnMesh(GenericMesh *mesh, double samplingDensity, GenericProgressCallback *progressCb=nullptr, std::vector< unsigned > *triIndices=nullptr)
Samples points on a mesh.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
int getCurrentInScalarFieldIndex()
Returns current INPUT scalar field index (or -1 if none)
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
A very simple point cloud (no point duplication)
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
unsigned size() const override
Returns the number of points.
virtual bool resize(unsigned n)
Presets the size of the vector used to store point references.
virtual unsigned capacity() const
Returns max capacity.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
Graphical progress indicator (thread-safe)
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
static const char REGISTRATION_DISTS_SF[]
Default temporary registration scalar field.
static const unsigned s_defaultSampledPointsOnDataMesh
Default number of points sampled on the 'data' mesh (if any)
@ MESH
Definition: CVTypes.h:105
@ POINT_CLOUD
Definition: CVTypes.h:104
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
A scaled geometrical transformation (scale + rotation + translation)