diff --git a/BGL/doc/BGL/BGL.txt b/BGL/doc/BGL/BGL.txt index 23c63b9f37d..8e90fb6bda3 100644 --- a/BGL/doc/BGL/BGL.txt +++ b/BGL/doc/BGL/BGL.txt @@ -653,6 +653,47 @@ can be done directly using `Face_filtered_graph`. Using \ref BGLNamedParameters some of the many options of METIS can be customized, as shown in \ref BGL_polyhedron_3/polyhedron_partition.cpp "this example". + +\section BGLGraphcut Graphcut + +Optimal partition from a set of labels can be computed through a +graphcut approach called Alpha Expansion +\cgalCite{boykov2001fastapproximate}. \cgal provides +`CGAL::alpha_expansion_graphcut()` which, or a graph \f$(V,E)\f$, this +function seeks for the partioning `f` that minimizes the following +energy: + +\f[ + \mathrm{E}(f) = \sum_{\{v0,v1\} \in E} W(v0,v1) + \sum_{v \in V} C(f_v) + \f] + +where \f$W(v0,v1)\f$ is the weight associated to the edge +\f$\{v0,v1\}\f$ and \f$C(f_v)\f$ is the cost of assigning the vertex +\f$v\f$ to the labeling \f$f\f$. + +Three different implementations are provided and can be selected by +using one of the following tags: + +- `CGAL::Alpha_expansion_boost_adjacency_list_tag` (default) +- `CGAL::Alpha_expansion_boost_compressed_sparse_raw_tag` +- `CGAL::Alpha_expansion_boost_MaxFlow_tag` + +All these implementation produce the exact same result but behave +differently in terms of timing and memory (see +\cgalFigureRef{alpha_exp}). The _MaxFlow_ implementation is the +fastest, but it grows rapidly in memory when increasing the complexity +of the input graph and labeling; the _compressed sparse raw_ (CSR) is very +efficient from a memory point of view but becomes very slow as the +complexity of the input graph and labeling increases; the _adjacency +list_ version provides a good compromise and is therefore the default +implementation. + +\cgalFigureBegin{alpha_exp, alpha_expansion.png} +Comparison of time and memory consumed by the different Alpha +Expansion implementation. +\cgalFigureEnd + + */ } /* namespace CGAL */ diff --git a/BGL/doc/BGL/Doxyfile.in b/BGL/doc/BGL/Doxyfile.in index c676a95fc13..8984e5b8082 100644 --- a/BGL/doc/BGL/Doxyfile.in +++ b/BGL/doc/BGL/Doxyfile.in @@ -15,7 +15,8 @@ INPUT += ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/Euler_operations.h \ ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/io.h \ ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/partition.h \ ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_graph.h \ - ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_dual_graph.h + ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_dual_graph.h \ + ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/alpha_expansion_graphcut.h EXAMPLE_PATH = ${CGAL_Surface_mesh_skeletonization_EXAMPLE_DIR} \ diff --git a/BGL/doc/BGL/PackageDescription.txt b/BGL/doc/BGL/PackageDescription.txt index 287b2662399..2b93f5cc4b9 100644 --- a/BGL/doc/BGL/PackageDescription.txt +++ b/BGL/doc/BGL/PackageDescription.txt @@ -710,6 +710,7 @@ user might encounter. \cgalCRPSection{Partitioning Methods} - `CGAL::METIS::partition_graph()` - `CGAL::METIS::partition_dual_graph()` +- `CGAL::alpha_expansion_graphcut()` \cgalCRPSection{I/O Functions} - \link PkgBGLIOFct CGAL::read_off() \endlink diff --git a/BGL/doc/BGL/fig/alpha_expansion.png b/BGL/doc/BGL/fig/alpha_expansion.png new file mode 100644 index 00000000000..afa8e5f457f Binary files /dev/null and b/BGL/doc/BGL/fig/alpha_expansion.png differ diff --git a/Surface_mesh_segmentation/include/CGAL/internal/Surface_mesh_segmentation/Alpha_expansion_graph_cut.h b/BGL/include/CGAL/boost/graph/alpha_expansion_graphcut.h similarity index 72% rename from Surface_mesh_segmentation/include/CGAL/internal/Surface_mesh_segmentation/Alpha_expansion_graph_cut.h rename to BGL/include/CGAL/boost/graph/alpha_expansion_graphcut.h index 14bec111050..35930c748ac 100644 --- a/Surface_mesh_segmentation/include/CGAL/internal/Surface_mesh_segmentation/Alpha_expansion_graph_cut.h +++ b/BGL/include/CGAL/boost/graph/alpha_expansion_graphcut.h @@ -1,4 +1,4 @@ -#ifndef CGAL_SURFACE_MESH_SEGMENTATION_ALPHA_EXPANSION_GRAPH_CUT_H +#ifndef CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H // Copyright (c) 2014 GeometryFactory Sarl (France). // All rights reserved. // @@ -15,32 +15,13 @@ // // $URL$ // $Id$ -// SPDX-License-Identifier: GPL-3.0+ +// SPDX-License-Identifier: LGPL-3.0+ // -// Author(s) : Ilker O. Yaz +// Author(s) : Ilker O. Yaz, Simon Giraudot -#define CGAL_SURFACE_MESH_SEGMENTATION_ALPHA_EXPANSION_GRAPH_CUT_H +#define CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H -#include - - -/// @cond CGAL_DOCUMENT_INTERNAL - -/** - * @file Alpha_expansion_graph_cut.h - * @brief This file contains 3 graph-cut algorithms, which can be used as a template parameter for CGAL::internal::Surface_mesh_segmentation. - * - * Main differences between implementations are underlying max-flow algorithm and graph type (i.e. results are the same, performance differs). - * - * By default, we use MAXFLOW and the class Alpha_expansion_graph_cut_boykov_kolmogorov. - * For deactivating MAXFLOW software and using boost implementation instead, define CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE. - * It deactivates Alpha_expansion_graph_cut_boykov_kolmogorov, activate boost versions - * and makes CGAL::internal::Surface_mesh_segmentation using Alpha_expansion_graph_cut_boost - * as default implementation for the graph-cut. - * - * Also algorithms can be used by their-own for applying alpha-expansion graph-cut on any graph. - * - */ +#include #include #ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT #include @@ -51,13 +32,6 @@ #include -#ifndef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE -namespace MaxFlow -{ -#include -} -#endif - #include #include @@ -74,6 +48,8 @@ namespace MaxFlow namespace CGAL { + +/// \cond SKIP_IN_MANUAL namespace internal { @@ -91,25 +67,25 @@ struct Alpha_expansion_old_API_wrapper_graph typedef CGAL::Identity_property_map Vertex_index_map; typedef CGAL::Pointer_property_map::type Vertex_label_map; - struct Vertex_label_probability_map + struct Vertex_label_cost_map { typedef std::size_t key_type; typedef std::vector value_type; typedef value_type reference; typedef boost::readable_property_map_tag category; - const std::vector >* probability_matrix; + const std::vector >* cost_matrix; - Vertex_label_probability_map (const std::vector >* probability_matrix) - : probability_matrix (probability_matrix) + Vertex_label_cost_map (const std::vector >* cost_matrix) + : cost_matrix (cost_matrix) { } - friend reference get (const Vertex_label_probability_map& pmap, key_type idx) + friend reference get (const Vertex_label_cost_map& pmap, key_type idx) { std::vector out; - out.reserve (pmap.probability_matrix->size()); - for (std::size_t i = 0; i < pmap.probability_matrix->size(); ++ i) - out.push_back ((*pmap.probability_matrix)[i][idx]); + out.reserve (pmap.cost_matrix->size()); + for (std::size_t i = 0; i < pmap.cost_matrix->size(); ++ i) + out.push_back ((*pmap.cost_matrix)[i][idx]); return out; } @@ -119,14 +95,14 @@ struct Alpha_expansion_old_API_wrapper_graph const std::vector >& edges; const std::vector& edge_weights; - const std::vector >& probability_matrix; + const std::vector >& cost_matrix; std::vector& labels; Alpha_expansion_old_API_wrapper_graph (const std::vector >& edges, const std::vector& edge_weights, - const std::vector >& probability_matrix, + const std::vector >& cost_matrix, std::vector& labels) - : edges (edges), edge_weights (edge_weights), probability_matrix (probability_matrix), labels (labels) + : edges (edges), edge_weights (edge_weights), cost_matrix (cost_matrix), labels (labels) { } friend counting_range vertices (const Alpha_expansion_old_API_wrapper_graph& graph) @@ -150,8 +126,8 @@ struct Alpha_expansion_old_API_wrapper_graph Vertex_index_map vertex_index_map() const { return Vertex_index_map(); } Vertex_label_map vertex_label_map() { return CGAL::make_property_map(labels); } - Vertex_label_probability_map vertex_label_probability_map() const - { return Vertex_label_probability_map(&probability_matrix); } + Vertex_label_cost_map vertex_label_cost_map() const + { return Vertex_label_cost_map(&cost_matrix); } Edge_weight_map edge_weight_map() const { return CGAL::make_property_map(edge_weights); } }; @@ -206,7 +182,7 @@ struct Alpha_expansion_old_API_wrapper_graph * For representing graph, it uses adjacency_list with OutEdgeList = vecS, VertexList = listS. * Also no pre-allocation is made for vertex-list. */ -class Alpha_expansion_boost_adjacency_list +class Alpha_expansion_boost_adjacency_list_tag { private: typedef boost::adjacency_list_traits @@ -295,15 +271,6 @@ public: put (vertex_label_map, vd, alpha); } - /** - * Adds two directional edges between @a v1 and @a v2 - * @param v1 first vertex - * @param v2 second vertex - * @param w1 weight for edge from v1 to v2 (v1->v2) - * @param w2 weight for edge from v2 to v1 (v2->v1) - * @param graph to be added - * @return pair of added edges, first: v1->v2 and second: v2->v1 - */ void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2) { Edge_descriptor v1_v2, v2_v1; @@ -327,7 +294,7 @@ public: // another implementation using compressed_sparse_row_graph // for now there is a performance problem while setting reverse edges // if that can be solved, it is faster than Alpha_expansion_graph_cut_boost -class Alpha_expansion_boost_compressed_sparse_row +class Alpha_expansion_boost_compressed_sparse_row_tag { private: // CSR only accepts bundled props @@ -475,98 +442,123 @@ public: }; -#ifndef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE -/** - * @brief Implements alpha-expansion graph cut algorithm. - * - * For underlying max-flow algorithm, it uses the MAXFLOW software implemented by Boykov & Kolmogorov. - * Also no pre-allocation is made. - */ -class Alpha_expansion_MaxFlow -{ -public: - - typedef MaxFlow::Graph::node_id Vertex_descriptor; - -private: - - MaxFlow::Graph graph; - -public: - - void clear_graph() - { - graph = MaxFlow::Graph(); - } - - Vertex_descriptor add_vertex() - { - return graph.add_node(); - } - - void add_tweight (Vertex_descriptor& v, double w1, double w2) - { - graph.add_tweights(v, w1, w2); - } - - void init_vertices() - { - } - - double max_flow() - { - return graph.maxflow(); - } - - template - void update(VertexLabelMap vertex_label_map, - const std::vector& inserted_vertices, - InputVertexDescriptor vd, - std::size_t vertex_i, - std::size_t alpha) - { - if(get(vertex_label_map, vd) != alpha - && graph.what_segment(inserted_vertices[vertex_i]) == MaxFlow::Graph::SINK) - put(vertex_label_map, vd, alpha); - } - - /** - * Adds two directional edges between @a v1 and @a v2 - * @param v1 first vertex - * @param v2 second vertex - * @param w1 weight for edge from v1 to v2 (v1->v2) - * @param w2 weight for edge from v2 to v1 (v2->v1) - * @param graph to be added - * @return pair of added edges, first: v1->v2 and second: v2->v1 - */ - void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2) - { - graph.add_edge(v1, v2, w1, w2); - } -}; -#endif //CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE -/// @endcond - +// Default version using boost adjacency list template -double alpha_expansion_graph_cut (const InputGraph& input_graph, - Edge_weight_map edge_weight_map, - Vertex_index_map vertex_index_map, - Vertex_label_map vertex_label_map, - Vertex_label_probability_map vertex_label_probability_map, - AlphaExpansionImplementation alpha_expansion = AlphaExpansionImplementation()) + typename EdgeWeightMap, + typename VertexIndexMap, + typename VertexLabelCostMap, + typename VertexLabelMap> +double alpha_expansion_graphcut (const InputGraph& input_graph, + EdgeWeightMap edge_weight_map, + VertexIndexMap vertex_index_map, + VertexLabelCostMap vertex_label_cost_map, + VertexLabelMap vertex_label_map) +{ + return alpha_expansion_graphcut + (input_graph, edge_weight_map, vertex_index_map, vertex_label_cost_map, vertex_label_map, + Alpha_expansion_boost_adjacency_list_tag()); +} + +/// \endcond + +// NOTE: latest performances check (2019-07-22) +// +// Using a random graph with 50000 vertices, 100000 edges and 30 labels: +// +// METHOD TIMING MEMORY +// Boost Adjacency list 49s 122MiB +// Boost CSR 187s 77MiB +// MaxFlow 12s 717MiB + +/** + \ingroup PkgBGLPartition + + Regularizes a partition of a graph into `n` labels using the Alpha + Expansion algorithm \cgalCite{Boykov2001FastApproximate}. + + For a graph \f$(V,E)\f$, this function seeks for the partioning `f` + that minimizes the following energy: + + \f[ + \mathrm{E}(f) = \sum_{\{v0,v1\} \in E} W(v0,v1) + \sum_{v \in V} C(f_v) + \f] + + where \f$W(v0,v1)\f$ is the weight associated to the edge + \f$\{v0,v1\}\f$ and \f$C(f_v)\f$ is the cost of assigning the + vertex \f$v\f$ to the labeling \f$f\f$. + + \tparam InputGraph a model of `Graph` + + \tparam EdgeWeightMap a model of `ReadablePropertyMap` with + `boost::graph_traits::%edge_descriptor` as key and `double` + as value. + + \tparam VertexIndexMap a model of `ReadablePropertyMap` with + `boost::graph_traits::%vertex_descriptor` as key and + `std::size_t` as value. + + \tparam VertexLabelCostMap a model of `ReadablePropertyMap` + with `boost::graph_traits::%vertex_descriptor` as key and + `std::vector` as value. + + \tparam VertexLabelMap a model of `ReadWritePropertyMap` with + `boost::graph_traits::%vertex_descriptor` as key and + `std::size_t` as value. + + \tparam AlphaExpansionImplementationTag optional tag used to select + which implementation of the Alpha Expansion should be + used. Available implementation tags are: + + - `CGAL::Alpha_expansion_boost_adjacency_list` (default) + - `CGAL::Alpha_expansion_boost_compressed_sparse_row_tag` + - `CGAL::Alpha_expansion_MaxFlow_tag` + + \note The `MaxFlow` implementation is provided separately under a + GPL license (whereas the rest of the package including this + function is under LGPL). The header + `` should be + included if users want to use this implementation. + + \param input_graph the input graph. + + \param edge_weight_map a property map providing the weight of each + edge. + + \param vertex_index_map a property map providing the index of each + vertex. + + \param vertex_label_map a property map providing the label of each + vertex. This map will be updated by the algorithm with the + regularized version of the partitioning. + + \param vertex_label_cost_map a property_map providing, for each + vertex, an `std::vector` containing the cost of this vertex to + belong to each label. For example, + `get(vertex_label_cost_map, vd)[label_idx]` returns the cost + of vertex `vd` to belong to the label `label_idx`. +*/ +template +double alpha_expansion_graphcut (const InputGraph& input_graph, + EdgeWeightMap edge_weight_map, + VertexIndexMap vertex_index_map, + VertexLabelCostMap vertex_label_cost_map, + VertexLabelMap vertex_label_map, + const AlphaExpansionImplementationTag&) { typedef boost::graph_traits GT; typedef typename GT::edge_descriptor input_edge_descriptor; typedef typename GT::vertex_descriptor input_vertex_descriptor; - typedef AlphaExpansionImplementation Alpha_expansion; + typedef AlphaExpansionImplementationTag Alpha_expansion; typedef typename Alpha_expansion::Vertex_descriptor Vertex_descriptor; + Alpha_expansion alpha_expansion; + // TODO: check this hardcoded parameter const double tolerance = 1e-10; @@ -580,7 +572,7 @@ double alpha_expansion_graph_cut (const InputGraph& input_graph, std::vector inserted_vertices; inserted_vertices.resize(num_vertices (input_graph)); - std::size_t number_of_labels = get(vertex_label_probability_map, *std::begin(vertices(input_graph))).size(); + std::size_t number_of_labels = get(vertex_label_cost_map, *std::begin(vertices(input_graph))).size(); bool success; do { @@ -602,12 +594,12 @@ double alpha_expansion_graph_cut (const InputGraph& input_graph, std::size_t vertex_i = get(vertex_index_map, vd); Vertex_descriptor new_vertex = alpha_expansion.add_vertex(); inserted_vertices[vertex_i] = new_vertex; - double source_weight = get(vertex_label_probability_map, vd)[alpha]; + double source_weight = get(vertex_label_cost_map, vd)[alpha]; // since it is expansion move, current alpha labeled vertices will be assigned to alpha again, // making sink_weight 'infinity' guarantee this. double sink_weight = (get(vertex_label_map, vd) == alpha ? (std::numeric_limits::max)() - : get(vertex_label_probability_map, vd)[get(vertex_label_map, vd)]); + : get(vertex_label_cost_map, vd)[get(vertex_label_map, vd)]); alpha_expansion.add_tweight(new_vertex, source_weight, sink_weight); } @@ -686,40 +678,41 @@ double alpha_expansion_graph_cut (const InputGraph& input_graph, return min_cut; } -template -double alpha_expansion_graph_cut (const InputGraph& input_graph, - Edge_weight_map edge_weight_map, - Vertex_index_map vertex_index_map, - Vertex_label_map vertex_label_map, - Vertex_label_probability_map vertex_label_probability_map) + +/// \cond SKIP_IN_MANUAL +// Old API +inline double alpha_expansion_graphcut (const std::vector >& edges, + const std::vector& edge_weights, + const std::vector >& cost_matrix, + std::vector& labels) { - return alpha_expansion_graph_cut - (input_graph, edge_weight_map, vertex_index_map, vertex_label_map, vertex_label_probability_map); + internal::Alpha_expansion_old_API_wrapper_graph graph (edges, edge_weights, cost_matrix, labels); + + return alpha_expansion_graphcut(graph, + graph.edge_weight_map(), + graph.vertex_index_map(), + graph.vertex_label_cost_map(), + graph.vertex_label_map()); } -// Old API -inline double alpha_expansion_graph_cut (const std::vector >& edges, - const std::vector& edge_weights, - const std::vector >& probability_matrix, - std::vector& labels) +template +double alpha_expansion_graphcut (const std::vector >& edges, + const std::vector& edge_weights, + const std::vector >& cost_matrix, + std::vector& labels, + const AlphaExpansionImplementationTag&) { - internal::Alpha_expansion_old_API_wrapper_graph graph (edges, edge_weights, probability_matrix, labels); + internal::Alpha_expansion_old_API_wrapper_graph graph (edges, edge_weights, cost_matrix, labels); - return alpha_expansion_graph_cut(graph, - graph.edge_weight_map(), - graph.vertex_index_map(), - graph.vertex_label_map(), - graph.vertex_label_probability_map()); + return alpha_expansion_graphcut(graph, + graph.edge_weight_map(), + graph.vertex_index_map(), + graph.vertex_label_cost_map(), + graph.vertex_label_map(), + AlphaExpansionImplementationTag()); } +/// \endcond + }//namespace CGAL -#endif //CGAL_SURFACE_MESH_SEGMENTATION_ALPHA_EXPANSION_GRAPH_CUT_H +#endif //CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H diff --git a/BGL/include/CGAL/boost/graph/alpha_expansion_graphcut_maxflow_gpl.h b/BGL/include/CGAL/boost/graph/alpha_expansion_graphcut_maxflow_gpl.h new file mode 100644 index 00000000000..c9457e816cf --- /dev/null +++ b/BGL/include/CGAL/boost/graph/alpha_expansion_graphcut_maxflow_gpl.h @@ -0,0 +1,97 @@ +#ifndef CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_MAXFLOW_GPL_H +// Copyright (c) 2014 GeometryFactory Sarl (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0+ +// +// Author(s) : Ilker O. Yaz, Simon Giraudot + +#define CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_MAXFLOW_GPL_H + +#include + +namespace MaxFlow +{ +#include +} + + +namespace CGAL +{ + +/** + * @brief Implements alpha-expansion graph cut algorithm. + * + * For underlying max-flow algorithm, it uses the MAXFLOW software implemented by Boykov & Kolmogorov. + * Also no pre-allocation is made. + */ +class Alpha_expansion_MaxFlow_tag +{ +public: + + typedef MaxFlow::Graph::node_id Vertex_descriptor; + +private: + + MaxFlow::Graph graph; + +public: + + void clear_graph() + { + graph = MaxFlow::Graph(); + } + + Vertex_descriptor add_vertex() + { + return graph.add_node(); + } + + void add_tweight (Vertex_descriptor& v, double w1, double w2) + { + graph.add_tweights(v, w1, w2); + } + + void init_vertices() + { + } + + double max_flow() + { + return graph.maxflow(); + } + + template + void update(VertexLabelMap vertex_label_map, + const std::vector& inserted_vertices, + InputVertexDescriptor vd, + std::size_t vertex_i, + std::size_t alpha) + { + if(get(vertex_label_map, vd) != alpha + && graph.what_segment(inserted_vertices[vertex_i]) == MaxFlow::Graph::SINK) + put(vertex_label_map, vd, alpha); + } + + void add_edge (Vertex_descriptor& v1, Vertex_descriptor& v2, double w1, double w2) + { + graph.add_edge(v1, v2, w1, w2); + } +}; + +}//namespace CGAL + +#endif //CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_MAXFLOW_GPL_H