ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PointCloudSegmentation.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 
8 #include <Logging.h>
9 
10 #include <Eigen/Dense>
11 #include <algorithm>
12 #include <iterator>
13 #include <numeric>
14 #include <random>
15 #include <unordered_set>
16 
17 #include "ecvMesh.h"
18 #include "ecvPointCloud.h"
19 
20 namespace cloudViewer {
21 namespace geometry {
22 
26 class RANSACResult {
27 public:
30 
31 public:
32  double fitness_;
33  double inlier_rmse_;
34 };
35 
36 // Calculates the number of inliers given a list of points and a plane model,
37 // and the total distance between the inliers and the plane. These numbers are
38 // then used to evaluate how well the plane model fits the given points.
39 RANSACResult EvaluateRANSACBasedOnDistance(const std::vector<CCVector3> &points,
40  const Eigen::Vector4d plane_model,
41  std::vector<size_t> &inliers,
42  double distance_threshold,
43  double error) {
45 
46  for (size_t idx = 0; idx < points.size(); ++idx) {
47  Eigen::Vector4d point(points[idx](0), points[idx](1), points[idx](2),
48  1);
49  double distance = std::abs(plane_model.dot(point));
50 
51  if (distance < distance_threshold) {
52  error += distance;
53  inliers.emplace_back(idx);
54  }
55  }
56 
57  size_t inlier_num = inliers.size();
58  if (inlier_num == 0) {
59  result.fitness_ = 0;
60  result.inlier_rmse_ = 0;
61  } else {
62  result.fitness_ = (double)inlier_num / (double)points.size();
63  result.inlier_rmse_ = error / std::sqrt((double)inlier_num);
64  }
65  return result;
66 }
67 
69  const std::vector<Eigen::Vector3d> &points,
70  const Eigen::Vector4d plane_model,
71  std::vector<size_t> &inliers,
72  double distance_threshold,
73  double error) {
75  plane_model, inliers,
76  distance_threshold, error);
77 }
78 
79 // Find the plane such that the summed squared distance from the
80 // plane to all points is minimized.
81 //
82 // Reference:
83 // https://www.ilikebigbits.com/2015_03_04_plane_from_points.html
84 
85 Eigen::Vector4d GetPlaneFromPoints(const std::vector<CCVector3> &points,
86  const std::vector<size_t> &inliers) {
87  CCVector3d sum(0, 0, 0);
88  for (size_t idx : inliers) {
89  sum += CCVector3d::fromArray(points[idx].u);
90  ;
91  }
92  sum /= static_cast<double>(inliers.size());
93  CCVector3 centroid = CCVector3::fromArray(sum.u);
94 
95  PointCoordinateType xx = 0, xy = 0, xz = 0, yy = 0, yz = 0, zz = 0;
96 
97  for (size_t idx : inliers) {
98  CCVector3 r = points[idx] - centroid;
99  xx += r(0) * r(0);
100  xy += r(0) * r(1);
101  xz += r(0) * r(2);
102  yy += r(1) * r(1);
103  yz += r(1) * r(2);
104  zz += r(2) * r(2);
105  }
106 
107  PointCoordinateType det_x = yy * zz - yz * yz;
108  PointCoordinateType det_y = xx * zz - xz * xz;
109  PointCoordinateType det_z = xx * yy - xy * xy;
110 
111  CCVector3 abc;
112  if (det_x > det_y && det_x > det_z) {
113  abc = CCVector3(det_x, xz * yz - xy * zz, xy * yz - xz * yy);
114  } else if (det_y > det_z) {
115  abc = CCVector3(xz * yz - xy * zz, det_y, xy * xz - yz * xx);
116  } else {
117  abc = CCVector3(xy * yz - xz * yy, xy * xz - yz * xx, det_z);
118  }
119 
120  PointCoordinateType norm = abc.norm();
121  // Return invalid plane if the points don't span a plane.
122  if (norm == 0) {
123  return Eigen::Vector4d(0, 0, 0, 0);
124  }
125  abc /= norm;
126  double d = static_cast<double>(-abc.dot(centroid));
127  return Eigen::Vector4d(abc(0), abc(1), abc(2), d);
128 }
129 
130 Eigen::Vector4d GetPlaneFromPoints(const std::vector<Eigen::Vector3d> &points,
131  const std::vector<size_t> &inliers) {
133 }
134 
135 } // namespace geometry
136 } // namespace cloudViewer
137 
138 using namespace cloudViewer::geometry;
139 std::tuple<Eigen::Vector4d, std::vector<size_t>> ccPointCloud::SegmentPlane(
140  const double distance_threshold /* = 0.01 */,
141  const int ransac_n /* = 3 */,
142  const int num_iterations /* = 100 */) const {
144  double error = 0;
145 
146  // Initialize the plane model ax + by + cz + d = 0.
147  Eigen::Vector4d plane_model = Eigen::Vector4d(0, 0, 0, 0);
148  // Initialize the best plane model.
149  Eigen::Vector4d best_plane_model = Eigen::Vector4d(0, 0, 0, 0);
150 
151  // Initialize consensus set.
152  std::vector<size_t> inliers;
153 
154  size_t num_points = size();
155  std::vector<size_t> indices(num_points);
156  std::iota(std::begin(indices), std::end(indices), 0);
157 
158  std::random_device rd;
159  std::mt19937 rng(rd());
160 
161  // Return if ransac_n is less than the required plane model parameters.
162  if (ransac_n < 3) {
164  "ransac_n should be set to higher than or equal to 3.");
165  return std::make_tuple(best_plane_model, inliers);
166  }
167  if (num_points < std::size_t(ransac_n)) {
169  "There must be at least 'ransac_n' points.");
170  return std::make_tuple(best_plane_model, inliers);
171  }
172 
173  for (int itr = 0; itr < num_iterations; itr++) {
174  for (std::size_t i = 0; i < std::size_t(ransac_n); ++i) {
175  std::swap(indices[i], indices[rng() % num_points]);
176  }
177  inliers.clear();
178  for (std::size_t idx = 0; idx < std::size_t(ransac_n); ++idx) {
179  inliers.emplace_back(indices[idx]);
180  }
181 
182  // Fit model to num_model_parameters randomly selected points among the
183  // inliers.
184  plane_model = ccMesh::ComputeTrianglePlane(getEigenPoint(inliers[0]),
185  getEigenPoint(inliers[1]),
186  getEigenPoint(inliers[2]));
187  if (plane_model.isZero(0)) {
188  continue;
189  }
190 
191  error = 0;
192  inliers.clear();
193  auto this_result = EvaluateRANSACBasedOnDistance(
194  getPoints(), plane_model, inliers, distance_threshold, error);
195  if (this_result.fitness_ > result.fitness_ ||
197  std::abs(this_result.fitness_ - result.fitness_)) &&
198  this_result.inlier_rmse_ < result.inlier_rmse_)) {
199  result = this_result;
200  best_plane_model = plane_model;
201  }
202  }
203 
204  // Find the final inliers using best_plane_model.
205  inliers.clear();
206  for (std::size_t idx = 0; idx < size(); ++idx) {
207  Eigen::Vector4d point(m_points[idx](0), m_points[idx](1),
208  m_points[idx](2), 1);
209  double distance = std::abs(best_plane_model.dot(point));
210 
211  if (distance < distance_threshold) {
212  inliers.emplace_back(idx);
213  }
214  }
215 
216  // Improve best_plane_model using the final inliers.
217  best_plane_model = GetPlaneFromPoints(getPoints(), inliers);
218 
220  "RANSAC | Inliers: {:d}, Fitness: {:e}, RMSE: {:e}", inliers.size(),
221  result.fitness_, result.inlier_rmse_);
222  return std::make_tuple(best_plane_model, inliers);
223 }
224 
225 std::shared_ptr<ccPointCloud> ccPointCloud::Crop(const ccBBox &bbox) const {
226  if (!bbox.isValid()) {
228 
229  "[CropPointCloud::Crop] ccBBox either has zeros "
230  "size, or has wrong bounds.");
231  }
232  return SelectByIndex(bbox.GetPointIndicesWithinBoundingBox(m_points));
233 }
234 
235 std::shared_ptr<ccPointCloud> ccPointCloud::Crop(
236  const ecvOrientedBBox &bbox) const {
237  if (bbox.IsEmpty()) {
239  "[CropPointCloud::Crop] ecvOrientedBBox either has zeros "
240  "size, or has wrong bounds.");
241  return nullptr;
242  }
243  return SelectByIndex(bbox.GetPointIndicesWithinBoundingBox(m_points));
244 }
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int size
int points
core::Tensor result
Definition: VtkUtils.cpp:76
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
static std::vector< Eigen::Matrix< double, 3, 1 > > fromArrayContainer(const std::vector< Vector3Tpl< PointCoordinateType >> &container)
Definition: CVGeom.h:301
Bounding box structure.
Definition: ecvBBox.h:25
static Eigen::Vector4d ComputeTrianglePlane(const Eigen::Vector3d &p0, const Eigen::Vector3d &p1, const Eigen::Vector3d &p2)
std::tuple< Eigen::Vector4d, std::vector< size_t > > SegmentPlane(const double distance_threshold=0.01, const int ransac_n=3, const int num_iterations=100) const
Segment ccPointCloud plane using the RANSAC algorithm.
std::shared_ptr< ccPointCloud > Crop(const ccBBox &bbox) const
Function to crop ccPointCloud into output ccPointCloud.
std::vector< std::size_t > GetPointIndicesWithinBoundingBox(const std::vector< Eigen::Vector3d > &points) const
Definition: BoundingBox.h:242
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
std::vector< size_t > GetPointIndicesWithinBoundingBox(const std::vector< Eigen::Vector3d > &points) const
Return indices to points that are within the bounding box.
Stores the current best result in the RANSAC algorithm.
virtual bool IsEmpty() const override
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
static double distance(T *pot1, T *pot2)
Definition: utils.h:111
Eigen::Vector4d GetPlaneFromPoints(const std::vector< CCVector3 > &points, const std::vector< size_t > &inliers)
RANSACResult EvaluateRANSACBasedOnDistance(const std::vector< CCVector3 > &points, const Eigen::Vector4d plane_model, std::vector< size_t > &inliers, double distance_threshold, double error)
void SelectByIndex(benchmark::State &state, bool remove_duplicates, const core::Device &device)
Definition: PointCloud.cpp:149
Generic file read and write utility for python interface.
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370