From 3024ab1f30341ca12021e0ed7a9c7448ac98e38c Mon Sep 17 00:00:00 2001 From: meshtag <63031630+meshtag@users.noreply.github.com> Date: Sat, 13 Feb 2021 21:31:22 +0530 Subject: [PATCH] Added all standard morphological transformations (#541) * Added all standard morphological transformations * Improved comments and some other things * Applied adviced changes * Applied adviced changes * Should handle grayscale dilation/erosion * Checking * Added test cases and improved code structure * Added command line control * Added command line control * Rectified some things * Rectified some more things * Improved comments * Improved comments * Improved doxygen comments and added more test cases * Improved compatibility for builds and rectifying whitespace use * Minor improvement in comments * Did clang formatting * pushed enum class inside namespace 'detail' and some other things * Should handle multichannel images * Clang formatting attempt * got rid of if/else comparators for target_element * Adds morphology.hpp declaration in boost/gil.hpp * Fix newline * (std::max)(a, b) instead of std::max(a, b) * Improved Formatting --- example/Jamfile | 1 + example/morphology.cpp | 139 ++++++++ example/morphology_original.png | Bin 0 -> 2082 bytes include/boost/gil.hpp | 1 + .../boost/gil/image_processing/morphology.hpp | 300 ++++++++++++++++++ test/core/image_processing/CMakeLists.txt | 3 +- test/core/image_processing/Jamfile | 1 + test/core/image_processing/morphology.cpp | 136 ++++++++ 8 files changed, 580 insertions(+), 1 deletion(-) create mode 100644 example/morphology.cpp create mode 100644 example/morphology_original.png create mode 100644 include/boost/gil/image_processing/morphology.hpp create mode 100644 test/core/image_processing/morphology.cpp diff --git a/example/Jamfile b/example/Jamfile index 48b9e51aef..df17d57c42 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -30,6 +30,7 @@ local sources = histogram.cpp interleaved_ptr.cpp mandelbrot.cpp + morphology.cpp packed_pixel.cpp resize.cpp sobel_scharr.cpp diff --git a/example/morphology.cpp b/example/morphology.cpp new file mode 100644 index 0000000000..e54bbfef6a --- /dev/null +++ b/example/morphology.cpp @@ -0,0 +1,139 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// 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) +// +#include +#include +#include +#include +#include +#include + +// Default structuring element is SE = [1,1,1] +// |1,1,1| +// [1,1,1] +// SE(1,1)(center pixel) is the one which coincides with the currently +// considered pixel of the image to be convolved. The structuring element can be +// easily changed by the user. +namespace gil = boost::gil; +int main(int argc, char** argv) +{ + std::map operations; + if (argc < 4 || argc > 11) + { + throw std::invalid_argument( + "Wrong format of command line arguments.\n" + "Correct format is " + " " + "" + " \n"); + // User has to enter atleast one operation and they can enter maximum 8 + // operations considering binary conversion to be an + // operation.Output_image_template argument is the common component which + // will be added in names of all output images followed by a hyphen and + // the operation name. + // Example : + // ./example_morphology morphology_original.png out black_hat top_hat + // morphological_gradient dilation erosion opening closing binary + // Order of arguments entered will not matter with the exception of binary + // operation used for binary morphological operations.If binary is entered + // through the command line, it will always be the first operation to be + // applied. + return -1; + } + else + { + for (int i = 3; i < argc; ++i) + operations[argv[i]] = true; + } + gil::gray8_image_t img; + gil::read_image(argv[1], img, gil::png_tag{}); + + // Image can be converted to a binary format with high value as 255 and low + // value as 0 by using the threshold operator . This can be used for binary + // morphological operations . Convenient threshold for binary conversion may + // be chosen by the user. + if (operations["binary"]) + { + threshold_binary(view(img), view(img), 170, 255); + std::string name = argv[2]; + name += "-binary.png"; + gil::write_view(name, view(img), gil::png_tag{}); + } + + std::vector ker_vec(9, 1.0f); // Structuring element + gil::detail::kernel_2d ker_mat(ker_vec.begin(), ker_vec.size(), 1, 1); + gil::gray8_image_t img_out_dilation(img.dimensions()), img_out_erosion(img.dimensions()), + img_out_opening(img.dimensions()); + gil::gray8_image_t img_out_closing(img.dimensions()), img_out_mg(img.dimensions()), + img_out_top_hat(img.dimensions()); + gil::gray8_image_t img_out_black_hat(img.dimensions()); + + // Do not pass empty input image views in functions defined below for + // morphological operations to avoid errors. + if (operations["dilation"]) + { + // dilate(input_image_view,output_image_view,structuring_element,iterations) + dilate(view(img), view(img_out_dilation), ker_mat, 1); + std::string name = argv[2]; + name += "-dilation.png"; + gil::write_view(name, view(img_out_dilation), gil::png_tag{}); + } + + if (operations["erosion"]) + { + // erode(input_image_view,output_image_view,structuring_element,iterations) + erode(view(img), view(img_out_erosion), ker_mat, 1); + std::string name = argv[2]; + name += "-erosion.png"; + gil::write_view(name, view(img_out_erosion), gil::png_tag{}); + } + + if (operations["opening"]) + { + // opening(input_image_view,output_image_view,structuring_element) + opening(view(img), view(img_out_opening), ker_mat); + std::string name = argv[2]; + name += "-opening.png"; + gil::write_view(name, view(img_out_opening), gil::png_tag{}); + } + + if (operations["closing"]) + { + // closing(input_image_view,output_image_view,structuring_element) + closing(view(img), view(img_out_closing), ker_mat); + std::string name = argv[2]; + name += "-closing.png"; + gil::write_view(name, view(img_out_closing), gil::png_tag{}); + } + + if (operations["morphological_gradient"]) + { + // morphological_gradient(input_image_view,output_image_view,structuring_element) + morphological_gradient(view(img), view(img_out_mg), ker_mat); + std::string name = argv[2]; + name += "-morphological_gradient.png"; + gil::write_view(name, view(img_out_mg), gil::png_tag{}); + } + + if (operations["top_hat"]) + { + // top_hat(input_image_view,output_image_view,structuring_element) + top_hat(view(img), view(img_out_top_hat), ker_mat); + std::string name = argv[2]; + name += "-top_hat.png"; + gil::write_view(name, view(img_out_top_hat), gil::png_tag{}); + } + + if (operations["black_hat"]) + { + // black_hat(input_image_view,output_image_view,structuring_element) + black_hat(view(img), view(img_out_black_hat), ker_mat); + std::string name = argv[2]; + name += "-black_hat.png"; + gil::write_view(name, view(img_out_black_hat), gil::png_tag{}); + } +} diff --git a/example/morphology_original.png b/example/morphology_original.png new file mode 100644 index 0000000000000000000000000000000000000000..b63e99ed9da7b769fd4fe45826f1f5118fb24297 GIT binary patch literal 2082 zcmZ8iZB!Fi8U~6e6lgL-Es3HqNe0s5Za%OUTm@l-02eGGm}5C53=p(+p&(Y24~YWK zK(s~zJ5gHBi8NB>s42CDEv14Z@)2P@Srsd~k^)M*T0KZn5ZybR-Tv4gbMJkhdGEdN zecmtn{lpkA521&Xlap7hir5V2d+@7Y0tY9@y4#~pPKzgFi49wdobPnWkNh0%QaSqS zjxOi@^TQ>-X)lhwvGd>?_lTT(es>3(tFzub(NvAy zIzL_N=%N<~u4pLwB>>1_qlr^ZWhdM<)3v6ru8s!PTca3@j-!I7^6GIpP-Cc@kD( zks#cOtGc|>&2*QNl z-@N~ND`WIcdN{QDW0^FG2q~KzompwR+{E4haX$RR>1DIiIl0YWB(C z_K7Nv)~o{Z13a*r3WW%+5Lo54{A?l}lG1*4jQtiQ8u_K%O}9bf&VeyPaBVBRWQ}Qs zRcaUYQyZ?^ztSFH-i`lH+1sWat0O6k$XzgUB>fM9Ywvt#c=&Nxe;Q zW`6;35cEMMAkh`){i+D%Gf-Lt5(%7#o<5a)H5d=XP>OUcwFe3z| z#s^FUXoj)3*|Mn+_*c{U+VTgCs-?@5=ZDgyW1TXt3HUFSpCtNS%?;aAHk~@2XY(~E zb?Rr~%I3JEtJ`P@dCAD=2_RC*sNSX!^z0zGmYLX2K*BgpbTszJ5|9vZQ2JO3I_5mA9KC5A zgw#;RNyoMLQ;NB zuIpv0R2`-J=4=?w6WDnJ^bvQWhrlk(&5n0-5K6yE)k7$R{kyFWDywzyWnvKDGT;jB zA{Uvnq2IQV|9mw4yu10A$t}u@1SflOIQ&177yja<=p+}nuK}^pB1sJhvjm(4G2q$w zsr3EYEf>OXddj@=v%^R~EF+qha?~~oB&pbDZN+efAm9dEFvC>>JXNI7Qp42(?1rdd zVx84*>I=1@ZT9A?m-CCaS~l(MdrGgS&xF5vazET1ruZ=TN?jkdn?AFDekOeXKVV0F u^w54g2gVt>+wmdSAYM%We=~M;&9AKA^Y#TxX!BmK`NwWdB+f=^4*UmVeU2^w literal 0 HcmV?d00001 diff --git a/include/boost/gil.hpp b/include/boost/gil.hpp index a2e2831114..b98af56e6a 100644 --- a/include/boost/gil.hpp +++ b/include/boost/gil.hpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/gil/image_processing/morphology.hpp b/include/boost/gil/image_processing/morphology.hpp new file mode 100644 index 0000000000..1149b2c165 --- /dev/null +++ b/include/boost/gil/image_processing/morphology.hpp @@ -0,0 +1,300 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// 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_MORPHOLOGY_HPP +#define BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP +#include +#include +#include + +namespace boost +{ +namespace gil +{ +namespace detail +{ +enum class morphological_operation +{ + dilation, + erosion, +}; +/// \addtogroup ImageProcessing +/// @{ + +/// \brief Implements morphological operations at pixel level.This function +/// compares neighbouring pixel values according to the kernel and choose +/// minimum/mamximum neighbouring pixel value and assigns it to the pixel under +/// consideration. +/// \param src_view - Source/Input image view. +/// \param dst_view - View which stores the final result of operations performed by this function. +/// \param kernel - Kernel matrix/structuring element containing 0's and 1's +/// which will be used for applying the required morphological operation. +/// \param identifier - Indicates the type of morphological operation to be applied. +/// \tparam SrcView type of source image. +/// \tparam DstView type of output image. +/// \tparam Kernel type of structuring element. +template +void morph_impl(SrcView const& src_view, DstView const& dst_view, Kernel const& kernel, + morphological_operation identifier) +{ + std::ptrdiff_t flip_ker_row, flip_ker_col, row_boundary, col_boundary; + typename channel_type::type target_element; + for (std::ptrdiff_t view_row = 0; view_row < src_view.height(); ++view_row) + { + for (std::ptrdiff_t view_col = 0; view_col < src_view.width(); ++view_col) + { + target_element = src_view(view_col, view_row); + for (std::size_t kernel_row = 0; kernel_row < kernel.size(); ++kernel_row) + { + flip_ker_row = kernel.size() - 1 - kernel_row; // row index of flipped kernel + + for (std::size_t kernel_col = 0; kernel_col < kernel.size(); ++kernel_col) + { + flip_ker_col = kernel.size() - 1 - kernel_col; // column index of flipped kernel + + // We ensure that we consider only those pixels which are overlapped + // on a non-zero kernel_element as + if (kernel.at(flip_ker_row, flip_ker_col) == 0) + { + continue; + } + // index of input signal, used for checking boundary + row_boundary = view_row + (kernel.center_y() - flip_ker_row); + col_boundary = view_col + (kernel.center_x() - flip_ker_col); + + // ignore input samples which are out of bound + if (row_boundary >= 0 && row_boundary < src_view.height() && + col_boundary >= 0 && col_boundary < src_view.width()) + { + + if (identifier == morphological_operation::dilation) + { + target_element = + (std::max)(src_view(col_boundary, row_boundary)[0], target_element); + } + else if (identifier == morphological_operation::erosion) + { + target_element = + (std::min)(src_view(col_boundary, row_boundary)[0], target_element); + } + } + } + } + dst_view(view_col, view_row) = target_element; + } + } +} + +/// \brief Checks feasibility of the desired operation and passes parameter +/// values to the function morph_impl alongwith individual channel views of the +/// input image. +/// \param src_view - Source/Input image view. +/// \param dst_view - View which stores the final result of operations performed by this function. +/// \param kernel - Kernel matrix/structuring element containing 0's and 1's +/// which will be used for applying the required morphological operation. +/// \param identifier - Indicates the type of morphological operation to be applied. +/// \tparam SrcView type of source image. +/// \tparam DstView type of output image. +/// \tparam Kernel type of structuring element. +template +void morph(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat, + morphological_operation identifier) +{ + BOOST_ASSERT(ker_mat.size() != 0 && src_view.dimensions() == dst_view.dimensions()); + gil_function_requires>(); + gil_function_requires>(); + + gil_function_requires::type, + typename color_space_type::type>>(); + + gil::image intermediate_img(src_view.dimensions()); + + for (std::size_t i = 0; i < src_view.num_channels(); i++) + { + morph_impl(nth_channel_view(src_view, i), nth_channel_view(view(intermediate_img), i), + ker_mat, identifier); + } + copy_pixels(view(intermediate_img), dst_view); +} + +/// \brief Calculates the difference between pixel values of first image_view +/// and second image_view. +/// \param src_view1 - First parameter for subtraction of views. +/// \param src_view2 - Second parameter for subtraction of views. +/// \param diff_view - View containing result of the subtraction of second view from +/// the first view. +/// \tparam SrcView type of source/Input images used for subtraction. +/// \tparam DiffView type of image view containing the result of subtraction. +template +void difference_impl(SrcView const& src_view1, SrcView const& src_view2, DiffView const& diff_view) +{ + for (std::ptrdiff_t view_row = 0; view_row < src_view1.height(); ++view_row) + for (std::ptrdiff_t view_col = 0; view_col < src_view1.width(); ++view_col) + diff_view(view_col, view_row) = + src_view1(view_col, view_row) - src_view2(view_col, view_row); +} + +/// \brief Passes parameter values to the function 'difference_impl' alongwith +/// individual channel views of input images. +/// \param src_view1 - First parameter for subtraction of views. +/// \param src_view2 - Second parameter for subtraction of views. +/// \param diff_view - View containing result of the subtraction of second view from the first view. +/// \tparam SrcView type of source/Input images used for subtraction. +/// \tparam DiffView type of image view containing the result of subtraction. +template +void difference(SrcView const& src_view1, SrcView const& src_view2, DiffView const& diff_view) +{ + gil_function_requires>(); + gil_function_requires>(); + + gil_function_requires::type, typename color_space_type::type>>(); + + for (std::size_t i = 0; i < src_view1.num_channels(); i++) + { + difference_impl(nth_channel_view(src_view1, i), nth_channel_view(src_view2, i), + nth_channel_view(diff_view, i)); + } +} +} // namespace detail + +/// \brief Applies morphological dilation on the input image view using given +/// structuring element. It gives the maximum overlapped value to the pixel +/// overlapping with the center element of structuring element. \param src_view +/// - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying dilation. +/// \param iterations - Specifies the number of times dilation is to be applied on the input image +/// view. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void dilate(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat, + int iterations) +{ + copy_pixels(src_view, int_op_view); + for (int i = 0; i < iterations; ++i) + morph(int_op_view, int_op_view, ker_mat, detail::morphological_operation::dilation); +} + +/// \brief Applies morphological erosion on the input image view using given +/// structuring element. It gives the minimum overlapped value to the pixel +/// overlapping with the center element of structuring element. +/// \param src_view - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying erosion. +/// \param iterations - Specifies the number of times erosion is to be applied on the input +/// image view. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void erode(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat, + int iterations) +{ + copy_pixels(src_view, int_op_view); + for (int i = 0; i < iterations; ++i) + morph(int_op_view, int_op_view, ker_mat, detail::morphological_operation::erosion); +} + +/// \brief Performs erosion and then dilation on the input image view . This +/// operation is utilized for removing noise from images. +/// \param src_view - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying the opening operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void opening(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat) +{ + erode(src_view, int_op_view, ker_mat, 1); + dilate(int_op_view, int_op_view, ker_mat, 1); +} + +/// \brief Performs dilation and then erosion on the input image view which is +/// exactly opposite to the opening operation . Closing operation can be +/// utilized for closing small holes inside foreground objects. +/// \param src_view - Source/input image view. +/// \param int_op_view - view for writing output and performing intermediate operations. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying the closing operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam IntOpView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void closing(SrcView const& src_view, IntOpView const& int_op_view, Kernel const& ker_mat) +{ + dilate(src_view, int_op_view, ker_mat, 1); + erode(int_op_view, int_op_view, ker_mat, 1); +} + +/// \brief Calculates the difference between image views generated after +/// applying dilation dilation and erosion on an image . The resultant image +/// will look like the outline of the object(s) present in the image. +/// \param src_view - Source/input image view. +/// \param dst_view - Destination view which will store the final result of morphological +/// gradient operation. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which +/// will be used for applying the morphological gradient operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam DstView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void morphological_gradient(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat) +{ + using namespace boost::gil; + gil::image int_dilate(src_view.dimensions()), + int_erode(src_view.dimensions()); + dilate(src_view, view(int_dilate), ker_mat, 1); + erode(src_view, view(int_erode), ker_mat, 1); + difference(view(int_dilate), view(int_erode), dst_view); +} + +/// \brief Calculates the difference between input image view and the view +/// generated by opening operation on the input image view. +/// \param src_view - Source/input image view. +/// \param dst_view - Destination view which will store the final result of top hat operation. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's which will be used for +/// applying the top hat operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam DstView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void top_hat(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat) +{ + using namespace boost::gil; + gil::image int_opening(src_view.dimensions()); + opening(src_view, view(int_opening), ker_mat); + difference(src_view, view(int_opening), dst_view); +} + +/// \brief Calculates the difference between closing of the input image and +/// input image. +/// \param src_view - Source/input image view. +/// \param dst_view - Destination view which will store the final result of black hat operation. +/// \param ker_mat - Kernel matrix/structuring element containing 0's and 1's +/// which will be used for applying the black hat operation. +/// \tparam SrcView type of source image, models gil::ImageViewConcept. +/// \tparam DstView type of output image, models gil::MutableImageViewConcept. +/// \tparam Kernel type of structuring element. +template +void black_hat(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat) +{ + using namespace boost::gil; + gil::image int_closing(src_view.dimensions()); + closing(src_view, view(int_closing), ker_mat); + difference(view(int_closing), src_view, dst_view); +} +/// @} +}} // namespace boost::gil +#endif // BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP diff --git a/test/core/image_processing/CMakeLists.txt b/test/core/image_processing/CMakeLists.txt index 2fc51c1d4a..1581fd3075 100644 --- a/test/core/image_processing/CMakeLists.txt +++ b/test/core/image_processing/CMakeLists.txt @@ -9,7 +9,8 @@ foreach(_name threshold_binary threshold_truncate - threshold_otsu) + threshold_otsu + morphology) set(_test t_core_image_processing_${_name}) set(_target test_core_image_processing_${_name}) diff --git a/test/core/image_processing/Jamfile b/test/core/image_processing/Jamfile index a67c4d9f21..acd74d08f2 100644 --- a/test/core/image_processing/Jamfile +++ b/test/core/image_processing/Jamfile @@ -22,3 +22,4 @@ run median_filter.cpp ; run anisotropic_diffusion.cpp ; run hough_line_transform.cpp ; run hough_circle_transform.cpp ; +run morphology.cpp ; diff --git a/test/core/image_processing/morphology.cpp b/test/core/image_processing/morphology.cpp new file mode 100644 index 0000000000..b57f605723 --- /dev/null +++ b/test/core/image_processing/morphology.cpp @@ -0,0 +1,136 @@ +// +// Copyright 2021 Prathamesh Tagore +// +// 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) +// + +#include +#include +#include + +namespace gil = boost::gil; + +// This function helps us fill pixels of a view given as 2nd argument with +// elements of the vector given as 1st argument. +void pixel_fill(std::vector>& original_binary_vector, + boost::gil::gray8_image_t& original_img) +{ + for (std::ptrdiff_t view_row = 0; view_row < view(original_img).height(); ++view_row) + { + for (std::ptrdiff_t view_col = 0; view_col < view(original_img).width(); ++view_col) + { + view(original_img)(view_col, view_row) = + gil::gray8_pixel_t(original_binary_vector[view_row][view_col]); + } + } +} + +int main() +{ + std::vector> original_binary_vector{ + {0, 0, 0, 0, 0, 0}, {0, 0, 127, 144, 143, 0}, {0, 0, 128, 0, 142, 0}, + {0, 0, 129, 0, 141, 0}, {0, 0, 130, 140, 139, 0}, {0, 0, 131, 0, 0, 0}, + {0, 0, 132, 137, 136, 138}, {0, 0, 133, 134, 135, 0}}; + std::vector> orig_dil_imp{ + {255, 100, 100, 100}, {100, 100, 100, 100}, {100, 100, 100, 100}}; + // All vectors defined below will be used for creating expected image views + // which are supposed to match the views obtained after applying morphological + // operations. + std::vector> exp_dil{ + {0, 127, 144, 144, 144, 143}, {0, 128, 144, 144, 144, 143}, {0, 129, 144, 144, 144, 143}, + {0, 130, 140, 142, 142, 142}, {0, 131, 140, 141, 141, 141}, {0, 132, 140, 140, 140, 139}, + {0, 133, 137, 137, 138, 138}, {0, 133, 137, 137, 138, 138}}; + // Following vector intends to check result of dilation operation when it is + // applied 2 times on the original image. + std::vector> exp_dil_iter2{ + {128, 144, 144, 144, 144, 144}, {129, 144, 144, 144, 144, 144}, + {130, 144, 144, 144, 144, 144}, {131, 144, 144, 144, 144, 144}, + {132, 140, 142, 142, 142, 142}, {133, 140, 141, 141, 141, 141}, + {133, 140, 140, 140, 140, 140}, {133, 137, 137, 138, 138, 138}}; + std::vector> exp_er{ + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 132, 0, 0}}; + // Following vector intends to check result of erosion operation when it is + // applied 2 times on the original image. + std::vector> exp_er_iter2{ + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}}; + std::vector> exp_opening{ + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 132, 132, 132, 0}, {0, 0, 132, 132, 132, 0}}; + std::vector> exp_closing{ + {0, 0, 127, 144, 143, 143}, {0, 0, 127, 144, 143, 143}, {0, 0, 128, 140, 142, 142}, + {0, 0, 129, 140, 141, 141}, {0, 0, 130, 140, 139, 139}, {0, 0, 131, 137, 137, 138}, + {0, 0, 132, 137, 137, 138}, {0, 0, 133, 137, 137, 138}}; + std::vector> exp_mg{{0, 127, 144, 144, 144, 143}, {0, 128, 144, 144, 144, 143}, + {0, 129, 144, 144, 144, 143}, {0, 130, 140, 142, 142, 142}, + {0, 131, 140, 141, 141, 141}, {0, 132, 140, 140, 140, 139}, + {0, 133, 137, 137, 138, 138}, {0, 133, 137, 5, 138, 138}}; + std::vector> exp_top_hat{{0, 0, 0, 0, 0, 0}, {0, 0, 127, 144, 143, 0}, + {0, 0, 128, 0, 142, 0}, {0, 0, 129, 0, 141, 0}, + {0, 0, 130, 140, 139, 0}, {0, 0, 131, 0, 0, 0}, + {0, 0, 0, 5, 4, 138}, {0, 0, 1, 2, 3, 0}}; + std::vector> exp_black_hat{ + {0, 0, 127, 144, 143, 143}, {0, 0, 0, 0, 0, 143}, {0, 0, 0, 140, 0, 142}, + {0, 0, 0, 140, 0, 141}, {0, 0, 0, 0, 0, 139}, {0, 0, 0, 137, 137, 138}, + {0, 0, 0, 0, 1, 0}, {0, 0, 0, 3, 2, 138}}; + std::vector> exp_dil_imp{ + {255, 255, 100, 100}, {255, 255, 100, 100}, {100, 100, 100, 100}}; + + gil::gray8_image_t original_img(6, 8), obtained_dilation(6, 8), expected_dilation(6, 8); + gil::gray8_image_t obtained_erosion(6, 8), expected_erosion(6, 8); + gil::gray8_image_t obtained_opening(6, 8), expected_opening(6, 8); + gil::gray8_image_t obtained_closing(6, 8), expected_closing(6, 8); + gil::gray8_image_t obtained_mg(6, 8), expected_mg(6, 8); + gil::gray8_image_t obtained_top_hat(6, 8), expected_top_hat(6, 8); + gil::gray8_image_t obtained_black_hat(6, 8), expected_black_hat(6, 8); + gil::gray8_image_t obtained_dil_iter2(6, 8), expected_dil_iter2(6, 8); + gil::gray8_image_t obtained_er_iter2(6, 8), expected_er_iter2(6, 8); + gil::gray8_image_t obtained_imp_dil(4, 3), expected_imp_dil(4, 3), original_imp_dil(4, 3); + + std::vector ker_vec(9, 1.0f); // Structuring element + gil::detail::kernel_2d ker_mat(ker_vec.begin(), ker_vec.size(), 1, 1); + + pixel_fill(original_binary_vector, original_img); + pixel_fill(exp_dil, expected_dilation); + pixel_fill(exp_er, expected_erosion); + pixel_fill(exp_opening, expected_opening); + pixel_fill(exp_closing, expected_closing); + pixel_fill(exp_mg, expected_mg); + pixel_fill(exp_top_hat, expected_top_hat); + pixel_fill(exp_black_hat, expected_black_hat); + pixel_fill(exp_dil_iter2, expected_dil_iter2); + pixel_fill(orig_dil_imp, original_imp_dil); + pixel_fill(exp_dil_imp, expected_imp_dil); + pixel_fill(exp_er_iter2, expected_er_iter2); + + // Different morphological operations are applied on the same initial image to + // obtain results of our implementation which are then compared with expected + // results. + gil::dilate(view(original_img), view(obtained_dilation), ker_mat, 1); + gil::erode(view(original_img), view(obtained_erosion), ker_mat, 1); + gil::opening(view(original_img), view(obtained_opening), ker_mat); + gil::closing(view(original_img), view(obtained_closing), ker_mat); + gil::morphological_gradient(view(original_img), view(obtained_mg), ker_mat); + gil::top_hat(view(original_img), view(obtained_top_hat), ker_mat); + gil::black_hat(view(original_img), view(obtained_black_hat), ker_mat); + gil::dilate(view(original_imp_dil), view(obtained_imp_dil), ker_mat, 1); + gil::dilate(view(original_img), view(obtained_dil_iter2), ker_mat, 2); + gil::erode(view(original_img), view(obtained_er_iter2), ker_mat, 2); + + // Testing obtained results with expected results. + BOOST_TEST(gil::equal_pixels(gil::view(obtained_dilation), gil::view(expected_dilation))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_erosion), gil::view(expected_erosion))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_opening), gil::view(expected_opening))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_closing), gil::view(expected_closing))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_mg), gil::view(expected_mg))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_top_hat), gil::view(expected_top_hat))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_black_hat), gil::view(expected_black_hat))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_imp_dil), gil::view(expected_imp_dil))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_dil_iter2), gil::view(expected_dil_iter2))); + BOOST_TEST(gil::equal_pixels(gil::view(obtained_er_iter2), gil::view(expected_er_iter2))); + + return boost::report_errors(); +}