Merge remote-tracking branch 'mine/Shape_detection-Region_growing_on_spheres-GF' into Shape_detection-Region_growing_on_spheres-GF

This commit is contained in:
Simon Giraudot 2021-03-03 11:52:14 +01:00
commit bbcab8e56f
19 changed files with 1460 additions and 20 deletions

View File

@ -117,7 +117,10 @@ on a polygon mesh.}
- `CGAL::Shape_detection::Point_set::K_neighbor_query<GeomTraits, InputRange, PointMap>` - `CGAL::Shape_detection::Point_set::K_neighbor_query<GeomTraits, InputRange, PointMap>`
- `CGAL::Shape_detection::Point_set::Sphere_neighbor_query<GeomTraits, InputRange, PointMap>` - `CGAL::Shape_detection::Point_set::Sphere_neighbor_query<GeomTraits, InputRange, PointMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_line_fit_region<GeomTraits, InputRange, PointMap, NormalMap>` - `CGAL::Shape_detection::Point_set::Least_squares_line_fit_region<GeomTraits, InputRange, PointMap, NormalMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_circle_fit_region<GeomTraits, InputRange, PointMap, NormalMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_plane_fit_region<GeomTraits, InputRange, PointMap, NormalMap>` - `CGAL::Shape_detection::Point_set::Least_squares_plane_fit_region<GeomTraits, InputRange, PointMap, NormalMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_sphere_fit_region<GeomTraits, InputRange, PointMap, NormalMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_cylinder_fit_region<GeomTraits, InputRange, PointMap, NormalMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_line_fit_sorting<GeomTraits, InputRange, NeighborQuery, PointMap>` - `CGAL::Shape_detection::Point_set::Least_squares_line_fit_sorting<GeomTraits, InputRange, NeighborQuery, PointMap>`
- `CGAL::Shape_detection::Point_set::Least_squares_plane_fit_sorting<GeomTraits, InputRange, NeighborQuery, PointMap>` - `CGAL::Shape_detection::Point_set::Least_squares_plane_fit_sorting<GeomTraits, InputRange, NeighborQuery, PointMap>`

View File

@ -236,8 +236,8 @@ Shapes are detected by growing regions from seed items, where each region is cre
Together with the generic algorithm's implementation `CGAL::Shape_detection::Region_growing`, three particular instances of this algorithm are provided: Together with the generic algorithm's implementation `CGAL::Shape_detection::Region_growing`, three particular instances of this algorithm are provided:
- Line detection in a \ref Shape_detection_RegionGrowingPoints "2D point set"; - Line and circle detection in a \ref Shape_detection_RegionGrowingPoints "2D point set";
- Plane detection in a \ref Shape_detection_RegionGrowingPoints "3D point set"; - Plane, sphere, and cylinder detection in a \ref Shape_detection_RegionGrowingPoints "3D point set";
- Plane detection on a \ref Shape_detection_RegionGrowingMesh "polygon mesh". - Plane detection on a \ref Shape_detection_RegionGrowingMesh "polygon mesh".
Other instances can be easily added by the user, as explained below. Other instances can be easily added by the user, as explained below.
@ -342,10 +342,13 @@ This class finds K (specified by the user) nearest neighbors of the query point
The component also provides The component also provides
- `CGAL::Shape_detection::Point_set::Least_squares_line_fit_region` - least squares line fit type of region for 2D points; - `CGAL::Shape_detection::Point_set::Least_squares_line_fit_region` - least squares line fit type of region for 2D points
- `CGAL::Shape_detection::Point_set::Least_squares_plane_fit_region` - least squares plane fit type of region for 3D points. - `CGAL::Shape_detection::Point_set::Least_squares_circle_fit_region` - least squares circle fit type of region for 2D points
- `CGAL::Shape_detection::Point_set::Least_squares_plane_fit_region` - least squares plane fit type of region for 3D points
- `CGAL::Shape_detection::Point_set::Least_squares_sphere_fit_region` - least squares sphere fit type of region for 3D points
- `CGAL::Shape_detection::Point_set::Least_squares_cylinder_fit_region` - least squares cylinder fit type of region for 3D points
The program associates all points from a region to the best-fit hyperplane (2D line or 3D plane) The program associates all points from a region to the best-fit object (2D line, 2D circle, 3D plane, 3D sphere, etc.)
and controls the quality of this fit. and controls the quality of this fit.
The quality of region growing in a point set (2D or 3D) can be improved by slightly sacrificing the running time. The quality of region growing in a point set (2D or 3D) can be improved by slightly sacrificing the running time.
@ -409,6 +412,8 @@ when `angle_threshold` is strict enough (c), or it can be recognized as only one
\subsubsection Shape_detection_RegionGrowingPoints_examples Examples \subsubsection Shape_detection_RegionGrowingPoints_examples Examples
\paragraph Shape_detection_RegionGrowingPoints_example_2D_lines Detecting 2D Lines
Typical usage of the Region Growing component consists of five steps: Typical usage of the Region Growing component consists of five steps:
-# Define an input range with points; -# Define an input range with points;
@ -421,15 +426,34 @@ Given a 2D point set, we detect 2D lines using the fuzzy sphere neighborhood. We
The points with assigned to them normal vectors are stored in `std::vector` and the used `Kernel` is `CGAL::Simple_cartesian`, where the number type is `double`. The points with assigned to them normal vectors are stored in `std::vector` and the used `Kernel` is `CGAL::Simple_cartesian`, where the number type is `double`.
\cgalExample{Shape_detection/region_growing_on_point_set_2.cpp} \cgalExample{Shape_detection/region_growing_lines_on_point_set_2.cpp}
\paragraph Shape_detection_RegionGrowingPoints_example_2D_circles Detecting 2D Circles
The following example shows a similar example, this time detecting circles instead of lines.
\cgalExample{Shape_detection/region_growing_circles_on_point_set_2.cpp}
\paragraph Shape_detection_RegionGrowingPoints_example_3D_planes Detecting 3D Planes
If we are given a 3D point set, then the example below shows how to detect 3D planes using the K nearest neighbors search. We color all points from the found regions If we are given a 3D point set, then the example below shows how to detect 3D planes using the K nearest neighbors search. We color all points from the found regions
and save them in a file (see Figure \cgalFigureRef{Region_growing_on_point_set_3}). The example also shows how to retrieve all points, which are not assigned to any region, and how to use a custom output iterator. and save them in a file (see Figure \cgalFigureRef{Region_growing_on_point_set_3}). The example also shows how to retrieve all points, which are not assigned to any region, and how to use a custom output iterator.
The point set with associated normals is stored in `CGAL::Point_set_3` and the used `Kernel` is `CGAL::Exact_predicates_inexact_constructions_kernel`. The point set with associated normals is stored in `CGAL::Point_set_3` and the used `Kernel` is `CGAL::Exact_predicates_inexact_constructions_kernel`.
\cgalExample{Shape_detection/region_growing_on_point_set_3.cpp} \cgalExample{Shape_detection/region_growing_planes_on_point_set_3.cpp}
\paragraph Shape_detection_RegionGrowingPoints_example_3D_spheres Detecting 3D Spheres
The following example shows a similar example, this time detecting spheres instead of planes.
\cgalExample{Shape_detection/region_growing_spheres_on_point_set_3.cpp}
\paragraph Shape_detection_RegionGrowingPoints_example_3D_cylinders Detecting 3D Cylinders
The following example shows another similar example, this time detecting (infinite) cylinders.
\cgalExample{Shape_detection/region_growing_cylinders_on_point_set_3.cpp}
\subsubsection Shape_detection_RegionGrowingPoints_performance Performance \subsubsection Shape_detection_RegionGrowingPoints_performance Performance
@ -518,7 +542,7 @@ a faster version of region growing, use a floating type based kernel.
We can improve the quality of region growing by providing a different seeding order (analogously to \ref Shape_detection_RegionGrowingPoints "Point Sets") that is why in this example we also We can improve the quality of region growing by providing a different seeding order (analogously to \ref Shape_detection_RegionGrowingPoints "Point Sets") that is why in this example we also
sort indices of input faces using the `CGAL::Shape_detection::Polygon_mesh::Least_squares_plane_fit_sorting` and only then detect regions. sort indices of input faces using the `CGAL::Shape_detection::Polygon_mesh::Least_squares_plane_fit_sorting` and only then detect regions.
\cgalExample{Shape_detection/region_growing_on_polygon_mesh.cpp} \cgalExample{Shape_detection/region_growing_planes_on_polygon_mesh.cpp}
\subsubsection Shape_detection_RegionGrowingMesh_performance Performance \subsubsection Shape_detection_RegionGrowingMesh_performance Performance

