Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Morphological Dilation and Erosion Algorithms #430

Closed
wants to merge 8 commits into from
36 changes: 36 additions & 0 deletions example/morphology.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <boost/gil/extension/io/png.hpp>
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
#include <boost/gil/image_processing/morphology.hpp>
#include <boost/gil/image_view_factory.hpp>
#include <boost/gil.hpp>
#include <boost/multi_array.hpp>

#define SE_SIZE 3
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved

using namespace boost::gil;
using namespace std;

int main()
{
gray8_image_t img;
read_image("test_adaptive.png", img, png_tag{});
gray8_image_t img_dilate(img.dimensions());
gray8_image_t img_erode(img.dimensions());

int se_arr[SE_SIZE][SE_SIZE] = {{0,1,0},{1,1,1},{0,1,0}};

typedef boost::multi_array<int, 2> se_type;
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
se_type se{boost::extents[SE_SIZE][SE_SIZE]};
for(int i=0; i < SE_SIZE; ++i)
{
for(int j=0; j < SE_SIZE; ++j)
{
se[i][j] = se_arr[i][j];
}
}

dilate(view(img), view(img_dilate), se);
erode(view(img), view(img_erode), se);

write_view("dilated_image.png", view(img_dilate), png_tag{});
write_view("eroded_image.png", view(img_erode), png_tag{});
}
109 changes: 109 additions & 0 deletions include/boost/gil/image_processing/morphology.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Copyright 2019 Ayush Bansal <[email protected]>
//
// Use, modification and distribution are subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_MORPH_HPP
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
#define BOOST_GIL_IMAGE_PROCESSING_MORPH_HPP

#include <boost/gil/image_view_factory.hpp>
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
#include <boost/assert.hpp>
#include <boost/gil.hpp>
mloskot marked this conversation as resolved.
Show resolved Hide resolved

namespace boost { namespace gil {

// SE refers to Structuring Element
// Result type after convolution with the structuring element
enum class morph_operator{
ERODE,
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
DILATE
};

namespace detail {

template <typename SrcView, typename DstView, typename StructElement>
void convolve_with_struct_element(SrcView const &src_view, DstView &dst_view,
StructElement const &struct_elem, std::ptrdiff_t src_y, std::ptrdiff_t src_x,
morph_operator const &op)
{
// Template argument validation
gil_function_requires<ImageViewConcept<SrcView>>();
gil_function_requires<MutableImageViewConcept<DstView>>();

using source_channel_t = typename channel_type<SrcView>::type;

// Initial min and max values to be replaced after apply morphplogical operator
auto min = (std::numeric_limits<source_channel_t>::max());
auto max = (std::numeric_limits<source_channel_t>::min());

std::size_t struct_elem_height = struct_elem.size();
std::size_t struct_elem_width = struct_elem[0].size();

// Iterating for every position in the Structuring Element
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
for(std::ptrdiff_t struct_elem_y = 0; struct_elem_y < struct_elem_height; ++struct_elem_y)
{
// The cooresponding value to be read from src_view is calculated as
// (src_y + struct_elem_y - struct_elem_height/2, src_x + struct_elem_x - struct_elem_width/2)
// NOTE: This calculation is only valid when the struct_elem dimensions are odd.
auto y_to_read = src_y + struct_elem_y - struct_elem_height/2;
if(y_to_read < 0 || y_to_read >= src_view.height()) continue;
typename SrcView::x_iterator src_it = src_view.row_begin(y_to_read);

for(std::ptrdiff_t struct_elem_x = 0; struct_elem_x < struct_elem_width; ++struct_elem_x)
{
// We need to consider only those positions which have 1 in Structuring Element
if(struct_elem[struct_elem_x][struct_elem_y] == 1)
{
auto x_to_read = src_x + struct_elem_x - struct_elem_width/2;
if(x_to_read < 0 || x_to_read>=src_view.width()) continue;
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
auto src_point_val = src_it[x_to_read];
if (src_point_val < min) min = src_point_val;
if (src_point_val > max) max = src_point_val;
}
}
}

typename DstView::x_iterator dst_it = dst_view.row_begin(src_y);

if (op == morph_operator::ERODE) dst_it[src_x] = min;
else if(op == morph_operator::DILATE) dst_it[src_x] = max;
}

template <typename SrcView, typename DstView, typename StructElement>
void morph_impl(SrcView const &src_view, DstView &dst_view, StructElement const &struct_elem, morph_operator const &op)
{
BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions());
BOOST_ASSERT(struct_elem.size() != 0 && struct_elem[0].size() != 0);

std::size_t height = src_view.height();
std::size_t width = dst_view.width();

// Iterate over every possible pixel in src_view
for(std::ptrdiff_t src_y = 0; src_y < height; ++src_y)
{
for(std::ptrdiff_t src_x = 0; src_x < width; ++src_x)
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
{
convolve_with_struct_element<SrcView, DstView, StructElement>(src_view, dst_view, struct_elem, src_y, src_x, op);
}
}
}

} //namespace boost::gil::detail

template <typename SrcView, typename DstView, typename StructElement>
void dilate(SrcView const &src_view, DstView &dst_view, StructElement const &struct_elem)
{
morph_impl(src_view, dst_view, struct_elem, morph_operator::DILATE);
}

template <typename SrcView, typename DstView, typename StructElement>
void erode(SrcView const &src_view, DstView &dst_view, StructElement const &struct_elem)
{
morph_impl(src_view, dst_view, struct_elem, morph_operator::ERODE);
}

}} //namespace boost::gil

