ccRegistrationTools.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccRegistrationTools.h"
  18. //CCCoreLib
  19. #include <CloudSamplingTools.h>
  20. #include <DistanceComputationTools.h>
  21. #include <Garbage.h>
  22. #include <GenericIndexedCloudPersist.h>
  23. #include <MeshSamplingTools.h>
  24. #include <ParallelSort.h>
  25. #include <PointCloud.h>
  26. #include <RegistrationTools.h>
  27. //qCC_db
  28. #include <ccGenericMesh.h>
  29. #include <ccHObjectCaster.h>
  30. #include <ccLog.h>
  31. #include <ccPointCloud.h>
  32. #include <ccProgressDialog.h>
  33. #include <ccScalarField.h>
  34. //system
  35. #include <set>
  36. //! Default number of points sampled on the 'data' mesh (if any)
  37. static const unsigned s_defaultSampledPointsOnDataMesh = 50000;
  38. //! Default temporary registration scalar field
  39. static const char REGISTRATION_DISTS_SF[] = "RegistrationDistances";
  40. bool ccRegistrationTools::ICP( ccHObject* data,
  41. ccHObject* model,
  42. ccGLMatrix& transMat,
  43. double& finalScale,
  44. double& finalRMS,
  45. unsigned& finalPointCount,
  46. const CCCoreLib::ICPRegistrationTools::Parameters& inputParameters,
  47. bool useDataSFAsWeights/*=false*/,
  48. bool useModelSFAsWeights/*=false*/,
  49. QWidget* parent/*=nullptr*/)
  50. {
  51. bool restoreColorState = false;
  52. bool restoreSFState = false;
  53. CCCoreLib::ICPRegistrationTools::Parameters params = inputParameters;
  54. //progress bar
  55. QScopedPointer<ccProgressDialog> progressDlg;
  56. if (parent)
  57. {
  58. progressDlg.reset(new ccProgressDialog(false, parent));
  59. }
  60. CCCoreLib::Garbage<CCCoreLib::GenericIndexedCloudPersist> cloudGarbage;
  61. //if the 'model' entity is a mesh, we need to sample points on it
  62. CCCoreLib::GenericIndexedCloudPersist* modelCloud = nullptr;
  63. ccGenericMesh* modelMesh = nullptr;
  64. if (model->isKindOf(CC_TYPES::MESH))
  65. {
  66. modelMesh = ccHObjectCaster::ToGenericMesh(model);
  67. modelCloud = modelMesh->getAssociatedCloud();
  68. }
  69. else
  70. {
  71. modelCloud = ccHObjectCaster::ToGenericPointCloud(model);
  72. }
  73. //if the 'data' entity is a mesh, we need to sample points on it
  74. CCCoreLib::GenericIndexedCloudPersist* dataCloud = nullptr;
  75. if (data->isKindOf(CC_TYPES::MESH))
  76. {
  77. dataCloud = CCCoreLib::MeshSamplingTools::samplePointsOnMesh(ccHObjectCaster::ToGenericMesh(data), s_defaultSampledPointsOnDataMesh, progressDlg.data());
  78. if (!dataCloud)
  79. {
  80. ccLog::Error("[ICP] Failed to sample points on 'data' mesh!");
  81. return false;
  82. }
  83. cloudGarbage.add(dataCloud);
  84. }
  85. else
  86. {
  87. dataCloud = ccHObjectCaster::ToGenericPointCloud(data);
  88. }
  89. //we activate a temporary scalar field for registration distances computation
  90. CCCoreLib::ScalarField* dataDisplayedSF = nullptr;
  91. int oldDataSfIdx = -1;
  92. int dataSfIdx = -1;
  93. //if the 'data' entity is a real ccPointCloud, we can even create a proper temporary SF for registration distances
  94. if (data->isA(CC_TYPES::POINT_CLOUD))
  95. {
  96. ccPointCloud* pc = static_cast<ccPointCloud*>(data);
  97. restoreColorState = pc->colorsShown();
  98. restoreSFState = pc->sfShown();
  99. dataDisplayedSF = pc->getCurrentDisplayedScalarField();
  100. oldDataSfIdx = pc->getCurrentInScalarFieldIndex();
  101. dataSfIdx = pc->getScalarFieldIndexByName(REGISTRATION_DISTS_SF);
  102. if (dataSfIdx < 0)
  103. dataSfIdx = pc->addScalarField(REGISTRATION_DISTS_SF);
  104. if (dataSfIdx >= 0)
  105. pc->setCurrentScalarField(dataSfIdx);
  106. else
  107. {
  108. ccLog::Error("[ICP] Couldn't create temporary scalar field! Not enough memory?");
  109. return false;
  110. }
  111. }
  112. else
  113. {
  114. if (!dataCloud->enableScalarField())
  115. {
  116. ccLog::Error("[ICP] Couldn't create temporary scalar field! Not enough memory?");
  117. return false;
  118. }
  119. }
  120. //add a 'safety' margin to input ratio
  121. static double s_overlapMarginRatio = 0.2;
  122. params.finalOverlapRatio = std::max(params.finalOverlapRatio, 0.01); //1% minimum
  123. //do we need to reduce the input point cloud (so as to be close
  124. //to the theoretical number of overlapping points - but not too
  125. //low so as we are not registered yet ;)
  126. if (params.finalOverlapRatio < 1.0 - s_overlapMarginRatio)
  127. {
  128. //DGM we can now use 'approximate' distances as SAITO algorithm is exact (but with a coarse resolution)
  129. //level = 7 if < 1.000.000
  130. //level = 8 if < 10.000.000
  131. //level = 9 if > 10.000.000
  132. int gridLevel = static_cast<int>(log10(static_cast<double>(std::max(dataCloud->size(), modelCloud->size())))) + 2; //static_cast is equivalent to floor if value >= 0
  133. gridLevel = std::min(std::max(gridLevel, 7), 9);
  134. int result = -1;
  135. if (modelMesh)
  136. {
  137. CCCoreLib::DistanceComputationTools::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;
  144. c2mParams.robust = true;
  145. result = CCCoreLib::DistanceComputationTools::computeCloud2MeshDistances( dataCloud,
  146. modelMesh,
  147. c2mParams,
  148. progressDlg.data());
  149. }
  150. else
  151. {
  152. result = CCCoreLib::DistanceComputationTools::computeApproxCloud2CloudDistance( dataCloud,
  153. modelCloud,
  154. gridLevel,
  155. -1,
  156. progressDlg.data());
  157. }
  158. if (result < 0)
  159. {
  160. ccLog::Error("Failed to determine the max (overlap) distance (not enough memory?)");
  161. return false;
  162. }
  163. //determine the max distance that (roughly) corresponds to the input overlap ratio
  164. ScalarType maxSearchDist = 0;
  165. {
  166. unsigned count = dataCloud->size();
  167. std::vector<ScalarType> distances;
  168. try
  169. {
  170. distances.resize(count);
  171. }
  172. catch (const std::bad_alloc&)
  173. {
  174. ccLog::Error("Not enough memory!");
  175. return false;
  176. }
  177. for (unsigned i = 0; i < count; ++i)
  178. {
  179. distances[i] = dataCloud->getPointScalarValue(i);
  180. }
  181. ParallelSort(distances.begin(), distances.end());
  182. //now look for the max value at 'finalOverlapRatio + margin' percent
  183. maxSearchDist = distances[static_cast<size_t>(std::max(1.0, count*(params.finalOverlapRatio + s_overlapMarginRatio))) - 1];
  184. }
  185. //evntually select the points with distance below 'maxSearchDist'
  186. //(should roughly correspond to 'finalOverlapRatio + margin' percent)
  187. {
  188. CCCoreLib::ReferenceCloud* refCloud = new CCCoreLib::ReferenceCloud(dataCloud);
  189. cloudGarbage.add(refCloud);
  190. unsigned countBefore = dataCloud->size();
  191. unsigned baseIncrement = static_cast<unsigned>(std::max(100.0, countBefore*params.finalOverlapRatio*0.05));
  192. for (unsigned i = 0; i < countBefore; ++i)
  193. {
  194. if (dataCloud->getPointScalarValue(i) <= maxSearchDist)
  195. {
  196. if ( refCloud->size() == refCloud->capacity()
  197. && !refCloud->reserve(refCloud->size() + baseIncrement) )
  198. {
  199. ccLog::Error("Not enough memory!");
  200. return false;
  201. }
  202. refCloud->addPointIndex(i);
  203. }
  204. }
  205. refCloud->resize(refCloud->size());
  206. dataCloud = refCloud;
  207. unsigned countAfter = dataCloud->size();
  208. double keptRatio = static_cast<double>(countAfter)/countBefore;
  209. ccLog::Print(QString("[ICP][Partial overlap] Selecting %1 points out of %2 (%3%) for registration").arg(countAfter).arg(countBefore).arg(static_cast<int>(100*keptRatio)));
  210. //update the relative 'final overlap' ratio
  211. params.finalOverlapRatio /= keptRatio;
  212. }
  213. }
  214. //weights
  215. params.modelWeights = nullptr;
  216. params.dataWeights = nullptr;
  217. {
  218. if (!modelMesh && useModelSFAsWeights)
  219. {
  220. if (modelCloud == dynamic_cast<CCCoreLib::GenericIndexedCloudPersist*>(model) && model->isA(CC_TYPES::POINT_CLOUD))
  221. {
  222. ccPointCloud* pc = static_cast<ccPointCloud*>(model);
  223. params.modelWeights = pc->getCurrentDisplayedScalarField();
  224. if (!params.modelWeights)
  225. ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but model has no displayed scalar field!");
  226. }
  227. else
  228. {
  229. ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but only point cloud scalar fields can be used as weights!");
  230. }
  231. }
  232. if (useDataSFAsWeights)
  233. {
  234. if (!dataDisplayedSF)
  235. {
  236. if (dataCloud == ccHObjectCaster::ToPointCloud(data))
  237. ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but data has no displayed scalar field!");
  238. else
  239. ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but only point cloud scalar fields can be used as weights!");
  240. }
  241. else
  242. {
  243. params.dataWeights = dataDisplayedSF;
  244. }
  245. }
  246. }
  247. ccLog::Print(QString("[ICP] Will use %1 threads").arg(params.maxThreadCount));
  248. CCCoreLib::ICPRegistrationTools::RESULT_TYPE result;
  249. CCCoreLib::PointProjectionTools::Transformation transform;
  250. result = CCCoreLib::ICPRegistrationTools::Register( modelCloud,
  251. modelMesh,
  252. dataCloud,
  253. params,
  254. transform,
  255. finalRMS,
  256. finalPointCount,
  257. static_cast<CCCoreLib::GenericProgressCallback*>(progressDlg.data()));
  258. if (result >= CCCoreLib::ICPRegistrationTools::ICP_ERROR)
  259. {
  260. ccLog::Error("Registration failed: an error occurred (code %i)",result);
  261. }
  262. else if (result == CCCoreLib::ICPRegistrationTools::ICP_APPLY_TRANSFO)
  263. {
  264. transMat = FromCCLibMatrix<double, float>(transform.R, transform.T, transform.s);
  265. finalScale = transform.s;
  266. }
  267. //remove temporary SF (if any)
  268. if (dataSfIdx >= 0)
  269. {
  270. assert(data->isA(CC_TYPES::POINT_CLOUD));
  271. ccPointCloud* pc = static_cast<ccPointCloud*>(data);
  272. pc->setCurrentScalarField(oldDataSfIdx);
  273. pc->deleteScalarField(dataSfIdx);
  274. pc->showColors(restoreColorState);
  275. pc->showSF(restoreSFState);
  276. }
  277. return (result < CCCoreLib::ICPRegistrationTools::ICP_ERROR);
  278. }