View File

@ -6,9 +6,12 @@
\example Shape_detection/include/efficient_RANSAC_with_custom_shape.h \example Shape_detection/include/efficient_RANSAC_with_custom_shape.h
\example Shape_detection/efficient_RANSAC_with_custom_shape.cpp \example Shape_detection/efficient_RANSAC_with_custom_shape.cpp
\example Shape_detection/efficient_RANSAC_and_plane_regularization.cpp \example Shape_detection/efficient_RANSAC_and_plane_regularization.cpp
\example Shape_detection/region_growing_on_point_set_2.cpp \example Shape_detection/region_growing_lines_on_point_set_2.cpp
\example Shape_detection/region_growing_on_point_set_3.cpp \example Shape_detection/region_growing_circles_on_point_set_2.cpp
\example Shape_detection/region_growing_on_polygon_mesh.cpp \example Shape_detection/region_growing_planes_on_point_set_3.cpp
\example Shape_detection/region_growing_spheres_on_point_set_3.cpp
\example Shape_detection/region_growing_cylinders_on_point_set_3.cpp
\example Shape_detection/region_growing_planes_on_polygon_mesh.cpp
\example Shape_detection/region_growing_with_custom_classes.cpp \example Shape_detection/region_growing_with_custom_classes.cpp
\example Shape_detection/shape_detection_basic_deprecated.cpp \example Shape_detection/shape_detection_basic_deprecated.cpp
*/ */

View File

@ -18,9 +18,12 @@ if(TARGET CGAL::Eigen3_support)
create_single_source_cgal_program( create_single_source_cgal_program(
"efficient_RANSAC_and_plane_regularization.cpp") "efficient_RANSAC_and_plane_regularization.cpp")
create_single_source_cgal_program("region_growing_on_point_set_2.cpp") create_single_source_cgal_program("region_growing_lines_on_point_set_2.cpp")
create_single_source_cgal_program("region_growing_on_point_set_3.cpp") create_single_source_cgal_program("region_growing_planes_on_point_set_3.cpp")
create_single_source_cgal_program("region_growing_on_polygon_mesh.cpp") create_single_source_cgal_program("region_growing_cylinders_on_point_set_3.cpp")
create_single_source_cgal_program("region_growing_spheres_on_point_set_3.cpp")
create_single_source_cgal_program("region_growing_circles_on_point_set_2.cpp")
create_single_source_cgal_program("region_growing_planes_on_polygon_mesh.cpp")
create_single_source_cgal_program("region_growing_with_custom_classes.cpp") create_single_source_cgal_program("region_growing_with_custom_classes.cpp")
create_single_source_cgal_program("shape_detection_basic_deprecated.cpp") create_single_source_cgal_program("shape_detection_basic_deprecated.cpp")
@ -30,9 +33,12 @@ if(TARGET CGAL::Eigen3_support)
efficient_RANSAC_basic efficient_RANSAC_basic
efficient_RANSAC_with_callback efficient_RANSAC_with_callback
efficient_RANSAC_and_plane_regularization efficient_RANSAC_and_plane_regularization
region_growing_on_point_set_2 region_growing_lines_on_point_set_2
region_growing_on_point_set_3 region_growing_planes_on_point_set_3
region_growing_on_polygon_mesh region_growing_cylinders_on_point_set_3
region_growing_spheres_on_point_set_3
region_growing_circles_on_point_set_2
region_growing_planes_on_polygon_mesh
region_growing_with_custom_classes region_growing_with_custom_classes
shape_detection_basic_deprecated) shape_detection_basic_deprecated)
target_link_libraries(${target} PUBLIC CGAL::Eigen3_support) target_link_libraries(${target} PUBLIC CGAL::Eigen3_support)

View File

