Merge remote-tracking branch 'necip/gsoc2019-pointmatcher_icp_wrapper-necipfazil' into gsoc2019-pointmatcher_icp_wrapper-necipfazil
|
|
@ -123,6 +123,15 @@ CGAL_add_named_parameter(plane_index_t, plane_index_map, plane_index_map)
|
|||
CGAL_add_named_parameter(select_percentage_t, select_percentage, select_percentage)
|
||||
CGAL_add_named_parameter(require_uniform_sampling_t, require_uniform_sampling, require_uniform_sampling)
|
||||
CGAL_add_named_parameter(point_is_constrained_t, point_is_constrained, point_is_constrained_map)
|
||||
CGAL_add_named_parameter(transformation_t, transformation, transformation)
|
||||
CGAL_add_named_parameter(point_set_filters_t, point_set_filters, point_set_filters)
|
||||
CGAL_add_named_parameter(matcher_t, matcher, matcher)
|
||||
CGAL_add_named_parameter(outlier_filters_t, outlier_filters, outlier_filters)
|
||||
CGAL_add_named_parameter(error_minimizer_t, error_minimizer, error_minimizer)
|
||||
CGAL_add_named_parameter(transformation_checkers_t, transformation_checkers, transformation_checkers)
|
||||
CGAL_add_named_parameter(inspector_t, inspector, inspector)
|
||||
CGAL_add_named_parameter(logger_t, logger, logger)
|
||||
CGAL_add_named_parameter(pointmatcher_config_t, pointmatcher_config, pointmatcher_config)
|
||||
|
||||
// List of named parameters used in Surface_mesh_approximation package
|
||||
CGAL_add_named_parameter(verbose_level_t, verbose_level, verbose_level)
|
||||
|
|
@ -144,3 +153,9 @@ CGAL_add_named_parameter(proxies_t, proxies, proxies)
|
|||
CGAL_add_named_parameter(anchors_t, anchors, anchors)
|
||||
CGAL_add_named_parameter(triangles_t, triangles, triangles)
|
||||
|
||||
CGAL_add_named_parameter(number_of_samples_t, number_of_samples, number_of_samples)
|
||||
CGAL_add_named_parameter(accuracy_t, accuracy, accuracy)
|
||||
CGAL_add_named_parameter(maximum_running_time_t, maximum_running_time, maximum_running_time)
|
||||
CGAL_add_named_parameter(overlap_t, overlap, overlap)
|
||||
CGAL_add_named_parameter(maximum_normal_deviation_t, maximum_normal_deviation, maximum_normal_deviation)
|
||||
|
||||
|
|
|
|||
|
|
@ -203,6 +203,43 @@ void test(const NamedParameters& np)
|
|||
check_same_type<42>(get_parameter(np, CGAL::internal_np::projection_functor));
|
||||
check_same_type<46>(get_parameter(np, CGAL::internal_np::apply_per_connected_component));
|
||||
check_same_type<47>(get_parameter(np, CGAL::internal_np::output_iterator));
|
||||
|
||||
// Named parameters used in the package 'Point Set Processing'
|
||||
check_same_type<9000>(get_parameter(np, CGAL::internal_np::point_map));
|
||||
check_same_type<9001>(get_parameter(np, CGAL::internal_np::query_point_map));
|
||||
check_same_type<9002>(get_parameter(np, CGAL::internal_np::normal_map));
|
||||
check_same_type<9003>(get_parameter(np, CGAL::internal_np::diagonalize_traits));
|
||||
check_same_type<9004>(get_parameter(np, CGAL::internal_np::svd_traits));
|
||||
check_same_type<9005>(get_parameter(np, CGAL::internal_np::callback));
|
||||
check_same_type<9006>(get_parameter(np, CGAL::internal_np::sharpness_angle));
|
||||
check_same_type<9007>(get_parameter(np, CGAL::internal_np::edge_sensitivity));
|
||||
check_same_type<9008>(get_parameter(np, CGAL::internal_np::neighbor_radius));
|
||||
check_same_type<9009>(get_parameter(np, CGAL::internal_np::number_of_output_points));
|
||||
check_same_type<9010>(get_parameter(np, CGAL::internal_np::size));
|
||||
check_same_type<9011>(get_parameter(np, CGAL::internal_np::maximum_variation));
|
||||
check_same_type<9012>(get_parameter(np, CGAL::internal_np::degree_fitting));
|
||||
check_same_type<9013>(get_parameter(np, CGAL::internal_np::degree_monge));
|
||||
check_same_type<9014>(get_parameter(np, CGAL::internal_np::threshold_percent));
|
||||
check_same_type<9015>(get_parameter(np, CGAL::internal_np::threshold_distance));
|
||||
check_same_type<9016>(get_parameter(np, CGAL::internal_np::attraction_factor));
|
||||
check_same_type<9017>(get_parameter(np, CGAL::internal_np::plane_map));
|
||||
check_same_type<9018>(get_parameter(np, CGAL::internal_np::plane_index_map));
|
||||
check_same_type<9019>(get_parameter(np, CGAL::internal_np::select_percentage));
|
||||
check_same_type<9020>(get_parameter(np, CGAL::internal_np::require_uniform_sampling));
|
||||
check_same_type<9021>(get_parameter(np, CGAL::internal_np::point_is_constrained));
|
||||
check_same_type<9022>(get_parameter(np, CGAL::internal_np::number_of_samples));
|
||||
check_same_type<9023>(get_parameter(np, CGAL::internal_np::accuracy));
|
||||
check_same_type<9024>(get_parameter(np, CGAL::internal_np::maximum_running_time));
|
||||
check_same_type<9025>(get_parameter(np, CGAL::internal_np::overlap));
|
||||
check_same_type<9026>(get_parameter(np, CGAL::internal_np::transformation));
|
||||
check_same_type<9027>(get_parameter(np, CGAL::internal_np::point_set_filters));
|
||||
check_same_type<9028>(get_parameter(np, CGAL::internal_np::matcher));
|
||||
check_same_type<9029>(get_parameter(np, CGAL::internal_np::outlier_filters));
|
||||
check_same_type<9030>(get_parameter(np, CGAL::internal_np::error_minimizer));
|
||||
check_same_type<9031>(get_parameter(np, CGAL::internal_np::transformation_checkers));
|
||||
check_same_type<9032>(get_parameter(np, CGAL::internal_np::inspector));
|
||||
check_same_type<9033>(get_parameter(np, CGAL::internal_np::logger));
|
||||
check_same_type<9034>(get_parameter(np, CGAL::internal_np::maximum_normal_deviation));
|
||||
}
|
||||
|
||||
int main()
|
||||
|
|
@ -280,7 +317,41 @@ int main()
|
|||
.dry_run(A<59>(59))
|
||||
.do_lock_mesh(A<60>(60))
|
||||
.do_simplify_border(A<61>(61))
|
||||
.point_map(A<9000>(9000))
|
||||
.query_point_map(A<9001>(9001))
|
||||
.normal_map(A<9002>(9002))
|
||||
.diagonalize_traits(A<9003>(9003))
|
||||
.svd_traits(A<9004>(9004))
|
||||
.callback(A<9005>(9005))
|
||||
.sharpness_angle(A<9006>(9006))
|
||||
.edge_sensitivity(A<9007>(9007))
|
||||
.neighbor_radius(A<9008>(9008))
|
||||
.number_of_output_points(A<9009>(9009))
|
||||
.size(A<9010>(9010))
|
||||
.maximum_variation(A<9011>(9011))
|
||||
.degree_fitting(A<9012>(9012))
|
||||
.degree_monge(A<9013>(9013))
|
||||
.threshold_percent(A<9014>(9014))
|
||||
.threshold_distance(A<9015>(9015))
|
||||
.attraction_factor(A<9016>(9016))
|
||||
.plane_map(A<9017>(9017))
|
||||
.plane_index_map(A<9018>(9018))
|
||||
.select_percentage(A<9019>(9019))
|
||||
.require_uniform_sampling(A<9020>(9020))
|
||||
.point_is_constrained_map(A<9021>(9021))
|
||||
.number_of_samples(A<9022>(9022))
|
||||
.accuracy(A<9023>(9023))
|
||||
.maximum_running_time(A<9024>(9024))
|
||||
.overlap(A<9025>(9025))
|
||||
.transformation(A<9026>(9026))
|
||||
.point_set_filters(A<9027>(9027))
|
||||
.matcher(A<9028>(9028))
|
||||
.outlier_filters(A<9029>(9029))
|
||||
.error_minimizer(A<9030>(9030))
|
||||
.transformation_checkers(A<9031>(9031))
|
||||
.inspector(A<9032>(9032))
|
||||
.logger(A<9033>(9033))
|
||||
.maximum_normal_deviation(A<9034>(9034))
|
||||
);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,27 @@ A package dependency over \sc{Eigen} is marked on the
|
|||
|
||||
The \sc{Eigen} web site is <A HREF="http://eigen.tuxfamily.org/index.php?title=Main_Page">`http://eigen.tuxfamily.org`</A>.
|
||||
|
||||
\subsection thirdpartyOpenGR OpenGR
|
||||
|
||||
\sc{OpenGR} is a set C++ libraries for 3D Global Registration released under the terms of the APACHE V2 licence.
|
||||
|
||||
\cgal provides wrappers for the Super4PCS algorithm of \sc{OpenGR} in the \ref PkgPointSetProcessing3Ref
|
||||
packages. In order to use \sc{OpenGR} in \cgal programs, the provided CMake function
|
||||
`CGAL_target_use_OpenGR(<target>)` should be used.
|
||||
|
||||
The \sc{OpenGR} web site is <A HREF="https://github.com/STORM-IRIT/OpenGR">`https://github.com/STORM-IRIT/OpenGR`</A>.
|
||||
|
||||
\subsection thirdpartylibpointmatcher PointMatcher
|
||||
|
||||
\sc{libpointmatcher} is a modular library implementing the Iterative Closest Point (ICP) algorithm for aligning point clouds, released under a permissive BSD license.
|
||||
|
||||
\cgal provides wrappers for the ICP algorithm of \sc{libpointmatcher} in the \ref PkgPointSetProcessing3Ref
|
||||
packages. In order to use \sc{libpointmatcher} in \cgal programs, the provided CMake function
|
||||
`CGAL_target_use_pointmatcher(<target>)` should be used.
|
||||
|
||||
The \sc{libpointmatcher} web site is <A
|
||||
HREF="https://github.com/ethz-asl/libpointmatcher">`https://github.com/ethz-asl/libpointmatcher`</A>.
|
||||
|
||||
\subsection thirdpartyLeda LEDA
|
||||
<b>Version 6.2 or later</b>
|
||||
|
||||
|
|
|
|||
|
|
@ -1543,6 +1543,13 @@ ABSTRACT = {We present the first complete, exact and efficient C++ implementatio
|
|||
,update = "97.04 schoenherr"
|
||||
}
|
||||
|
||||
@misc{ cgal:m-ogr-17,
|
||||
author = {Nicolas Mellado and others},
|
||||
title = {OpenGR: A C++ library for 3D Global Registration},
|
||||
howpublished = {https://storm-irit.github.io/OpenGR/},
|
||||
year = {2017}
|
||||
}
|
||||
|
||||
@inproceedings{ cgal:m-pppd-96
|
||||
,author = "Kurt Mehlhorn"
|
||||
,title = "Position Paper for Panel Discussion"
|
||||
|
|
@ -1583,6 +1590,19 @@ ABSTRACT = {We present the first complete, exact and efficient C++ implementatio
|
|||
, booktitle = "Proceeding of IEEE Visualization"
|
||||
}
|
||||
|
||||
@article {cgal:mam-sffgp-14,
|
||||
author = {Mellado, Nicolas and Aiger, Dror and Mitra, Niloy J.},
|
||||
title = {Super 4PCS Fast Global Pointcloud Registration via Smart Indexing},
|
||||
journal = {Computer Graphics Forum},
|
||||
volume = {33},
|
||||
number = {5},
|
||||
issn = {1467-8659},
|
||||
url = {http://dx.doi.org/10.1111/cgf.12446},
|
||||
doi = {10.1111/cgf.12446},
|
||||
pages = {205--215},
|
||||
year = {2014}
|
||||
}
|
||||
|
||||
@inproceedings{ cgal:mdsb-ddgot-02,
|
||||
author="M. Meyer and M. Desbrun and P. Schr{\"o}der and A. H. Barr",
|
||||
title="Discrete Differential-Geometry Operators for Triangulated 2-Manifolds",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,20 @@ Release date: June 2020
|
|||
- Added parallel versions of the functions `CGAL::Polygon_mesh_processing::does_self_intersect()`
|
||||
and `CGAL::Polygon_mesh_processing::self_intersections()`.
|
||||
|
||||
### Point Set Processing
|
||||
- Added wrapper functions for registration:
|
||||
- `CGAL::OpenGR::compute_registration_transformation()` computes the registration transformation
|
||||
for two point sets using Super4PCS algorithm implemented in the third party library OpenGR.
|
||||
- `CGAL::OpenGR::register_point_sets()` computes the registration transformation for two point
|
||||
sets using Super4PCS algorithm implemented in the third party library OpenGR, and registers
|
||||
the points sets by transforming the data point set using the computed transformation.
|
||||
- `CGAL::pointmatcher::compute_registration_transformation()` computes the registration
|
||||
transformation for two point sets using ICP algorithm implemented in the third party library
|
||||
libpointmatcher.
|
||||
- `CGAL::pointmatcher::register_point_sets()` computes the registration transformation for two point
|
||||
sets using ICP algorithm implemented in the third party library libpointmatcher, and registers
|
||||
the points sets by transforming the data point set using the computed transformation.
|
||||
|
||||
### 2D Triangulations
|
||||
- To fix an inconsistency between code and documentation and to clarify which types of intersections
|
||||
are truly allowed in constrained Delaunay triangulations, the tag `CGAL::No_intersection_tag`
|
||||
|
|
|
|||
|
|
@ -61,5 +61,7 @@ if( NOT CGAL_COMMON_FILE_INCLUDED )
|
|||
# set use-file for Eigen3 (needed to have default solvers)
|
||||
set(EIGEN3_USE_FILE "UseEigen3")
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/CGAL_target_use_OpenGR.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/CGAL_target_use_pointmatcher.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/CGAL_target_use_TBB.cmake)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
if (CGAL_target_use_OpenGR_included)
|
||||
return()
|
||||
endif()
|
||||
set(CGAL_target_use_OpenGR_included TRUE)
|
||||
|
||||
function(CGAL_target_use_OpenGR target)
|
||||
target_include_directories(${target} PUBLIC ${OpenGR_INCLUDE_DIR})
|
||||
target_compile_options( ${target} PUBLIC -DCGAL_LINKED_WITH_OPENGR)
|
||||
endfunction()
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
if (CGAL_target_use_pointmatcher_included)
|
||||
return()
|
||||
endif()
|
||||
set(CGAL_target_use_pointmatcher_included TRUE)
|
||||
|
||||
function(CGAL_target_use_pointmatcher target)
|
||||
target_include_directories(${target} PUBLIC ${libpointmatcher_INCLUDE_DIR})
|
||||
target_compile_options( ${target} PUBLIC -DCGAL_LINKED_WITH_POINTMATCHER)
|
||||
target_link_libraries(${target} PUBLIC ${libpointmatcher_LIBRARIES})
|
||||
endfunction()
|
||||
|
|
@ -191,6 +191,84 @@ Constrained points are left unaltered and are used as seeds in `mst_orient_norma
|
|||
<b>Default value</b>: a property map with only the highest point constrained.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{number_of_samples} \anchor PSP_number_of_samples
|
||||
is the number of input points used for computation.\n
|
||||
\b Type: \c unsigned \c int \n
|
||||
<b>Default value</b>: `200`
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{accuracy} \anchor PSP_accuracy
|
||||
is the wanted output accuracy, expressed in scene units.\n
|
||||
\b Type: floating scalar value\n
|
||||
<b>Default value</b>: `5.00`
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{maximum_normal_deviation} \anchor PSP_maximum_normal_deviation
|
||||
is the maximum angle between the normals of a pair of points, expressed in degrees.\n
|
||||
\b Type: floating scalar value\n
|
||||
<b>Default value</b>: `90.00`
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{overlap} \anchor PSP_overlap
|
||||
is the expected overlap ratio (between 0 and 1) between two points sets to register.\n
|
||||
\b Type: floating scalar value\n
|
||||
<b>Default value</b>: `0.20`
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{maximum_running_time} \anchor PSP_maximum_running_time
|
||||
is the maximum time (in seconds) allowed to the function before ending and returning the best solution found so far.\n
|
||||
\b Type: floating scalar value\n
|
||||
<b>Default value</b>: `1000`
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{transformation} \anchor PSP_transformation
|
||||
is the transformation to be applied to the point set before the function starts to further process.\n
|
||||
\b Type: Aff_transformation_3\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{point_set_filters} \anchor PSP_point_set_filters
|
||||
is the chain of filters to be applied to the point set.\n
|
||||
\b Type: a model of `Range` with `CGAL::pointmatcher::ICP_config` as value type.\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{matcher} \anchor PSP_matcher
|
||||
is the method used for matching (linking) the point set to the reference point set.\n
|
||||
\b Type: a model of `CGAL::pointmatcher::ICP_config`\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{outlier_filters} \anchor PSP_outlier_filters
|
||||
is the chain of filters to be applied to the matched (linked) point set after each processing iteration of the registration to reject the outliers for the next iteration.\n
|
||||
\b Type: a model of `Range` with `CGAL::pointmatcher::ICP_config` as value type.\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{error_minimizer} \anchor PSP_error_minimizer
|
||||
is the error minimizer that computes a transformation matrix such as to minimize the error between the point sets in registration process.\n
|
||||
\b Type: a model of `CGAL::pointmatcher::ICP_config`\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{transformation_checkers} \anchor PSP_transformation_checkers
|
||||
is the chain of transformation checkers that can stop the iteration depending on the conditions it defines.\n
|
||||
\b Type: a model of `Range` with `CGAL::pointmatcher::ICP_config` as value type.\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{inspector} \anchor PSP_inspector
|
||||
is the inspector that allows to log data at different steps of the running algorithm to allow analysis.\n
|
||||
\b Type: a model of `CGAL::pointmatcher::ICP_config`\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPBegin{logger} \anchor PSP_logger
|
||||
is for logging information regarding the process.\n
|
||||
\b Type: a model of `CGAL::pointmatcher::ICP_config`\n
|
||||
No default value.
|
||||
\cgalNPEnd
|
||||
|
||||
\cgalNPTableEnd
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ format.
|
|||
\cgalPkgDescriptionBegin{Point Set Processing,PkgPointSetProcessing3}
|
||||
\cgalPkgPicture{point_set_processing_detail.png}
|
||||
\cgalPkgSummaryBegin
|
||||
\cgalPkgAuthors{Pierre Alliez, Simon Giraudot, Clément Jamin, Florent Lafarge, Quentin Mérigot, Jocelyn Meyron, Laurent Saboret, Nader Salman, Shihao Wu}
|
||||
\cgalPkgDesc{This \cgal component implements methods to analyze and process unorganized point sets. The input is an unorganized point set, possibly with normal attributes (unoriented or oriented). The point set can be analyzed to measure its average spacing, and processed through functions devoted to the simplification, outlier removal, smoothing, normal estimation, normal orientation and feature edges estimation.}
|
||||
\cgalPkgAuthors{Pierre Alliez, Simon Giraudot, Clément Jamin, Florent Lafarge, Quentin Mérigot, Jocelyn Meyron, Laurent Saboret, Nader Salman, Shihao Wu, Necip Fazil Yildiran}
|
||||
\cgalPkgDesc{This \cgal component implements methods to analyze and process unorganized point sets. The input is an unorganized point set, possibly with normal attributes (unoriented or oriented). The point set can be analyzed to measure its average spacing, and processed through functions devoted to the simplification, outlier removal, smoothing, normal estimation, normal orientation, feature edges estimation and registration.}
|
||||
\cgalPkgManuals{Chapter_Point_Set_Processing,PkgPointSetProcessing3Ref}
|
||||
\cgalPkgSummaryEnd
|
||||
\cgalPkgShortInfoBegin
|
||||
|
|
@ -51,6 +51,10 @@ format.
|
|||
- `CGAL::estimate_global_range_scale()`
|
||||
- `CGAL::estimate_local_k_neighbor_scales()`
|
||||
- `CGAL::estimate_local_range_scales()`
|
||||
- `CGAL::OpenGR::compute_registration_transformation()`
|
||||
- `CGAL::OpenGR::register_point_sets()`
|
||||
- `CGAL::pointmatcher::compute_registration_transformation()`
|
||||
- `CGAL::pointmatcher::register_point_sets()`
|
||||
- `CGAL::remove_outliers()`
|
||||
- `CGAL::grid_simplify_point_set()`
|
||||
- `CGAL::random_simplify_point_set()`
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
\cgalAutoToc
|
||||
|
||||
\authors Pierre Alliez, Simon Giraudot, Clément Jamin, Florent Lafarge, Quentin Mérigot, Jocelyn Meyron, Laurent Saboret, Nader Salman, Shihao Wu
|
||||
\authors Pierre Alliez, Simon Giraudot, Clément Jamin, Florent Lafarge, Quentin Mérigot, Jocelyn Meyron, Laurent Saboret, Nader Salman, Shihao Wu, Necip Fazil Yildiran
|
||||
|
||||
\section Point_set_processing_3Introduction Introduction
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ In the context of surface reconstruction we can position the elements
|
|||
of this component along the common surface reconstruction pipeline
|
||||
(\cgalFigureRef{Point_set_processing_3figpipeline}) which involves the
|
||||
following steps:
|
||||
-# Scanning and scan alignment to produce a set of
|
||||
points or points with normals (alignment is not covered in \cgal);
|
||||
-# Scanning and scan registration to produce a set of
|
||||
points or points with normals;
|
||||
-# Outlier removal;
|
||||
-# Simplification to reduce the number of input points;
|
||||
-# Smoothing to reduce noise in the input data;
|
||||
|
|
@ -272,7 +272,7 @@ number of points (or the minimal local range) such that the subset of
|
|||
points has the appearance of a curve in 2D or a surface in 3D
|
||||
\cgalCite{cgal:gcsa-nasr-13}.
|
||||
|
||||
\cgal provides 2 functions that automatically estimate the scale of a
|
||||
\cgal provides two functions that automatically estimate the scale of a
|
||||
2D point set sampling a curve or a 3D point set sampling a surface:
|
||||
|
||||
- `estimate_global_k_neighbor_scale()`
|
||||
|
|
@ -285,7 +285,7 @@ K neighbor scale or a range scale.
|
|||
|
||||
In some specific cases, the scale of a point set might not be
|
||||
homogeneous (for example if the point set contains variable
|
||||
noise). \cgal also provides 2 functions that automatically estimate
|
||||
noise). \cgal also provides two functions that automatically estimate
|
||||
the scales of a point set at a set of user-defined query points:
|
||||
|
||||
- `estimate_local_k_neighbor_scales()`
|
||||
|
|
@ -314,6 +314,304 @@ points in the domain.
|
|||
|
||||
\cgalExample{Point_set_processing_3/scale_estimation_2d_example.cpp}
|
||||
|
||||
\section Point_set_processing_3Registration Registration
|
||||
|
||||
\cgal provides two functions as wrapper for the \ref thirdpartyOpenGR library
|
||||
\cgalCite{cgal:m-ogr-17}, and two functions as wrapper for the \ref thirdpartylibpointmatcher
|
||||
library :
|
||||
|
||||
- `CGAL::OpenGR::compute_registration_transformation()` computes the
|
||||
registration of one point set w.r.t. another in the form of a
|
||||
`CGAL::Aff_transformation_3` object, using the Super4PCS algorithm
|
||||
\cgalCite{cgal:mam-sffgp-14};
|
||||
|
||||
- `CGAL::OpenGR::register_point_sets()` computes the registration of
|
||||
one point set w.r.t. another and directly aligns it to it;
|
||||
|
||||
- `CGAL::pointmatcher::compute_registration_transformation()` computes the
|
||||
registration of one point set w.r.t. another in the form of a
|
||||
`CGAL::Aff_transformation_3` object, using the ICP (Iterative Closest Point)
|
||||
algorithm;
|
||||
|
||||
- `CGAL::pointmatcher::register_point_sets()` computes the registration of
|
||||
one point set w.r.t. another and directly aligns it to it.
|
||||
|
||||
\subsection Point_set_processing_3Examples_registration_OpenGR OpenGR Example
|
||||
|
||||
The following example reads two point sets and aligns them using the
|
||||
\ref thirdpartyOpenGR library, using the Super4PCS algorithm:
|
||||
\cgalExample{Point_set_processing_3/registration_with_OpenGR.cpp}
|
||||
|
||||
\cgalFigureRef{Point_set_processing_3tableRegistrationRegistration_visualization_table} demonstrates
|
||||
visualization of a scan data before and after different registration methods are applied,
|
||||
including the %OpenGR registration method. To obtain the results for %OpenGR registration
|
||||
in the visualization table, above-mentioned example was used.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_OpenGR_parameter_number_of_samples Parameter: number_of_samples
|
||||
|
||||
Input clouds are sub-sampled prior exploration, to ensure fast computations.
|
||||
Super4PCS has a linear complexity w.r.t. the number of input samples, allowing
|
||||
to use larger values than 4PCS.
|
||||
Simple geometry with large overlap can be matched with only 200 samples.
|
||||
However, with Super4PCS, smaller details can be used during the process by using
|
||||
up to thousands of points. There is no theoretical limit to this parameter;
|
||||
however, using too large values leads to very a large congruent set,
|
||||
which requires more time and memory to be explored.
|
||||
|
||||
Using a large number of samples is recommended when:
|
||||
|
||||
- geometrical details are required to perform the matching, for instance to disambiguate
|
||||
between several similar configurations,
|
||||
|
||||
- the clouds have a very low overlap: using a too sparse sampling can result in
|
||||
an empty overlapping area, causing the algorithm to fail,
|
||||
|
||||
- the clouds are very noisy, and require a dense sampling.
|
||||
|
||||
Note that Super4PCS is a global registration algorithm, which finds a good approximate
|
||||
of the rigid transformation aligning two clouds. Increasing the number of samples
|
||||
in order to get a fine registration is not optimal: it is usually faster to use
|
||||
fewer samples, and refine the transformation using a local algorithm, like the ICP,
|
||||
or its variant SparseICP.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_OpenGR_parameter_accuracy Parameter: accuracy
|
||||
|
||||
This parameter controls the registration accuracy: setting a small value means that
|
||||
the two clouds need to be very close to be considered as well aligned. It is expressed in scene units.
|
||||
|
||||
A simple way to understand its impact is to consider the computation of the Largest Common Pointset (LCP),
|
||||
the metric used to verify how aligned the clouds are. For each transformation matrix produced
|
||||
by Super4PCS, OpenGR computes the LCP measure by considering a shell around the reference cloud, and
|
||||
count the percentage of points of the target cloud lying in the shell. The thickness of the shell is
|
||||
defined by the parameter delta (accuracy).
|
||||
|
||||
Using too wide values will slow down the algorithm by increasing the size of the congruent set,
|
||||
while using to small values prevents to find a solution. This parameter impacts other steps of
|
||||
the algorithm, see the paper \cgalCite{cgal:mam-sffgp-14} for more details.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_OpenGR_parameter_normal Parameter: maximum normal deviation
|
||||
|
||||
This parameter sets an angle threshold above which two pairs of points are discarded as candidates
|
||||
for matching. It is expressed in degrees.
|
||||
|
||||
The default value is 90° (no filtering). Decreasing this value allows to decrease the computation
|
||||
time by being more selective on candidates. Using too small values might result in ignoring
|
||||
candidates that should indeed have been matched and may thus result in a quality decrease.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_OpenGR_parameter_overlap Parameter: overlap
|
||||
|
||||
Ratio of expected overlap between the two point sets: it is ranging between 0 (no overlap) to 1 (100% overlap).
|
||||
|
||||
The overlap parameter controls the size of the basis used for registration, as shown below:
|
||||
|
||||
\cgalFigureBegin{Point_set_processing_3figOpenGR_parameter_overlap,super4PCS_overlap.png}
|
||||
The effect of varying overlap parameter on the size of the basis used for registration. The overlap is smaller for left (a) than right (b).
|
||||
\cgalFigureEnd
|
||||
|
||||
Usually, the larger the overlap, the faster the algorithm.
|
||||
When the overlap is unknown, a simple way to set this parameter is to start from
|
||||
100% overlap, and decrease the value until obtaining a good result.
|
||||
Using too small values will slow down the algorithm, and
|
||||
reduce the accuracy of the result.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_OpenGR_parameter_maximum_running_time Parameter: maximum_running_time
|
||||
|
||||
Maximum number of seconds after which the algorithm stops. Super4PCS explores the
|
||||
transformation space to align the two input clouds. Since the exploration is performed
|
||||
randomly, it is recommended to use a large time value to explore the whole space.
|
||||
|
||||
\subsection Point_set_processing_3Examples_registration_PointMatcher PointMatcher Example
|
||||
|
||||
The following example reads two point sets and aligns them using the
|
||||
\ref thirdpartylibpointmatcher library, using the ICP algorithm. It also shows how
|
||||
to customize ICP algorithm by using possible configurations:
|
||||
\cgalExample{Point_set_processing_3/registration_with_pointmatcher.cpp}
|
||||
|
||||
\cgalFigureRef{Point_set_processing_3tableRegistrationRegistration_visualization_table} demonstrates
|
||||
visualization of a scan data before and after different registration methods are applied,
|
||||
including the PointMatcher registration method. To obtain the results for PointMatcher registration
|
||||
in the visualization table, above-mentioned example was used.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_point_set_filters Parameter: point_set_filters
|
||||
|
||||
The chain of filters to be applied to the point cloud. The
|
||||
point cloud is processed into an intermediate point cloud with the given chain
|
||||
of filters to be used in the alignment procedure. The chain is organized with
|
||||
the forward traversal order of the point set filters range.
|
||||
|
||||
The chain of point set filters are applied only once at the beginning of the
|
||||
ICP procedure, i.e., before the first iteration of the ICP algorithm.
|
||||
|
||||
The filters can have several purposes, including but not limited to:
|
||||
|
||||
- removal of noisy points which render alignment of point clouds difficult,
|
||||
|
||||
- removal of redundant points so as to speed up alignment,
|
||||
|
||||
- addition of descriptive information to the points such as a surface normal vector or the direction from the point to the sensor.
|
||||
|
||||
In registration, there are two point clouds in consideration, one of which is the reference point
|
||||
cloud while the other one is the point cloud to register. The point set filters corresponds to `readingDataPointsFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library while it corresponds to the `referenceDataPointsFilters` for the other point cloud.
|
||||
The filters should be chosen and set from possible components of those configuration modules.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_matcher Parameter: matcher
|
||||
|
||||
The method used for matching (linking) the points from to the points in the reference cloud.
|
||||
|
||||
Corresponds to `matcher` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The matcher should be chosen and set from possible components of
|
||||
the `matcher` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_outlier_filters Parameter: outlier_filters
|
||||
|
||||
The chain of filters to be applied to the matched (linked) point clouds after
|
||||
each processing iteration of the ICP algorithm to remove the links which do not
|
||||
correspond to true point correspondences. The outliers are rejected. Points
|
||||
with no link are ignored in the subsequent error minimization step.
|
||||
The chain is organized with the forward traversal order of the outlier filters
|
||||
range.
|
||||
|
||||
Corresponds to `outlierFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `outlierFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_error_minimizer Parameter: error_minimizer
|
||||
|
||||
The error minimizer that computes a transformation matrix such as to minimize
|
||||
the error between the point sets.
|
||||
|
||||
Corresponds to `errorMinimizer` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The error minimizer should be chosen and set from possible components of
|
||||
the `errorMinimizer` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_inspector Parameter: inspector
|
||||
|
||||
The inspector allows to log data at different steps for analysis. Inspectors
|
||||
typically provide deeper scrutiny than the logger.
|
||||
|
||||
Corresponds to `inspector` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The inspector should be chosen and set from possible components of
|
||||
the `inspector` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_logger Parameter: logger
|
||||
|
||||
The method for logging information regarding the registration process outputted
|
||||
by \ref thirdpartylibpointmatcher library. The logs generated by CGAL library
|
||||
does not get effected by this configuration.
|
||||
|
||||
Corresponds to `logger` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The logger should be chosen and set from possible components of
|
||||
the `logger` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
\subsubsection Point_set_processing_3Examples_registration_PointMatcher_parameter_transformation Parameter: transformation
|
||||
|
||||
The affine transformation that is used as the initial transformation for the reference point cloud.
|
||||
|
||||
\subsection Point_set_processing_3Examples_registration_OpenGR_PointMatcher_Pipeline OpenGR/PointMatcher Pipeline Example
|
||||
|
||||
The following example reads two point sets and aligns them by using both
|
||||
\ref thirdpartyOpenGR and \ref thirdpartylibpointmatcher libraries, respectively.
|
||||
It depicts a use case where a coarse estimation of a registration transformation
|
||||
is done using the Super4PCS algorithm. Then, a fine registration from this coarse
|
||||
registration using the ICP algorithm.
|
||||
\cgalExample{Point_set_processing_3/registration_with_opengr_pointmatcher_pipeline.cpp}
|
||||
|
||||
\cgalFigureRef{Point_set_processing_3tableRegistrationRegistration_visualization_table} demonstrates
|
||||
visualization of a scan data before and after different registration methods are applied,
|
||||
including the pipeline of %OpenGR and PointMatcher registration methods. To obtain the results
|
||||
for the pipeline of %OpenGR and PointMatcher registration methods in the visualization table,
|
||||
above-mentioned example was used.
|
||||
|
||||
\cgalFigureAnchor{Point_set_processing_3tableRegistrationRegistration_visualization_table}
|
||||
<table>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Scan 1 </th>
|
||||
<th>Scan 1 (possibly transformed, green) and Scan 2 (the reference, red)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Unregistered</th>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationUnregistered_hippo2_view1, registration_view1_hippo2_unregistered.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationUnregistered_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_unregistered.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Registered
|
||||
using
|
||||
%OpenGR
|
||||
</th>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationOpenGR_hippo2_view1, registration_view1_hippo2_opengr.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationOpenGR_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_opengr.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Registered
|
||||
using
|
||||
PointMatcher
|
||||
</th>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationPointMatcher_hippo2_view1, registration_view1_hippo2_pointmatcher.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationPointMatcher_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_pointmatcher.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Registered
|
||||
using
|
||||
OpenGR+PointMatcher
|
||||
Pipeline
|
||||
</th>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationPipeline_hippo2_view1, registration_view1_hippo2_opengr_pointmatcher_pipeline.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
<td>
|
||||
\cgalFigureBegin{Point_set_processing_3figRegistrationPipeline_hippo1_hippo2_view1, registration_view1_hippo1_hippo2_opengr_pointmatcher_pipeline.png}
|
||||
\cgalFigureEnd
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
\cgalFigureCaptionBegin{Point_set_processing_3tableRegistrationRegistration_visualization_table}
|
||||
Visualization of registered hippo scans with different registration methods.
|
||||
Two scans are used: red as the reference, green as the one for which the transformation
|
||||
is computed and applied. To obtain the results, the example code given in
|
||||
\ref Point_set_processing_3Examples_registration_OpenGR ,
|
||||
\ref Point_set_processing_3Examples_registration_PointMatcher ,
|
||||
\ref Point_set_processing_3Examples_registration_OpenGR_PointMatcher_Pipeline
|
||||
were applied, respectively. The parameters of the algorithms used to obtain those
|
||||
results are not optimized for the shown scans; therefore, better parameter choice
|
||||
might result in better results in terms of registration accuracy for each algorithm
|
||||
individually.
|
||||
\cgalFigureCaptionEnd
|
||||
|
||||
|
||||
|
||||
|
||||
\section Point_set_processing_3OutlierRemoval Outlier Removal
|
||||
|
|
@ -664,6 +962,9 @@ Started from GSoC'2013, three new algorithms were implemented by Shihao Wu and C
|
|||
Started from GSoC'2014, Jocelyn Meyron with the help of Quentin Mérigot introduced the computation of the Voronoi covariance measure of a point set,
|
||||
as well as the normal and feature edge estimation functions based on it.
|
||||
Florent Lafarge with the help of Simon Giraudot contributed the point set structuring algorithm.
|
||||
Started from GSoC'2019, Necip Fazil Yildiran with the help of Nicolas Mellado and Simon Giraudot introduced the wrappers for OpenGR and PointMatcher
|
||||
libraries that perform registration on two point sets.
|
||||
|
||||
|
||||
*/
|
||||
} /* namespace CGAL */
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
\example Point_set_processing_3/average_spacing_example.cpp
|
||||
\example Point_set_processing_3/scale_estimation_example.cpp
|
||||
\example Point_set_processing_3/scale_estimation_2d_example.cpp
|
||||
\example Point_set_processing_3/registration_with_OpenGR.cpp
|
||||
\example Point_set_processing_3/registration_with_pointmatcher.cpp
|
||||
\example Point_set_processing_3/registration_with_opengr_pointmatcher_pipeline.cpp
|
||||
\example Point_set_processing_3/remove_outliers_example.cpp
|
||||
\example Point_set_processing_3/grid_simplification_example.cpp
|
||||
\example Point_set_processing_3/grid_simplify_indices.cpp
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
|
@ -78,6 +78,34 @@ if ( CGAL_FOUND )
|
|||
create_single_source_cgal_program( "jet_smoothing_example.cpp" )
|
||||
create_single_source_cgal_program( "normal_estimation.cpp" )
|
||||
create_single_source_cgal_program( "edges_example.cpp" )
|
||||
|
||||
# Executables that require libpointmatcher
|
||||
find_package(libpointmatcher QUIET)
|
||||
if (libpointmatcher_FOUND)
|
||||
create_single_source_cgal_program( "registration_with_pointmatcher.cpp" )
|
||||
CGAL_target_use_pointmatcher(registration_with_pointmatcher)
|
||||
else()
|
||||
message(STATUS "NOTICE : the registration_with_pointmatcher test requires libpointmatcher and will not be compiled.")
|
||||
endif()
|
||||
|
||||
# Executables that require OpenGR
|
||||
find_package(OpenGR QUIET)
|
||||
if (OpenGR_FOUND)
|
||||
create_single_source_cgal_program( "registration_with_OpenGR.cpp" )
|
||||
CGAL_target_use_OpenGR(registration_with_OpenGR)
|
||||
else()
|
||||
message(STATUS "NOTICE : registration_with_OpenGR requires OpenGR, and will not be compiled.")
|
||||
endif()
|
||||
|
||||
# Executables that require both libpointmatcher and OpenGR
|
||||
if (libpointmatcher_FOUND AND OpenGR_FOUND)
|
||||
create_single_source_cgal_program( "registration_with_opengr_pointmatcher_pipeline.cpp" )
|
||||
CGAL_target_use_OpenGR(registration_with_opengr_pointmatcher_pipeline)
|
||||
CGAL_target_use_pointmatcher(registration_with_opengr_pointmatcher_pipeline)
|
||||
else()
|
||||
message(STATUS "NOTICE : registration_with_opengr_pointmatcher_pipeline requires libpointmatcher and OpenGR, and will not be compiled.")
|
||||
endif()
|
||||
|
||||
else()
|
||||
|
||||
message(STATUS "NOTICE: Some of the executables in this directory need Eigen 3.1 (or greater) and will not be compiled.")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
#include <CGAL/Simple_cartesian.h>
|
||||
#include <CGAL/IO/read_ply_points.h>
|
||||
#include <CGAL/IO/write_ply_points.h>
|
||||
#include <CGAL/property_map.h>
|
||||
|
||||
#include <CGAL/OpenGR/compute_registration_transformation.h>
|
||||
#include <CGAL/OpenGR/register_point_sets.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
typedef CGAL::Simple_cartesian<double> K;
|
||||
typedef K::Point_3 Point_3;
|
||||
typedef K::Vector_3 Vector_3;
|
||||
typedef std::pair<Point_3, Vector_3> Pwn;
|
||||
typedef CGAL::First_of_pair_property_map<Pwn> Point_map;
|
||||
typedef CGAL::Second_of_pair_property_map<Pwn> Normal_map;
|
||||
|
||||
namespace params = CGAL::parameters;
|
||||
|
||||
int main(int argc, const char** argv)
|
||||
{
|
||||
const char* fname1 = (argc>1)?argv[1]:"data/hippo1.ply";
|
||||
const char* fname2 = (argc>2)?argv[2]:"data/hippo2.ply";
|
||||
|
||||
std::vector<Pwn> pwns1, pwns2;
|
||||
std::ifstream input(fname1);
|
||||
if (!input ||
|
||||
!CGAL::read_ply_points(input, std::back_inserter(pwns1),
|
||||
CGAL::parameters::point_map (CGAL::First_of_pair_property_map<Pwn>()).
|
||||
normal_map (Normal_map())))
|
||||
{
|
||||
std::cerr << "Error: cannot read file " << fname1 << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
input.close();
|
||||
|
||||
input.open(fname2);
|
||||
if (!input ||
|
||||
!CGAL::read_ply_points(input, std::back_inserter(pwns2),
|
||||
CGAL::parameters::point_map (Point_map()).
|
||||
normal_map (Normal_map())))
|
||||
{
|
||||
std::cerr << "Error: cannot read file " << fname2 << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
input.close();
|
||||
|
||||
// EITHER call the registration method Super4PCS from OpenGR to get the transformation to apply to pwns2
|
||||
// std::pair<K::Aff_transformation_3, double> res =
|
||||
CGAL::OpenGR::compute_registration_transformation(pwns1, pwns2,
|
||||
params::point_map(Point_map())
|
||||
.normal_map(Normal_map())
|
||||
.number_of_samples(200)
|
||||
.maximum_running_time(60)
|
||||
.accuracy(0.01),
|
||||
params::point_map(Point_map())
|
||||
.normal_map(Normal_map()));
|
||||
|
||||
// OR call the registration method Super4PCS from OpenGR and apply the transformation to pwn2
|
||||
double score =
|
||||
CGAL::OpenGR::register_point_sets(pwns1, pwns2,
|
||||
params::point_map(Point_map())
|
||||
.normal_map(Normal_map())
|
||||
.number_of_samples(200)
|
||||
.maximum_running_time(60)
|
||||
.accuracy(0.01),
|
||||
params::point_map(Point_map())
|
||||
.normal_map(Normal_map()));
|
||||
|
||||
std::ofstream out("pwns2_aligned.ply");
|
||||
if (!out ||
|
||||
!CGAL::write_ply_points(
|
||||
out, pwns2,
|
||||
CGAL::parameters::point_map(Point_map()).
|
||||
normal_map(Normal_map())))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::cout << "Registration score: " << score << ".\n"
|
||||
<< "Transformed version of " << fname2
|
||||
<< " written to pwn2_aligned.ply.\n";
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
#include <CGAL/Simple_cartesian.h>
|
||||
#include <CGAL/IO/read_ply_points.h>
|
||||
#include <CGAL/IO/write_ply_points.h>
|
||||
#include <CGAL/property_map.h>
|
||||
#include <CGAL/Aff_transformation_3.h>
|
||||
|
||||
#include <CGAL/pointmatcher/register_point_sets.h>
|
||||
|
||||
#include <CGAL/OpenGR/compute_registration_transformation.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
typedef CGAL::Simple_cartesian<double> K;
|
||||
typedef K::Point_3 Point_3;
|
||||
typedef K::Vector_3 Vector_3;
|
||||
typedef std::pair<Point_3, Vector_3> Pwn;
|
||||
typedef CGAL::First_of_pair_property_map<Pwn> Point_map;
|
||||
typedef CGAL::Second_of_pair_property_map<Pwn> Normal_map;
|
||||
|
||||
namespace params = CGAL::parameters;
|
||||
|
||||
int main(int argc, const char** argv)
|
||||
{
|
||||
const char* fname1 = (argc>1)?argv[1]:"data/hippo1.ply";
|
||||
const char* fname2 = (argc>2)?argv[2]:"data/hippo2.ply";
|
||||
|
||||
std::vector<Pwn> pwns1, pwns2;
|
||||
std::ifstream input(fname1);
|
||||
if (!input ||
|
||||
!CGAL::read_ply_points(input, std::back_inserter(pwns1),
|
||||
CGAL::parameters::point_map (CGAL::First_of_pair_property_map<Pwn>()).
|
||||
normal_map (Normal_map())))
|
||||
{
|
||||
std::cerr << "Error: cannot read file " << fname1 << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
input.close();
|
||||
|
||||
input.open(fname2);
|
||||
if (!input ||
|
||||
!CGAL::read_ply_points(input, std::back_inserter(pwns2),
|
||||
CGAL::parameters::point_map (Point_map()).
|
||||
normal_map (Normal_map())))
|
||||
{
|
||||
std::cerr << "Error: cannot read file " << fname2 << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
input.close();
|
||||
|
||||
std::cerr << "Computing registration transformation using OpenGR Super4PCS.." << std::endl;
|
||||
// First, compute registration transformation using OpenGR Super4PCS
|
||||
K::Aff_transformation_3 res =
|
||||
std::get<0>( // get first of pair, which is the transformation
|
||||
CGAL::OpenGR::compute_registration_transformation
|
||||
(pwns1, pwns2,
|
||||
params::point_map(Point_map()).normal_map(Normal_map()),
|
||||
params::point_map(Point_map()).normal_map(Normal_map()))
|
||||
);
|
||||
|
||||
std::cerr << "Computing registration transformation using PointMatcher ICP, "
|
||||
<< "taking transformation computed by OpenGR Super4PCS as initial transformation.." << std::endl;
|
||||
// Then, compute registration transformation using PointMatcher ICP, taking transformation computed
|
||||
// by OpenGR as initial transformation, and apply the transformation to pwns2
|
||||
// bool converged =
|
||||
CGAL::pointmatcher::register_point_sets
|
||||
(pwns1, pwns2,
|
||||
params::point_map(Point_map()).normal_map(Normal_map()),
|
||||
params::point_map(Point_map()).normal_map(Normal_map())
|
||||
.transformation(res));
|
||||
|
||||
std::ofstream out("pwns2_aligned.ply");
|
||||
if (!out ||
|
||||
!CGAL::write_ply_points(
|
||||
out, pwns2,
|
||||
CGAL::parameters::point_map(Point_map()).
|
||||
normal_map(Normal_map())))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::cerr << "Transformed version of " << fname2
|
||||
<< " written to pwn2_aligned.ply.\n";
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#include <CGAL/Simple_cartesian.h>
|
||||
#include <CGAL/IO/read_ply_points.h>
|
||||
#include <CGAL/IO/write_ply_points.h>
|
||||
#include <CGAL/property_map.h>
|
||||
#include <CGAL/Aff_transformation_3.h>
|
||||
#include <CGAL/aff_transformation_tags.h>
|
||||
|
||||
#include <CGAL/pointmatcher/compute_registration_transformation.h>
|
||||
#include <CGAL/pointmatcher/register_point_sets.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
typedef CGAL::Simple_cartesian<double> K;
|
||||
typedef K::Point_3 Point_3;
|
||||
typedef K::Vector_3 Vector_3;
|
||||
typedef std::pair<Point_3, Vector_3> Pwn;
|
||||
typedef CGAL::First_of_pair_property_map<Pwn> Point_map;
|
||||
typedef CGAL::Second_of_pair_property_map<Pwn> Normal_map;
|
||||
|
||||
namespace params = CGAL::parameters;
|
||||
|
||||
int main(int argc, const char** argv)
|
||||
{
|
||||
const char* fname1 = (argc>1)?argv[1]:"data/hippo1.ply";
|
||||
const char* fname2 = (argc>2)?argv[2]:"data/hippo2.ply";
|
||||
|
||||
std::vector<Pwn> pwns1, pwns2;
|
||||
std::ifstream input(fname1);
|
||||
if (!input ||
|
||||
!CGAL::read_ply_points(input, std::back_inserter(pwns1),
|
||||
CGAL::parameters::point_map (CGAL::First_of_pair_property_map<Pwn>()).
|
||||
normal_map (Normal_map())))
|
||||
{
|
||||
std::cerr << "Error: cannot read file " << fname1 << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
input.close();
|
||||
|
||||
input.open(fname2);
|
||||
if (!input ||
|
||||
!CGAL::read_ply_points(input, std::back_inserter(pwns2),
|
||||
CGAL::parameters::point_map (Point_map()).
|
||||
normal_map (Normal_map())))
|
||||
{
|
||||
std::cerr << "Error: cannot read file " << fname2 << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
input.close();
|
||||
|
||||
//
|
||||
// Prepare ICP config
|
||||
//
|
||||
using CGAL::pointmatcher::ICP_config;
|
||||
|
||||
// Possible config modules/components: https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain
|
||||
// See documentation of optional named parameters for CGAL PM ICP configuration / pointmatcher config module mapping
|
||||
|
||||
// Prepare point set 1 filters (PM::ReferenceDataPointsFilters)
|
||||
std::vector<ICP_config> point_set_1_filters;
|
||||
point_set_1_filters.push_back( ICP_config { /*.name=*/"MinDistDataPointsFilter" , /*.params=*/{ {"minDist", "0.5" }} } );
|
||||
point_set_1_filters.push_back( ICP_config { /*.name=*/"RandomSamplingDataPointsFilter", /*.params=*/{ {"prob" , "0.05"}} } );
|
||||
|
||||
// Prepare point set 2 filters (PM::ReadingDataPointsFilters)
|
||||
std::vector<ICP_config> point_set_2_filters;
|
||||
point_set_2_filters.push_back( ICP_config { /*.name=*/"MinDistDataPointsFilter" , /*.params=*/{ {"minDist", "0.5" }} } );
|
||||
point_set_2_filters.push_back( ICP_config { /*.name=*/"RandomSamplingDataPointsFilter", /*.params=*/{ {"prob" , "0.05"}} } );
|
||||
|
||||
// Prepare matcher function
|
||||
ICP_config matcher { /*.name=*/"KDTreeMatcher", /*.params=*/{ {"knn", "1"}, {"epsilon", "3.16"} } };
|
||||
|
||||
// Prepare outlier filters
|
||||
std::vector<ICP_config> outlier_filters;
|
||||
outlier_filters.push_back( ICP_config { /*.name=*/"TrimmedDistOutlierFilter", /*.params=*/{ {"ratio", "0.75" }} } );
|
||||
|
||||
// Prepare error minimizer
|
||||
ICP_config error_minimizer { /*.name=*/"PointToPointErrorMinimizer"};
|
||||
|
||||
// Prepare transformation checker
|
||||
std::vector<ICP_config> transformation_checkers;
|
||||
transformation_checkers.push_back( ICP_config { /*.name=*/"CounterTransformationChecker", /*.params=*/{ {"maxIterationCount", "150" }} } );
|
||||
transformation_checkers.push_back( ICP_config { /*.name=*/"DifferentialTransformationChecker", /*.params=*/{ {"minDiffRotErr" , "0.001" },
|
||||
{"minDiffTransErr", "0.01" },
|
||||
{"smoothLength" , "4" } }
|
||||
} );
|
||||
// Prepare inspector
|
||||
ICP_config inspector { /*.name=*/"NullInspector" };
|
||||
|
||||
// Prepare logger
|
||||
ICP_config logger { /*.name=*/"FileLogger" };
|
||||
|
||||
const K::Aff_transformation_3 identity_transform = K::Aff_transformation_3(CGAL::Identity_transformation());
|
||||
|
||||
// EITHER call the ICP registration method pointmatcher to get the transformation to apply to pwns2
|
||||
std::pair<K::Aff_transformation_3, bool> res =
|
||||
CGAL::pointmatcher::compute_registration_transformation
|
||||
(pwns1, pwns2,
|
||||
params::point_map(Point_map()).normal_map(Normal_map())
|
||||
.point_set_filters(point_set_1_filters)
|
||||
.matcher(matcher)
|
||||
.outlier_filters(outlier_filters)
|
||||
.error_minimizer(error_minimizer)
|
||||
.transformation_checkers(transformation_checkers)
|
||||
.inspector(inspector)
|
||||
.logger(logger),
|
||||
params::point_map(Point_map()).normal_map(Normal_map())
|
||||
.point_set_filters(point_set_2_filters)
|
||||
.transformation(identity_transform) /* initial transform for pwns2.
|
||||
* default value is already identity transform.
|
||||
* a proper initial transform could be given, for example,
|
||||
* a transform returned from a coarse registration algorithm.
|
||||
* */
|
||||
);
|
||||
|
||||
// OR call the ICP registration method from pointmatcher and apply the transformation to pwn2
|
||||
bool converged =
|
||||
CGAL::pointmatcher::register_point_sets
|
||||
(pwns1, pwns2,
|
||||
params::point_map(Point_map()).normal_map(Normal_map())
|
||||
.point_set_filters(point_set_1_filters)
|
||||
.matcher(matcher)
|
||||
.outlier_filters(outlier_filters)
|
||||
.error_minimizer(error_minimizer)
|
||||
.transformation_checkers(transformation_checkers)
|
||||
.inspector(inspector)
|
||||
.logger(logger),
|
||||
params::point_map(Point_map()).normal_map(Normal_map())
|
||||
.point_set_filters(point_set_2_filters)
|
||||
.transformation(res.first) /* pass the above computed transformation as initial transformation.
|
||||
* as a result, the registration will require less iterations to converge.
|
||||
* */
|
||||
);
|
||||
|
||||
std::ofstream out("pwns2_aligned.ply");
|
||||
if (!out ||
|
||||
!CGAL::write_ply_points(
|
||||
out, pwns2,
|
||||
CGAL::parameters::point_map(Point_map()).
|
||||
normal_map(Normal_map())))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::cout << "Transformed version of " << fname2
|
||||
<< " written to pwn2_aligned.ply.\n";
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
// Copyright (c) 2019 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) : Sebastien Loriot, Necip Fazil Yildiran
|
||||
|
||||
#ifndef CGAL_OPENGR_COMPUTE_REGISTRATION_TRANSFORMATION_H
|
||||
#define CGAL_OPENGR_COMPUTE_REGISTRATION_TRANSFORMATION_H
|
||||
|
||||
#include <CGAL/license/Point_set_processing_3.h>
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_OPENGR
|
||||
|
||||
#include <CGAL/Aff_transformation_3.h>
|
||||
#include <CGAL/assertions.h>
|
||||
#include <CGAL/boost/graph/Named_function_parameters.h>
|
||||
#include <CGAL/boost/graph/named_params_helper.h>
|
||||
#include <CGAL/Iterator_range.h>
|
||||
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include <gr/algorithms/FunctorSuper4pcs.h>
|
||||
#include <gr/algorithms/PointPairFilter.h>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace CGAL {
|
||||
|
||||
namespace OpenGR {
|
||||
|
||||
template<typename Kernel>
|
||||
using Options = typename gr::Match4pcsBase<gr::FunctorSuper4PCS,
|
||||
gr::Point3D<typename Kernel::FT>,
|
||||
gr::DummyTransformVisitor,
|
||||
gr::AdaptivePointFilter,
|
||||
gr::AdaptivePointFilter::Options>::OptionsType;
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename Scalar, typename InputRange, typename PointMap, typename VectorMap>
|
||||
struct CGAL_range_and_pmaps_to_opengr_point3d_range
|
||||
{
|
||||
typedef typename InputRange::const_iterator::value_type argument_type;
|
||||
typedef gr::Point3D<Scalar> result_type;
|
||||
typedef typename result_type::VectorType vector_type;
|
||||
|
||||
PointMap point_map;
|
||||
VectorMap normal_map;
|
||||
|
||||
CGAL_range_and_pmaps_to_opengr_point3d_range (PointMap point_map, VectorMap normal_map)
|
||||
: point_map (point_map), normal_map (normal_map)
|
||||
{ }
|
||||
|
||||
result_type operator() (const argument_type& arg) const
|
||||
{
|
||||
const auto& p = get (point_map, arg);
|
||||
const auto& n = get (normal_map, arg);
|
||||
|
||||
result_type out (p.x(), p.y(), p.z());
|
||||
out.set_normal ( vector_type(n.x(), n.y(), n.z()) );
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Kernel,
|
||||
class PointRange1,
|
||||
class PointRange2,
|
||||
class PointMap1,
|
||||
class PointMap2,
|
||||
class VectorMap1,
|
||||
class VectorMap2>
|
||||
std::pair<typename Kernel::Aff_transformation_3, double>
|
||||
compute_registration_transformation(const PointRange1& range1, const PointRange2& range2,
|
||||
PointMap1 point_map1, PointMap2 point_map2,
|
||||
VectorMap1 vector_map1, VectorMap2 vector_map2,
|
||||
Options<Kernel>& options)
|
||||
{
|
||||
typedef typename Kernel::Point_3 Point_3;
|
||||
typedef typename Kernel::Vector_3 Vector_3;
|
||||
|
||||
typedef gr::Point3D<typename Kernel::FT> PointType;
|
||||
|
||||
namespace GR=gr;
|
||||
|
||||
// TODO: see if should allow user to change those types
|
||||
typedef Eigen::Matrix<typename PointType::Scalar, 4, 4> MatrixType;
|
||||
typedef gr::UniformDistSampler<PointType> SamplerType;
|
||||
typedef gr::DummyTransformVisitor TrVisitorType;
|
||||
typedef gr::Match4pcsBase<gr::FunctorSuper4PCS,
|
||||
PointType,
|
||||
TrVisitorType,
|
||||
gr::AdaptivePointFilter,
|
||||
gr::AdaptivePointFilter::Options> MatcherType;
|
||||
|
||||
MatrixType mat (MatrixType::Identity());
|
||||
SamplerType sampler;
|
||||
TrVisitorType visitor;
|
||||
|
||||
// Unary functions that convert value_type of point ranges to gr::Point3D
|
||||
CGAL_range_and_pmaps_to_opengr_point3d_range<typename Kernel::FT, PointRange1, PointMap1, VectorMap1> // TODO: remove deductible ones
|
||||
unary_function_1 (point_map1, vector_map1);
|
||||
|
||||
CGAL_range_and_pmaps_to_opengr_point3d_range<typename Kernel::FT, PointRange2, PointMap2, VectorMap2> // TODO: remove deductible ones
|
||||
unary_function_2 (point_map2, vector_map2);
|
||||
|
||||
auto gr_point_range_1 = boost::make_iterator_range(
|
||||
boost::make_transform_iterator (range1.begin(), unary_function_1),
|
||||
boost::make_transform_iterator (range1.end(), unary_function_1));
|
||||
|
||||
auto gr_point_range_2 = boost::make_iterator_range(
|
||||
boost::make_transform_iterator (range2.begin(), unary_function_2),
|
||||
boost::make_transform_iterator (range2.end(), unary_function_2));
|
||||
|
||||
// logger
|
||||
GR::Utils::Logger logger(GR::Utils::NoLog);
|
||||
|
||||
// matcher
|
||||
MatcherType matcher(options, logger);
|
||||
double score =
|
||||
matcher.ComputeTransformation(gr_point_range_1, gr_point_range_2, mat, sampler, visitor);
|
||||
|
||||
#ifdef CGAL_OPENGR_VERBOSE
|
||||
std::cerr << "Transformation matrix: " << std::endl;
|
||||
for (std::size_t i = 0; i < 4; ++ i)
|
||||
{
|
||||
for (std::size_t j = 0; j < 4; ++ j)
|
||||
std::cerr << mat.coeff(i,j) << " ";
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
typename Kernel::Aff_transformation_3 cgal_trsf(
|
||||
mat.coeff(0,0), mat.coeff(0,1), mat.coeff(0,2), mat.coeff(0,3),
|
||||
mat.coeff(1,0), mat.coeff(1,1), mat.coeff(1,2), mat.coeff(1,3),
|
||||
mat.coeff(2,0), mat.coeff(2,1), mat.coeff(2,2), mat.coeff(2,3));
|
||||
|
||||
return std::make_pair(cgal_trsf, score);
|
||||
}
|
||||
|
||||
} // end of namespace internal
|
||||
|
||||
/**
|
||||
\ingroup PkgPointSetProcessing3Algorithms
|
||||
|
||||
Computes the registration of `point_set_2` with respect to `point_set_1` and
|
||||
returns the corresponding affine transformation along with the registration
|
||||
score.
|
||||
|
||||
Registration is computed using the Super4PCS algorithm \cgalCite{cgal:mam-sffgp-14}.
|
||||
|
||||
\note This function requires the \ref thirdpartyOpenGR library.
|
||||
|
||||
\warning Although this may seem counter-intuitive, if one of the
|
||||
two point set matches only a small section of the other one, it is
|
||||
advised to _use the small point set as reference_ instead of the
|
||||
big one. The reason is that the reference point set is used to
|
||||
construct a base that is sought after in the other point set: if
|
||||
the big point set is used as reference, chances are the constructed
|
||||
base will not be present in the small point set.
|
||||
|
||||
\tparam PointRange1 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters1`.
|
||||
\tparam PointRange2 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters2`.
|
||||
|
||||
\param point_set_1 input point range used as reference.
|
||||
\param point_set_2 input point range whose registration w.r.t. `point_set_1` will be computed.
|
||||
\param np1 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange1` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange1` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{number_of_samples} size of the subset of input points used
|
||||
to compute registration. Input clouds are sub-sampled prior exploration,
|
||||
to ensure fast computations. Super4PCS has a linear complexity w.r.t. the
|
||||
number of input samples, allowing to use larger values than 4PCS. Simple
|
||||
geometry with large overlap can be matched with only 200 samples. However,
|
||||
with Super4PCS, smaller details can be used during the process by using up
|
||||
to thousands of points. There is no theoretical limit to this parameter,
|
||||
however using too large values leads to very a large congruent set, which
|
||||
requires more time and memory to be explored. Using a large number of
|
||||
samples is recommended when: geometrical details are required to perform
|
||||
the matching, for instance to disambiguate between several similar
|
||||
configurations; the clouds have a very low overlap: using a too sparse
|
||||
sampling can prevent to have samples in the overlapping area, causing the
|
||||
algorithm to fail; the clouds are very noisy, and require a dense
|
||||
sampling. Note that Super4PCS is a global registration algorithm, which
|
||||
finds a good approximate of the rigid transformation aligning too
|
||||
clouds. Increasing the number of samples in order to get a fine
|
||||
registration is not optimal: it is usually faster to use less samples, and
|
||||
refine the transformation using a local algorithm, like the ICP, or its
|
||||
variant SparseICP.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{maximum_normal_deviation} angle threshold (in
|
||||
degrees) used to filter pairs of points according to their normal
|
||||
consistency. Small values decrease computation time but may also
|
||||
decrease the quality if pairs of points that should match have
|
||||
a normal deviation higher than the threshold.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{accuracy} registration accuracy (delta in the
|
||||
paper). Setting a small value means that the two clouds needs to be very
|
||||
close to be considered as well aligned. It is expressed in scene units. A
|
||||
simple way to understand its impact is to consider the computation of the
|
||||
Largest Common Pointset (LCP), the metric used to verify how much the
|
||||
clouds are aligned. For each transformation matrix produced by Super4PCS,
|
||||
we compute the LCP measure by considering a shell around the reference
|
||||
cloud, and count the percentage of points of the target cloud lying in the
|
||||
shell. The thickness of the shell is defined by the parameter
|
||||
delta.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{overlap} ratio of expected overlap between the two point
|
||||
sets: it is ranging between 0 (no overlap) to 1 (100% overlap). The
|
||||
overlap parameter controls the size of the basis used for
|
||||
registration. Usually, the larger the overlap, the faster the
|
||||
algorithm. When the overlap is unknown, a simple way to set this parameter
|
||||
is to start from 100% overlap, and decrease the value until obtaining a
|
||||
good result. Using too small values will slow down the algorithm, and
|
||||
reduce the accuracy of the result.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{maximum_running_time} maximum number of seconds after
|
||||
which the algorithm stops. Super4PCS explores the transformation space to
|
||||
align the two input clouds. Since the exploration is performed randomly,
|
||||
it is recommended to use a large time value to explore the whole space.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{geom_traits} an instance of a geometric traits class,
|
||||
model of `Kernel`\cgalParamEnd
|
||||
\cgalNamedParamsEnd
|
||||
|
||||
\param np2 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange2` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange2` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
\cgalNamedParamsEnd
|
||||
|
||||
\return a pair containing the affine transformation that should be applied
|
||||
to `point_set_2` to make it registered w.r.t. `point_set_1` and the
|
||||
registration score.
|
||||
*/
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1, class NamedParameters2>
|
||||
#ifdef DOXYGEN_RUNNING
|
||||
std::pair<geom_traits::Aff_transformation_3, double>
|
||||
#else
|
||||
std::pair<typename CGAL::Point_set_processing_3::GetK<PointRange1, NamedParameters1>
|
||||
::Kernel::Aff_transformation_3, double>
|
||||
#endif
|
||||
compute_registration_transformation (const PointRange1& point_set_1, const PointRange2& point_set_2,
|
||||
const NamedParameters1& np1, const NamedParameters2& np2)
|
||||
{
|
||||
namespace PSP = CGAL::Point_set_processing_3;
|
||||
namespace GR = gr;
|
||||
using parameters::choose_parameter;
|
||||
using parameters::get_parameter;
|
||||
|
||||
// property map types
|
||||
typedef typename PSP::GetPointMap<PointRange1, NamedParameters1>::type PointMap1;
|
||||
typedef typename PSP::GetPointMap<PointRange2, NamedParameters2>::type PointMap2;
|
||||
CGAL_static_assertion_msg((boost::is_same< typename boost::property_traits<PointMap1>::value_type,
|
||||
typename boost::property_traits<PointMap2>::value_type> ::value),
|
||||
"The point type of input ranges must be the same");
|
||||
|
||||
typedef typename PSP::GetNormalMap<PointRange1, NamedParameters1>::type NormalMap1;
|
||||
typedef typename PSP::GetNormalMap<PointRange2, NamedParameters2>::type NormalMap2;
|
||||
CGAL_static_assertion_msg((boost::is_same< typename boost::property_traits<NormalMap1>::value_type,
|
||||
typename boost::property_traits<NormalMap2>::value_type> ::value),
|
||||
"The vector type of input ranges must be the same");
|
||||
|
||||
typedef typename PSP::GetK<PointRange1, NamedParameters1>::Kernel Kernel;
|
||||
|
||||
PointMap1 point_map1 = choose_parameter(get_parameter(np1, internal_np::point_map), PointMap1());
|
||||
NormalMap1 normal_map1 = choose_parameter(get_parameter(np1, internal_np::normal_map), NormalMap1());
|
||||
PointMap2 point_map2 = choose_parameter(get_parameter(np2, internal_np::point_map), PointMap2());
|
||||
NormalMap2 normal_map2 = choose_parameter(get_parameter(np2, internal_np::normal_map), NormalMap2());
|
||||
|
||||
Options<Kernel> options;
|
||||
options.sample_size = choose_parameter(get_parameter(np1, internal_np::number_of_samples), 200);
|
||||
options.delta = choose_parameter(get_parameter(np1, internal_np::accuracy), 5.00);
|
||||
options.max_time_seconds = choose_parameter(get_parameter(np1, internal_np::maximum_running_time), 1000);
|
||||
options.max_normal_difference = choose_parameter(get_parameter(np1, internal_np::maximum_normal_deviation), 90.);
|
||||
|
||||
bool overlap_ok = options.configureOverlap (choose_parameter(get_parameter(np1, internal_np::overlap), 0.20));
|
||||
CGAL_USE (overlap_ok);
|
||||
CGAL_assertion_msg (overlap_ok, "Invalid overlap configuration.");
|
||||
|
||||
return internal::compute_registration_transformation<Kernel>(point_set_1, point_set_2,
|
||||
point_map1, point_map2,
|
||||
normal_map1, normal_map2,
|
||||
options);
|
||||
|
||||
}
|
||||
|
||||
// convenience overloads
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1>
|
||||
std::pair<typename CGAL::Point_set_processing_3::GetK<PointRange1, NamedParameters1>
|
||||
::Kernel::Aff_transformation_3, double>
|
||||
compute_registration_transformation(const PointRange1& point_set_1, PointRange2& point_set_2,
|
||||
const NamedParameters1& np1)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return compute_registration_transformation(point_set_1, point_set_2, np1, params::all_default(point_set_1));
|
||||
}
|
||||
|
||||
template <class PointRange1, class PointRange2>
|
||||
std::pair<typename CGAL::Point_set_processing_3::GetK<PointRange1,
|
||||
Named_function_parameters<bool, internal_np::all_default_t> >
|
||||
::Kernel::Aff_transformation_3, double>
|
||||
compute_registration_transformation(const PointRange1& point_set_1, PointRange2& point_set_2)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return compute_registration_transformation(point_set_1, point_set_2,
|
||||
params::all_default(point_set_1),
|
||||
params::all_default(point_set_2));
|
||||
}
|
||||
|
||||
} } // end of namespace CGAL::OpenGR
|
||||
|
||||
#endif // CGAL_LINKED_WITH_OPENGR
|
||||
|
||||
#endif // CGAL_OPENGR_COMPUTE_REGISTRATION_TRANSFORMATION_H
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
// Copyright (c) 2019 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) : Sebastien Loriot, Necip Fazil Yildiran
|
||||
|
||||
#ifndef CGAL_OPENGR_REGISTER_POINT_SETS_H
|
||||
#define CGAL_OPENGR_REGISTER_POINT_SETS_H
|
||||
|
||||
#include <CGAL/license/Point_set_processing_3.h>
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_OPENGR
|
||||
|
||||
#include <CGAL/Aff_transformation_3.h>
|
||||
#include <CGAL/assertions.h>
|
||||
#include <CGAL/boost/graph/Named_function_parameters.h>
|
||||
#include <CGAL/boost/graph/named_params_helper.h>
|
||||
|
||||
#include <CGAL/OpenGR/compute_registration_transformation.h>
|
||||
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace CGAL {
|
||||
|
||||
namespace OpenGR {
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <class Kernel,
|
||||
class PointRange1,
|
||||
class PointRange2,
|
||||
class PointMap1,
|
||||
class PointMap2,
|
||||
class VectorMap1,
|
||||
class VectorMap2>
|
||||
double
|
||||
register_point_sets(const PointRange1& range1, PointRange2& range2,
|
||||
PointMap1 point_map1, PointMap2 point_map2,
|
||||
VectorMap1 vector_map1, VectorMap2 vector_map2,
|
||||
Options<Kernel>& options)
|
||||
{
|
||||
std::pair<typename Kernel::Aff_transformation_3, double> res =
|
||||
compute_registration_transformation<Kernel>(range1, range2,
|
||||
point_map1, point_map2,
|
||||
vector_map1, vector_map2,
|
||||
options);
|
||||
|
||||
// update CGAL points
|
||||
for (typename PointRange2::iterator it=range2.begin(),
|
||||
end=range2.end(); it!=end; ++it)
|
||||
{
|
||||
put(point_map2, *it, get(point_map2, *it).transform(res.first));
|
||||
}
|
||||
|
||||
return res.second;
|
||||
}
|
||||
|
||||
} // end of namespace internal
|
||||
|
||||
/**
|
||||
\ingroup PkgPointSetProcessing3Algorithms
|
||||
|
||||
Computes the registration of `point_set_2` with respect to `point_set_1` and
|
||||
applies it.
|
||||
|
||||
Registration is computed using the Super4PCS algorithm
|
||||
\cgalCite{cgal:mam-sffgp-14}. Parameters documentation is copy-pasted from [the official documentation of OpenGR](https://storm-irit.github.io/OpenGR/a00012.html). For more details on this method, please refer to it.
|
||||
|
||||
\note This function requires the \ref thirdpartyOpenGR library.
|
||||
|
||||
\warning Although this may seem counter-intuitive, if one of the two
|
||||
point set matches only a small section of the other one, it is
|
||||
advised to _use the small point set as reference_ instead of the
|
||||
big one. The reason is that the reference point set is used to
|
||||
construct a base that is sought after in the other point set: if
|
||||
the big point set is used as reference, chances are the constructed
|
||||
base will not be present in the small point set.
|
||||
|
||||
\tparam PointRange1 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters1`.
|
||||
\tparam PointRange2 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters2`.
|
||||
|
||||
\param point_set_1 input point range used as reference.
|
||||
\param point_set_2 input point range whose registration w.r.t. `point_set_1` will be computed.
|
||||
\param np1 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange1` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange1` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{number_of_samples} size of the subset of input points used
|
||||
to compute registration. Input clouds are sub-sampled prior exploration,
|
||||
to ensure fast computations. Super4PCS has a linear complexity w.r.t. the
|
||||
number of input samples, allowing to use larger values than 4PCS. Simple
|
||||
geometry with large overlap can be matched with only 200 samples. However,
|
||||
with Super4PCS, smaller details can be used during the process by using up
|
||||
to thousands of points. There is no theoretical limit to this parameter,
|
||||
however using too large values leads to very a large congruent set, which
|
||||
requires more time and memory to be explored. Using a large number of
|
||||
samples is recommended when: geometrical details are required to perform
|
||||
the matching, for instance to disambiguate between several similar
|
||||
configurations; the clouds have a very low overlap: using a too sparse
|
||||
sampling can prevent to have samples in the overlapping area, causing the
|
||||
algorithm to fail; the clouds are very noisy, and require a dense
|
||||
sampling. Note that Super4PCS is a global registration algorithm, which
|
||||
finds a good approximate of the rigid transformation aligning too
|
||||
clouds. Increasing the number of samples in order to get a fine
|
||||
registration is not optimal: it is usually faster to use less samples, and
|
||||
refine the transformation using a local algorithm, like the ICP, or its
|
||||
variant SparseICP.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{accuracy} registration accuracy (delta in the
|
||||
paper). Setting a small value means that the two clouds needs to be very
|
||||
close to be considered as well aligned. It is expressed in scene units. A
|
||||
simple way to understand its impact is to consider the computation of the
|
||||
Largest Common Pointset (LCP), the metric used to verify how much the
|
||||
clouds are aligned. For each transformation matrix produced by Super4PCS,
|
||||
we compute the LCP measure by considering a shell around the reference
|
||||
cloud, and count the percentage of points of the target cloud lying in the
|
||||
shell. The thickness of the shell is defined by the parameter
|
||||
delta.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{maximum_normal_deviation} angle threshold (in
|
||||
degrees) used to filter pairs of points according to their normal
|
||||
consistency. Small values decrease computation time but may also
|
||||
decrease the quality if pairs of points that should match have
|
||||
a normal deviation higher than the threshold.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{overlap} ratio of expected overlap between the two point
|
||||
sets: it is ranging between 0 (no overlap) to 1 (100% overlap). The
|
||||
overlap parameter controls the size of the basis used for
|
||||
registration. Usually, the larger the overlap, the faster the
|
||||
algorithm. When the overlap is unknown, a simple way to set this parameter
|
||||
is to start from 100% overlap, and decrease the value until obtaining a
|
||||
good result. Using too small values will slow down the algorithm, and
|
||||
reduce the accuracy of the result.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{maximum_running_time} maximum number of seconds after
|
||||
which the algorithm stops. Super4PCS explores the transformation space to
|
||||
align the two input clouds. Since the exploration is performed randomly,
|
||||
it is recommended to use a large time value to explore the whole space.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{geom_traits} an instance of a geometric traits class,
|
||||
model of `Kernel`\cgalParamEnd
|
||||
\cgalNamedParamsEnd
|
||||
|
||||
\param np2 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange2` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange2` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
\cgalNamedParamsEnd
|
||||
|
||||
\return the registration score.
|
||||
*/
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1, class NamedParameters2>
|
||||
double
|
||||
register_point_sets (const PointRange1& point_set_1, PointRange2& point_set_2,
|
||||
const NamedParameters1& np1, const NamedParameters2& np2)
|
||||
{
|
||||
namespace PSP = CGAL::Point_set_processing_3;
|
||||
namespace GR = gr;
|
||||
using parameters::choose_parameter;
|
||||
using parameters::get_parameter;
|
||||
|
||||
// property map types
|
||||
typedef typename PSP::GetPointMap<PointRange1, NamedParameters1>::type PointMap1;
|
||||
typedef typename PSP::GetPointMap<PointRange2, NamedParameters2>::type PointMap2;
|
||||
CGAL_static_assertion_msg((boost::is_same< typename boost::property_traits<PointMap1>::value_type,
|
||||
typename boost::property_traits<PointMap2>::value_type> ::value),
|
||||
"The point type of input ranges must be the same");
|
||||
|
||||
typedef typename PSP::GetNormalMap<PointRange1, NamedParameters1>::type NormalMap1;
|
||||
typedef typename PSP::GetNormalMap<PointRange2, NamedParameters2>::type NormalMap2;
|
||||
CGAL_static_assertion_msg((boost::is_same< typename boost::property_traits<NormalMap1>::value_type,
|
||||
typename boost::property_traits<NormalMap2>::value_type> ::value),
|
||||
"The vector type of input ranges must be the same");
|
||||
|
||||
typedef typename PSP::GetK<PointRange1, NamedParameters1>::Kernel Kernel;
|
||||
|
||||
PointMap1 point_map1 = choose_parameter(get_parameter(np1, internal_np::point_map), PointMap1());
|
||||
NormalMap1 normal_map1 = choose_parameter(get_parameter(np1, internal_np::normal_map), NormalMap1());
|
||||
PointMap1 point_map2 = choose_parameter(get_parameter(np2, internal_np::point_map), PointMap2());
|
||||
NormalMap2 normal_map2 = choose_parameter(get_parameter(np2, internal_np::normal_map), NormalMap2());
|
||||
|
||||
Options<Kernel> options;
|
||||
options.sample_size = choose_parameter(get_parameter(np1, internal_np::number_of_samples), 200);
|
||||
options.delta = choose_parameter(get_parameter(np1, internal_np::accuracy), 5.00);
|
||||
options.max_time_seconds = choose_parameter(get_parameter(np1, internal_np::maximum_running_time), 1000);
|
||||
options.max_normal_difference = choose_parameter(get_parameter(np1, internal_np::maximum_normal_deviation), 90.);
|
||||
|
||||
bool overlap_ok = options.configureOverlap (choose_parameter(get_parameter(np1, internal_np::overlap), 0.20));
|
||||
CGAL_USE (overlap_ok);
|
||||
CGAL_assertion_msg (overlap_ok, "Invalid overlap configuration.");
|
||||
|
||||
return internal::register_point_sets<Kernel>(point_set_1, point_set_2,
|
||||
point_map1, point_map2,
|
||||
normal_map1, normal_map2,
|
||||
options);
|
||||
}
|
||||
|
||||
// convenience overloads
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1>
|
||||
double
|
||||
register_point_sets(const PointRange1& point_set_1, PointRange2& point_set_2,
|
||||
const NamedParameters1& np1)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return register_point_sets(point_set_1, point_set_2, np1, params::all_default(point_set_1));
|
||||
}
|
||||
|
||||
template <class PointRange1, class PointRange2>
|
||||
double
|
||||
register_point_sets(const PointRange1& point_set_1, PointRange2& point_set_2)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return register_point_sets(point_set_1, point_set_2,
|
||||
params::all_default(point_set_1),
|
||||
params::all_default(point_set_2));
|
||||
}
|
||||
|
||||
} } // end of namespace CGAL::OpenGR
|
||||
|
||||
#endif // CGAL_LINKED_WITH_OPENGR
|
||||
|
||||
#endif // CGAL_OPENGR_REGISTER_POINT_SETS_H
|
||||
|
|
@ -0,0 +1,628 @@
|
|||
// Copyright (c) 2019 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) : Necip Fazil Yildiran
|
||||
|
||||
#ifndef CGAL_POINTMATCHER_COMPUTE_REGISTRATION_TRANSFORMATION_H
|
||||
#define CGAL_POINTMATCHER_COMPUTE_REGISTRATION_TRANSFORMATION_H
|
||||
|
||||
#include <CGAL/license/Point_set_processing_3.h>
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_POINTMATCHER
|
||||
|
||||
#include <CGAL/Aff_transformation_3.h>
|
||||
#include <CGAL/assertions.h>
|
||||
#include <CGAL/boost/graph/Named_function_parameters.h>
|
||||
#include <CGAL/boost/graph/named_params_helper.h>
|
||||
#include <CGAL/aff_transformation_tags.h>
|
||||
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
|
||||
#include <pointmatcher/PointMatcher.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace CGAL {
|
||||
|
||||
namespace pointmatcher {
|
||||
|
||||
template<typename Scalar>
|
||||
using ICP = typename PointMatcher<Scalar>::ICP;
|
||||
|
||||
/*!
|
||||
\ingroup PkgPointSetProcessing3Algorithms
|
||||
|
||||
\brief The class `ICP_config` is designed to handle preparing and passing configurations
|
||||
to the registration methods `CGAL::pointmatcher::compute_registration_transformation()`
|
||||
and `CGAL::pointmatcher::register_point_sets()`.
|
||||
|
||||
\details A configuration corresponds to a component of a configuration module
|
||||
of \ref thirdpartylibpointmatcher library. The name and the parameters of any
|
||||
configuration for the corresponding registration methods are directly passed
|
||||
to \ref thirdpartylibpointmatcher library to be parsed and registered at the
|
||||
\ref thirdpartylibpointmatcher side.
|
||||
*/
|
||||
struct ICP_config {
|
||||
/// The name of the configuration component
|
||||
std::string name;
|
||||
|
||||
/// The set of (parameter name, parameter value) pairs as a map
|
||||
std::map<std::string, std::string> params;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
void dump_invalid_point_matcher_config_exception_msg(const PointMatcherSupport::InvalidElement& err) {
|
||||
std::cerr << "ERROR Invalid configuration for PM::ICP, omitting configuration: " << std::endl;
|
||||
std::cerr << " " << err.what() << std::endl;
|
||||
}
|
||||
|
||||
template<typename Scalar, typename NamedParameters1, typename NamedParameters2>
|
||||
ICP<Scalar>
|
||||
construct_icp(const NamedParameters1& np1, const NamedParameters2& np2)
|
||||
{
|
||||
typedef PointMatcher<Scalar> PM;
|
||||
|
||||
using parameters::choose_parameter;
|
||||
using parameters::get_parameter;
|
||||
|
||||
ICP<Scalar> icp;
|
||||
|
||||
icp.setDefault();
|
||||
|
||||
const ICP_config null_config { "_null_pm_config_in_cgal" };
|
||||
const std::vector<ICP_config> null_config_chain { null_config };
|
||||
auto is_null_config = [&](const ICP_config& c) { return !c.name.compare(null_config.name); };
|
||||
|
||||
// Config file
|
||||
std::istream* pointmatcher_config = choose_parameter(get_parameter(np1, internal_np::pointmatcher_config), nullptr);
|
||||
if(pointmatcher_config != nullptr)
|
||||
{
|
||||
icp.loadFromYaml(*pointmatcher_config);
|
||||
return icp;
|
||||
}
|
||||
|
||||
// In CGAL, point_set_1 is the reference while point_set_2 is the data
|
||||
// However, in pointmatcher, the order is reverse: point_set_1 is the data while point_set_2 is the reference
|
||||
// Therefore, filter params from np1 applies to reference data points while params from np2 applies to reading data points
|
||||
|
||||
// np1.point_set_filters -> PM::ReferenceDataPointsFilter
|
||||
auto reference_data_points_filter_configs = choose_parameter(get_parameter(np1, internal_np::point_set_filters), null_config_chain);
|
||||
if(!reference_data_points_filter_configs.empty() && is_null_config(*reference_data_points_filter_configs.cbegin())) {
|
||||
// No config provided: use default values as been set above, do nothing
|
||||
;
|
||||
} else {
|
||||
// Some config chain is given: clear default values and use the provided configs
|
||||
icp.referenceDataPointsFilters.clear();
|
||||
|
||||
for(const auto& conf : reference_data_points_filter_configs)
|
||||
{
|
||||
try {
|
||||
icp.referenceDataPointsFilters.push_back( PM::get().DataPointsFilterRegistrar.create(conf.name, conf.params) );
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// np2.point_set_filters -> PM::ReadingDataPointsFilters
|
||||
auto reading_data_points_filter_configs = choose_parameter(get_parameter(np2, internal_np::point_set_filters), null_config_chain);
|
||||
if(!reading_data_points_filter_configs.empty() && is_null_config(*reading_data_points_filter_configs.cbegin())) {
|
||||
// No config provided: use default values as been set above, do nothing
|
||||
;
|
||||
} else {
|
||||
// Some config chain is given: clear default values and use the provided configs
|
||||
icp.readingDataPointsFilters.clear();
|
||||
|
||||
for(const auto& conf : reading_data_points_filter_configs)
|
||||
{
|
||||
try {
|
||||
icp.readingDataPointsFilters.push_back( PM::get().DataPointsFilterRegistrar.create(conf.name, conf.params) );
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matcher
|
||||
auto matcher_config = choose_parameter(get_parameter(np1, internal_np::matcher), null_config);
|
||||
if(!is_null_config(matcher_config))
|
||||
{
|
||||
try {
|
||||
icp.matcher = PM::get().MatcherRegistrar.create(matcher_config.name, matcher_config.params);
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Outlier Filters
|
||||
auto outlier_filters_config = choose_parameter(get_parameter(np1, internal_np::outlier_filters), null_config_chain);
|
||||
if(!outlier_filters_config.empty() && is_null_config(*outlier_filters_config.cbegin())) {
|
||||
// No config provided: use default values as been set above, do nothing
|
||||
;
|
||||
} else {
|
||||
// Some config chain is given: clear default values and use the provided configs
|
||||
icp.outlierFilters.clear();
|
||||
|
||||
for(const auto& conf : outlier_filters_config)
|
||||
{
|
||||
try {
|
||||
icp.outlierFilters.push_back( PM::get().OutlierFilterRegistrar.create(conf.name, conf.params) );
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error Minimizer
|
||||
auto error_minimizer_config = choose_parameter(get_parameter(np1, internal_np::error_minimizer), null_config);
|
||||
if(!is_null_config(error_minimizer_config))
|
||||
{
|
||||
try {
|
||||
icp.errorMinimizer = PM::get().ErrorMinimizerRegistrar.create(error_minimizer_config.name, error_minimizer_config.params);
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Transformation Checkers
|
||||
auto transformation_checkers_config = choose_parameter(get_parameter(np1, internal_np::transformation_checkers), null_config_chain);
|
||||
if(!transformation_checkers_config.empty() && is_null_config(*transformation_checkers_config.cbegin())) {
|
||||
// No config provided: use default values as been set above, do nothing
|
||||
;
|
||||
} else {
|
||||
// Some config chain is given: clear default values and use the provided configs
|
||||
icp.transformationCheckers.clear();
|
||||
|
||||
for(const auto& conf : transformation_checkers_config)
|
||||
{
|
||||
try {
|
||||
icp.transformationCheckers.push_back( PM::get().TransformationCheckerRegistrar.create(conf.name, conf.params) );
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inspector
|
||||
auto inspector_config = choose_parameter(get_parameter(np1, internal_np::inspector), null_config);
|
||||
if(!is_null_config(error_minimizer_config))
|
||||
{
|
||||
try {
|
||||
icp.inspector = PM::get().InspectorRegistrar.create(inspector_config.name, inspector_config.params);
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Logger
|
||||
auto logger_config = choose_parameter(get_parameter(np1, internal_np::logger), null_config);
|
||||
if(!is_null_config(logger_config))
|
||||
{
|
||||
try {
|
||||
PointMatcherSupport::setLogger( PM::get().LoggerRegistrar.create(logger_config.name, logger_config.params) );
|
||||
} catch(typename PointMatcherSupport::InvalidElement& error) {
|
||||
dump_invalid_point_matcher_config_exception_msg(error);
|
||||
}
|
||||
}
|
||||
|
||||
return icp;
|
||||
}
|
||||
|
||||
template<typename Scalar,
|
||||
typename PointRange,
|
||||
typename PointMap,
|
||||
typename VectorMap,
|
||||
typename PM_matrix>
|
||||
void
|
||||
copy_cgal_points_to_pm_matrix
|
||||
(const PointRange& prange, PointMap point_map, VectorMap vector_map, PM_matrix& pm_points, PM_matrix& pm_normals)
|
||||
{
|
||||
int idx = 0;
|
||||
for(const auto& p : prange)
|
||||
{
|
||||
// position
|
||||
const auto& pos = get(point_map, p);
|
||||
pm_points(0, idx) = pos.x();
|
||||
pm_points(1, idx) = pos.y();
|
||||
pm_points(2, idx) = pos.z();
|
||||
pm_points(3, idx) = Scalar(1.);
|
||||
|
||||
// normal
|
||||
const auto& normal = get (vector_map, p);
|
||||
pm_normals(0, idx) = normal.x();
|
||||
pm_normals(1, idx) = normal.y();
|
||||
pm_normals(2, idx) = normal.z();
|
||||
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Kernel,
|
||||
class PointRange1,
|
||||
class PointRange2,
|
||||
class PointMap1,
|
||||
class PointMap2,
|
||||
class VectorMap1,
|
||||
class VectorMap2>
|
||||
std::pair<typename Kernel::Aff_transformation_3, bool>
|
||||
compute_registration_transformation(const PointRange1& range1, const PointRange2& range2,
|
||||
PointMap1 point_map1, PointMap2 point_map2,
|
||||
VectorMap1 vector_map1, VectorMap2 vector_map2,
|
||||
const typename Kernel::Aff_transformation_3& initial_transform,
|
||||
ICP<typename Kernel::FT> icp)
|
||||
{
|
||||
using Scalar = typename Kernel::FT;
|
||||
|
||||
using PM = PointMatcher<Scalar>;
|
||||
using PM_cloud = typename PM::DataPoints;
|
||||
using PM_matrix = typename PM::Matrix;
|
||||
using PM_labels = typename PM_cloud::Labels;
|
||||
using PM_transform = typename PM::Transformation;
|
||||
using PM_transform_params = typename PM::TransformationParameters;
|
||||
|
||||
// ref_points: 1, points: 2
|
||||
std::size_t nb_ref_points = range1.size();
|
||||
std::size_t nb_points = range2.size();
|
||||
|
||||
PM_matrix ref_points_pos_matrix = PM_matrix (4, nb_ref_points);
|
||||
PM_matrix ref_points_normal_matrix = PM_matrix (3, nb_ref_points);
|
||||
PM_matrix points_pos_matrix = PM_matrix (4, nb_points);
|
||||
PM_matrix points_normal_matrix = PM_matrix (3, nb_points);
|
||||
|
||||
// In CGAL, point_set_1 is the reference while point_set_2 is the data
|
||||
|
||||
// convert cgal points to pointmatcher points
|
||||
internal::copy_cgal_points_to_pm_matrix<Scalar>(range1,
|
||||
point_map1,
|
||||
vector_map1,
|
||||
ref_points_pos_matrix, // out
|
||||
ref_points_normal_matrix); // out
|
||||
|
||||
internal::copy_cgal_points_to_pm_matrix<Scalar>(range2,
|
||||
point_map2,
|
||||
vector_map2,
|
||||
points_pos_matrix, // out
|
||||
points_normal_matrix); // out
|
||||
|
||||
auto construct_PM_cloud = [](const PM_matrix& positions, const PM_matrix& normals) -> PM_cloud
|
||||
{
|
||||
PM_cloud cloud;
|
||||
|
||||
cloud.addFeature("x", positions.row(0));
|
||||
cloud.addFeature("y", positions.row(1));
|
||||
cloud.addFeature("z", positions.row(2));
|
||||
cloud.addFeature("pad", positions.row(3));
|
||||
cloud.addDescriptor("normals", normals);
|
||||
|
||||
return cloud;
|
||||
};
|
||||
|
||||
PM_cloud ref_cloud = construct_PM_cloud(ref_points_pos_matrix, ref_points_normal_matrix);
|
||||
PM_cloud cloud = construct_PM_cloud(points_pos_matrix, points_normal_matrix);
|
||||
|
||||
PM_transform_params pm_transform_params = PM_transform_params::Identity(4,4);
|
||||
|
||||
// Convert CGAL transform to pm transform
|
||||
for(int i = 0; i < 4; i++)
|
||||
for(int j = 0; j < 4; j++)
|
||||
pm_transform_params(i,j) = initial_transform.m(i,j);
|
||||
|
||||
bool converged = false;
|
||||
try
|
||||
{
|
||||
const PM_transform_params prior = pm_transform_params;
|
||||
pm_transform_params = icp(cloud, ref_cloud, prior);
|
||||
converged = true;
|
||||
}
|
||||
catch (typename PM::ConvergenceError& error)
|
||||
{
|
||||
std::cerr << "ERROR CGAL::pointmatcher registration (PM::ICP) failed to converge: " << std::endl;
|
||||
std::cerr << " " << error.what() << std::endl;
|
||||
converged = false;
|
||||
}
|
||||
|
||||
// Rigid transformation
|
||||
std::shared_ptr<PM_transform> transform = PM::get().REG(Transformation).create("RigidTransformation");
|
||||
pm_transform_params = transform->correctParameters(pm_transform_params);
|
||||
|
||||
typename Kernel::Aff_transformation_3 cgal_transform
|
||||
(pm_transform_params(0,0), pm_transform_params(0,1), pm_transform_params(0,2), pm_transform_params(0,3),
|
||||
pm_transform_params(1,0), pm_transform_params(1,1), pm_transform_params(1,2), pm_transform_params(1,3),
|
||||
pm_transform_params(2,0), pm_transform_params(2,1), pm_transform_params(2,2), pm_transform_params(2,3));
|
||||
|
||||
#ifdef CGAL_POINTMATCHER_VERBOSE
|
||||
std::cerr << "Transformation matrix: " << std::endl;
|
||||
for (std::size_t i = 0; i < 4; ++ i)
|
||||
{
|
||||
for (std::size_t j = 0; j < 4; ++ j)
|
||||
std::cerr << cgal_transform.coeff(i,j) << " ";
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::make_pair(cgal_transform, converged);
|
||||
}
|
||||
|
||||
} // end of namespace internal
|
||||
|
||||
// point_set_1 is reference while point_set_2 is data
|
||||
/**
|
||||
\ingroup PkgPointSetProcessing3Algorithms
|
||||
|
||||
Computes the registration of `point_set_2` with respect to `point_set_1` and
|
||||
returns the corresponding affine transformation.
|
||||
Registration is computed using the Iterative Closest Point (ICP) algorithm.
|
||||
\note This function requires the \ref thirdpartylibpointmatcher library.
|
||||
\tparam PointRange1 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters1`.
|
||||
\tparam PointRange2 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters2`.
|
||||
\param point_set_1 input point range used as reference.
|
||||
\param point_set_2 input point range whose registration w.r.t. `point_set_1` will be computed.
|
||||
\param np1 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange1` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange1` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{point_set_filters} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
|
||||
The chain of filters to be applied to the reference point cloud. The reference
|
||||
point cloud is processed into an intermediate point cloud with the given chain
|
||||
of filters to be used in the alignment procedure. The chain is organized with
|
||||
the forward traversal order of the point set filters range.
|
||||
|
||||
The chain of point set filters are applied only once at the beginning of the
|
||||
ICP procedure, i.e., before the first iteration of the ICP algorithm.
|
||||
|
||||
The filters can have several purposes, including but are not limited to
|
||||
i) removal of noisy points which render alignment of point clouds difficult,
|
||||
ii) removal of redundant points so as to speed up alignment, iii) addition
|
||||
of descriptive information to the points such as a surface normal vector,
|
||||
or the direction from the point to the sensor.
|
||||
|
||||
Corresponds to `referenceDataPointsFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `referenceDataPointsFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `RandomSamplingDataPointsFilter` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{matcher} is a model of `ICP_config`.
|
||||
The method used for matching (linking) the points from `point_set_2`, to the
|
||||
points in the reference cloud, `point_set_1`.
|
||||
|
||||
Corresponds to `matcher` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The matcher should be chosen and set from possible components of
|
||||
the `matcher` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `KDTreeMatcher` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
|
||||
\cgalParamBegin{outlier_filters} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
The chain of filters to be applied to the matched (linked) point clouds after
|
||||
each processing iteration of the ICP algorithm to remove the links which do not
|
||||
correspond to true point correspondences. The outliers are rejected. Points
|
||||
with no link are ignored in the subsequent error minimization step.
|
||||
The chain is organized with the forward traversal order of the outlier filters
|
||||
range.
|
||||
|
||||
Corresponds to `outlierFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `outlierFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `TrimmedDistOutlierFilter` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{error_minimizer} is a model of `ICP_config`.
|
||||
The error minimizer that computes a transformation matrix such as to minimize
|
||||
the error between the point sets.
|
||||
|
||||
Corresponds to `errorMinimizer` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The error minimizer should be chosen and set from possible components of
|
||||
the `errorMinimizer` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `PointToPlaneErrorMinimizer` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{transformation_checkers} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
The chain of transformation checkers. A transformation checker can stop the
|
||||
iteration depending on the conditions it defines.
|
||||
|
||||
The chain is organized with the forward traversal order of the transformation
|
||||
checkers range.
|
||||
|
||||
Corresponds to `transformationCheckers` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The transformation checkers should be chosen and set from possible components of
|
||||
the `transformationCheckers` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, the chain of `CounterTransformationChecker` and
|
||||
`DifferentialTransformationChecker` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{inspector} is a model of `ICP_config`.
|
||||
The inspector allows to log data at different steps for analysis. Inspectors
|
||||
typically provide deeper scrutiny than the logger.
|
||||
|
||||
Corresponds to `inspector` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The inspector should be chosen and set from possible components of
|
||||
the `inspector` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `NullInspector` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{logger} is a model of `ICP_config`.
|
||||
The method for logging information regarding the registration process outputted
|
||||
by \ref thirdpartylibpointmatcher library. The logs generated by CGAL library
|
||||
does not get effected by this configuration.
|
||||
|
||||
Corresponds to `logger` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The logger should be chosen and set from possible components of
|
||||
the `logger` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `NullLogger` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{geom_traits} an instance of a geometric traits class,
|
||||
model of `Kernel`\cgalParamEnd
|
||||
|
||||
\cgalNamedParamsEnd
|
||||
|
||||
\param np2 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange2` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange2` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{point_set_filters} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
|
||||
The chain of filters to be applied to the point cloud, `point_set_2`. The
|
||||
point cloud is processed into an intermediate point cloud with the given chain
|
||||
of filters to be used in the alignment procedure. The chain is organized with
|
||||
the forward traversal order of the point set filters range.
|
||||
|
||||
The chain of point set filters are applied only once at the beginning of the
|
||||
ICP procedure, i.e., before the first iteration of ICP algorithm.
|
||||
|
||||
The filters can have several purposes, including but are not limited to
|
||||
i) removal of noisy points which render alignment of point clouds difficult,
|
||||
ii) removal of redundant points so as to speed up alignment, iii) addition
|
||||
of descriptive information to the points such as a surface normal vector,
|
||||
or the direction from the point to the sensor.
|
||||
|
||||
Corresponds to `readingDataPointsFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `readingDataPointsFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `SamplingSurfaceNormalDataPointsFilter` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{transformation} The affine transformation that is used as the
|
||||
initial transformation for `point_set_2`.
|
||||
|
||||
If this parameter is omitted, identity transformation is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalNamedParamsEnd
|
||||
\return a pair containing the affine transformation that should be applied
|
||||
to `point_set_2` to make it registered w.r.t. `point_set_1` and the
|
||||
boolean value indicating if the registration converged. The second
|
||||
of the pair is `true` if converged, `false` otherwise. A log why it failed to
|
||||
converge is written to `std::cerr` if the registration cannot converge.
|
||||
*/
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1, class NamedParameters2>
|
||||
#ifdef DOXYGEN_RUNNING
|
||||
std::pair<geom_traits::Aff_transformation_3, bool>
|
||||
#else
|
||||
std::pair<typename CGAL::Point_set_processing_3::GetK<PointRange1, NamedParameters1>
|
||||
::Kernel::Aff_transformation_3, bool>
|
||||
#endif
|
||||
compute_registration_transformation (const PointRange1& point_set_1, const PointRange2& point_set_2,
|
||||
const NamedParameters1& np1, const NamedParameters2& np2)
|
||||
{
|
||||
using parameters::choose_parameter;
|
||||
using parameters::get_parameter;
|
||||
|
||||
namespace PSP = CGAL::Point_set_processing_3;
|
||||
|
||||
// property map types
|
||||
typedef typename PSP::GetPointMap<PointRange1, NamedParameters1>::type PointMap1;
|
||||
typedef typename PSP::GetPointMap<PointRange2, NamedParameters2>::type PointMap2;
|
||||
CGAL_static_assertion_msg((boost::is_same< typename boost::property_traits<PointMap1>::value_type,
|
||||
typename boost::property_traits<PointMap2>::value_type> ::value),
|
||||
"The point type of input ranges must be the same");
|
||||
|
||||
typedef typename PSP::GetNormalMap<PointRange1, NamedParameters1>::type NormalMap1;
|
||||
typedef typename PSP::GetNormalMap<PointRange2, NamedParameters2>::type NormalMap2;
|
||||
CGAL_static_assertion_msg((boost::is_same< typename boost::property_traits<NormalMap1>::value_type,
|
||||
typename boost::property_traits<NormalMap2>::value_type> ::value),
|
||||
"The vector type of input ranges must be the same");
|
||||
|
||||
typedef typename PSP::GetK<PointRange1, NamedParameters1>::Kernel Kernel;
|
||||
typedef typename Kernel::FT Scalar;
|
||||
typedef typename Kernel::Aff_transformation_3 Transformation;
|
||||
|
||||
PointMap1 point_map1 = choose_parameter(get_parameter(np1, internal_np::point_map), PointMap1());
|
||||
NormalMap1 normal_map1 = choose_parameter(get_parameter(np1, internal_np::normal_map), NormalMap1());
|
||||
PointMap2 point_map2 = choose_parameter(get_parameter(np2, internal_np::point_map), PointMap2());
|
||||
NormalMap2 normal_map2 = choose_parameter(get_parameter(np2, internal_np::normal_map), NormalMap2());
|
||||
|
||||
// initial transformation
|
||||
Transformation initial_transformation
|
||||
= choose_parameter(get_parameter(np2, internal_np::transformation), Transformation(Identity_transformation()));
|
||||
|
||||
return internal::compute_registration_transformation<Kernel>(point_set_1, point_set_2,
|
||||
point_map1, point_map2,
|
||||
normal_map1, normal_map2,
|
||||
initial_transformation,
|
||||
internal::construct_icp<Scalar>(np1, np2));
|
||||
}
|
||||
|
||||
// convenience overloads
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1>
|
||||
std::pair<typename CGAL::Point_set_processing_3::GetK<PointRange1, NamedParameters1>
|
||||
::Kernel::Aff_transformation_3, bool>
|
||||
compute_registration_transformation(const PointRange1& point_set_1, const PointRange2& point_set_2,
|
||||
const NamedParameters1& np1)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return compute_registration_transformation(point_set_1, point_set_2, np1, params::all_default(point_set_1));
|
||||
}
|
||||
|
||||
template <class PointRange1, class PointRange2>
|
||||
std::pair<typename CGAL::Point_set_processing_3::GetK<PointRange1,
|
||||
Named_function_parameters<bool, internal_np::all_default_t> >
|
||||
::Kernel::Aff_transformation_3, bool>
|
||||
compute_registration_transformation(const PointRange1& point_set_1, const PointRange2& point_set_2)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return compute_registration_transformation(point_set_1, point_set_2,
|
||||
params::all_default(point_set_1),
|
||||
params::all_default(point_set_2));
|
||||
}
|
||||
|
||||
|
||||
} } // end of namespace CGAL::pointmatcher
|
||||
|
||||
#endif // CGAL_LINKED_WITH_POINTMATCHER
|
||||
|
||||
#endif // CGAL_POINTMATCHER_COMPUTE_REGISTRATION_TRANSFORMATION_H
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright (c) 2019 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) : Necip Fazil Yildiran
|
||||
|
||||
#ifndef CGAL_POINTMATCHER_REGISTER_POINT_SETS_H
|
||||
#define CGAL_POINTMATCHER_REGISTER_POINT_SETS_H
|
||||
|
||||
#include <CGAL/license/Point_set_processing_3.h>
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_POINTMATCHER
|
||||
|
||||
#include <CGAL/Aff_transformation_3.h>
|
||||
#include <CGAL/assertions.h>
|
||||
#include <CGAL/boost/graph/Named_function_parameters.h>
|
||||
#include <CGAL/boost/graph/named_params_helper.h>
|
||||
|
||||
#include <CGAL/pointmatcher/compute_registration_transformation.h>
|
||||
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace CGAL {
|
||||
|
||||
namespace pointmatcher {
|
||||
|
||||
// point_set_1 is reference while point_set_2 is data
|
||||
/**
|
||||
\ingroup PkgPointSetProcessing3Algorithms
|
||||
|
||||
Computes the registration of `point_set_2` with respect to `point_set_1` and
|
||||
applies it.
|
||||
|
||||
Registration is computed using the Iterative Closest Point (ICP) algorithm.
|
||||
|
||||
\note This function requires the \ref thirdpartylibpointmatcher library.
|
||||
|
||||
\tparam PointRange1 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters1`.
|
||||
\tparam PointRange2 is a model of `Range`. The value type of its iterator is
|
||||
the key type of the named parameter `point_map` in `NamedParameters2`.
|
||||
|
||||
\param point_set_1 input point range used as reference.
|
||||
\param point_set_2 input point range whose registration w.r.t. `point_set_1` will be computed.
|
||||
\param np1 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange1` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange1` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{point_set_filters} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
|
||||
The chain of filters to be applied to the reference point cloud. The reference
|
||||
point cloud is processed into an intermediate point cloud with the given chain
|
||||
of filters to be used in the alignment procedure. The chain is organized with
|
||||
the forward traversal order of the point set filters range.
|
||||
|
||||
The chain of point set filters are applied only once at the beginning of the
|
||||
ICP procedure, i.e., before the first iteration of the ICP algorithm.
|
||||
|
||||
The filters can have several purposes, including but are not limited to
|
||||
i) removal of noisy points which render alignment of point clouds difficult,
|
||||
ii) removal of redundant points so as to speed up alignment, iii) addition
|
||||
of descriptive information to the points such as a surface normal vector,
|
||||
or the direction from the point to the sensor.
|
||||
|
||||
Corresponds to `referenceDataPointsFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `referenceDataPointsFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `RandomSamplingDataPointsFilter` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{matcher} is a model of `ICP_config`.
|
||||
The method used for matching (linking) the points from `point_set_2`, to the
|
||||
points in the reference cloud, `point_set_1`.
|
||||
|
||||
Corresponds to `matcher` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The matcher should be chosen and set from possible components of
|
||||
the `matcher` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `KDTreeMatcher` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{outlier_filters} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
The chain of filters to be applied to the matched (linked) point clouds after
|
||||
each processing iteration of the ICP algorithm to remove the links which do not
|
||||
correspond to true point correspondences. The outliers are rejected. Points
|
||||
with no link are ignored in the subsequent error minimization step.
|
||||
The chain is organized with the forward traversal order of the outlier filters
|
||||
range.
|
||||
|
||||
Corresponds to `outlierFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `outlierFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `TrimmedDistOutlierFilter` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{error_minimizer} is a model of `ICP_config`.
|
||||
The error minimizer that computes a transformation matrix such as to minimize
|
||||
the error between the point sets.
|
||||
|
||||
Corresponds to `errorMinimizer` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The error minimizer should be chosen and set from possible components of
|
||||
the `errorMinimizer` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `PointToPlaneErrorMinimizer` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{transformation_checkers} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
The chain of transformation checkers. A transformation checker can stop the
|
||||
iteration depending on the conditions it defines.
|
||||
|
||||
The chain is organized with the forward traversal order of the transformation
|
||||
checkers range.
|
||||
|
||||
Corresponds to `transformationCheckers` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The transformation checkers should be chosen and set from possible components of
|
||||
the `transformationCheckers` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, the chain of `CounterTransformationChecker` and
|
||||
`DifferentialTransformationChecker` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{inspector} is a model of `ICP_config`.
|
||||
The inspector allows to log data at different steps for analysis. Inspectors
|
||||
typically provide deeper scrutiny than the logger.
|
||||
|
||||
Corresponds to `inspector` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The inspector should be chosen and set from possible components of
|
||||
the `inspector` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `NullInspector` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{logger} is a model of `ICP_config`.
|
||||
The method for logging information regarding the registration process outputted
|
||||
by \ref thirdpartylibpointmatcher library. The logs generated by CGAL library
|
||||
does not get effected by this configuration.
|
||||
|
||||
Corresponds to `logger` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The logger should be chosen and set from possible components of
|
||||
the `logger` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `NullLogger` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{geom_traits} an instance of a geometric traits class,
|
||||
model of `Kernel`\cgalParamEnd
|
||||
\cgalNamedParamsEnd
|
||||
|
||||
\param np2 optional sequence of \ref psp_namedparameters "Named Parameters" among the ones listed below.
|
||||
\cgalNamedParamsBegin
|
||||
\cgalParamBegin{point_map} a model of `ReadablePropertyMap` whose key type
|
||||
is the value type of the iterator of `PointRange2` and whose value type is
|
||||
`geom_traits::Point_3`. If this parameter is omitted,
|
||||
`CGAL::Identity_property_map<geom_traits::Point_3>` is used.\cgalParamEnd
|
||||
\cgalParamBegin{normal_map} a model of `ReadablePropertyMap` whose key
|
||||
type is the value type of the iterator of `PointRange2` and whose value
|
||||
type `geom_traits::Vector_3`.\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{point_set_filters} is a model of `Range`. The value type of
|
||||
its iterator is `ICP_config`.
|
||||
|
||||
The chain of filters to be applied to the point cloud, `point_set_2`. The
|
||||
point cloud is processed into an intermediate point cloud with the given chain
|
||||
of filters to be used in the alignment procedure. The chain is organized with
|
||||
the forward traversal order of the point set filters range.
|
||||
|
||||
The chain of point set filters are applied only once at the beginning of the
|
||||
ICP procedure, i.e., before the first iteration of ICP algorithm.
|
||||
|
||||
The filters can have several purposes, including but are not limited to
|
||||
i) removal of noisy points which render alignment of point clouds difficult,
|
||||
ii) removal of redundant points so as to speed up alignment, iii) addition
|
||||
of descriptive information to the points such as a surface normal vector,
|
||||
or the direction from the point to the sensor.
|
||||
|
||||
Corresponds to `readingDataPointsFilters` configuration module of \ref thirdpartylibpointmatcher
|
||||
library. The filters should be chosen and set from possible components of
|
||||
the `readingDataPointsFilters` configuration module.
|
||||
See <a href="https://libpointmatcher.readthedocs.io/en/latest/Configuration/#configuration-of-an-icp-chain">libpointmatcher documentation</a>
|
||||
for possible configurations.
|
||||
|
||||
If this parameter is omitted, `SamplingSurfaceNormalDataPointsFilter` is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalParamBegin{transformation} The affine transformation that is used as the
|
||||
initial transformation for `point_set_2`.
|
||||
|
||||
If this parameter is omitted, identity transformation is used.
|
||||
\cgalParamEnd
|
||||
|
||||
\cgalNamedParamsEnd
|
||||
\return `true` if registration is converged, `false` otherwise. A log why it
|
||||
failed to converge is written to `std::cerr` if the registration cannot converge.
|
||||
*/
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1, class NamedParameters2>
|
||||
bool
|
||||
register_point_sets (const PointRange1& point_set_1, PointRange2& point_set_2,
|
||||
const NamedParameters1& np1, const NamedParameters2& np2)
|
||||
{
|
||||
using parameters::choose_parameter;
|
||||
using parameters::get_parameter;
|
||||
|
||||
namespace PSP = CGAL::Point_set_processing_3;
|
||||
typedef typename PSP::GetK<PointRange1, NamedParameters1>::Kernel Kernel;
|
||||
|
||||
// compute registration transformation
|
||||
std::pair<typename Kernel::Aff_transformation_3, bool> res =
|
||||
compute_registration_transformation(point_set_1, point_set_2, np1, np2);
|
||||
|
||||
// property map type of point_set_2
|
||||
typedef typename PSP::GetPointMap<PointRange2, NamedParameters2>::type PointMap2;
|
||||
PointMap2 point_map2 = choose_parameter(get_parameter(np2, internal_np::point_map), PointMap2());
|
||||
|
||||
// update CGAL points
|
||||
for (typename PointRange2::iterator it=point_set_2.begin(),
|
||||
end=point_set_2.end(); it!=end; ++it)
|
||||
{
|
||||
put(point_map2, *it, get(point_map2, *it).transform(res.first));
|
||||
}
|
||||
|
||||
return res.second;
|
||||
}
|
||||
|
||||
// convenience overloads
|
||||
template <class PointRange1, class PointRange2,
|
||||
class NamedParameters1>
|
||||
bool
|
||||
register_point_sets(const PointRange1& point_set_1, PointRange2& point_set_2,
|
||||
const NamedParameters1& np1)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return register_point_sets(point_set_1, point_set_2, np1, params::all_default(point_set_1));
|
||||
}
|
||||
|
||||
template <class PointRange1, class PointRange2>
|
||||
bool
|
||||
register_point_sets(const PointRange1& point_set_1, PointRange2& point_set_2)
|
||||
{
|
||||
namespace params = CGAL::Point_set_processing_3::parameters;
|
||||
return register_point_sets(point_set_1, point_set_2,
|
||||
params::all_default(point_set_1),
|
||||
params::all_default(point_set_2));
|
||||
}
|
||||
|
||||
} } // end of namespace CGAL::pointmatcher
|
||||
|
||||
#endif // CGAL_LINKED_WITH_POINTMATCHER
|
||||
|
||||
#endif // CGAL_POINTMATCHER_REGISTER_POINT_SETS_H
|
||||
|
|
@ -44,6 +44,22 @@ if(EIGEN3_FOUND)
|
|||
polyhedron_demo_plugin(point_set_shape_detection_plugin Point_set_shape_detection_plugin ${point_set_shape_detectionUI_FILES} KEYWORDS PointSetProcessing Classification)
|
||||
target_link_libraries(point_set_shape_detection_plugin PUBLIC scene_surface_mesh_item scene_points_with_normal_item scene_polygon_soup_item scene_callback_signaler)
|
||||
|
||||
find_package(OpenGR QUIET)
|
||||
find_package(libpointmatcher QUIET)
|
||||
|
||||
if (OpenGR_FOUND OR libpointmatcher_FOUND)
|
||||
qt5_wrap_ui(register_point_setsUI_FILES Register_point_sets_plugin.ui)
|
||||
polyhedron_demo_plugin(register_point_sets_plugin Register_point_sets_plugin ${register_point_setsUI_FILES} KEYWORDS PointSetProcessing)
|
||||
if (OpenGR_FOUND)
|
||||
CGAL_target_use_OpenGR(register_point_sets_plugin)
|
||||
endif()
|
||||
if (libpointmatcher_FOUND)
|
||||
CGAL_target_use_pointmatcher(register_point_sets_plugin)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "NOTICE: OpenGR and libpointmatcher were not found. Registrationp plugin will not be available.")
|
||||
endif()
|
||||
|
||||
else(EIGEN3_FOUND)
|
||||
message(STATUS "NOTICE: Eigen 3.1 (or greater) was not found. Surface reconstruction plugin will not be available.")
|
||||
message(STATUS "NOTICE: Eigen 3.1 (or greater) was not found. Normal estimation plugins will not be available.")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
#include "config.h"
|
||||
#include "Scene_points_with_normal_item.h"
|
||||
#include <CGAL/Three/Polyhedron_demo_plugin_helper.h>
|
||||
#include <CGAL/Three/Polyhedron_demo_plugin_interface.h>
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_OPENGR
|
||||
#define CGAL_OPENGR_VERBOSE
|
||||
#include <CGAL/OpenGR/register_point_sets.h>
|
||||
#endif
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_POINTMATCHER
|
||||
#include <CGAL/pointmatcher/register_point_sets.h>
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <CGAL/Timer.h>
|
||||
#include <CGAL/Memory_sizer.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QApplication>
|
||||
#include <QtPlugin>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QRadioButton>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
#include <QDoubleSpinBox>
|
||||
|
||||
#include "ui_Register_point_sets_plugin.h"
|
||||
|
||||
|
||||
using namespace CGAL::Three;
|
||||
|
||||
|
||||
class Point_set_demo_register_dialog : public QDialog, private Ui::RegisterPointSetsDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Point_set_demo_register_dialog(const std::vector<Scene_points_with_normal_item*>& items,
|
||||
QWidget* /*parent*/ = 0)
|
||||
{
|
||||
setupUi(this);
|
||||
|
||||
for (std::size_t i = 0; i < items.size(); ++ i)
|
||||
{
|
||||
QRadioButton* button = new QRadioButton(items[i]->name().toStdString().c_str(), this);
|
||||
buttons.push_back (button);
|
||||
pointSets->addRow (button);
|
||||
if (i == 0)
|
||||
button->setChecked(true);
|
||||
}
|
||||
|
||||
#ifndef CGAL_LINKED_WITH_OPENGR
|
||||
coarseRegistration->setText("Coarse registration (disabled, requires OpenGR)");
|
||||
coarseRegistration->setChecked(false);
|
||||
coarseRegistration->setEnabled(false);
|
||||
opengr_frame->setEnabled(false);
|
||||
#endif
|
||||
#ifndef CGAL_LINKED_WITH_POINTMATCHER
|
||||
fineRegistration->setText("Fine registration (disabled, requires libpointmatcher)");
|
||||
fineRegistration->setChecked(false);
|
||||
fineRegistration->setEnabled(false);
|
||||
pointmatcher_frame->setEnabled(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t ref_index() const
|
||||
{
|
||||
for (std::size_t i = 0; i < buttons.size(); ++ i)
|
||||
if (buttons[i]->isChecked())
|
||||
return i;
|
||||
return std::size_t(-1);
|
||||
}
|
||||
|
||||
bool coarse_registration() const { return coarseRegistration->isChecked(); }
|
||||
bool fine_registration() const { return fineRegistration->isChecked(); }
|
||||
|
||||
int nb_samples() const { return numberOfSamplesSpinBox->value(); }
|
||||
double accuracy() const { return accuracyDoubleSpinBox->value(); }
|
||||
double overlap() const { return double(overlapSpinBox->value()) / 100.0; }
|
||||
int max_time() const { return maximumRunningTimeSpinBox->value(); }
|
||||
|
||||
std::string pointmatcher_config() const { return config->toPlainText().toStdString(); }
|
||||
|
||||
private:
|
||||
|
||||
std::vector<QRadioButton*> buttons;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Polyhedron_demo_register_point_sets_plugin :
|
||||
public QObject,
|
||||
public Polyhedron_demo_plugin_helper
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
|
||||
Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0")
|
||||
|
||||
private:
|
||||
QAction* actionRegisterPointSets;
|
||||
|
||||
public:
|
||||
void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface*) {
|
||||
scene = scene_interface;
|
||||
mw = mainWindow;
|
||||
actionRegisterPointSets = new QAction(tr("Register point sets"), mainWindow);
|
||||
actionRegisterPointSets->setObjectName("actionRegisterPointSets");
|
||||
connect(actionRegisterPointSets, SIGNAL(triggered()), this, SLOT(on_actionRegisterPointSets_triggered()));
|
||||
}
|
||||
|
||||
QList<QAction*> actions() const {
|
||||
return QList<QAction*>() << actionRegisterPointSets;
|
||||
}
|
||||
|
||||
bool applicable(QAction*) const {
|
||||
return get_point_set_items().size() >= 2;
|
||||
}
|
||||
|
||||
std::vector<Scene_points_with_normal_item*> get_point_set_items() const
|
||||
{
|
||||
std::vector<Scene_points_with_normal_item*> items;
|
||||
|
||||
Q_FOREACH(int index, scene->selectionIndices())
|
||||
{
|
||||
Scene_points_with_normal_item* item =
|
||||
qobject_cast<Scene_points_with_normal_item*>(scene->item(index));
|
||||
if(item && item->point_set()->has_normal_map())
|
||||
items.push_back (item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void on_actionRegisterPointSets_triggered();
|
||||
private :
|
||||
Scene_interface *scene;
|
||||
}; // end Polyhedron_demo_register_point_sets_plugin
|
||||
|
||||
void Polyhedron_demo_register_point_sets_plugin::on_actionRegisterPointSets_triggered()
|
||||
{
|
||||
std::vector<Scene_points_with_normal_item*> items = get_point_set_items();
|
||||
|
||||
Point_set_demo_register_dialog dialog(items);
|
||||
if(!dialog.exec())
|
||||
return;
|
||||
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
|
||||
std::size_t ref = dialog.ref_index();
|
||||
|
||||
for (std::size_t i = 0; i < items.size(); ++ i)
|
||||
{
|
||||
if (i == ref)
|
||||
continue;
|
||||
|
||||
std::cerr << "Registering " << items[i]->name().toStdString() << " with " << items[ref]->name().toStdString() << std::endl;
|
||||
|
||||
Point_set& ps1 = *(items[ref]->point_set());
|
||||
Point_set& ps2 = *(items[i]->point_set());
|
||||
|
||||
#ifdef CGAL_LINKED_WITH_OPENGR
|
||||
if (dialog.coarse_registration())
|
||||
{
|
||||
std::cerr << "* Coarse registration:" << std::endl;
|
||||
CGAL::Timer task_timer; task_timer.start();
|
||||
double score =
|
||||
CGAL::OpenGR::register_point_sets(ps1, ps2,
|
||||
CGAL::parameters::point_map(ps1.point_map())
|
||||
.normal_map(ps1.normal_map())
|
||||
.number_of_samples(dialog.nb_samples())
|
||||
.maximum_running_time(dialog.max_time())
|
||||
.accuracy(dialog.accuracy())
|
||||
.overlap(dialog.overlap()),
|
||||
CGAL::parameters::point_map(ps2.point_map())
|
||||
.normal_map(ps2.normal_map()));
|
||||
|
||||
std::size_t memory = CGAL::Memory_sizer().virtual_size();
|
||||
std::cerr << " -> Registration score = " << score << " ("
|
||||
<< task_timer.time() << " seconds, "
|
||||
<< (memory>>20) << " Mb allocated)"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
#ifdef CGAL_LINKED_WITH_POINTMATCHER
|
||||
if (dialog.fine_registration())
|
||||
{
|
||||
std::cerr << "* Fine registration: " << std::endl;
|
||||
|
||||
std::istringstream ss (dialog.pointmatcher_config());
|
||||
|
||||
CGAL::Timer task_timer; task_timer.start();
|
||||
if (CGAL::pointmatcher::register_point_sets(ps1, ps2,
|
||||
CGAL::parameters::point_map(ps1.point_map())
|
||||
.normal_map(ps1.normal_map())
|
||||
.pointmatcher_config(&ss),
|
||||
CGAL::parameters::point_map(ps2.point_map())
|
||||
.normal_map(ps2.normal_map())))
|
||||
std::cerr << " -> Success";
|
||||
else
|
||||
std::cerr << " -> Failure";
|
||||
|
||||
std::size_t memory = CGAL::Memory_sizer().virtual_size();
|
||||
std::cerr << " ("
|
||||
<< task_timer.time() << " seconds, "
|
||||
<< (memory>>20) << " Mb allocated)"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < items.size(); ++ i)
|
||||
{
|
||||
items[i]->invalidateOpenGLBuffers();
|
||||
scene->itemChanged(items[i]);
|
||||
}
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
|
||||
#include "Register_point_sets_plugin.moc"
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RegisterPointSetsDialog</class>
|
||||
<widget class="QDialog" name="RegisterPointSetsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>418</width>
|
||||
<height>515</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Register point sets</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Which point set is the reference? (others will be altered)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="pointSets"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="coarseRegistration">
|
||||
<property name="text">
|
||||
<string>Coarse registration using OpenGR</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="opengr_frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="numberOfSamplesLabel">
|
||||
<property name="text">
|
||||
<string>Number of samples:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="numberOfSamplesSpinBox">
|
||||
<property name="minimum">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>200</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="accuracyLabel">
|
||||
<property name="text">
|
||||
<string>Accuracy:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="accuracyDoubleSpinBox">
|
||||
<property name="decimals">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000010000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="overlapLabel">
|
||||
<property name="text">
|
||||
<string>Overlap:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="maximumRunningTimeLabel">
|
||||
<property name="text">
|
||||
<string>Maximum running time:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="maximumRunningTimeSpinBox">
|
||||
<property name="suffix">
|
||||
<string> s</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>36000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="overlapSpinBox">
|
||||
<property name="suffix">
|
||||
<string> %</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fineRegistration">
|
||||
<property name="text">
|
||||
<string>Fine registration using libpointmatcher</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="pointmatcher_frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>398</width>
|
||||
<height>198</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="config">
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">readingDataPointsFilters:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> - RandomSamplingDataPointsFilter:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> prob: 0.5</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">referenceDataPointsFilters:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> - SamplingSurfaceNormalDataPointsFilter:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> knn: 10</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">matcher:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> KDTreeMatcher:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> knn: 1</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> epsilon: 0 </span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">outlierFilters:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> - TrimmedDistOutlierFilter:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> ratio: 0.75</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">errorMinimizer:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> PointToPlaneErrorMinimizer</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">transformationCheckers:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> - CounterTransformationChecker:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> maxIterationCount: 40</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> - DifferentialTransformationChecker:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> minDiffRotErr: 0.001</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> minDiffTransErr: 0.01</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> smoothLength: 4 </span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">inspector:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> VTKFileInspector:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> baseFileName: pointmatcher-run1</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> dumpPerfOnExit: 0</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> dumpStats: 0</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> dumpIterationInfo: 1 </span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> dumpDataLinks: 0 </span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> dumpReading: 0 </span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> dumpReference: 0 </span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">logger:</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';"> NullLogger</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>RegisterPointSetsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>RegisterPointSetsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>coarseRegistration</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>opengr_frame</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>208</x>
|
||||
<y>63</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>208</x>
|
||||
<y>158</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>fineRegistration</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>pointmatcher_frame</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>208</x>
|
||||
<y>253</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>208</x>
|
||||
<y>370</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||