-
Notifications
You must be signed in to change notification settings - Fork 0
Get started example
To get started with the Pomar library, we are going to develop an application to remove salt and pepper noise. This program is based on http://adessowiki.fee.unicamp.br/adesso/wiki/iamxt/simple_filtering_ex/view/ and the solution is also very similar.
By using Pomar, the final application can be implemented in a few lines of code. We first set up the project by following the steps below:
- create a directory 'simple-area-opening' e enter on it.
- inside 'simple-area-opening', create the directories 'lib', 'include'.
- download the Pomar library and build it.
- move the 'libpomar.a' file into the 'lib' which you just created.
- move the entire directory 'pomar/include/pomar' into the include directory you created.
- download the apple image from the adessowiki webpage (http://adessowiki.fee.unicamp.br/media/_xsb/iamxt/simple_filtering_ex/xsb_mmorph43348_001.png) into your 'simple-area-opening'.
- The Pomar library is just a morphological tree framework, therefore, it does not have image input and output functions. In order to read and write images in this example, we are going to use the stbs' libraries. Since they are header-only libraries we will just add them to our include directory. First, we make a directory called 'stb' inside the include directory.
- Then we download the image reader from stb (https://github.com/nothings/stb/blob/master/stb_image.h) and the image writer (https://github.com/nothings/stb/blob/master/stb_image_write.h) into the 'stb' directory.
- create area_opening.cpp file to hold your application code.
In the end, your filesystem should have something similar to:
simple-area-opening
-- include
---- stb
------ stb_image.h
------ stb_image_write.h
---- pomar
------ all include subdirectories of Pomar library
-- lib
---- libpomar (.a,.lib)
-- area_opening.cpp
We are going to code the whole application in the 'area_opening.cpp' file. The first thing to do is to include all necessary header.
#include <pomar/AdjacencyRelation/AdjacencyByTranslating.hpp>
#include <pomar/ComponentTree/CTBuilder.hpp>
#include <vector>
#include <iostream>
The code above includes the definitions for the pre-coded adjacency relation for an image, it contains the so common 8- and 4-connected adjacency relation for images. It also includes the definition for the component tree builder which encapsulates the component tree building algorithm. The other twos lines include the C++ STL vector data structure and standard input and output functionalities. We also should include the stbs' libraries and its macros, like the code below:
#define STB_IMAGE_IMPLEMENTATION
#include <std/stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
We start the implementation by reading the image from the disk. Since you may choose whatever image you like, we read the file path from the command line argument, the next code goes exactly at the beginning of the main function:
int width, height, nchannels;
unsigned char *data = stbi_load(argv[1], &width, &height,
&nchannels, 1);
In this code snippet, we declare the variable which is going to hold the values of the image width, height and number of channels. Then by using 'stb_load' function, we read an image from the disk. The argv[1] indicates the file path, and the final argument (1) indicates that we want the image with just one channel. The next step will be the component tree construction itself.
std::vector<unsigned char> f(data, data + (width*height));
std::shared_ptr<pomar::CTMeta> meta = std::make_shared<pomar::CTMetaImage2D>(width, height, nchannels);
pomar::CTBuilder builder;
std::unique_ptr<pomar::Adjacency> adj = pomar::AdjacencyByTranslating2D::createAdjacency8(width, height);
auto tree = builder.build(meta, f, std::move(adj), pomar::CTBuilder::TreeType::MinTree);
In this snippet, we encapsulate the read image into a vector, create an 8-connected adjacency relation, and set up the component tree metadata (which corresponds to image width, height and channel number for a 2D image). Then, we use the component tree builder to build a min-tree, using the read image and the adjacency relation created. Following the application development, we calculate the area for each component tree node in the snippet below:
std::vector<int> area(tree.numberOfNodes());
tree.transverse([&area](const pomar::UCCTNode& node) {
area[node.id()] += node.elementIndices().size();
if (node.parent() != -1)
area[node.parent()] += area[node.id()];
});
We calculated the area for each tree node by transversing the tree and counting the number of pixels for each node. It implemented an incremental algorithm by reusing the area computed in a child node in its parent node. The transverse method visits each node applying the lambda function in the parameter. It guarantees that all child nodes are visited before it visits the parent node. It implies that when the algorithm accumulates the area in its parent node, the node area is already computed. Now we just call the pruning method passing a criterion:
tree.prune([&area](const pomar::UCCTNode &node) {
return area[node.id()] < 50;
});
The area calculated is used as the criterion of the pruning algorithm. We select all nodes with an area below 50 to be removed from the tree. The pruning method receives a lambda function which should return true for all nodes that should be removed. This method considers that the criterion function is increasing, that is, if a node is selected, all its descendant nodes are also selected. If the criterion is not increasing the result cannot be predicted. The pixels from the removed nodes go up until its closer kept ancestral node. The next step consists of converting the tree to its array representation and writing the resulting image in the disk.
auto output = tree.convertToVector();
stbi_write_png(argv[2], width, height, 1, (void *)output.data(), 0);
stbi_image_free(data);
std::cout << "DONE." << '\n';
The last lines of code just convert the tree into its array representation, write an image in the location indicated by argv[2] using just one channel, free the memory held by the image and print "DONE" in the screen.
We have to indicate include directory and lib directory to the compiler, then it can find the linkage dependencies. We also need to tell the compiler to use c++ 11 standard and to link the libpomar into the executable file. For example, in g++ we can run:
>$ g++ -std=c++11 area_opening.cpp -oarea_opening -L./lib -I./include -lpomar
you can also include '-O3' to an optimised building but with a larger time of compilation.
If you set up the project as described in this webpage, you should have an executable file in your 'simple-area-opening' directory as well as the image downloaded from the adessowiki webpage. Then, you have to pass the file path from the input image and output image. Using terminal, you can run:
>$ area_opening simple_filtering_ex/xsb_mmorph43348_001.png out.png
And the program should have created a file called out.png. In our development, we use the program described using the image at the left side as the input image and it results in the image at the right as output:


The whole code can be found in the following gist.