Compute normals using flann and CloudCompare
This commit is contained in:
+38
-21
@@ -11,10 +11,10 @@ if ( PLUGIN_G3POINT )
|
||||
|
||||
AddPlugin( NAME ${PROJECT_NAME} )
|
||||
|
||||
target_sources(G3PointPlugin
|
||||
PRIVATE
|
||||
ui/WolmanCustomPlot.ui
|
||||
)
|
||||
target_sources(G3PointPlugin
|
||||
PRIVATE
|
||||
ui/WolmanCustomPlot.ui
|
||||
)
|
||||
|
||||
set(QG3POINT_PLUGIN_VERSION "0.6")
|
||||
|
||||
@@ -23,14 +23,6 @@ target_sources(G3PointPlugin
|
||||
add_subdirectory( ui )
|
||||
|
||||
# target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) # for mlpack
|
||||
|
||||
target_include_directories( ${PROJECT_NAME} PRIVATE
|
||||
C:/opt/eigen-3.4.0
|
||||
C:/Users/PaulLeroy/miniconda3/envs/env_4_CloudCompare/include
|
||||
C:/opt/open3d-devel-windows-amd64-0.19.0/include
|
||||
C:/opt/GeometricTools/GTE
|
||||
C:/opt/boost_1_77_0
|
||||
)
|
||||
|
||||
# may be needed for debug
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
|
||||
@@ -42,19 +34,44 @@ target_sources(G3PointPlugin
|
||||
Qt5::PrintSupport
|
||||
)
|
||||
|
||||
# Find installed Open3D, which exports Open3D::Open3D
|
||||
find_package( Open3D REQUIRED )
|
||||
target_link_libraries( ${PROJECT_NAME} Open3D::Open3D )
|
||||
# EIGEN
|
||||
set( EIGEN_ROOT_DIR "" CACHE PATH "Eigen root (contains the Eigen directory)" )
|
||||
if ( NOT EIGEN_ROOT_DIR )
|
||||
message( SEND_ERROR "No Eigen root directory specified (EIGEN_ROOT_DIR)" )
|
||||
else()
|
||||
message( STATUS "EIGEN_ROOT_DIR " ${EIGEN_ROOT_DIR} )
|
||||
endif()
|
||||
|
||||
# OPEN3D
|
||||
option(USE_OPEN3D_WITH_G3POINT "Enable the use of Open3D with G3Point" OFF)
|
||||
if (USE_OPEN3D_WITH_G3POINT)
|
||||
find_package( Open3D REQUIRED ) # Find installed Open3D, which exports Open3D::Open3D
|
||||
target_link_libraries( ${PROJECT_NAME} Open3D::Open3D )
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set( OPENCV_DEP_DLL_FILES
|
||||
${Open3D_DIR}/../bin/Open3D.dll
|
||||
${Open3D_DIR}/../bin/tbb12_debug.dll)
|
||||
elseif(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
set( OPENCV_DEP_DLL_FILES
|
||||
${Open3D_DIR}/../bin/Open3D.dll
|
||||
${Open3D_DIR}/../bin/tbb12.dll)
|
||||
endif()
|
||||
message( "Open3D_DIR ${Open3D_DIR}" )
|
||||
copy_files( "${OPENCV_DEP_DLL_FILES}" "${CLOUDCOMPARE_DEST_FOLDER}" 1) #mind the quotes!
|
||||
endif()
|
||||
|
||||
message( "Open3D_DIR ${Open3D_DIR}" )
|
||||
set( OPENCV_DEP_DLL_FILES
|
||||
${Open3D_DIR}/../bin/Open3D.dll
|
||||
${Open3D_DIR}/../bin/tbb12.dll
|
||||
)
|
||||
copy_files( "${OPENCV_DEP_DLL_FILES}" "${CLOUDCOMPARE_DEST_FOLDER}" 1) #mind the quotes!
|
||||
copy_files( "${CMAKE_CURRENT_SOURCE_DIR}/shaders/DrawGrains.vs" "${CLOUDCOMPARE_DEST_FOLDER}/shaders/G3Point" 1) #mind the quotes!
|
||||
copy_files( "${CMAKE_CURRENT_SOURCE_DIR}/shaders/DrawGrains.fs" "${CLOUDCOMPARE_DEST_FOLDER}/shaders/G3Point" 1) #mind the quotes!
|
||||
|
||||
target_include_directories( ${PROJECT_NAME} PRIVATE
|
||||
C:/Users/PaulLeroy/miniconda3/envs/env_4_CloudCompare/include
|
||||
C:/opt/open3d-devel-windows-amd64-0.19.0/include
|
||||
C:/opt/GeometricTools/GTE
|
||||
C:/opt/boost_1_77_0
|
||||
../../../../libs/qCC_db/extern/CCCoreLib/extern/nanoflann/include
|
||||
${EIGEN_ROOT_DIR}
|
||||
)
|
||||
|
||||
#================
|
||||
# git commit hash
|
||||
# Get the current working branch
|
||||
|
||||
+48
-4
@@ -1,24 +1,27 @@
|
||||
#include "Eigen/Dense"
|
||||
#include <nanoflann.hpp>
|
||||
|
||||
// qCC_db
|
||||
#include <ccOctree.h>
|
||||
#include <ccScalarField.h>
|
||||
#include <ccPointCloud.h>
|
||||
|
||||
// CCCoreLib
|
||||
#include <DgmOctree.h>
|
||||
#include <Neighbourhood.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <G3PointDialog.h>
|
||||
|
||||
#include <GrainsAsEllipsoids.h>
|
||||
|
||||
#include <AnglesCustomPlot.h>
|
||||
|
||||
#include <G3PointPlots.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
class ccMainAppInterface;
|
||||
class ccPointCloud;
|
||||
|
||||
namespace G3Point
|
||||
{
|
||||
@@ -53,6 +56,38 @@ public:
|
||||
|
||||
template<typename T> static bool EigenArrayToFile(QString name, T array);
|
||||
|
||||
|
||||
// A small adaptor to let nanoflann access ccPointCloud data
|
||||
struct CloudAdaptor
|
||||
{
|
||||
const ccPointCloud* cloud;
|
||||
|
||||
CloudAdaptor(const ccPointCloud* c) : cloud(c) {}
|
||||
|
||||
// Must return the number of data points
|
||||
inline size_t kdtree_get_point_count() const { return cloud->size(); }
|
||||
|
||||
// Returns the dim'th component of the idx'th point
|
||||
inline float kdtree_get_pt(const size_t idx, int dim) const
|
||||
{
|
||||
if (dim == 0)
|
||||
return cloud->getPoint(static_cast<unsigned>(idx))->x;
|
||||
else if (dim == 1)
|
||||
return cloud->getPoint(static_cast<unsigned>(idx))->y;
|
||||
else
|
||||
return cloud->getPoint(static_cast<unsigned>(idx))->z;
|
||||
}
|
||||
|
||||
// Optional bounding-box computation: return false to default to a standard bbox computation loop.
|
||||
template<class BBOX>
|
||||
bool kdtree_get_bbox(BBOX&) const { return false; }
|
||||
};
|
||||
|
||||
// Typedef for a 3D KD-tree index
|
||||
using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<float, CloudAdaptor>,
|
||||
CloudAdaptor,
|
||||
3 /* dim */>;
|
||||
|
||||
private:
|
||||
bool sfConvertToRandomRGB(const ccHObject::Container &selectedEntities, QWidget* parent);
|
||||
void addToStack(int index, const Eigen::ArrayXi& n_donors, const Eigen::ArrayXXi& donors, std::vector<int>& stack);
|
||||
@@ -70,6 +105,13 @@ private:
|
||||
bool computeNormalsAndOrientThemWithCloudCompare();
|
||||
void orientNormals(const Eigen::Vector3d &sensorCenter);
|
||||
bool computeNormalsWithOpen3D();
|
||||
static bool FindNearestNeighborsNanoFlann(ccPointCloud* cloud, unsigned globalIndex, int k,
|
||||
CCCoreLib::ReferenceCloud *points, KDTree *kdTree);
|
||||
static bool ComputeNormsAtLevel(const CCCoreLib::DgmOctree::octreeCell& cell,
|
||||
void** additionalParameters,
|
||||
CCCoreLib::NormalizedProgress* nProgress=nullptr);
|
||||
bool computeNormalsWithCloudCompare();
|
||||
bool computeNormals();
|
||||
bool queryNeighbors(ccPointCloud* cloud, ccMainAppInterface* appInterface, bool useParallelStrategy=true);
|
||||
void init();
|
||||
void showDlg();
|
||||
@@ -118,5 +160,7 @@ private:
|
||||
GrainsAsEllipsoids* m_grainsAsEllipsoids;
|
||||
|
||||
int m_currentNumberOfSteps;
|
||||
|
||||
QSharedPointer<KDTree> m_kdTree;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,22 +15,6 @@
|
||||
//# #
|
||||
//##########################################################################
|
||||
|
||||
// First:
|
||||
// Replace all occurrences of 'G3PointPlugin' by your own plugin class name in this file.
|
||||
// This includes the resource path to info.json in the constructor.
|
||||
|
||||
// Second:
|
||||
// Open G3PointPlugin.qrc, change the "prefix" and the icon filename for your plugin.
|
||||
// Change the name of the file to <yourPluginName>.qrc
|
||||
|
||||
// Third:
|
||||
// Open the info.json file and fill in the information about the plugin.
|
||||
// "type" should be one of: "Standard", "GL", or "I/O" (required)
|
||||
// "name" is the name of the plugin (required)
|
||||
// "icon" is the Qt resource path to the plugin's icon (from the .qrc file)
|
||||
// "description" is used as a tootip if the plugin has actions and is displayed in the plugin dialog
|
||||
// "authors", "maintainers", and "references" show up in the plugin dialog as well
|
||||
|
||||
#include <QtGui>
|
||||
|
||||
#include "G3Point.h"
|
||||
|
||||
+225
-1
@@ -1,4 +1,6 @@
|
||||
#include "G3PointAction.h"
|
||||
#include "DgmOctreeReferenceCloud.h"
|
||||
#include "Neighbourhood.h"
|
||||
|
||||
// CCPluginAPI
|
||||
#include <ccMainAppInterface.h>
|
||||
@@ -33,7 +35,9 @@
|
||||
#include <random>
|
||||
|
||||
// Open3D
|
||||
#ifdef USE_OPEN3D_WITH_G3POINT
|
||||
#include <open3d/geometry/PointCloud.h>
|
||||
#endif
|
||||
|
||||
// Eigen
|
||||
#include <Eigen/Geometry>
|
||||
@@ -1755,6 +1759,7 @@ void G3PointAction::orientNormals(const Eigen::Vector3d& sensorCenter)
|
||||
|
||||
bool G3PointAction::computeNormalsWithOpen3D()
|
||||
{
|
||||
#ifdef USE_OPEN3D_WITH_G3POINT
|
||||
// create an open3D point cloud from the original point cloud
|
||||
std::vector<Eigen::Vector3d> points(m_cloud->size());
|
||||
for (int index =0; index < points.size(); index++) // copy all points
|
||||
@@ -1807,6 +1812,225 @@ bool G3PointAction::computeNormalsWithOpen3D()
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool G3PointAction::FindNearestNeighborsNanoFlann(ccPointCloud* cloud,
|
||||
unsigned globalIndex,
|
||||
int kNN,
|
||||
CCCoreLib::ReferenceCloud* points,
|
||||
KDTree* kdTree)
|
||||
{
|
||||
// Prepare query
|
||||
const CCVector3* Q = cloud->getPoint(globalIndex);
|
||||
float query[3] = { Q->x, Q->y, Q->z };
|
||||
|
||||
std::vector<size_t> retIndexes(kNN);
|
||||
std::vector<float> outDistsSqr(kNN);
|
||||
|
||||
// Perform search
|
||||
nanoflann::KNNResultSet<float> resultSet(kNN);
|
||||
resultSet.init(&retIndexes[0], &outDistsSqr[0]);
|
||||
if(kdTree->findNeighbors(resultSet, &query[0]))
|
||||
{
|
||||
points->resize(kNN);
|
||||
for (int i = 0; i < kNN; ++i)
|
||||
{
|
||||
points->setPointIndex(i, retIndexes[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool G3PointAction::ComputeNormsAtLevel(const CCCoreLib::DgmOctree::octreeCell& cell,
|
||||
void** additionalParameters,
|
||||
CCCoreLib::NormalizedProgress* nProgress /*=nullptr*/)
|
||||
{
|
||||
// additional parameters
|
||||
NormsTableType* theNorms = static_cast<NormsTableType*>(additionalParameters[0]);
|
||||
int* kNN = static_cast<int*>(additionalParameters[1]);
|
||||
KDTree* kdTree = static_cast<KDTree*>(additionalParameters[2]);
|
||||
ccPointCloud* cloud = static_cast<ccPointCloud*>(additionalParameters[3]);
|
||||
|
||||
CCCoreLib::DgmOctree::NearestNeighboursSearchStruct nNSS;
|
||||
nNSS.level = cell.level;
|
||||
nNSS.minNumberOfNeighbors = *kNN;
|
||||
cell.parentOctree->getCellPos(cell.truncatedCode, cell.level, nNSS.cellPos, true);
|
||||
cell.parentOctree->computeCellCenter(nNSS.cellPos, cell.level, nNSS.cellCenter);
|
||||
|
||||
// we already know which points are lying in the current cell
|
||||
unsigned pointCount = cell.points->size();
|
||||
nNSS.pointsInNeighbourhood.resize(pointCount);
|
||||
CCCoreLib::DgmOctree::NeighboursSet::iterator it = nNSS.pointsInNeighbourhood.begin();
|
||||
{
|
||||
for (unsigned j = 0; j < pointCount; ++j, ++it)
|
||||
{
|
||||
it->point = cell.points->getPointPersistentPtr(j);
|
||||
it->pointIndex = cell.points->getPointGlobalIndex(j);
|
||||
}
|
||||
}
|
||||
nNSS.alreadyVisitedNeighbourhoodSize = 1;
|
||||
|
||||
for (unsigned i = 0; i < pointCount; ++i)
|
||||
{
|
||||
cell.points->getPoint(i, nNSS.queryPoint);
|
||||
unsigned int globalIndex = cell.points->getPointGlobalIndex(i);
|
||||
|
||||
CCVector3 N;
|
||||
|
||||
QScopedPointer<CCCoreLib::ReferenceCloud> points(new CCCoreLib::ReferenceCloud(cloud));
|
||||
if(FindNearestNeighborsNanoFlann(cloud, globalIndex, *kNN, points.data(), kdTree))
|
||||
{
|
||||
CCCoreLib::Neighbourhood neighbourhood(points.data());
|
||||
N = *neighbourhood.getLSPlaneNormal();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
theNorms->setValue(globalIndex, N);
|
||||
|
||||
if (nProgress && !nProgress->oneStep())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool G3PointAction::computeNormalsWithCloudCompare()
|
||||
{
|
||||
unsigned pointCount = m_cloud->size();
|
||||
|
||||
if (!m_cloud || m_cloud->size() == 0)
|
||||
{
|
||||
std::cerr << "Invalid cloud.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
CloudAdaptor adaptor(m_cloud);
|
||||
|
||||
// Build KD-tree (parameter: number of leaf nodes to inspect per query)
|
||||
|
||||
size_t leaf_max_size = 10;
|
||||
nanoflann::KDTreeSingleIndexAdaptorFlags flags = nanoflann::KDTreeSingleIndexAdaptorFlags::None;
|
||||
unsigned int n_thread_build = 0; // 0 => nanoflann automatically determines the number of threads to use
|
||||
|
||||
nanoflann::KDTreeSingleIndexAdaptorParams params(leaf_max_size, flags, n_thread_build);
|
||||
m_kdTree.reset(new KDTree(3, adaptor, params));
|
||||
m_kdTree->buildIndex();
|
||||
|
||||
ccOctree::Shared octree = m_cloud->getOctree();
|
||||
if (octree.isNull())
|
||||
{
|
||||
octree.reset(new ccOctree(m_cloud));
|
||||
if (octree->build() <= 0)
|
||||
{
|
||||
octree.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
unsigned char level = octree->findBestLevelForAGivenPopulationPerCell(m_kNN);
|
||||
|
||||
// we instantiate 3D normal vectors
|
||||
QScopedPointer<NormsTableType> theNorms(new NormsTableType);
|
||||
QScopedPointer<NormsIndexesTableType> normsIndexes(new NormsIndexesTableType);
|
||||
static const CCVector3 blankN(0, 0, 0);
|
||||
if (!theNorms->resizeSafe(pointCount, true, &blankN))
|
||||
{
|
||||
normsIndexes->resize(0);
|
||||
if (nullptr == octree)
|
||||
{
|
||||
octree.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// theNorms->fill(0);
|
||||
|
||||
void* additionalParameters[4] = {reinterpret_cast<void*>(theNorms.data()),
|
||||
reinterpret_cast<void*>(&m_kNN),
|
||||
reinterpret_cast<void*>(m_kdTree.data()),
|
||||
reinterpret_cast<void*>(m_cloud)};
|
||||
|
||||
unsigned processedCells = 0;
|
||||
QScopedPointer<ccProgressDialog> progressCb;
|
||||
processedCells = octree->executeFunctionForAllCellsStartingAtLevel(level,
|
||||
&(ComputeNormsAtLevel),
|
||||
additionalParameters,
|
||||
m_kNN / 2,
|
||||
m_kNN * 3,
|
||||
true,
|
||||
progressCb.data(),
|
||||
"Normals Computation [G3Point]");
|
||||
|
||||
// error or canceled by user?
|
||||
if (processedCells == 0 || (progressCb && progressCb->isCancelRequested()))
|
||||
{
|
||||
normsIndexes->resize(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_cloud->hasNormals())
|
||||
{
|
||||
if (!m_cloud->resizeTheNormsTable())
|
||||
{
|
||||
ccLog::Error(QString("Not enough memory to compute normals on cloud '%1'").arg(m_cloud->getName()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we hide normals during process
|
||||
m_cloud->showNormals(false);
|
||||
|
||||
// compress the normals
|
||||
for (unsigned i = 0; i < theNorms->currentSize(); i++)
|
||||
{
|
||||
const CCVector3& N = theNorms->at(i);
|
||||
const CompressedNormType nCode = ccNormalVectors::GetNormIndex(N);
|
||||
m_cloud->setPointNormalIndex(i, nCode);
|
||||
}
|
||||
|
||||
// preferred orientation
|
||||
ccNormalVectors::UpdateNormalOrientations(m_cloud, *m_cloud->normals(), ccNormalVectors::PLUS_Z);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool G3PointAction::computeNormals()
|
||||
{
|
||||
// if there are normals, already, propose to keep them
|
||||
if (m_cloud->hasNormals())
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setInformativeText("Recompute normals?");
|
||||
msgBox.setText("There are existing normals, keep them or recompute.");
|
||||
QPushButton *keepButton = msgBox.addButton(tr("Keep"), QMessageBox::ActionRole);
|
||||
QPushButton *recomputeButton = msgBox.addButton(tr("Recompute"), QMessageBox::AcceptRole);
|
||||
QPushButton *cancelButton = msgBox.addButton(tr("Cancel"), QMessageBox::AcceptRole);
|
||||
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == keepButton)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (msgBox.clickedButton() == cancelButton)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_OPEN3D_WITH_G3POINT
|
||||
return computeNormalsWithOpen3D();
|
||||
#else
|
||||
return computeNormalsWithCloudCompare();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool G3PointAction::queryNeighbors(ccPointCloud* cloud, ccMainAppInterface* appInterface, bool useParallelStrategy)
|
||||
@@ -1879,7 +2103,7 @@ void G3PointAction::segment()
|
||||
|
||||
computeNodeSurfaces();
|
||||
|
||||
computeNormalsWithOpen3D();
|
||||
computeNormals();
|
||||
|
||||
// compute the centroid
|
||||
unsigned pointCount = m_cloud->size();
|
||||
|
||||
Reference in New Issue
Block a user