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) {
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
pcl::PointCloud< PointT > PointCloudT
Definition: PCLCloud.h:17
pcl::PCLPointCloud2 PCLCloud
Definition: PCLCloud.h:34
pcl::Normal NormalT
Definition: PCLCloud.h:26
pcl::PointXYZ PointT
Definition: PCLCloud.h:16
#define TO_PCL_CLOUD
Definition: PCLConv.h:12
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
CC to PCL cloud converter.
Definition: cc2sm.h:43
pcl::PointCloud< pcl::PointXYZ >::Ptr getXYZ2() const
Definition: cc2sm.cpp:218
virtual void setGLTransformation(const ccGLMatrix &trans)
Associates entity with a GL transformation (rotation + translation)
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.
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)
Definition: ecvHObject.cpp:948
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
Definition: ecvHObject.cpp:534
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 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 void setGlobalScale(double scale)
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
static ccMesh * Convert(PCLTextureMesh::ConstPtr textureMesh)
Converts a PCL point cloud to a ccPointCloud.
int EstimateGeometricConsistencyGrouping(const typename pcl::PointCloud< PointModelT >::ConstPtr modelKeypoints, const typename pcl::PointCloud< PointSceneT >::ConstPtr sceneKeypoints, const typename pcl::CorrespondencesConstPtr modelSceneCorrs, std::vector< Eigen::Matrix4f, Eigen::aligned_allocator< Eigen::Matrix4f >> &rotoTranslations, std::vector< pcl::Correspondences > &clusteredCorrs, float gcSize=0.01f, float gcThreshold=20.0f)
Definition: PCLModules.h:878
int EstimateHough3DGrouping(const typename pcl::PointCloud< PointModelT >::ConstPtr modelKeypoints, const typename pcl::PointCloud< PointSceneT >::ConstPtr sceneKeypoints, const typename pcl::PointCloud< PointModelRfT >::ConstPtr modelRF, const typename pcl::PointCloud< PointSceneRfT >::ConstPtr sceneRF, const typename pcl::CorrespondencesConstPtr modelSceneCorrs, std::vector< Eigen::Matrix4f, Eigen::aligned_allocator< Eigen::Matrix4f >> &rotoTranslations, std::vector< pcl::Correspondences > &clusteredCorrs, float houghBinSize=0.01f, float houghThreshold=5.0f)
Definition: PCLModules.h:848
int EstimateLocalReferenceFrame(const typename pcl::PointCloud< PointInT >::ConstPtr inCloud, const typename pcl::PointCloud< PointInT >::ConstPtr keyPoints, const typename pcl::PointCloud< NormalType >::ConstPtr normals, typename pcl::PointCloud< RFType >::Ptr outRF, float searchRadius=0.015f)
Definition: PCLModules.h:827
std::vector< Pair > Correspondences
PCL filter description.
Definition: BasePclModule.h:26