ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
CorrespondenceMatching.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 <Utils/cc2sm.h>
11 #include <Utils/sm2cc.h>
12 
13 #include "PclUtils/PCLModules.h"
15 
16 // CV_DB_LIB
17 #include <ecvMesh.h>
18 #include <ecvPointCloud.h>
19 
20 // ECV_PLUGINS
21 #include <ecvMainAppInterface.h>
22 
23 // QT
24 #include <QMainWindow>
25 
26 // SYSTEM
27 #include <iostream>
28 #include <sstream>
29 
32  tr("Correspondence Matching"),
33  tr("Correspondence Matching"),
34  tr("Correspondence Matching from clouds"),
35  ":/toolbar/PclAlgorithms/icons/correspondence_grouping.png")),
36  m_dialog(nullptr),
37  m_sceneCloud(nullptr),
38  m_useVoxelGrid(false),
39  m_gcMode(true),
40  m_verification(true),
41  m_maxThreadCount(1),
42  m_leafSize(0.005f),
43  m_modelSearchRadius(0.02f),
44  m_sceneSearchRadius(0.03f),
45  m_shotDescriptorRadius(0.03f),
46  m_normalKSearch(10.0f),
47  m_consensusResolution(0.01f),
48  m_gcMinClusterSize(20.0f),
49  m_lrfRadius(0.015f),
50  m_houghBinSize(0.01f),
51  m_houghThreshold(5.0f) {}
52 
54  // we must delete parent-less dialogs ourselves!
55  if (m_dialog && m_dialog->parent() == nullptr) delete m_dialog;
56 }
57 
59  // do we have a selected cloud?
60  int have_cloud = isFirstSelectedCcPointCloud();
61  if (have_cloud != 1) return -11;
62 
63  return 1;
64 }
65 
67  // initialize the dialog object
70 
71  if (!m_dialog->exec()) return 0;
72 
74 
75  return 1;
76 }
77 
79  if (!m_dialog) return;
80 
81  // get the parameters from the dialog
84  m_useVoxelGrid = m_leafSize > 0 ? true : false;
86 
88 
98 
99  // get scales
100  m_scales.clear();
101  {
102  if (!m_dialog->getScales(m_scales)) {
103  m_app->dispToConsole(tr("Invalid scale parameters!"),
105  return;
106  }
107  // make sure values are in descending order!
108  std::sort(m_scales.begin(), m_scales.end(), std::greater<float>());
109  }
110 
113  m_modelClouds.clear();
114  {
115  if (!cloud1 && !cloud2) {
116  if (m_app)
117  m_app->dispToConsole(tr("At least one cloud (model #1 or #2) "
118  "was not defined!"),
120  return;
121  }
122  assert(cloud1 != cloud2);
123  if (cloud1) m_modelClouds.push_back(cloud1);
124  if (cloud2) m_modelClouds.push_back(cloud2);
125  }
126 
128 }
129 
131  if (!m_sceneCloud || m_modelClouds.size() == 0) {
132  return -52;
133  }
134  return 1;
135 }
136 
138  typedef PointT PointType;
139  typedef NormalT NormalType;
140  typedef pcl::ReferenceFrame RFType;
141  typedef pcl::SHOT352 DescriptorType;
142 
143  std::vector<std::vector<ccGLMatrixd>> transMatVecs;
144  std::vector<std::vector<Eigen::Matrix4f,
145  Eigen::aligned_allocator<Eigen::Matrix4f>>>
146  rotoTranslationsVec;
147  std::vector<std::vector<pcl::Correspondences>> clusteredCorrs;
148  std::vector<std::vector<pcl::PointCloud<PointType>::ConstPtr>> instancesVec;
149  std::vector<std::vector<bool>> hypothesesMaskVec;
150 
151  PointCloudT::Ptr sceneKeypoints(new PointCloudT());
152  pcl::PointCloud<NormalType>::Ptr sceneNormals(
153  new pcl::PointCloud<NormalType>());
154  pcl::PointCloud<DescriptorType>::Ptr sceneDescriptors(
155  new pcl::PointCloud<DescriptorType>());
156  std::vector<pcl::CorrespondencesPtr> modelSceneCorrs;
157  std::vector<pcl::PointCloud<DescriptorType>::Ptr> modelsDescriptors;
158  std::vector<pcl::PointCloud<NormalType>::Ptr> modelsWithNormal;
159  std::vector<PointCloudT::Ptr> modelsKeypoints;
160  std::vector<PointCloudT::Ptr> modelClouds;
161 
162  // 1. Load the scene cloud
163  PointCloudT::Ptr scene = cc2smReader(m_sceneCloud).getXYZ2();
164  {
165  // down sampling the model point cloud 1
166  if (m_useVoxelGrid) {
167  PointCloudT::Ptr tempCloud(new PointCloudT);
168  if (!PCLModules::VoxelGridFilter<PointType>(
169  scene, tempCloud, m_leafSize, m_leafSize, m_leafSize)) {
170  return -1;
171  }
172  scene = tempCloud;
173  }
174 
175  if (!scene) {
176  return -1;
177  }
178  }
179 
180  // 2. Compute the model and scene normals
181  for (size_t i = 0; i < m_modelClouds.size(); ++i) {
182  PointCloudT::Ptr modelCloud = cc2smReader(m_modelClouds[i]).getXYZ2();
183  if (modelCloud->width * modelCloud->height != 0) {
184  modelClouds.push_back(modelCloud);
185  pcl::PointCloud<NormalType>::Ptr model_normals(
186  new pcl::PointCloud<NormalType>());
187  if (PCLModules::ComputeNormals<PointType, NormalType>(
188  modelCloud, model_normals, m_normalKSearch, true,
189  m_maxThreadCount)) {
190  modelsWithNormal.push_back(model_normals);
191  }
192  }
193  }
194  if (!PCLModules::ComputeNormals<PointType, NormalType>(
195  scene, sceneNormals, m_normalKSearch, true, m_maxThreadCount)) {
196  return -1;
197  }
198 
199  // 3. Downsample Clouds to Extract keypoints
200  if (!PCLModules::GetUniformSampling<PointType>(scene, sceneKeypoints,
202  return -1;
203  }
204  CVLog::Print(tr("Scene total points: %1; Selected Keypoints: %2")
205  .arg(scene->size())
206  .arg(sceneKeypoints->size()));
207  for (size_t i = 0; i < modelClouds.size(); ++i) {
208  PointCloudT::Ptr model_keypoints(new PointCloudT());
209  if (!PCLModules::GetUniformSampling<PointType>(
210  modelClouds[i], model_keypoints, m_modelSearchRadius)) {
211  return -1;
212  }
213  modelsKeypoints.push_back(model_keypoints);
214  CVLog::Print(tr("Model %1 total points: %2; Selected Keypoints: %3")
215  .arg(i + 1)
216  .arg(modelClouds[i]->size())
217  .arg(model_keypoints->size()));
218  }
219 
220  // 4. Compute Descriptor for keypoints
221  if (!PCLModules::EstimateShot<PointType, NormalType, DescriptorType>(
222  scene, sceneKeypoints, sceneNormals, sceneDescriptors,
224  return -1;
225  }
226  for (size_t i = 0; i < modelClouds.size(); ++i) {
227  pcl::PointCloud<DescriptorType>::Ptr model_descriptors(
228  new pcl::PointCloud<DescriptorType>());
229  if (!PCLModules::EstimateShot<PointType, NormalType, DescriptorType>(
230  modelClouds[i], modelsKeypoints[i], modelsWithNormal[i],
231  model_descriptors, m_shotDescriptorRadius,
232  m_maxThreadCount)) {
233  return -1;
234  }
235  modelsDescriptors.push_back(model_descriptors);
236  }
237 
238  // 5. Find Model-Scene Correspondences with KdTree
239  pcl::KdTreeFLANN<DescriptorType> match_search;
240  for (size_t j = 0; j < modelClouds.size(); ++j) {
241  pcl::CorrespondencesPtr model_scene_corrs(new pcl::Correspondences());
242  match_search.setInputCloud(modelsDescriptors[j]);
243 
244  // For each scene keypoint descriptor, find nearest neighbor into the
245  // model keypoints descriptor cloud and add it to the correspondences
246  // vector.
247  for (size_t i = 0; i < sceneDescriptors->size(); ++i) {
248  std::vector<int> neigh_indices(1);
249  std::vector<float> neigh_sqr_dists(1);
250  if (!std::isfinite(sceneDescriptors->at(i)
251  .descriptor[0])) // skipping NaNs
252  {
253  continue;
254  }
255  int found_neighs = match_search.nearestKSearch(
256  sceneDescriptors->at(i), 1, neigh_indices, neigh_sqr_dists);
257  if (found_neighs == 1 &&
258  neigh_sqr_dists[0] <
259  0.25f) // add match only if the squared descriptor
260  // distance is less than 0.25 (SHOT descriptor
261  // distances are between 0 and 1 by design)
262  {
263  pcl::Correspondence corr(neigh_indices[0], static_cast<int>(i),
264  neigh_sqr_dists[0]);
265  model_scene_corrs->push_back(corr);
266  }
267  }
268  CVLog::Print(tr("Correspondences found for model %1 : %2")
269  .arg(j + 1)
270  .arg(model_scene_corrs->size()));
271  modelSceneCorrs.push_back(model_scene_corrs);
272  }
273 
274  // 6. Actual Clustering
275  for (size_t j = 0; j < modelClouds.size(); ++j) {
276  std::vector<Eigen::Matrix4f, Eigen::aligned_allocator<Eigen::Matrix4f>>
277  transformMatrix;
278  std::vector<pcl::Correspondences> clustered_corrs;
279 
280  pcl::CorrespondencesPtr model_scene_corrs = modelSceneCorrs[j];
281  PointCloudT::Ptr model_keypoints = modelsKeypoints[j];
282  PointCloudT::Ptr model = modelClouds[j];
283  pcl::PointCloud<NormalType>::Ptr model_normals = modelsWithNormal[j];
284 
285  if (m_gcMode) {
286  if (!PCLModules::EstimateGeometricConsistencyGrouping<PointType,
287  PointType>(
288  model_keypoints, sceneKeypoints, model_scene_corrs,
289  transformMatrix, clustered_corrs, m_consensusResolution,
291  return -1;
292  }
293  } else {
294  //
295  // Compute (Keypoints) Reference Frames only for Hough
296  //
297  pcl::PointCloud<RFType>::Ptr model_rf(
298  new pcl::PointCloud<RFType>());
299  pcl::PointCloud<RFType>::Ptr scene_rf(
300  new pcl::PointCloud<RFType>());
301 
302  if (!PCLModules::EstimateLocalReferenceFrame<PointType, NormalType,
303  RFType>(
304  model, model_keypoints, model_normals, model_rf,
305  m_lrfRadius)) {
306  return -1;
307  }
308 
309  if (!PCLModules::EstimateLocalReferenceFrame<PointType, NormalType,
310  RFType>(
311  scene, sceneKeypoints, sceneNormals, scene_rf,
312  m_lrfRadius)) {
313  return -1;
314  }
315 
316  // Clustering
317  if (!PCLModules::EstimateHough3DGrouping<PointType, PointType,
318  RFType, RFType>(
319  model_keypoints, sceneKeypoints, model_rf, scene_rf,
320  model_scene_corrs, transformMatrix, clustered_corrs,
322  return -1;
323  }
324  }
325 
326  rotoTranslationsVec.push_back(transformMatrix);
327  clusteredCorrs.push_back(clustered_corrs);
328  }
329 
330  // 7. verify result with icp and global
331  for (size_t j = 0; j < rotoTranslationsVec.size(); ++j) {
332  if (rotoTranslationsVec[j].size() <= 0) {
333  CVLog::Print(tr("No instances found in Model %1 ").arg(j + 1));
334  continue;
335  }
336 
337  CVLog::Print(tr("Model %1 instances found number : %2")
338  .arg(j + 1)
339  .arg(rotoTranslationsVec[j].size()));
340 
344  std::vector<pcl::PointCloud<PointType>::ConstPtr> instances;
345 
346  for (size_t i = 0; i < rotoTranslationsVec[j].size(); ++i) {
347  pcl::PointCloud<PointType>::Ptr rotated_model(
348  new pcl::PointCloud<PointType>());
349  pcl::transformPointCloud(*modelClouds[j], *rotated_model,
350  rotoTranslationsVec[j][i]);
351  instances.push_back(rotated_model);
352  }
353  instancesVec.push_back(instances);
354 
355  std::vector<bool>
356  hypotheses_mask; // Mask Vector to identify positive hypotheses
357  if (m_verification) {
361  std::vector<pcl::PointCloud<PointType>::ConstPtr>
362  registered_instances;
363  {
364  CVLog::Print(tr("--- ICP Start ---------"));
365 
366  for (size_t i = 0; i < rotoTranslationsVec[j].size(); ++i) {
367  pcl::PointCloud<PointType>::Ptr registered(
368  new pcl::PointCloud<PointType>);
369  if (PCLModules::ICPRegistration<PointType, PointType>(
370  scene, instances[i], registered,
372  CVLog::Print(tr("Model %1 Instances %2 Aligned!")
373  .arg(j + 1)
374  .arg(i + 1));
375  } else {
376  CVLog::Print(tr("Model %1 Instances %2 Not Aligned!")
377  .arg(j + 1)
378  .arg(i + 1));
379  }
380  registered_instances.push_back(registered);
381  }
382  CVLog::Print(tr("--- ICP End ---------"));
383  }
384 
388  CVLog::Print(
389  tr("--- Hypotheses bugs and do not support! ---------"));
390  // CVLog::Print(tr("--- Hypotheses Verification
391  // Start
392  //---------")); if
393  //(!PCLModules::GetHypothesesVerification<PointType, PointType>(
394  // scene, registered_instances,
395  // hypotheses_mask, clusterReg, inlierThreshold,
396  // occlusionThreshold, radiusClutter, regularizer, radiusNormals,
397  // detectClutter))
398  // {
399  // return -1;
400  // }
401  // for (int i = 0; i < hypotheses_mask.size(); i++)
402  // {
403  // if (hypotheses_mask[i])
404  // {
405  // CVLog::Print(tr("Model %1
406  // Instances %2 is GOOD!").arg(j
407  //+ 1).arg(i + 1));
408  // }
409  // else
410  // {
411  // CVLog::Print(tr("Model %1
412  // Instances %2 is bad and will be discarded!").arg(j + 1).arg(i +
413  // 1));
414  // }
415  // }
416  // CVLog::Print(tr("--- Hypotheses Verification End
417  //---------"));
418  } else {
419  for (size_t i = 0; i < rotoTranslationsVec[j].size(); ++i) {
420  hypotheses_mask.push_back(true);
421  }
422  }
423 
424  hypothesesMaskVec.push_back(hypotheses_mask);
425  }
426 
427  // 8. export result
428  ccHObject* ecvGroup =
429  new ccHObject(tr("correspondence-grouping-cluster(s)"));
430  ecvGroup->setVisible(true);
431 
432  for (int j = 0; j < static_cast<int>(instancesVec.size()); ++j) {
433  for (int i = 0; i < static_cast<int>(instancesVec[j].size()); ++i) {
434  // Save the aligned model for visualization
435  if (hypothesesMaskVec[j][i]) {
436  PCLCloud out_cloud_sm;
437  TO_PCL_CLOUD(*instancesVec[j][i], out_cloud_sm);
438  if (out_cloud_sm.height * out_cloud_sm.width == 0) {
439  // cloud is empty
440  return -53;
441  }
442 
443  ccPointCloud* out_cloud_cc = pcl2cc::Convert(out_cloud_sm);
444  if (!out_cloud_cc) {
445  // conversion failed (not enough memory?)
446  return -1;
447  }
448 
449  // copy global shift & scale and set name
450  ccPointCloud* cloud = m_dialog->getModelCloudByIndex(j + 1);
452  {
453  out_cloud_cc->setRGBColor(col);
454  out_cloud_cc->showColors(true);
455  out_cloud_cc->showSF(false);
456 
457  if (cloud) {
458  QString outName = tr("%1-correspondence-%2-%3")
459  .arg(cloud->getName())
460  .arg(j + 1)
461  .arg(i + 1);
462  out_cloud_cc->setName(outName);
463  // copy global shift & scale
464  out_cloud_cc->setGlobalScale(cloud->getGlobalScale());
465  out_cloud_cc->setGlobalShift(cloud->getGlobalShift());
466  }
467  }
468  if (out_cloud_cc) {
469  ecvGroup->addChild(out_cloud_cc);
470  }
471  }
472  }
473  }
474 
475  if (ecvGroup->getChildrenNumber() == 0) {
476  delete ecvGroup;
477  ecvGroup = nullptr;
478  return -51;
479  } else {
480  if (m_sceneCloud->getParent())
481  m_sceneCloud->getParent()->addChild(ecvGroup);
482 
483  emit newEntity(ecvGroup);
484  }
485 
486  return 1;
487 }
488 
490  const ccGLMatrixd& mat) {
491  entity->setGLTransformation(ccGLMatrix(mat.data()));
492  // DGM FIXME: we only test the entity own bounding box (and we update its
493  // shift & scale info) but we apply the transformation to all its children?!
495 }
496 
498  switch (errorCode) {
499  // THESE CASES CAN BE USED TO OVERRIDE OR ADD FILTER-SPECIFIC ERRORS
500  // CODES ALSO IN DERIVED CLASSES DEFULAT MUST BE ""
501 
502  case -51:
503  return tr("Cannot match anything by this model !");
504  case -52:
505  return tr(
506  "Wrong Parameters. One or more parameters cannot be "
507  "accepted");
508  case -53:
509  return tr(
510  "Correspondence Matching does not returned any point. Try "
511  "relaxing your parameters");
512  }
513 
514  return BasePclModule::getErrorMessage(errorCode);
515 }
int size
Base abstract class for each implemented PCL filter.
Definition: BasePclModule.h:53
int isFirstSelectedCcPointCloud()
Returns 1 if the first selected object is a ccPointCloud.
void newEntity(ccHObject *)
Signal emitted when a new entity is created by the filter.
ecvMainAppInterface * m_app
Associated application interface.
virtual QString getErrorMessage(int errorCode)
Returns the error message corresponding to a given error code.
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
CANUPO plugin's training dialog.
void saveParamsToPersistentSettings()
Saves parameters to persistent settings.
ccPointCloud * getModelCloudByIndex(int index)
float getModelSearchRadius() const
Returns the Model Search Radius.
float getShotDescriptorRadius() const
Returns the Shot Descriptor Radius.
float getNormalKSearch() const
Returns the normal KSearch.
ccPointCloud * getEvaluationCloud()
Get evaluation point cloud.
int getMaxThreadCount() const
Returns the max number of threads to use.
bool getScales(std::vector< float > &scales) const
Returns input scales.
float getSceneSearchRadius() const
Returns the Scene Search Radius.
virtual int compute()
Performs the actual filter job.
std::vector< ccPointCloud * > m_modelClouds
virtual QString getErrorMessage(int errorCode)
Returns the error message corresponding to a given error code.
void applyTransformation(ccHObject *entity, const ccGLMatrixd &mat)
virtual void getParametersFromDialog()
Collects parameters from the filter dialog (if openDialog is successful)
CorrespondenceMatchingDialog * m_dialog
virtual int checkSelected()
Checks if current selection is compliant with the filter.
std::vector< float > m_scales
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual void setGLTransformation(const ccGLMatrix &trans)
Associates entity with a GL transformation (rotation + translation)
T * data()
Returns a pointer to internal data.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Double version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:56
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool setRGBColor(ColorCompType r, ColorCompType g, ColorCompType b)
Set a unique color for the whole cloud (shortcut)
virtual void setGlobalScale(double scale)
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
static Rgb Random(bool lightOnly=true)
Generates a random color.
RGB color structure.
Definition: ecvColorTypes.h:49
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
std::vector< Pair > Correspondences
PCL filter description.
Definition: BasePclModule.h:26