├── data ├── 3063.jpg └── 35049.jpg ├── screenshot.png ├── .travis.yml ├── CMakeLists.txt ├── lib ├── CMakeLists.txt ├── graph_segmentation.cpp ├── image_graph.h └── graph_segmentation.h ├── cli ├── CMakeLists.txt └── main.cpp └── README.md /data/3063.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidstutz/graph-based-image-segmentation/HEAD/data/3063.jpg -------------------------------------------------------------------------------- /data/35049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidstutz/graph-based-image-segmentation/HEAD/data/35049.jpg -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidstutz/graph-based-image-segmentation/HEAD/screenshot.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | compiler: 4 | - gcc 5 | 6 | before_script: 7 | - sudo apt-get install build-essential 8 | - sudo apt-get install cmake 9 | - sudo apt-get install libboost-all-dev 10 | - sudo apt-get install libopencv-dev 11 | - mkdir build 12 | - cd build 13 | - cmake .. 14 | 15 | script: make -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016, David Stutz 3 | # Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without modification, 7 | # are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyright holder nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | cmake_minimum_required(VERSION 2.8) 32 | project(refh) 33 | 34 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) 35 | set(CMAKE_CXX_FLAGS "-O4") 36 | 37 | add_subdirectory(lib) 38 | add_subdirectory(cli) -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016, David Stutz 3 | # Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without modification, 7 | # are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyright holder nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | cmake_minimum_required(VERSION 2.8) 32 | project(refh) 33 | 34 | find_package(OpenCV REQUIRED) 35 | 36 | include_directories(${OpenCV_INCLUDE_DIRS}) 37 | add_library(refh graph_segmentation.cpp) 38 | target_link_libraries(refh ${OpenCV_LIBS}) -------------------------------------------------------------------------------- /cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016, David Stutz 3 | # Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without modification, 7 | # are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # 3. Neither the name of the copyright holder nor the names of its contributors 17 | # may be used to endorse or promote products derived from this software 18 | # without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | # 31 | cmake_minimum_required(VERSION 2.8) 32 | project(refh) 33 | 34 | find_package(OpenCV REQUIRED) 35 | find_package(Boost COMPONENTS system filesystem program_options REQUIRED) 36 | 37 | include_directories(../lib/ ${OpenCV_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) 38 | add_executable(refh_cli main.cpp) 39 | target_link_libraries(refh_cli refh ${OpenCV_LIBS} ${Boost_LIBRARIES}) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graph Based Image Segmentation 2 | 3 | [![Build Status](https://travis-ci.org/davidstutz/graph-based-image-segmentation.svg?branch=master)](https://travis-ci.org/davidstutz/graph-based-image-segmentation) 4 | 5 | **Update:** This implementation is also part of [davidstutz/superpixel-benchmark](https://github.com/davidstutz/superpixel-benchmark). 6 | 7 | This repository contains an implementation of the graph-based image segmentation algorithms described in [1] focussing on generating oversegmentations, also referred to as superpixels. 8 | 9 | [1] P. F. Felzenswalb and D. P. Huttenlocher. 10 | Efficient Graph-Based Image Segmentation. 11 | International Journal of Computer Vision, volume 59, number 2, 2004. 12 | 13 | The implementation was used in [2] for evaluation. 14 | 15 | [2] D. Stutz, A. Hermans, B. Leibe. 16 | Superpixels: An Evaluation of the State-of-the-Art. 17 | Computer Vision and Image Understanding, 2018. 18 | 19 | ![Example: several oversegmentations.](screenshot.png?raw=true "Example: several oversegmentations.") 20 | 21 | ## Building 22 | 23 | The implementation is based on [CMake](https://cmake.org/), [OpenCV](http://opencv.org/) and [Boost](http://www.boost.org/). The following steps have been tested on Ubuntu 12.04: 24 | 25 | $ sudo apt-get install build-essential 26 | $ sudo apt-get install cmake 27 | $ sudo apt-get install libboost-all-dev 28 | 29 | OpenCV can either be installed following [these instructions](http://docs.opencv.org/2.4/doc/tutorials/introduction/linux_install/linux_install.html#linux-installation), or using: 30 | 31 | $ sudo apt-get install libopencv-dev 32 | 33 | With all requirements installed, run: 34 | 35 | $ mkdir build 36 | $ cd build 37 | $ cmake .. 38 | $ make 39 | 40 | ## Usage 41 | 42 | The provided tool can easily be used as follows (from within the `build` directory): 43 | 44 | # Show a help message. 45 | $ ../bin/refh_cli --help 46 | Allowed options: 47 | -h [ --help ] produce help message 48 | --input arg folder containing the images to process 49 | --threshold arg (=20) constant for threshold function 50 | --minimum-size arg (=10) minimum component size 51 | --output arg (=output) save segmentation as CSV file and contour images 52 | # Oversegment the provided examples: 53 | $ ../bin/refh_cli ../data/ ../output --threshold 255 54 | 55 | The latter command will create the `output` directory containing the oversegmentations as `.csv` files and visualizations as `.png` files. 56 | 57 | ## License 58 | 59 | Licenses for source code corresponding to: 60 | 61 | D. Stutz, A. Hermans, B. Leibe. **Superpixels: An Evaluation of the State-of-the-Art.** Computer Vision and Image Understanding, 2018. 62 | 63 | Note that the two provided images are taken from the [BSDS500](https://www2.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/resources.html). 64 | 65 | Copyright (c) 2014-2018 David Stutz, RWTH Aachen University 66 | 67 | **Please read carefully the following terms and conditions and any accompanying documentation before you download and/or use this software and associated documentation files (the "Software").** 68 | 69 | The authors hereby grant you a non-exclusive, non-transferable, free of charge right to copy, modify, merge, publish, distribute, and sublicense the Software for the sole purpose of performing non-commercial scientific research, non-commercial education, or non-commercial artistic projects. 70 | 71 | Any other use, in particular any use for commercial purposes, is prohibited. This includes, without limitation, incorporation in a commercial product, use in a commercial service, or production of other artefacts for commercial purposes. 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 74 | 75 | You understand and agree that the authors are under no obligation to provide either maintenance services, update services, notices of latent defects, or corrections of defects with regard to the Software. The authors nevertheless reserve the right to update, modify, or discontinue the Software at any time. 76 | 77 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. You agree to cite the corresponding papers (see above) in documents and papers that report on research using the Software. 78 | -------------------------------------------------------------------------------- /lib/graph_segmentation.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, David Stutz 3 | * Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, 7 | * are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * 3. Neither the name of the copyright holder nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #include "graph_segmentation.h" 33 | #include 34 | 35 | void GraphSegmentation::buildGraph(const cv::Mat &image) { 36 | 37 | H = image.rows; 38 | W = image.cols; 39 | 40 | int N = H*W; 41 | graph = ImageGraph(N); 42 | 43 | for (int i = 0; i < H; i++) { 44 | for (int j = 0; j < W; j++) { 45 | 46 | int n = W*i + j; 47 | ImageNode & node = graph.getNode(n); 48 | 49 | cv::Vec3b bgr = image.at(i, j); 50 | node.b = bgr[0]; 51 | node.g = bgr[1]; 52 | node.r = bgr[2]; 53 | 54 | // Initialize label. 55 | node.l = n; 56 | node.id = n; 57 | node.n = 1; 58 | } 59 | } 60 | 61 | for (int i = 0; i < H; i++) { 62 | for (int j = 0; j < W; j++) { 63 | int n = W*i + j; 64 | const ImageNode & node = graph.getNode(n); 65 | 66 | if (i < H - 1) { 67 | int m = W*(i + 1) + j; 68 | ImageNode & other = graph.getNode(m); 69 | 70 | ImageEdge edge; 71 | edge.n = n; 72 | edge.m = m; 73 | edge.w = (*distance)(node, other); 74 | 75 | graph.addEdge(edge); 76 | } 77 | 78 | if (j < W - 1) { 79 | int m = W*i + (j + 1); 80 | ImageNode & other = graph.getNode(m); 81 | 82 | ImageEdge edge; 83 | edge.n = n; 84 | edge.m = m; 85 | edge.w = (*distance)(node, other); 86 | 87 | graph.addEdge(edge); 88 | } 89 | } 90 | } 91 | } 92 | 93 | void GraphSegmentation::oversegmentGraph() { 94 | 95 | // Sort edges. 96 | graph.sortEdges(); 97 | 98 | for (int e = 0; e < graph.getNumEdges(); e++) { 99 | ImageEdge edge = graph.getEdge(e%graph.getNumEdges()); 100 | 101 | ImageNode & n = graph.getNode(edge.n); 102 | ImageNode & m = graph.getNode(edge.m); 103 | 104 | ImageNode & S_n = graph.findNodeComponent(n); 105 | ImageNode & S_m = graph.findNodeComponent(m); 106 | 107 | // Are the nodes in different components? 108 | if (S_m.id != S_n.id) { 109 | 110 | // Here comes the magic! 111 | if ((*magic)(S_n, S_m, edge)) { 112 | graph.merge(S_n, S_m, edge); 113 | } 114 | } 115 | } 116 | } 117 | 118 | void GraphSegmentation::enforceMinimumSegmentSize(int M) { 119 | assert(graph.getNumNodes() > 0); 120 | // assert(graph.getNumEdges() > 0); 121 | 122 | for (int e = 0; e < graph.getNumEdges(); e++) { 123 | ImageEdge edge = graph.getEdge(e); 124 | 125 | ImageNode & n = graph.getNode(edge.n); 126 | ImageNode & m = graph.getNode(edge.m); 127 | 128 | ImageNode & S_n = graph.findNodeComponent(n); 129 | ImageNode & S_m = graph.findNodeComponent(m); 130 | 131 | if (S_n.l != S_m.l) { 132 | if (S_n.n < M || S_m.n < M) { 133 | graph.merge(S_n, S_m, edge); 134 | } 135 | } 136 | } 137 | } 138 | 139 | cv::Mat GraphSegmentation::deriveLabels() { 140 | 141 | cv::Mat labels(H, W, CV_32SC1, cv::Scalar(0)); 142 | for (int i = 0; i < H; i++) { 143 | for (int j = 0; j < W; j++) { 144 | int n = W*i + j; 145 | 146 | ImageNode & node = graph.getNode(n); 147 | ImageNode & S_node = graph.findNodeComponent(node); 148 | 149 | const int max = std::numeric_limits::max(); 150 | 151 | labels.at(i, j) = S_node.id; 152 | } 153 | } 154 | 155 | return labels; 156 | } -------------------------------------------------------------------------------- /lib/image_graph.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, David Stutz 3 | * Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, 7 | * are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * 3. Neither the name of the copyright holder nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef IMAGE_GRAPH_H 33 | #define IMAGE_GRAPH_H 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /** \brief Represents an edge between two pixels in an image. 41 | * Each edge is characterized by a weight and the adjacent nodes. 42 | * \author David Stutz 43 | */ 44 | class ImageEdge { 45 | public: 46 | /** \brief Default constructor. 47 | */ 48 | ImageEdge() : n(0), m(0), w(0) {}; 49 | 50 | /** \brief Index of first node. */ 51 | unsigned long int n; 52 | 53 | /** \brief Index of second node. */ 54 | unsigned long int m; 55 | 56 | /** \brief Edge weight. */ 57 | float w; 58 | 59 | }; 60 | 61 | /** \brief Class for sorting edges according to weight. 62 | * \author David Stutz 63 | */ 64 | class ImageEdgeSorter { 65 | public: 66 | /** \brief Compare to edges according to their weights. 67 | * \param[in] g first edge 68 | * \param[in] h second edge 69 | * \return true if h.w greater than h.w 70 | */ 71 | inline bool operator()(const ImageEdge & g, const ImageEdge h) { 72 | return (h.w > g.w); 73 | } 74 | }; 75 | 76 | /** \brief Represents a pixel in a video. Each pixel is represented by its 77 | * color which is needed to compute the weights between pixels. 78 | * \author David Stutz 79 | */ 80 | class ImageNode { 81 | public: 82 | /** \brief Default constructor. 83 | */ 84 | ImageNode() : b(0), g(0), r(0), l(0), n(1), id(0), max_w(0) { 85 | 86 | }; 87 | 88 | /** \brief Blue channel. */ 89 | unsigned char b; 90 | 91 | /** \brief Green channel. */ 92 | unsigned char g; 93 | 94 | /** \brief Red channel. */ 95 | unsigned char r; 96 | 97 | /** \brief The label of the pixel. */ 98 | unsigned long int l; // label, i.e. the index of the node this node belongs to 99 | 100 | /** \brief Size of node after merging with other nodes. */ 101 | unsigned long int n; 102 | 103 | /** \brief Id of the node. */ 104 | unsigned long int id; 105 | 106 | /** \brief Maximum weight. */ 107 | float max_w; 108 | 109 | }; 110 | 111 | /** \brief Represents an image graph, consisting of one node per pixel which are 112 | * 4-connected. 113 | * \author David Stutz 114 | */ 115 | class ImageGraph { 116 | public: 117 | /** \brief Default constructor. 118 | */ 119 | ImageGraph() { 120 | K = 0; 121 | }; 122 | 123 | /** \brief Constructs an image graph with the given exact number of nodes. 124 | * \param[in] N number of nodes to allocate 125 | */ 126 | ImageGraph(int N) { 127 | nodes = std::vector(N); 128 | K = N; 129 | } 130 | 131 | /** \brief Assignment operator. 132 | * \param[in] graph graph to copy 133 | */ 134 | void operator=(const ImageGraph & graph) { 135 | nodes = graph.nodes; 136 | edges = graph.edges; 137 | K = graph.K; 138 | } 139 | 140 | /** \brief Set the node of the given index. 141 | * \param[in] n index of node 142 | * \param[in] node 143 | */ 144 | void setNode(int n, ImageNode & node) { 145 | nodes[n] = node; 146 | } 147 | 148 | /** \brief Add a new node. 149 | * \param[in] node 150 | */ 151 | void addNode(ImageNode & node) { 152 | nodes.push_back(node); 153 | K++; 154 | } 155 | 156 | /** \brief Add a new edge. 157 | * \param[in] edge 158 | */ 159 | void addEdge(ImageEdge & edge) { 160 | edges.push_back(edge); 161 | } 162 | 163 | /** \param[in] Get the n-th node. 164 | * \param[in] n node index 165 | * \return node at index n 166 | */ 167 | ImageNode & getNode(int n) { 168 | assert(n >= 0 && n < static_cast(nodes.size())); 169 | return nodes[n]; 170 | } 171 | 172 | /** \brief Get the e-th edge in the current sorting. 173 | * \param[in] e edge index 174 | */ 175 | ImageEdge & getEdge(int e) { 176 | assert(e >= 0 && e < static_cast(edges.size())); 177 | return edges[e]; 178 | } 179 | 180 | /** \brief Get the number of nodes. 181 | * \return number of nodes 182 | */ 183 | int getNumNodes() { 184 | return nodes.size(); 185 | } 186 | 187 | /** \brief Get the number of edges. 188 | * \return number of edges 189 | */ 190 | int getNumEdges() { 191 | return edges.size(); 192 | } 193 | /** \brief Get number of connected components. 194 | * \return 195 | */ 196 | int getNumComponents() { 197 | return K; 198 | } 199 | 200 | /** \brief Sort the edges by weight. 201 | */ 202 | void sortEdges() { 203 | std::sort(edges.begin(), edges.end(), ImageEdgeSorter()); 204 | } 205 | 206 | /** \brief When two nodes get merged, the first node is assigned the id of the second 207 | * node as label. By traversing this labeling, the current component of each 208 | * node (that is, pixel) can easily be identified and the label can be updated 209 | * for efficiency. 210 | * \param[in] node node to find component for 211 | * \return node representing found component 212 | */ 213 | ImageNode & findNodeComponent(ImageNode & n) { 214 | 215 | // Get component of node n. 216 | int l = n.l; 217 | int id = n.id; 218 | 219 | while (l != id) { 220 | id = nodes[l].id; 221 | l = nodes[l].l; 222 | } 223 | 224 | ImageNode & S = nodes[l]; 225 | assert(S.l == S.id); 226 | 227 | // Save latest component. 228 | n.l = S.id; 229 | 230 | return S; 231 | } 232 | 233 | /** \brief Merge two pixels (that is merge two nodes). 234 | * 235 | * Depending on the used "Distance", some lines may be commented out 236 | * to speed up the algorithm. 237 | * 238 | * \param[in] S_n first node 239 | * \param[in] S_m second node 240 | * \param[in] e corresponding edge 241 | */ 242 | void merge(ImageNode & S_n, ImageNode & S_m, ImageEdge & e) { 243 | S_m.l = S_n.id; 244 | 245 | // Update cound. 246 | S_n.n += S_m.n; 247 | 248 | // Update maximum weight. 249 | S_n.max_w = std::max(std::max(S_n.max_w, S_m.max_w), e.w); 250 | 251 | // Update component count. 252 | K--; 253 | } 254 | 255 | private: 256 | 257 | /** \brief Number of components. */ 258 | int K; 259 | 260 | /** \brief All edges in this graph. */ 261 | std::vector edges; 262 | 263 | /** \brief All nodes in this graph. */ 264 | std::vector nodes; 265 | 266 | }; 267 | 268 | #endif /* IMAGE_GRAPH_H */ 269 | 270 | -------------------------------------------------------------------------------- /lib/graph_segmentation.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, David Stutz 3 | * Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, 7 | * are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * 3. Neither the name of the copyright holder nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef GRAPH_SEGMENTATION_H 33 | #define GRAPH_SEGMENTATION_H 34 | 35 | #include 36 | #include "image_graph.h" 37 | 38 | #define RAND() ((float) std::rand() / (RAND_MAX)) 39 | 40 | /** \brief Interface to be implemented by a concerete distance. The distance defines 41 | * how the weights between nodes in the image graph are computed. See the paper 42 | * by Felzenswalb and Huttenlocher for details. Essentially, derived classes 43 | * only need to overwrite the () operator. 44 | * \author David Stutz 45 | */ 46 | class GraphSegmentationDistance { 47 | public: 48 | /** \brief Constructor. 49 | */ 50 | GraphSegmentationDistance() {}; 51 | 52 | /** \brief Destructor. 53 | */ 54 | virtual ~GraphSegmentationDistance() {}; 55 | 56 | /** \brief Compute the distance given 2 nodes. 57 | * \param[in] n first node 58 | * \param[in] m second node 59 | */ 60 | virtual float operator()(const ImageNode & n, const ImageNode & m) = 0; 61 | 62 | }; 63 | 64 | /** \brief Manhatten (i.e. L1) distance. 65 | * \author David Stutz 66 | */ 67 | class GraphSegmentationManhattenRGB : public GraphSegmentationDistance { 68 | public: 69 | /** \brief Constructor; sets normalization constant. 70 | */ 71 | GraphSegmentationManhattenRGB() { 72 | // Normalization. 73 | D = 255 + 255 + 255; 74 | } 75 | 76 | /** \brief Compute the distance given 2 nodes. 77 | * \param[in] n first node 78 | * \param[in] m second node 79 | */ 80 | virtual float operator()(const ImageNode & n, const ImageNode & m) { 81 | float dr = std::abs(n.r - m.r); 82 | float dg = std::abs(n.g - m.g); 83 | float db = std::abs(n.b - m.b); 84 | 85 | return (dr + dg + db); 86 | } 87 | 88 | private: 89 | 90 | /** \brief Normalization term. */ 91 | float D; 92 | 93 | }; 94 | 95 | /** \brief Euclidean RGB distance. 96 | * \author David Stutz 97 | */ 98 | class GraphSegmentationEuclideanRGB : public GraphSegmentationDistance { 99 | public: 100 | /** \brief Constructor; sets normalization constant. 101 | */ 102 | GraphSegmentationEuclideanRGB() { 103 | // Normalization. 104 | D = std::sqrt(255*255 + 255*255 + 255*255); 105 | } 106 | 107 | /** \brief Compute the distance given 2 nodes. 108 | * \param[in] n first node 109 | * \param[in] m second node 110 | */ 111 | virtual float operator()(const ImageNode & n, const ImageNode & m) { 112 | float dr = n.r - m.r; 113 | float dg = n.g - m.g; 114 | float db = n.b - m.b; 115 | 116 | return std::sqrt(dr*dr + dg*dg + db*db); 117 | } 118 | 119 | private: 120 | 121 | /** \brief Normalization term. */ 122 | float D; 123 | 124 | }; 125 | 126 | /** \brief The magic part of the graph segmentation, i.e. s given two nodes decide 127 | * whether to add an edge between them (i.e. merge the corresponding segments). 128 | * See the paper by Felzenswalb and Huttenlocher for details. 129 | * \author David Stutz 130 | */ 131 | class GraphSegmentationMagic { 132 | public: 133 | /** \brief Constructor. 134 | */ 135 | GraphSegmentationMagic() {}; 136 | 137 | /** \brief Decide whether to merge the two segments corresponding to the 138 | * given nodes or not. 139 | * \param[in] S_n node representing the first segment 140 | * \param[in] S_m node representing the second segment 141 | * \param[in] e the edge between the two segments 142 | * \rturn true if merge 143 | */ 144 | virtual bool operator()(const ImageNode & S_n, const ImageNode & S_m, 145 | const ImageEdge & e) = 0; 146 | 147 | }; 148 | 149 | /** 150 | * The original criterion employed by [2]. 151 | */ 152 | class GraphSegmentationMagicThreshold : public GraphSegmentationMagic { 153 | public: 154 | /** \brief Constructor; sets the threshold. 155 | * \param[in] c the threshold to use 156 | */ 157 | GraphSegmentationMagicThreshold(float c) : c(c) {}; 158 | 159 | /** \brief Decide whether to merge the two segments corresponding to the 160 | * given nodes or not. 161 | * \param[in] S_n node representing the first segment 162 | * \param[in] S_m node representing the second segment 163 | * \param[in] e the edge between the two segments 164 | * \rturn true if merge 165 | */ 166 | virtual bool operator()(const ImageNode & S_n, const ImageNode & S_m, 167 | const ImageEdge & e) { 168 | 169 | float threshold = std::min(S_n.max_w + c/S_n.n, S_m.max_w + c/S_m.n); 170 | 171 | if (e.w < threshold) { 172 | return true; 173 | } 174 | 175 | return false; 176 | } 177 | 178 | private: 179 | 180 | /** \brief T hreshold. */ 181 | float c; 182 | 183 | }; 184 | 185 | /** \brief Implementation of graph based image segmentation as described in the 186 | * paper by Felzenswalb and Huttenlocher. 187 | * \author David Stutz 188 | */ 189 | class GraphSegmentation { 190 | public: 191 | /** \brief Default constructor; uses the Manhatten distance. 192 | */ 193 | GraphSegmentation() : distance(new GraphSegmentationManhattenRGB()), 194 | magic(new GraphSegmentationMagicThreshold(1)) { 195 | 196 | }; 197 | 198 | /** \brief Destructor. 199 | */ 200 | virtual ~GraphSegmentation() {}; 201 | 202 | /** \brief Set the distance to use. 203 | * \param[in] _distance pointer to a GraphSegmentationDistance to use 204 | */ 205 | void setDistance(GraphSegmentationDistance* _distance) { 206 | distance = _distance; 207 | } 208 | 209 | /** \brief Set the magic part of graph segmentation. 210 | * \param[in] _magix pointer to a GraphSegmentationMagic to use 211 | */ 212 | void setMagic(GraphSegmentationMagic* _magic) { 213 | magic = _magic; 214 | } 215 | 216 | /** \brief Build the graph nased on the image, i.e. compute the weights 217 | * between pixels using the underlying distance. 218 | * \param[in] image image to oversegment 219 | */ 220 | void buildGraph(const cv::Mat &image); 221 | 222 | /** \brief Oversegment the given graph. 223 | */ 224 | void oversegmentGraph(); 225 | 226 | /** \brief Enforces the given minimum segment size. 227 | * \pram[in] M minimum segment size in pixels 228 | */ 229 | void enforceMinimumSegmentSize(int M); 230 | 231 | /** \brief Derive labels from the produced oversegmentation. 232 | * \return labels as integer matrix 233 | */ 234 | cv::Mat deriveLabels(); 235 | 236 | protected: 237 | 238 | /** \brief Image height. */ 239 | int H; 240 | 241 | /** \brief Image widt.h */ 242 | int W; 243 | 244 | /** \brief The constructed and segmented image graph. */ 245 | ImageGraph graph; 246 | 247 | /** \brief The underlying distance to use. */ 248 | GraphSegmentationDistance* distance; 249 | 250 | /** \brief The magic part of graph segmentation. */ 251 | GraphSegmentationMagic* magic; 252 | 253 | }; 254 | 255 | #endif /* GRAPH_SEGMENTATION_H */ 256 | 257 | -------------------------------------------------------------------------------- /cli/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016, David Stutz 3 | * Contact: david.stutz@rwth-aachen.de, davidstutz.de 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without modification, 7 | * are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * 3. Neither the name of the copyright holder nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "graph_segmentation.h" 38 | 39 | /** \brief Read all image files (.png and .jpg) in the given directory. 40 | * \param[in] directory directory to read 41 | * \param[out] files found files 42 | */ 43 | void readDirectory(boost::filesystem::path directory, 44 | std::multimap &files) { 45 | 46 | assert(boost::filesystem::is_directory(directory)); 47 | 48 | files.clear(); 49 | boost::filesystem::directory_iterator end; 50 | 51 | for (boost::filesystem::directory_iterator it(directory); it != end; ++it) { 52 | std::string extension = it->path().extension().string(); 53 | if (extension == ".png" || extension == ".jpg" 54 | || extension == ".PNG" || extension == ".JPG") { 55 | files.insert(std::multimap::value_type(it->path().string(), it->path())); 56 | } 57 | } 58 | } 59 | 60 | /** \brief Write the given matrix as CSV file. 61 | * \param[in] file path to file to write 62 | * \param[in] mat matrix to write, expected to be integer matrix 63 | */ 64 | void writeMatCSV(boost::filesystem::path file, const cv::Mat& mat) { 65 | 66 | assert(!mat.empty()); 67 | assert(mat.channels() == 1); 68 | 69 | std::ofstream file_stream(file.c_str()); 70 | for (int i = 0; i < mat.rows; i++) { 71 | for (int j = 0; j < mat.cols; j++) { 72 | file_stream << mat.at(i, j); 73 | 74 | if (j < mat.cols - 1) { 75 | file_stream << ","; 76 | } 77 | } 78 | 79 | if (i < mat.rows - 1) { 80 | file_stream << "\n"; 81 | } 82 | } 83 | 84 | file_stream.close(); 85 | } 86 | 87 | /** \brief Check if the given pixel is a boundary pixel in the given 88 | * segmentation. 89 | * \param[in] labels segments as integer image 90 | * \param[in] i y coordinate 91 | * \param[in] j x coordinate 92 | * \return true if boundary pixel, false otherwise 93 | */ 94 | bool is4ConnectedBoundaryPixel(const cv::Mat &labels, int i, int j) { 95 | 96 | if (i > 0) { 97 | if (labels.at(i, j) != labels.at(i - 1, j)) { 98 | return true; 99 | } 100 | } 101 | 102 | if (i < labels.rows - 1) { 103 | if (labels.at(i, j) != labels.at(i + 1, j)) { 104 | return true; 105 | } 106 | } 107 | 108 | if (j > 0) { 109 | if (labels.at(i, j) != labels.at(i, j - 1)) { 110 | return true; 111 | } 112 | } 113 | 114 | if (j < labels.cols - 1) { 115 | if (labels.at(i, j) != labels.at(i, j + 1)) { 116 | return true; 117 | } 118 | } 119 | 120 | return false; 121 | } 122 | 123 | /** \brief Draw the segments as contours in the image. 124 | * \param[in] image image to draw contours in (color image expected) 125 | * \param[in] labels segments to draw as integer image 126 | * \param[out] contours image with segments indicated by contours 127 | */ 128 | void drawContours(const cv::Mat &image, const cv::Mat &labels, cv::Mat &contours) { 129 | 130 | assert(!image.empty()); 131 | assert(image.channels() == 3); 132 | assert(image.rows == labels.rows && image.cols == labels.cols); 133 | assert(labels.type() == CV_32SC1); 134 | 135 | contours.create(image.rows, image.cols, CV_8UC3); 136 | cv::Vec3b color(0, 0, 0); // Black contours 137 | 138 | for (int i = 0; i < contours.rows; ++i) { 139 | for (int j = 0; j < contours.cols; ++j) { 140 | if (is4ConnectedBoundaryPixel(labels, i, j)) { 141 | 142 | contours.at(i, j) = color; 143 | } 144 | else { 145 | contours.at(i, j) = image.at(i, j); 146 | } 147 | } 148 | } 149 | } 150 | 151 | /** \brief Example of running graph based image segmentation for oversegmentation on 152 | * a directory possibly containing multiple images. Segmentations are written as CSV 153 | * and visualizations to the provided output directory. 154 | * 155 | * Usage: 156 | * 157 | * \author David Stutz 158 | */ 159 | int main (int argc, char ** argv) { 160 | 161 | boost::program_options::options_description desc("Allowed options"); 162 | desc.add_options() 163 | ("help,h", "produce help message") 164 | ("input", boost::program_options::value(), "folder containing the images to process") 165 | ("threshold", boost::program_options::value()->default_value(20.0f), "constant for threshold function") 166 | ("minimum-size", boost::program_options::value()->default_value(10), "minimum component size") 167 | ("output", boost::program_options::value()->default_value("output"), "save segmentation as CSV file and contour images"); 168 | 169 | boost::program_options::positional_options_description positionals; 170 | positionals.add("input", 1); 171 | positionals.add("output", 1); 172 | 173 | boost::program_options::variables_map parameters; 174 | boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(desc).positional(positionals).run(), parameters); 175 | boost::program_options::notify(parameters); 176 | 177 | if (parameters.find("help") != parameters.end()) { 178 | std::cout << desc << std::endl; 179 | return 1; 180 | } 181 | 182 | boost::filesystem::path output_dir(parameters["output"].as()); 183 | if (!output_dir.empty()) { 184 | if (!boost::filesystem::is_directory(output_dir)) { 185 | boost::filesystem::create_directories(output_dir); 186 | } 187 | } 188 | 189 | boost::filesystem::path input_dir(parameters["input"].as()); 190 | if (!boost::filesystem::is_directory(input_dir)) { 191 | std::cout << "Image directory not found ..." << std::endl; 192 | return 1; 193 | } 194 | 195 | float threshold = parameters["threshold"].as(); 196 | int minimum_segment_size = parameters["minimum-size"].as(); 197 | 198 | std::multimap images; 199 | readDirectory(input_dir, images); 200 | 201 | for (std::multimap::iterator it = images.begin(); 202 | it != images.end(); ++it) { 203 | 204 | cv::Mat image = cv::imread(it->first); 205 | 206 | GraphSegmentationMagicThreshold magic(threshold); 207 | GraphSegmentationEuclideanRGB distance; 208 | 209 | GraphSegmentation segmenter; 210 | segmenter.setMagic(&magic); 211 | segmenter.setDistance(&distance); 212 | 213 | segmenter.buildGraph(image); 214 | segmenter.oversegmentGraph(); 215 | segmenter.enforceMinimumSegmentSize(minimum_segment_size); 216 | 217 | cv::Mat labels = segmenter.deriveLabels(); 218 | 219 | boost::filesystem::path csv_file(output_dir 220 | / boost::filesystem::path(it->second.stem().string() + ".csv")); 221 | writeMatCSV(csv_file, labels); 222 | 223 | boost::filesystem::path contours_file(output_dir 224 | / boost::filesystem::path(it->second.stem().string() + ".png")); 225 | cv::Mat image_contours; 226 | drawContours(image, labels, image_contours); 227 | cv::imwrite(contours_file.string(), image_contours); 228 | } 229 | 230 | return 0; 231 | } 232 | --------------------------------------------------------------------------------