diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 5ef8f0eb3f4..10511c02879 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,6 +1,6 @@ set(SUBSYS_NAME benchmarks) set(SUBSYS_DESC "Point cloud library benchmarks") -set(SUBSYS_DEPS common filters features search kdtree io sample_consensus) +set(SUBSYS_DEPS common filters features search kdtree io sample_consensus surface) PCL_SUBSYS_OPTION(build "${SUBSYS_NAME}" "${SUBSYS_DESC}" OFF) PCL_SUBSYS_DEPEND(build NAME ${SUBSYS_NAME} DEPS ${SUBSYS_DEPS}) @@ -35,3 +35,7 @@ PCL_ADD_BENCHMARK(sample_consensus_sac_model_cylinder FILES sample_consensus/sac PCL_ADD_BENCHMARK(search_radius_search FILES search/radius_search.cpp LINK_WITH pcl_io pcl_search pcl_filters ARGUMENTS "${PCL_SOURCE_DIR}/test/table_scene_mug_stereo_textured.pcd") + +PCL_ADD_BENCHMARK(gp3 FILES surface/gp3.cpp + LINK_WITH pcl_io pcl_filters pcl_surface + ARGUMENTS "${PCL_SOURCE_DIR}/test/table_scene_mug_stereo_textured.pcd") diff --git a/benchmarks/surface/gp3.cpp b/benchmarks/surface/gp3.cpp new file mode 100644 index 00000000000..d7ee5d224e5 --- /dev/null +++ b/benchmarks/surface/gp3.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#include + +#include + +void +print_help() +{ + std::cout << "Usage: benchmark_gp3 \n"; +} + +static void +BM_gp3(benchmark::State& state, const pcl::PointCloud::Ptr cloudIn) +{ + pcl::GreedyProjectionTriangulation gp3; + gp3.setSearchRadius(0.025); + gp3.setMu(2.5); + gp3.setMaximumNearestNeighbors(100); + gp3.setMinimumAngle(M_PI / 18); // 10 degrees + gp3.setMaximumAngle(2 * M_PI / 3); // 120 degrees + gp3.setNormalConsistency(false); + pcl::PolygonMesh triangles; + gp3.setInputCloud(cloudIn); + + for (auto _ : state) { + gp3.reconstruct(triangles); + } +} + +int +main(int argc, char** argv) +{ + if (argc < 2) { + std::cerr << "No test file given. Please provide a PCD file for the benchmark." + << std::endl; + print_help(); + return -1; + } + + pcl::PointCloud::Ptr cloudIn(new pcl::PointCloud); + pcl::PCDReader reader; + reader.read(argv[1], *cloudIn); + + // Filter out nans + pcl::PointCloud::Ptr cloudFiltered( + new pcl::PointCloud); + pcl::Indices indices; + pcl::removeNaNFromPointCloud(*cloudIn, *cloudFiltered, indices); + + benchmark::RegisterBenchmark("gp3", &BM_gp3, cloudFiltered) + ->Unit(benchmark::kMillisecond); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/surface/include/pcl/surface/gp3.h b/surface/include/pcl/surface/gp3.h index 98d91fd8d16..2a3f3b6a789 100644 --- a/surface/include/pcl/surface/gp3.h +++ b/surface/include/pcl/surface/gp3.h @@ -309,7 +309,12 @@ namespace pcl /** \brief Temporary variable to store a triangle (as a set of point indices) **/ pcl::Vertices triangle_{}; /** \brief Temporary variable to store point coordinates **/ - std::vector > coords_{}; + std::vector coords_{}; + // Precomputed per-point data (normal, local basis u,v, and projection of point onto its normal plane) + std::vector normals_{}; + std::vector u_basis_{}; + std::vector v_basis_{}; + std::vector proj_qp_list_{}; /** \brief A list of angles to neighbors **/ std::vector angles_{}; @@ -369,7 +374,8 @@ namespace pcl /** \brief Temporary variable to store 3 coordinates **/ Eigen::Vector3f tmp_; - + /** \brief Reusable buffer for projected boundary edges to avoid repeated allocations **/ + std::vector double_edges_{}; /** \brief The actual surface reconstruction method. * \param[out] output the resultant polygonal mesh */ @@ -473,19 +479,6 @@ namespace pcl part_[v] = part_[s]; fringe_queue_.push_back(v); } - - /** \brief Function for ascending sort of nnAngle, taking visibility into account - * (angles to visible neighbors will be first, to the invisible ones after). - * \param[in] a1 the first angle - * \param[in] a2 the second angle - */ - static inline bool - nnAngleSortAsc (const nnAngle& a1, const nnAngle& a2) - { - if (a1.visible == a2.visible) - return (a1.angle < a2.angle); - return a1.visible; - } }; } // namespace pcl diff --git a/surface/include/pcl/surface/impl/gp3.hpp b/surface/include/pcl/surface/impl/gp3.hpp index 2b5dcf656d2..d58315e1373 100644 --- a/surface/include/pcl/surface/impl/gp3.hpp +++ b/surface/include/pcl/surface/impl/gp3.hpp @@ -40,6 +40,9 @@ #include +#include +#include + ///////////////////////////////////////////////////////////////////////////////////////////// template void pcl::GreedyProjectionTriangulation::performReconstruction (pcl::PolygonMesh &output) @@ -120,27 +123,56 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

size ()); + normals_.clear (); + normals_.reserve (indices_->size ()); + u_basis_.clear (); + u_basis_.reserve (indices_->size ()); + v_basis_.clear (); + v_basis_.reserve (indices_->size ()); + proj_qp_list_.clear (); + proj_qp_list_.reserve (indices_->size ()); std::vector point2index (input_->size (), -1); for (int cp = 0; cp < static_cast (indices_->size ()); ++cp) { - coords_.push_back((*input_)[(*indices_)[cp]].getVector3fMap()); - point2index[(*indices_)[cp]] = cp; + const auto idx = (*indices_)[cp]; + const auto& p = (*input_)[idx]; + const Eigen::Vector3f xyz = p.getVector3fMap(); + coords_.push_back(xyz); + point2index[idx] = cp; + const Eigen::Vector3f n = p.getNormalVector3fMap(); + normals_.push_back(n); + // local basis + const Eigen::Vector3f v_local = n.unitOrthogonal(); + const Eigen::Vector3f u_local = n.cross(v_local); + v_basis_.push_back(v_local); + u_basis_.push_back(u_local); + const float dist = n.dot(xyz); + proj_qp_list_.emplace_back(xyz - dist * n); } // Initializing - int is_free=0, nr_parts=0, increase_nnn4fn=0, increase_nnn4s=0, increase_dist=0; + int nr_parts=0, increase_nnn4fn=0, increase_nnn4s=0, increase_dist=0; angles_.resize(nnn_); std::vector > uvn_nn (nnn_); Eigen::Vector2f uvn_s; + const double cos_eps_angle_ = std::cos(eps_angle_); + + // Initialize queue with initial FREE points (avoid repeated scans) + std::deque free_queue; + for (int i = 0; i < static_cast(indices_->size()); ++i) + if (state_[i] == FREE) + free_queue.push_back(i); // iterating through fringe points and finishing them until everything is done - while (is_free != NONE) + while (!free_queue.empty()) { - R_ = is_free; - if (state_[R_] == FREE) + R_ = free_queue.front(); + free_queue.pop_front(); + if (state_[R_] != FREE) + continue; { state_[R_] = NONE; part_[R_] = part_index++; @@ -149,7 +181,7 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

nearestKSearch ((*input_)[(*indices_)[R_]], nnn_, nnIdx, sqrDists); tree_->nearestKSearch (indices_->at (R_), nnn_, nnIdx, sqrDists); - double sqr_dist_threshold = (std::min)(sqr_max_edge, sqr_mu * sqrDists[1]); + const double sqr_dist_threshold = (std::min)(sqr_max_edge, sqr_mu * sqrDists[1]); // Search tree returns indices into the original cloud, but we are working with indices. TODO: make that optional! for (int i = 1; i < nnn_; i++) @@ -159,20 +191,15 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

doubleEdges; + double_edges_.clear(); + double_edges_.reserve(nnn_); for (int i = 1; i < nnn_; i++) // nearest neighbor with index 0 is the query point R_ itself { // Transforming coordinates @@ -203,7 +230,7 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

::reconstructPolygons (std::vector

::reconstructPolygons (std::vector

size (); temp++) - { - if (state_[temp] == FREE) - { - is_free = temp; - break; - } - } - bool is_fringe = true; while (is_fringe) { @@ -311,13 +328,11 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

at (R_) << "] " << i << ": " << nnIdx[i] << " / " << point2index[nnIdx[i]] << std::endl; nnIdx[i] = point2index[nnIdx[i]]; } - - // Locating FFN and SFN to adapt distance threshold - double sqr_source_dist = (coords_[R_] - coords_[source_[R_]]).squaredNorm (); - double sqr_ffn_dist = (coords_[R_] - coords_[ffn_[R_]]).squaredNorm (); - double sqr_sfn_dist = (coords_[R_] - coords_[sfn_[R_]]).squaredNorm (); - double max_sqr_fn_dist = (std::max)(sqr_ffn_dist, sqr_sfn_dist); - double sqr_dist_threshold = (std::min)(sqr_max_edge, sqr_mu * sqrDists[1]); //sqr_mu * sqr_avg_conn_dist); + const double sqr_source_dist = (coords_[R_] - coords_[source_[R_]]).squaredNorm (); + const double sqr_ffn_dist = (coords_[R_] - coords_[ffn_[R_]]).squaredNorm (); + const double sqr_sfn_dist = (coords_[R_] - coords_[sfn_[R_]]).squaredNorm (); + const double max_sqr_fn_dist = (std::max)(sqr_ffn_dist, sqr_sfn_dist); + const double sqr_dist_threshold = (std::min)(sqr_max_edge, sqr_mu * sqrDists[1]); if (max_sqr_fn_dist > sqrDists[nnn_-1]) { if (0 == increase_nnn4fn) @@ -326,7 +341,7 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

sqrDists[nnn_-1]) { if (0 == increase_nnn4s) @@ -334,21 +349,15 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

doubleEdges; - for (int i = 1; i < nnn_; i++) // nearest neighbor with index 0 is the query point R_ itself + double_edges_.clear(); + double_edges_.reserve(nnn_); + for (int i = 1; i < nnn_; ++i) // nearest neighbor with index 0 is the query point R_ itself { tmp_ = coords_[nnIdx[i]] - proj_qp_; uvn_nn[i][0] = tmp_.dot(u_); @@ -370,14 +379,17 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

1) cosine = 1; if (cosine < -1) cosine = -1; - double angle = std::acos (cosine); - if ((!consistent_) && (angle > M_PI/2)) - angle = M_PI - angle; - if (angle > eps_angle_) + + bool too_large_angle; + if (consistent_) + too_large_angle = (cosine < cos_eps_angle_); + else + too_large_angle = (std::fabs(cosine) < cos_eps_angle_); + if (too_large_angle) { angles_[i].visible = false; same_side = false; @@ -394,7 +406,7 @@ pcl::GreedyProjectionTriangulation::reconstructPolygons (std::vector

::reconstructPolygons (std::vector

::reconstructPolygons (std::vector

::nnAngleSortAsc); + const auto visible_end = std::partition(angles_.begin(), angles_.end(), [](const nnAngle& a){ return a.visible; }); + std::sort(angles_.begin(), visible_end, [](const nnAngle& a, const nnAngle& b){ return a.angle < b.angle; }); // Triangulating if (angles_[2].visible == false)