#endif //BOOST_GIL_IMAGE_PROCESSING_MORPH_HPP
73 changes: 73 additions & 0 deletions test/core/image_processing/morphology.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <boost/gil/image_processing/morphology.hpp>
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
#include <boost/core/lightweight_test.hpp>

#include <vector>

namespace gil = boost::gil;

const int width = 5, height = 5;
const int struct_elem_height = 3, struct_elem_width = 3;

gil::gray8_image_t original_gray(width, height), processed_gray(width, height), expected_gray(width, height);

const int original_gray_dilation_values[width][height] = {
{ 0, 0, 0, 0, 0},
{ 0, 255, 255, 0, 0},
{ 0, 255, 0, 0, 0},
{ 0, 255, 0, 0, 0},
{ 0, 0, 0, 0, 0}};

const int original_gray_erosion_values[width][height] = {
{ 0, 0, 0, 0, 0},
{ 0, 255, 255, 255, 0},
{ 0, 255, 255, 255, 0},
{ 0, 255, 255, 255, 0},
{ 0, 0, 0, 0, 0}};

std::vector<std::vector<int> > struct_element{{0,1,0}, {1,1,1}, {0,1,0}};

void fill_values(const int values[width][height], gil::gray8_image_t &img)
{
for(std::ptrdiff_t y=0; y<height; ++y)
{
for(std::ptrdiff_t x=0; x<width; ++x)
{
gil::view(img)(x,y) = gil::gray8_pixel_t(values[y][x]);
}
}
}

void test_dilation()
{
int expected_values[width][height] = {
{ 0, 255, 255, 0, 0},
{ 255, 255, 255, 255, 0},
{ 255, 255, 255, 0, 0},
{ 255, 255, 255, 0, 0},
{ 0, 255, 0, 0, 0}};
fill_values(expected_values, expected_gray);

gil::dilate(gil::view(original_gray),gil::view(processed_gray),struct_element);
BOOST_TEST(gil::equal_pixels(gil::view(processed_gray), gil::view(expected_gray)));
}

void test_erosion()
{
int expected_values[width][height] = {0};
expected_values[width/2][height/2] = 255;
fill_values(expected_values, expected_gray);

gil::erode(gil::view(original_gray),gil::view(processed_gray),struct_element);
BOOST_TEST(gil::equal_pixels(gil::view(processed_gray), gil::view(expected_gray)));
}

int main()
{
fill_values(original_gray_dilation_values, original_gray);
test_dilation();

fill_values(original_gray_erosion_values, original_gray);
test_erosion();

return boost::report_errors();
ayushbansal07 marked this conversation as resolved.
Show resolved Hide resolved
}