Skip to content

Commit 4ea8161

Browse files
feat: add FitPlaneRANSAC method to DFPointCloud
1 parent f51f3c0 commit 4ea8161

File tree

2 files changed

+22
-70
lines changed

2 files changed

+22
-70
lines changed

src/diffCheck/geometry/DFPointCloud.cc

Lines changed: 12 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -216,74 +216,21 @@ namespace diffCheck::geometry
216216
this->Normals.push_back(normal);
217217
}
218218

219-
std::vector<Eigen::Vector3d> DFPointCloud::GetPrincipalAxes(int nComponents)
219+
Eigen::Vector3d DFPointCloud::FitPlaneRANSAC(
220+
double distanceThreshold,
221+
int ransacN,
222+
int numIterations)
220223
{
221-
std::vector<Eigen::Vector3d> principalAxes;
222-
223-
if (! this->HasNormals())
224-
{
225-
DIFFCHECK_WARN("The point cloud has no normals. Normals will be estimated with knn = 20.");
226-
this->EstimateNormals(true, 20);
227-
}
228-
229-
// Convert normals to Eigen matrix
230-
Eigen::Matrix<double, 3, Eigen::Dynamic> normalMatrix(3, this->Normals.size());
231-
for (size_t i = 0; i < this->Normals.size(); ++i)
232-
{
233-
normalMatrix.col(i) = this->Normals[i].cast<double>();
234-
}
235-
236-
cilantro::KMeans<double, 3> kmeans(normalMatrix);
237-
kmeans.cluster(nComponents);
238-
239-
const cilantro::VectorSet3d& centroids = kmeans.getClusterCentroids();
240-
const std::vector<size_t>& assignments = kmeans.getPointToClusterIndexMap();
241-
std::vector<int> clusterSizes(nComponents, 0);
242-
for (size_t i = 0; i < assignments.size(); ++i)
243-
{
244-
clusterSizes[assignments[i]]++;
245-
}
246-
// Sort clusters by size
247-
std::vector<std::pair<int, Eigen::Vector3d>> sortedClustersBySize(nComponents);
248-
for (size_t i = 0; i < nComponents; ++i)
224+
if (this->Points.size() < ransacN)
249225
{
250-
sortedClustersBySize[i] = {clusterSizes[i], centroids.col(i)};
226+
DIFFCHECK_ERROR("Not enough points to fit a plane with RANSAC.");
227+
return Eigen::Vector3d::Zero();
251228
}
252-
std::sort(sortedClustersBySize.begin(), sortedClustersBySize.end(), [](const auto& a, const auto& b)
253-
{
254-
return a.first > b.first;
255-
});
256-
257-
for(size_t i = 0; i < nComponents; ++i)
258-
{
259-
if(principalAxes.size() == 0)
260-
{
261-
principalAxes.push_back(sortedClustersBySize[i].second);
262-
}
263-
else
264-
{
265-
bool isAlreadyPresent = false;
266-
for (const auto& axis : principalAxes)
267-
{
268-
double dotProduct = std::abs(axis.dot(sortedClustersBySize[i].second));
269-
if (std::abs(dotProduct) > 0.7) // Threshold to consider as similar direction
270-
{
271-
isAlreadyPresent = true;
272-
break;
273-
}
274-
}
275-
if (!isAlreadyPresent)
276-
{
277-
principalAxes.push_back(sortedClustersBySize[i].second);
278-
}
279-
}
280-
}
281-
if (principalAxes.size() < 2) // Fallback to OBB if k-means fails to provide enough distinct axes
282-
{
283-
open3d::geometry::OrientedBoundingBox obb = this->Cvt2O3DPointCloud()->GetOrientedBoundingBox();
284-
principalAxes = {obb.R_.col(0), obb.R_.col(1), obb.R_.col(2)};
285-
}
286-
return principalAxes;
229+
230+
auto O3DPointCloud = this->Cvt2O3DPointCloud();
231+
std::tuple< Eigen::Vector4d, std::vector<size_t>> planeModel = O3DPointCloud->SegmentPlane(distanceThreshold, ransacN, numIterations);
232+
Eigen::Vector3d planeParameters = std::get<0>(planeModel).head<3>();
233+
return planeParameters;
287234
}
288235

289236
void DFPointCloud::Crop(const Eigen::Vector3d &minBound, const Eigen::Vector3d &maxBound)

src/diffCheck/geometry/DFPointCloud.hh

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,17 @@ namespace diffCheck::geometry
9292
void RemoveStatisticalOutliers(int nbNeighbors, double stdRatio);
9393

9494
/**
95-
* @brief Get the nCompoments principal axes of the normals of the point cloud
96-
* It is used to compute the pose of "boxy" point clouds. It relies on KMeans clustering to find the main axes of the point cloud.
97-
* @param nComponents the number of components to compute (default 6, each of 3 main axes in both directions)
98-
* @return std::vector<Eigen::Vector3d> the principal axes of the point cloud ordered by number of normals
95+
* @brief Fit a plane to the point cloud using RANSAC
96+
*
97+
* @param distanceThreshold the distance threshold to consider a point as an inlier
98+
* @param ransacN the number of points to sample for each RANSAC iteration
99+
* @param numIterations the number of RANSAC iterations
100+
* @return The Normal vector of the fitted plane as an Eigen::Vector3d
99101
*/
100-
std::vector<Eigen::Vector3d> GetPrincipalAxes(int nComponents = 6);
102+
Eigen::Vector3d FitPlaneRANSAC(
103+
double distanceThreshold = 0.01,
104+
int ransacN = 3,
105+
int numIterations = 100);
101106

102107
/**
103108
* @brief Crop the point cloud to a bounding box defined by the min and max bounds

0 commit comments

Comments
 (0)