@ -0,0 +1,109 @@
#include <CGAL/Real_timer.h>
#include <CGAL/Random.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set.h>
#include <boost/iterator/function_output_iterator.hpp>
#include <fstream>
using Kernel = CGAL::Simple_cartesian<double>;
using Point_3 = Kernel::Point_3;
using Vector_3 = Kernel::Vector_3;
using Point_2 = Kernel::Point_2;
using Vector_2 = Kernel::Vector_2;
using Point_set_3 = CGAL::Point_set_3<Point_3, Vector_3>;
using Point_set_2 = CGAL::Point_set_3<Point_2, Vector_2>;
using Point_map = Point_set_2::Point_map;
using Normal_map = Point_set_2::Vector_map;
namespace Shape_detection = CGAL::Shape_detection::Point_set;
using Neighbor_query = Shape_detection::K_neighbor_query
<Kernel, Point_set_2, Point_map>;
using Region_type = Shape_detection::Least_squares_circle_fit_region
<Kernel, Point_set_2, Point_map, Normal_map>;
using Region_growing = CGAL::Shape_detection::Region_growing
<Point_set_2, Neighbor_query, Region_type>;
int main (int argc, char** argv)
{
std::ifstream ifile (argc > 1 ? argv[1] : "data/circles.ply");
Point_set_3 points3;
ifile >> points3;
std::cerr << points3.size() << " points read" << std::endl;
// Input should have normals
assert (points.has_normal_map());
Point_set_2 points;
points.add_normal_map();
for (Point_set_3::Index idx : points3)
{
const Point_3& p = points3.point(idx);
const Vector_3& n = points3.normal(idx);
points.insert (Point_2 (p.x(), p.y()), Vector_2 (n.x(), n.y()));
}
// Default parameters for data/circles.ply
const std::size_t k = 12;
const double tolerance = 0.01;
const double max_angle = 10.;
const std::size_t min_region_size = 20;
// No constraint on radius
const double min_radius = 0.;
const double max_radius = std::numeric_limits<double>::infinity();
Neighbor_query neighbor_query(points, k, points.point_map());
Region_type region_type(points, tolerance, max_angle, min_region_size,
min_radius, max_radius,
points.point_map(), points.normal_map());
Region_growing region_growing(points, neighbor_query, region_type);
// Add maps to get colored output
Point_set_3::Property_map<unsigned char>
red = points3.add_property_map<unsigned char>("red", 0).first,
green = points3.add_property_map<unsigned char>("green", 0).first,
blue = points3.add_property_map<unsigned char>("blue", 0).first;
CGAL::Random random;
std::size_t nb_circles = 0;
CGAL::Real_timer timer;
timer.start();
region_growing.detect
(boost::make_function_output_iterator
([&](const std::vector<std::size_t>& region)
{
// Assign a random color to each region
unsigned char r = (unsigned char)(random.get_int(64, 192));
unsigned char g = (unsigned char)(random.get_int(64, 192));
unsigned char b = (unsigned char)(random.get_int(64, 192));
for (const std::size_t& idx : region)
{
red[idx] = r;
green[idx] = g;
blue[idx] = b;
}
++ nb_circles;
}));
timer.stop();
std::cerr << nb_circles << " circles detected in "
<< timer.time() << " seconds" << std::endl;
// Save in colored_circles.ply
std::ofstream out ("colored_circles.ply");
CGAL::set_binary_mode (out);
out << points3;
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,97 @@
#include <fstream>
#include <CGAL/Real_timer.h>
#include <CGAL/Random.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set.h>
#include <boost/iterator/function_output_iterator.hpp>
using Kernel = CGAL::Simple_cartesian<double>;
using Point_3 = Kernel::Point_3;
using Vector_3 = Kernel::Vector_3;
using Point_set = CGAL::Point_set_3<Point_3>;
using Point_map = typename Point_set::Point_map;
using Normal_map = typename Point_set::Vector_map;
namespace Shape_detection = CGAL::Shape_detection::Point_set;
using Neighbor_query = Shape_detection::K_neighbor_query
<Kernel, Point_set, Point_map>;
using Region_type = Shape_detection::Least_squares_cylinder_fit_region
<Kernel, Point_set, Point_map, Normal_map>;
using Region_growing = CGAL::Shape_detection::Region_growing
<Point_set, Neighbor_query, Region_type>;
int main (int argc, char** argv)
{
std::ifstream ifile (argc > 1 ? argv[1] : "data/cube.pwn");
Point_set points;
ifile >> points;
std::cerr << points.size() << " points read" << std::endl;
// Input should have normals
assert (points.has_normal_map());
// Default parameters for data/cube.pwn
const std::size_t k = 24;
const double tolerance = 0.05;
const double max_angle = 5.;
const std::size_t min_region_size = 200;
// No constraint on radius
const double min_radius = 0.;
const double max_radius = std::numeric_limits<double>::infinity();
Neighbor_query neighbor_query(points, k, points.point_map());
Region_type region_type(points, tolerance, max_angle, min_region_size,
min_radius, max_radius,
points.point_map(), points.normal_map());
Region_growing region_growing(points, neighbor_query, region_type);
// Add maps to get colored output
Point_set::Property_map<unsigned char>
red = points.add_property_map<unsigned char>("red", 0).first,
green = points.add_property_map<unsigned char>("green", 0).first,
blue = points.add_property_map<unsigned char>("blue", 0).first;
CGAL::Random random;
std::size_t nb_cylinders = 0;
CGAL::Real_timer timer;
timer.start();
region_growing.detect
(boost::make_function_output_iterator
([&](const std::vector<std::size_t>& region)
{
// Assign a random color to each region
unsigned char r = (unsigned char)(random.get_int(64, 192));
unsigned char g = (unsigned char)(random.get_int(64, 192));
unsigned char b = (unsigned char)(random.get_int(64, 192));
for (const std::size_t& idx : region)
{
red[idx] = r;
green[idx] = g;
blue[idx] = b;
}
++ nb_cylinders;
}));
timer.stop();
std::cerr << nb_cylinders << " cylinders detected in "
<< timer.time() << " seconds" << std::endl;
// Save in colored_cylinders.ply
std::ofstream out ("colored_cylinders.ply");
CGAL::set_binary_mode (out);
out << points;
return EXIT_SUCCESS;
}

View File

@ -77,7 +77,7 @@ int main(int argc, char *argv[]) {
<< std::endl << std::endl; << std::endl << std::endl;
// Load xyz data either from a local folder or a user-provided file. // Load xyz data either from a local folder or a user-provided file.
std::ifstream in(argc > 1 ? argv[1] : "data/point_set_2.xyz"); std::ifstream in(argc > 1 ? argv[1] : "data/buildings_outline.xyz");
CGAL::set_ascii_mode(in); CGAL::set_ascii_mode(in);
if (!in) { if (!in) {

View File

@ -108,7 +108,7 @@ int main(int argc, char *argv[]) {
<< std::endl << std::endl; << std::endl << std::endl;
// Load xyz data either from a local folder or a user-provided file. // Load xyz data either from a local folder or a user-provided file.
std::ifstream in(argc > 1 ? argv[1] : "data/point_set_3.xyz"); std::ifstream in(argc > 1 ? argv[1] : "data/building.xyz");
CGAL::set_ascii_mode(in); CGAL::set_ascii_mode(in);
if (!in) { if (!in) {

View File

@ -64,7 +64,7 @@ int main(int argc, char *argv[]) {
<< std::endl << std::endl; << std::endl << std::endl;
// Load off data either from a local folder or a user-provided file. // Load off data either from a local folder or a user-provided file.
std::ifstream in(argc > 1 ? argv[1] : "data/polygon_mesh.off"); std::ifstream in(argc > 1 ? argv[1] : "data/building.off");
CGAL::set_ascii_mode(in); CGAL::set_ascii_mode(in);
if (!in) { if (!in) {

View File

@ -0,0 +1,97 @@
#include <CGAL/Real_timer.h>
#include <CGAL/Random.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Point_set_3.h>
#include <CGAL/Point_set_3/IO.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set.h>
#include <boost/iterator/function_output_iterator.hpp>
#include <fstream>
using Kernel = CGAL::Simple_cartesian<double>;
using Point_3 = Kernel::Point_3;
using Vector_3 = Kernel::Vector_3;
using Point_set = CGAL::Point_set_3<Point_3>;
using Point_map = typename Point_set::Point_map;
using Normal_map = typename Point_set::Vector_map;
namespace Shape_detection = CGAL::Shape_detection::Point_set;
using Neighbor_query = Shape_detection::K_neighbor_query
<Kernel, Point_set, Point_map>;
using Region_type = Shape_detection::Least_squares_sphere_fit_region
<Kernel, Point_set, Point_map, Normal_map>;
using Region_growing = CGAL::Shape_detection::Region_growing
<Point_set, Neighbor_query, Region_type>;
int main (int argc, char** argv)
{
std::ifstream ifile (argc > 1 ? argv[1] : "data/spheres.ply");
Point_set points;
ifile >> points;
std::cerr << points.size() << " points read" << std::endl;
// Input should have normals
assert (points.has_normal_map());
// Default parameters for data/spheres.ply
const std::size_t k = 12;
const double tolerance = 0.01;
const double max_angle = 10.;
const std::size_t min_region_size = 50;
// No constraint on radius
const double min_radius = 0.;
const double max_radius = std::numeric_limits<double>::infinity();
Neighbor_query neighbor_query(points, k, points.point_map());
Region_type region_type(points, tolerance, max_angle, min_region_size,
min_radius, max_radius,
points.point_map(), points.normal_map());
Region_growing region_growing(points, neighbor_query, region_type);
// Add maps to get colored output
Point_set::Property_map<unsigned char>
red = points.add_property_map<unsigned char>("red", 0).first,
green = points.add_property_map<unsigned char>("green", 0).first,
blue = points.add_property_map<unsigned char>("blue", 0).first;
CGAL::Random random;
std::size_t nb_spheres = 0;
CGAL::Real_timer timer;
timer.start();
region_growing.detect
(boost::make_function_output_iterator
([&](const std::vector<std::size_t>& region)
{
// Assign a random color to each region
unsigned char r = (unsigned char)(random.get_int(64, 192));
unsigned char g = (unsigned char)(random.get_int(64, 192));
unsigned char b = (unsigned char)(random.get_int(64, 192));
for (const std::size_t& idx : region)
{
red[idx] = r;
green[idx] = g;
blue[idx] = b;
}
++ nb_spheres;
}));
timer.stop();
std::cerr << nb_spheres << " spheres detected in "
<< timer.time() << " seconds" << std::endl;
// Save in colored_spheres.ply
std::ofstream out ("colored_spheres.ply");
CGAL::set_binary_mode (out);
out << points;
return EXIT_SUCCESS;
}

View File

@ -20,7 +20,10 @@
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Sphere_neighbor_query.h> #include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Sphere_neighbor_query.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_line_fit_region.h> #include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_line_fit_region.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_circle_fit_region.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_plane_fit_region.h> #include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_plane_fit_region.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_sphere_fit_region.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_cylinder_fit_region.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_line_fit_sorting.h> #include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_line_fit_sorting.h>
#include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_plane_fit_sorting.h> #include <CGAL/Shape_detection/Region_growing/Region_growing_on_point_set/Least_squares_plane_fit_sorting.h>

View File

@ -0,0 +1,368 @@
// Copyright (c) 2020 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Simon Giraudot
//
#ifndef CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_CIRCLE_FIT_REGION_H
#define CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_CIRCLE_FIT_REGION_H
#include <CGAL/license/Shape_detection.h>
#include <vector>
#include <CGAL/assertions.h>
#include <CGAL/number_utils.h>
#include <CGAL/Cartesian_converter.h>
#include <CGAL/Eigen_diagonalize_traits.h>
#include <CGAL/Shape_detection/Region_growing/internal/utils.h>
namespace CGAL {
namespace Shape_detection {
namespace Point_set {
/*!
\ingroup PkgShapeDetectionRGOnPoints
\brief Region type based on the quality of the least squares circle
fit applied to 2D points.
This class fits a circle to chunks of points in a 2D point set and
controls the quality of this fit. If all quality conditions are
satisfied, the chunk is accepted as a valid region, otherwise
rejected.
\tparam GeomTraits
must be a model of `Kernel`.
\tparam InputRange
must be a model of `ConstRange` whose iterator type is `RandomAccessIterator`.
\tparam PointMap
must be an `LvaluePropertyMap` whose key type is the value type of the input
range and value type is `Kernel::Point_3`.
\tparam NormalMap
must be an `LvaluePropertyMap` whose key type is the value type of the input
range and value type is `Kernel::Vector_3`.
\cgalModels `RegionType`
*/
template<typename GeomTraits,
typename InputRange,
typename PointMap,
typename NormalMap>
class Least_squares_circle_fit_region
{
public:
/// \name Types
/// @{
/// \cond SKIP_IN_MANUAL
using Traits = GeomTraits;
using Input_range = InputRange;
using Point_map = PointMap;
using Normal_map = NormalMap;
/// \endcond
/// Number type.
typedef typename GeomTraits::FT FT;
/// \cond SKIP_IN_MANUAL
using Point_2 = typename Traits::Point_2;
using Vector_2 = typename Traits::Vector_2;
using Squared_distance_2 = typename Traits::Compute_squared_distance_2;
using Get_sqrt = internal::Get_sqrt<Traits>;
using Sqrt = typename Get_sqrt::Sqrt;
private:
const Input_range& m_input_range;
const FT m_distance_threshold;
const FT m_normal_threshold;
const std::size_t m_min_region_size;
const FT m_min_radius;
const FT m_max_radius;
const Point_map m_point_map;
const Normal_map m_normal_map;
const Squared_distance_2 m_squared_distance_2;
const Sqrt m_sqrt;
Point_2 m_center;
FT m_radius;
public:
/// \endcond
/// @}
/// \name Initialization
/// @{
/*!
\brief initializes all internal data structures.
\param input_range an instance of `InputRange` with 2D points and
corresponding 2D normal vectors
\param distance_threshold the maximum distance from a point to a
circle. %Default is 1.
\param angle_threshold the maximum accepted angle in degrees
between the normal of a point and the radius of a circle. %Default
is 25 degrees.
\param min_region_size the minimum number of 2D points a region
must have. %Default is 3.
\param minimum_radius the radius below which an estimated circle
is considered as invalid and discarded. %Default is 0 (no limit).
\param maximum_radius the radius above which an estimated circle
is considered as invalid and discarded. %Default is infinity (no
limit).
\param point_map an instance of `PointMap` that maps an item from
`input_range` to `Kernel::Point_3`
\param normal_map an instance of `NormalMap` that maps an item
from `input_range` to `Kernel::Vector_3`
\param traits an instance of `GeomTraits`.
\pre `input_range.size() > 0`
\pre `distance_threshold >= 0`
\pre `angle_threshold >= 0 && angle_threshold <= 90`
\pre `min_region_size > 0`
*/
Least_squares_circle_fit_region (const InputRange& input_range,
const FT distance_threshold = FT(1),
const FT angle_threshold = FT(25),
const std::size_t min_region_size = 3,
const FT minimum_radius = FT(0),
const FT maximum_radius = std::numeric_limits<FT>::infinity(),
const PointMap point_map = PointMap(),
const NormalMap normal_map = NormalMap(),
const GeomTraits traits = GeomTraits())
: m_input_range(input_range)
, m_distance_threshold(distance_threshold)
, m_normal_threshold(static_cast<FT>(
std::cos(
CGAL::to_double(
(angle_threshold * static_cast<FT>(CGAL_PI)) / FT(180)))))
, m_min_region_size(min_region_size)
, m_min_radius (minimum_radius)
, m_max_radius (maximum_radius)
, m_point_map(point_map)
, m_normal_map(normal_map)
, m_squared_distance_2(traits.compute_squared_distance_2_object())
, m_sqrt(Get_sqrt::sqrt_object(traits))
{
CGAL_precondition(input_range.size() > 0);
CGAL_precondition(distance_threshold >= FT(0));
CGAL_precondition(angle_threshold >= FT(0) && angle_threshold <= FT(90));
CGAL_precondition(min_region_size > 0);
CGAL_precondition(minimum_radius >= 0);
CGAL_precondition(maximum_radius > minimum_radius);
}
/// @}
/// \name Access
/// @{
/*!
\brief implements `RegionType::is_part_of_region()`.
This function controls if a point with the index `query_index` is
within the `distance_threshold` from the corresponding circle and
if the angle between its normal and the circle radius is within
the `angle_threshold`. If both conditions are satisfied, it
returns `true`, otherwise `false`.
\param query_index
index of the query point
\param indices indices of the inliers of the region
The first parameter is not used in this implementation.
\return Boolean `true` or `false`
\pre `query_index >= 0 && query_index < input_range.size()`
*/
bool is_part_of_region (const std::size_t,
const std::size_t query_index,
const std::vector<std::size_t>& indices) const
{
CGAL_precondition(query_index < m_input_range.size());
// First, we need to integrate at least 6 points so that the
// computed circle means something
if (indices.size() < 6)
return true;
// If radius is out of bound, nothing fits, early ending
if (m_radius < m_min_radius || m_radius > m_max_radius)
return false;
const auto& key = *(m_input_range.begin() + query_index);
const Point_2& query_point = get(m_point_map, key);
Vector_2 normal = get(m_normal_map, key);
FT distance_to_center = m_sqrt(m_squared_distance_2 (query_point, m_center));
FT distance_to_circle = CGAL::abs (distance_to_center - m_radius);
if (distance_to_circle > m_distance_threshold)
return false;
normal = normal / m_sqrt (normal * normal);
Vector_2 ray (m_center, query_point);
ray = ray / m_sqrt (ray * ray);
if (CGAL::abs (normal * ray) < m_normal_threshold)
return false;
return true;
}
/*!
\brief implements `RegionType::is_valid_region()`.
This function controls if the estimated radius is between
`minimum_radius` and `maximum_radius` and if the `region` contains
at least `min_region_size` points.
\param region
indices of points included in the region
\return Boolean `true` or `false`
*/
inline bool is_valid_region(const std::vector<std::size_t>& region) const
{
return ((m_min_radius <= m_radius && m_radius <= m_max_radius)
&& (region.size() >= m_min_region_size));
}
/*!
\brief implements `RegionType::update()`.
This function fits the least squares circle to all points from the `region`.
\param region
indices of points included in the region
\pre `region.size() > 0`
*/
void update(const std::vector<std::size_t>& region)
{
CGAL_precondition(region.size() > 0);
auto unary_function
= [&](const std::size_t& idx) -> const Point_2&
{
return get (m_point_map, *(m_input_range.begin() + idx));
};
// Use bbox to compute diagonalization with smaller coordinates to
// avoid loss of precision when inverting large coordinates
CGAL::Bbox_2 bbox = CGAL::bbox_2
(boost::make_transform_iterator
(region.begin(), unary_function),
boost::make_transform_iterator
(region.end(), unary_function));
using Diagonalize_traits = Eigen_diagonalize_traits<FT, 4>;
using Covariance_matrix = typename Diagonalize_traits::Covariance_matrix;
using Vector = typename Diagonalize_traits::Vector;
using Matrix = typename Diagonalize_traits::Matrix;
// Circle least squares fitting,
// Circle of center (a,b) and radius R
// Ri = sqrt((xi - a)^2 + (yi - b)^2)
// Minimize Sum(Ri^2 - R^2)^2
// -> Minimize Sum(xi^2 + yi^2 2 a*xi 2 b*yi + a^2 + b^2 R^2)^2
// let B=-2a ; C=-2b; D= a^2 + b^2 - R^2
// let ri = x^2 + y^2
// -> Minimize Sum(D + B*xi + C*yi + ri)^2
// -> Minimize Sum(1 + B/D*xi + C/D*yi + ri/D)^2
// -> system of linear equations
// -> diagonalize matrix
// NB x y r
// xx xy xr
// yy yr
// rr
//
// -> center coordinates = -0.5 * eigenvector(1) / eigenvector(3) ; -0.5 * eigenvector(2) / eigenvector(3)
Covariance_matrix A
= { FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0) };
A[0] = region.size();
for (const std::size_t& idx : region)
{
const auto& key = *(m_input_range.begin() + idx);
const Point_2& p = get(m_point_map, key);
FT x = p.x() - bbox.xmin();
FT y = p.y() - bbox.ymin();
FT r = x*x + y*y;
A[1] += x;
A[2] += y;
A[3] += r;
A[4] += x * x;
A[5] += x * y;
A[6] += x * r;
A[7] += y * y;
A[8] += y * r;
A[9] += r * r;
}
Vector eigenvalues = { FT(0), FT(0), FT(0), FT(0) };
Matrix eigenvectors = { FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0) };
Diagonalize_traits::diagonalize_selfadjoint_covariance_matrix
(A, eigenvalues, eigenvectors);
m_center = Point_2 (bbox.xmin() - FT(0.5) * (eigenvectors[1] / eigenvectors[3]),
bbox.ymin() - FT(0.5) * (eigenvectors[2] / eigenvectors[3]));
m_radius = FT(0);
for (const std::size_t& idx : region)
{
const auto& key = *(m_input_range.begin() + idx);
const Point_2& p = get(m_point_map, key);
m_radius += m_sqrt (m_squared_distance_2 (p, m_center));
}
m_radius /= region.size();
}
/// @}
};
} // namespace Point_set
} // namespace Shape_detection
} // namespace CGAL
#endif // CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_CIRCLE_FIT_REGION_H

View File

@ -0,0 +1,362 @@
// Copyright (c) 2020 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Simon Giraudot
//
#ifndef CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_CYLINDER_FIT_REGION_H
#define CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_CYLINDER_FIT_REGION_H
#include <CGAL/license/Shape_detection.h>
#include <vector>
#include <CGAL/assertions.h>
#include <CGAL/number_utils.h>
#include <CGAL/Cartesian_converter.h>
#include <CGAL/Eigen_diagonalize_traits.h>
#include <CGAL/Shape_detection/Region_growing/internal/utils.h>
namespace CGAL {
namespace Shape_detection {
namespace Point_set {
/*!
\ingroup PkgShapeDetectionRGOnPoints
\brief Region type based on the quality of the least squares cylinder
fit applied to 3D points.
This class fits an infinite cylinder to chunks of points in a 3D
point set and controls the quality of this fit. If all quality
conditions are satisfied, the chunk is accepted as a valid region,
otherwise rejected.
\tparam GeomTraits
must be a model of `Kernel`.
\tparam InputRange
must be a model of `ConstRange` whose iterator type is `RandomAccessIterator`.
\tparam PointMap
must be an `LvaluePropertyMap` whose key type is the value type of the input
range and value type is `Kernel::Point_3`.
\tparam NormalMap
must be an `LvaluePropertyMap` whose key type is the value type of the input
range and value type is `Kernel::Vector_3`.
\cgalModels `RegionType`
*/
template<typename GeomTraits,
typename InputRange,
typename PointMap,
typename NormalMap>
class Least_squares_cylinder_fit_region
{
public:
/// \name Types
/// @{
/// \cond SKIP_IN_MANUAL
using Traits = GeomTraits;
using Input_range = InputRange;
using Point_map = PointMap;
using Normal_map = NormalMap;
/// \endcond
/// Number type.
typedef typename GeomTraits::FT FT;
/// \cond SKIP_IN_MANUAL
using Point_3 = typename Traits::Point_3;
using Vector_3 = typename Traits::Vector_3;
using Line_3 = typename Traits::Line_3;
using Squared_distance_3 = typename Traits::Compute_squared_distance_3;
using Get_sqrt = internal::Get_sqrt<Traits>;
using Sqrt = typename Get_sqrt::Sqrt;
private:
const Input_range& m_input_range;
const FT m_distance_threshold;
const FT m_normal_threshold;
const std::size_t m_min_region_size;
const FT m_min_radius;
const FT m_max_radius;
const Point_map m_point_map;
const Normal_map m_normal_map;
const Squared_distance_3 m_squared_distance_3;
const Sqrt m_sqrt;
Line_3 m_axis;
FT m_radius;
public:
/// \endcond
/// @}
/// \name Initialization
/// @{
/*!
\brief initializes all internal data structures.
\param input_range an instance of `InputRange` with 3D points and
corresponding 3D normal vectors
\param distance_threshold the maximum distance from a point to a
cylinder. %Default is 1.
\param angle_threshold the maximum accepted angle in degrees
between the normal of a point and the radius of a cylinder. %Default
is 25 degrees.
\param min_region_size the minimum number of 3D points a region
must have. %Default is 3.
\param minimum_radius the radius below which an estimated cylinder
is considered as invalid and discarded. %Default is 0 (no limit).
\param maximum_radius the radius above which an estimated cylinder
is considered as invalid and discarded. %Default is infinity (no
limit).
\param point_map an instance of `PointMap` that maps an item from
`input_range` to `Kernel::Point_3`
\param normal_map an instance of `NormalMap` that maps an item
from `input_range` to `Kernel::Vector_3`
\param traits an instance of `GeomTraits`.
\pre `input_range.size() > 0`
\pre `distance_threshold >= 0`
\pre `angle_threshold >= 0 && angle_threshold <= 90`
\pre `min_region_size > 0`
*/
Least_squares_cylinder_fit_region (const InputRange& input_range,
const FT distance_threshold = FT(1),
const FT angle_threshold = FT(25),
const std::size_t min_region_size = 3,
const FT minimum_radius = FT(0),
const FT maximum_radius = std::numeric_limits<FT>::infinity(),
const PointMap point_map = PointMap(),
const NormalMap normal_map = NormalMap(),
const GeomTraits traits = GeomTraits())
: m_input_range(input_range)
, m_distance_threshold(distance_threshold)
, m_normal_threshold(static_cast<FT>(
std::cos(
CGAL::to_double(
(angle_threshold * static_cast<FT>(CGAL_PI)) / FT(180)))))
, m_min_region_size(min_region_size)
, m_min_radius (minimum_radius)
, m_max_radius (maximum_radius)
, m_point_map(point_map)
, m_normal_map(normal_map)
, m_squared_distance_3(traits.compute_squared_distance_3_object())
, m_sqrt(Get_sqrt::sqrt_object(traits))
, m_radius(std::numeric_limits<FT>::quiet_NaN())
{
CGAL_precondition(input_range.size() > 0);
CGAL_precondition(distance_threshold >= FT(0));
CGAL_precondition(angle_threshold >= FT(0) && angle_threshold <= FT(90));
CGAL_precondition(min_region_size > 0);
}
/// @}
/// \name Access
/// @{
/*!
\brief implements `RegionType::is_part_of_region()`.
This function controls if a point with the index `query_index` is
within the `distance_threshold` from the corresponding cylinder and
if the angle between its normal and the cylinder radius is within
the `angle_threshold`. If both conditions are satisfied, it
returns `true`, otherwise `false`.
\param query_index
index of the query point
\param indices indices of the inliers of the region
The first parameter is not used in this implementation.
\return Boolean `true` or `false`
\pre `query_index >= 0 && query_index < input_range.size()`
*/
bool is_part_of_region (const std::size_t,
const std::size_t query_index,
const std::vector<std::size_t>& indices) const
{
CGAL_precondition(query_index < m_input_range.size());
// First, we need to integrate at least 6 points so that the
// computed cylinder means something
if (indices.size() < 6)
return true;
if (std::isnan(m_radius))
return false;
// If radius is out of bound, nothing fits, early ending
if (m_radius < m_min_radius || m_radius > m_max_radius)
return false;
const auto& key = *(m_input_range.begin() + query_index);
const Point_3& query_point = get(m_point_map, key);
Vector_3 normal = get(m_normal_map, key);
FT distance_to_center = m_sqrt(m_squared_distance_3 (query_point, m_axis));
FT distance_to_cylinder = CGAL::abs (distance_to_center - m_radius);
if (distance_to_cylinder > m_distance_threshold)
return false;
normal = normal / m_sqrt (normal * normal);
Point_3 proj = m_axis.projection(query_point);
Vector_3 ray (proj, query_point);
ray = ray / m_sqrt (ray * ray);
if (CGAL::abs (normal * ray) < m_normal_threshold)
return false;
return true;
}
/*!
\brief implements `RegionType::is_valid_region()`.
This function controls if the estimated radius is between
`minimum_radius` and `maximum_radius` and if the `region` contains
at least `min_region_size` points.
\param region
indices of points included in the region
\return Boolean `true` or `false`
*/
inline bool is_valid_region(const std::vector<std::size_t>& region) const
{
return ((m_min_radius <= m_radius && m_radius <= m_max_radius)
&& (region.size() >= m_min_region_size));
}
/*!
\brief implements `RegionType::update()`.
This function fits the least squares cylinder to all points from the `region`.
\param region
indices of points included in the region
\pre `region.size() > 0`
*/
void update(const std::vector<std::size_t>& region)
{
CGAL_precondition(region.size() > 0);
Vector_3 mean_axis = CGAL::NULL_VECTOR;
std::size_t nb = 0;
// Shuffle to avoid always picking 2 close points
std::vector<std::size_t>& aregion
= const_cast<std::vector<std::size_t>&>(region);
cpp98::random_shuffle (aregion.begin(), aregion.end());
const Point_3& ref = get(m_point_map, *(m_input_range.begin() + region.front()));
m_radius = FT(0);
Point_3 point_on_axis = CGAL::ORIGIN;
for (std::size_t i = 0; i < aregion.size() - 1; ++ i)
{
Vector_3 v0 = get(m_normal_map, *(m_input_range.begin() + aregion[i]));
v0 = v0 / CGAL::sqrt(v0*v0);
Vector_3 v1 = get(m_normal_map, *(m_input_range.begin() + aregion[i+1]));
v1 = v1 / CGAL::sqrt(v1*v1);
Vector_3 axis = CGAL::cross_product (v0, v1);
if (CGAL::sqrt(axis.squared_length()) < (FT)(0.01))
continue;
axis = axis / CGAL::sqrt(axis * axis);
const Point_3& p0 = get(m_point_map, *(m_input_range.begin() + aregion[i]));
const Point_3& p1 = get(m_point_map, *(m_input_range.begin() + aregion[i+1]));
Vector_3 xdir = v0 - axis * (v0 * axis);
xdir = xdir / CGAL::sqrt (xdir * xdir);
Vector_3 ydir = CGAL::cross_product (axis, xdir);
ydir = ydir / CGAL::sqrt (ydir * ydir);
FT v1x = v1 * ydir;
FT v1y = -v1 * xdir;
Vector_3 d (p0, p1);
FT ox = xdir * d;
FT oy = ydir * d;
FT ldist = v1x * ox + v1y * oy;
FT radius = ldist / v1x;
Point_3 point = p0 + xdir * radius;
Line_3 line (point, axis);
point = line.projection(ref);
point_on_axis = CGAL::barycenter (point_on_axis, nb, point, 1);
m_radius += CGAL::abs(radius);
if (nb != 0 && axis * mean_axis < 0)
axis = -axis;
mean_axis = mean_axis + axis;
++ nb;
}
if (nb == 0)
return;
mean_axis = mean_axis / CGAL::sqrt(mean_axis * mean_axis);
m_axis = Line_3 (point_on_axis, mean_axis);
m_radius /= nb;
m_radius = FT(0);
for (std::size_t i = 0; i < aregion.size(); ++ i)
{
const Point_3& p0 = get(m_point_map, *(m_input_range.begin() + aregion[i]));
m_radius += m_sqrt(m_squared_distance_3(p0, m_axis));
}
m_radius /= aregion.size();
}
/// @}
};
} // namespace Point_set
} // namespace Shape_detection
} // namespace CGAL
#endif // CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_CYLINDER_FIT_REGION_H

View File

@ -0,0 +1,368 @@
// Copyright (c) 2020 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s) : Simon Giraudot
//
#ifndef CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_SPHERE_FIT_REGION_H
#define CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_SPHERE_FIT_REGION_H
#include <CGAL/license/Shape_detection.h>
#include <vector>
#include <CGAL/assertions.h>
#include <CGAL/number_utils.h>
#include <CGAL/Cartesian_converter.h>
#include <CGAL/Eigen_diagonalize_traits.h>
#include <CGAL/Shape_detection/Region_growing/internal/utils.h>
namespace CGAL {
namespace Shape_detection {
namespace Point_set {
/*!
\ingroup PkgShapeDetectionRGOnPoints
\brief Region type based on the quality of the least squares sphere
fit applied to 3D points.
This class fits a sphere to chunks of points in a 3D point set and
controls the quality of this fit. If all quality conditions are
satisfied, the chunk is accepted as a valid region, otherwise
rejected.
\tparam GeomTraits
must be a model of `Kernel`.
\tparam InputRange
must be a model of `ConstRange` whose iterator type is `RandomAccessIterator`.
\tparam PointMap
must be an `LvaluePropertyMap` whose key type is the value type of the input
range and value type is `Kernel::Point_3`.
\tparam NormalMap
must be an `LvaluePropertyMap` whose key type is the value type of the input
range and value type is `Kernel::Vector_3`.
\cgalModels `RegionType`
*/
template<typename GeomTraits,
typename InputRange,
typename PointMap,
typename NormalMap>
class Least_squares_sphere_fit_region
{
public:
/// \name Types
/// @{
/// \cond SKIP_IN_MANUAL
using Traits = GeomTraits;
using Input_range = InputRange;
using Point_map = PointMap;
using Normal_map = NormalMap;
/// \endcond
/// Number type.
typedef typename GeomTraits::FT FT;
/// \cond SKIP_IN_MANUAL
using Point_3 = typename Traits::Point_3;
using Vector_3 = typename Traits::Vector_3;
using Plane_3 = typename Traits::Plane_3;
using Squared_length_3 = typename Traits::Compute_squared_length_3;
using Squared_distance_3 = typename Traits::Compute_squared_distance_3;
using Scalar_product_3 = typename Traits::Compute_scalar_product_3;
using Get_sqrt = internal::Get_sqrt<Traits>;
using Sqrt = typename Get_sqrt::Sqrt;
private:
const Input_range& m_input_range;
const FT m_distance_threshold;
const FT m_normal_threshold;
const std::size_t m_min_region_size;
const FT m_min_radius;
const FT m_max_radius;
const Point_map m_point_map;
const Normal_map m_normal_map;
const Squared_length_3 m_squared_length_3;
const Squared_distance_3 m_squared_distance_3;
const Scalar_product_3 m_scalar_product_3;
const Sqrt m_sqrt;
Point_3 m_center;
FT m_radius;
public:
/// \endcond
/// @}
/// \name Initialization
/// @{
/*!
\brief initializes all internal data structures.
\param input_range an instance of `InputRange` with 3D points and
corresponding 3D normal vectors
\param distance_threshold the maximum distance from a point to a
sphere. %Default is 1.
\param angle_threshold the maximum accepted angle in degrees
between the normal of a point and the radius of a sphere. %Default
is 25 degrees.
\param min_region_size the minimum number of 3D points a region
must have. %Default is 3.
\param minimum_radius the radius below which an estimated sphere
is considered as invalid and discarded. %Default is 0 (no limit).
\param maximum_radius the radius above which an estimated sphere
is considered as invalid and discarded. %Default is infinity (no
limit).
\param point_map an instance of `PointMap` that maps an item from
`input_range` to `Kernel::Point_3`
\param normal_map an instance of `NormalMap` that maps an item
from `input_range` to `Kernel::Vector_3`
\param traits an instance of `GeomTraits`.
\pre `input_range.size() > 0`
\pre `distance_threshold >= 0`
\pre `angle_threshold >= 0 && angle_threshold <= 90`
\pre `min_region_size > 0`
*/
Least_squares_sphere_fit_region (const InputRange& input_range,
const FT distance_threshold = FT(1),
const FT angle_threshold = FT(25),
const std::size_t min_region_size = 3,
const FT minimum_radius = FT(0),
const FT maximum_radius = std::numeric_limits<FT>::infinity(),
const PointMap point_map = PointMap(),
const NormalMap normal_map = NormalMap(),
const GeomTraits traits = GeomTraits())
: m_input_range(input_range)
, m_distance_threshold(distance_threshold)
, m_normal_threshold(static_cast<FT>(
std::cos(
CGAL::to_double(
(angle_threshold * static_cast<FT>(CGAL_PI)) / FT(180)))))
, m_min_region_size(min_region_size)
, m_min_radius (minimum_radius)
, m_max_radius (maximum_radius)
, m_point_map(point_map)
, m_normal_map(normal_map)
, m_squared_length_3(traits.compute_squared_length_3_object())
, m_squared_distance_3(traits.compute_squared_distance_3_object())
, m_scalar_product_3(traits.compute_scalar_product_3_object())
, m_sqrt(Get_sqrt::sqrt_object(traits))
{
CGAL_precondition(input_range.size() > 0);
CGAL_precondition(distance_threshold >= FT(0));
CGAL_precondition(angle_threshold >= FT(0) && angle_threshold <= FT(90));
CGAL_precondition(min_region_size > 0);
CGAL_precondition(minimum_radius >= 0);
CGAL_precondition(maximum_radius > minimum_radius);
}
/// @}
/// \name Access
/// @{
/*!
\brief implements `RegionType::is_part_of_region()`.
This function controls if a point with the index `query_index` is
within the `distance_threshold` from the corresponding sphere and
if the angle between its normal and the sphere radius is within
the `angle_threshold`. If both conditions are satisfied, it
returns `true`, otherwise `false`.
\param query_index index of the query point
\param indices indices of the inliers of the region
The first parameter is not used in this implementation.
\return Boolean `true` or `false`
\pre `query_index >= 0 && query_index < input_range.size()`
*/
bool is_part_of_region (const std::size_t,
const std::size_t query_index,
const std::vector<std::size_t>& indices) const
{
CGAL_precondition(query_index < m_input_range.size());
// First, we need to integrate at least 6 points so that the
// computed sphere means something
if (indices.size() < 6)
return true;
// If radius is out of bound, nothing fits, early ending
if (m_radius < m_min_radius || m_radius > m_max_radius)
return false;
const auto& key = *(m_input_range.begin() + query_index);
const Point_3& query_point = get(m_point_map, key);
Vector_3 normal = get(m_normal_map, key);
FT distance_to_center = m_sqrt(m_squared_distance_3 (query_point, m_center));
FT distance_to_sphere = CGAL::abs (distance_to_center - m_radius);
if (distance_to_sphere > m_distance_threshold)
return false;
normal = normal / m_sqrt (normal * normal);
Vector_3 ray (m_center, query_point);
ray = ray / m_sqrt (ray * ray);
if (CGAL::abs (normal * ray) < m_normal_threshold)
return false;
return true;
}
/*!
\brief implements `RegionType::is_valid_region()`.
This function controls if the estimated radius is between
`minimum_radius` and `maximum_radius` and if the `region` contains
at least `min_region_size` points.
\param region
indices of points included in the region
\return Boolean `true` or `false`
*/
inline bool is_valid_region(const std::vector<std::size_t>& region) const
{
return ((m_min_radius <= m_radius && m_radius <= m_max_radius)
&& (region.size() >= m_min_region_size));
}
/*!
\brief implements `RegionType::update()`.
This function fits the least squares sphere to all points from the `region`.
\param region
indices of points included in the region
\pre `region.size() > 0`
*/
void update(const std::vector<std::size_t>& region)
{
CGAL_precondition(region.size() > 0);
auto unary_function
= [&](const std::size_t& idx) -> const Point_3&
{
return get (m_point_map, *(m_input_range.begin() + idx));
};
// Use bbox to compute diagonalization with smaller coordinates to
// avoid loss of precision when inverting large coordinates
CGAL::Bbox_3 bbox = CGAL::bbox_3
(boost::make_transform_iterator
(region.begin(), unary_function),
boost::make_transform_iterator
(region.end(), unary_function));
using Diagonalize_traits = Eigen_diagonalize_traits<FT, 5>;
using Covariance_matrix = typename Diagonalize_traits::Covariance_matrix;
using Vector = typename Diagonalize_traits::Vector;
using Matrix = typename Diagonalize_traits::Matrix;
// Sphere least squares fitting
// (see Least_square_circle_fit_region for details about computation)
Covariance_matrix A
= { FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0) };
A[0] = region.size();
for (const std::size_t& idx : region)
{
const auto& key = *(m_input_range.begin() + idx);
const Point_3& p = get(m_point_map, key);
FT x = p.x() - bbox.xmin();
FT y = p.y() - bbox.ymin();
FT z = p.z() - bbox.zmin();
FT r = x*x + y*y + z*z;
A[1] += x;
A[2] += y;
A[3] += z;
A[4] += r;
A[5] += x * x;
A[6] += x * y;
A[7] += x * z;
A[8] += x * r;
A[9] += y * y;
A[10] += y * z;
A[11] += y * r;
A[12] += z * z;
A[13] += z * r;
A[14] += r * r;
}
Vector eigenvalues = { FT(0), FT(0), FT(0), FT(0), FT(0) };
Matrix eigenvectors = { FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0),
FT(0), FT(0), FT(0), FT(0), FT(0) };
Diagonalize_traits::diagonalize_selfadjoint_covariance_matrix
(A, eigenvalues, eigenvectors);
m_center = Point_3 (bbox.xmin() - FT(0.5) * (eigenvectors[1] / eigenvectors[4]),
bbox.ymin() - FT(0.5) * (eigenvectors[2] / eigenvectors[4]),
bbox.zmin() - FT(0.5) * (eigenvectors[3] / eigenvectors[4]));
m_radius = FT(0);
for (const std::size_t& idx : region)
{
const auto& key = *(m_input_range.begin() + idx);
const Point_3& p = get(m_point_map, key);
m_radius += m_sqrt (m_squared_distance_3 (p, m_center));
}
m_radius /= region.size();
}
/// @}
};
} // namespace Point_set
} // namespace Shape_detection
} // namespace CGAL
#endif // CGAL_SHAPE_DETECTION_REGION_GROWING_POINT_SET_LEAST_SQUARES_SPHERE_FIT_REGION_H