Finalize API of alpha expansion + doc

This commit is contained in:
Simon Giraudot 2019-07-23 08:39:16 +02:00
parent f0df168096
commit 497c121e55
6 changed files with 305 additions and 172 deletions

View File

@ -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 */

View File

@ -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} \

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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 <CGAL/license/Surface_mesh_segmentation.h>
/// @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 <CGAL/Iterator_range.h>
#include <CGAL/assertions.h>
#ifdef CGAL_SEGMENTATION_BENCH_GRAPHCUT
#include <CGAL/Timer.h>
@ -51,13 +32,6 @@
#include <boost/version.hpp>
#ifndef CGAL_DO_NOT_USE_BOYKOV_KOLMOGOROV_MAXFLOW_SOFTWARE
namespace MaxFlow
{
#include <CGAL/internal/auxiliary/graph.h>
}
#endif
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/compressed_sparse_row_graph.hpp>
@ -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<std::size_t> Vertex_index_map;
typedef CGAL::Pointer_property_map<std::size_t>::type Vertex_label_map;
struct Vertex_label_probability_map
struct Vertex_label_cost_map
{
typedef std::size_t key_type;
typedef std::vector<double> value_type;
typedef value_type reference;
typedef boost::readable_property_map_tag category;
const std::vector<std::vector<double> >* probability_matrix;
const std::vector<std::vector<double> >* cost_matrix;
Vertex_label_probability_map (const std::vector<std::vector<double> >* probability_matrix)
: probability_matrix (probability_matrix)
Vertex_label_cost_map (const std::vector<std::vector<double> >* 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<double> 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<std::pair<std::size_t, std::size_t> >& edges;
const std::vector<double>& edge_weights;
const std::vector<std::vector<double> >& probability_matrix;
const std::vector<std::vector<double> >& cost_matrix;
std::vector<std::size_t>& labels;
Alpha_expansion_old_API_wrapper_graph (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
const std::vector<double>& edge_weights,
const std::vector<std::vector<double> >& probability_matrix,
const std::vector<std::vector<double> >& cost_matrix,
std::vector<std::size_t>& 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<boost::vecS, boost::listS, boost::directedS>
@ -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 <typename VertexLabelMap, typename InputVertexDescriptor>
void update(VertexLabelMap vertex_label_map,
const std::vector<Vertex_descriptor>& 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 <typename InputGraph,
typename Edge_weight_map,
typename Vertex_index_map,
typename Vertex_label_map,
typename Vertex_label_probability_map,
typename AlphaExpansionImplementation>
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<Graph>::%edge_descriptor` as key and `double`
as value.
\tparam VertexIndexMap a model of `ReadablePropertyMap` with
`boost::graph_traits<Graph>::%vertex_descriptor` as key and
`std::size_t` as value.
\tparam VertexLabelCostMap a model of `ReadablePropertyMap`
with `boost::graph_traits<Graph>::%vertex_descriptor` as key and
`std::vector<double>` as value.
\tparam VertexLabelMap a model of `ReadWritePropertyMap` with
`boost::graph_traits<Graph>::%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
`<CGAL/boost/graph/alpha_shape_graphcut_maxflow_gpl.h>` 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 <typename InputGraph,
typename EdgeWeightMap,
typename VertexIndexMap,
typename VertexLabelCostMap,
typename VertexLabelMap,
typename AlphaExpansionImplementationTag>
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<InputGraph> 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<Vertex_descriptor> 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<double>::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 <typename InputGraph,
typename Edge_weight_map,
typename Vertex_index_map,
typename Vertex_label_map,
typename Vertex_label_probability_map>
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)
{
return alpha_expansion_graph_cut<InputGraph,
Edge_weight_map,
Vertex_index_map,
Vertex_label_map,
Vertex_label_probability_map,
Alpha_expansion_boost_adjacency_list>
(input_graph, edge_weight_map, vertex_index_map, vertex_label_map, vertex_label_probability_map);
}
/// \cond SKIP_IN_MANUAL
// Old API
inline double alpha_expansion_graph_cut (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
inline double alpha_expansion_graphcut (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
const std::vector<double>& edge_weights,
const std::vector<std::vector<double> >& probability_matrix,
const std::vector<std::vector<double> >& cost_matrix,
std::vector<std::size_t>& labels)
{
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,
return alpha_expansion_graphcut(graph,
graph.edge_weight_map(),
graph.vertex_index_map(),
graph.vertex_label_map(),
graph.vertex_label_probability_map());
graph.vertex_label_cost_map(),
graph.vertex_label_map());
}
template <typename AlphaExpansionImplementationTag>
double alpha_expansion_graphcut (const std::vector<std::pair<std::size_t, std::size_t> >& edges,
const std::vector<double>& edge_weights,
const std::vector<std::vector<double> >& cost_matrix,
std::vector<std::size_t>& labels,
const AlphaExpansionImplementationTag&)
{
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(),
AlphaExpansionImplementationTag());
}
/// \endcond
}//namespace CGAL
#endif //CGAL_SURFACE_MESH_SEGMENTATION_ALPHA_EXPANSION_GRAPH_CUT_H
#endif //CGAL_BOOST_GRAPH_ALPHA_EXPANSION_GRAPHCUT_H

View File

@ -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 <CGAL/boost/graph/alpha_expansion_graphcut.h>
namespace MaxFlow
{
#include <CGAL/internal/auxiliary/graph.h>
}
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 <typename VertexLabelMap, typename InputVertexDescriptor>
void update(VertexLabelMap vertex_label_map,
const std::vector<Vertex_descriptor>& 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