From ca696ce6d0aa311af0df2d5a1bbeb0e521d9f6c8 Mon Sep 17 00:00:00 2001 From: MIRAL SHAH <37846212+miralshah365@users.noreply.github.com> Date: Fri, 9 Aug 2019 03:17:09 +0530 Subject: [PATCH] Added 2D convolution definitions to numeric extension (#367) 2D convolution tests added `convolve` function renamed to `convolve_1d` closes #356 --- example/convolve2d.cpp | 41 ++++++++++ .../boost/gil/extension/numeric/convolve.hpp | 76 ++++++++++++++++++- .../boost/gil/image_processing/threshold.hpp | 2 +- test/extension/numeric/CMakeLists.txt | 3 +- test/extension/numeric/Jamfile | 1 + test/extension/numeric/convolve.cpp | 10 +-- test/extension/numeric/convolve_2d.cpp | 67 ++++++++++++++++ 7 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 example/convolve2d.cpp create mode 100644 test/extension/numeric/convolve_2d.cpp diff --git a/example/convolve2d.cpp b/example/convolve2d.cpp new file mode 100644 index 0000000000..ec7a4f6b15 --- /dev/null +++ b/example/convolve2d.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +#include +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_out(img.dimensions()); + + gray8_image_t img; + read_image("src_view.png", img, png_tag{}); + gray8_image_t img_out(img.dimensions()), img_out1(img.dimensions()); + + std::vector v(9, 1.0f / 9.0f); + kernel_2d kernel(v.begin(), v.size(), 1, 1); + convolve_2d(view(img), kernel, view(img_out1)); + + //write_view("out-convolve2d.png", view(img_out), png_tag{}); + write_view("out-convolve2d.png", view(img_out1), jpeg_tag{}); + + + //------------------------------------// + std::vector v1(3, 1.0f / 3.0f); + kernel_1d kernel1(v1.begin(), v1.size(), 1); + + convolve_1d(const_view(img), kernel1, view(img_out), convolve_boundary_option::convolve_option_extend_zero); + write_view("out-convolve_option_extend_zero.png", view(img_out), png_tag{}); + + if (equal_pixels(view(img_out1), view(img_out)))cout << "convolve_option_extend_zero" << endl; + + cout << "done\n"; + cin.get(); + + return 0; +} diff --git a/include/boost/gil/extension/numeric/convolve.hpp b/include/boost/gil/extension/numeric/convolve.hpp index 357689bcf6..e626828064 100644 --- a/include/boost/gil/extension/numeric/convolve.hpp +++ b/include/boost/gil/extension/numeric/convolve.hpp @@ -274,7 +274,7 @@ void convolve_cols( /// \tparam DstView Models MutableImageViewConcept template BOOST_FORCEINLINE -void convolve( +void convolve_1d( SrcView const& src_view, Kernel const& kernel, DstView const& dst_view, @@ -355,6 +355,80 @@ void convolve_cols_fixed( transposed_view(src_view), kernel, transposed_view(dst_view), option); } +namespace detail +{ + +template +void convolve_2d_impl(SrcView const& src_view, DstView const& dst_view, Kernel const& kernel) +{ + int flip_ker_row, flip_ker_col, row_boundary, col_boundary; + float aux_total; + 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) + { + aux_total = 0.0f; + 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 + + // index of input signal, used for checking boundary + row_boundary = view_row + (kernel.center_vertical() - flip_ker_row); + col_boundary = view_col + (kernel.center_horizontal() - 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()) + { + aux_total += + src_view(col_boundary, row_boundary) * + kernel.at(flip_ker_row, flip_ker_col); + } + } + } + dst_view(view_col, view_row) = aux_total; + } + } +} + +} //namespace detail + + +/// \ingroup ImageAlgorithms +/// \brief convolve_2d can only use convolve_option_extend_zero as convolve_boundary_option +/// this is the default option and cannot be changed for now +/// (In future there are plans to improve the algorithm and allow user to use other options as well) +/// \tparam SrcView Models ImageViewConcept +/// \tparam Kernel TODO +/// \tparam DstView Models MutableImageViewConcept +template +void convolve_2d(SrcView const& src_view, Kernel const& kernel, DstView const& dst_view) +{ + BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions()); + BOOST_ASSERT(kernel.size() != 0); + + gil_function_requires>(); + gil_function_requires>(); + static_assert(color_spaces_are_compatible + < + typename color_space_type::type, + typename color_space_type::type + >::value, "Source and destination views must have pixels with the same color space"); + + for (std::size_t i = 0; i < src_view.num_channels(); i++) + { + detail::convolve_2d_impl( + nth_channel_view(src_view, i), + nth_channel_view(dst_view, i), + kernel + ); + } +} + }} // namespace boost::gil #endif diff --git a/include/boost/gil/image_processing/threshold.hpp b/include/boost/gil/image_processing/threshold.hpp index 5dff594dc7..ec13f8799f 100644 --- a/include/boost/gil/image_processing/threshold.hpp +++ b/include/boost/gil/image_processing/threshold.hpp @@ -412,7 +412,7 @@ void threshold_adaptive typename image::view_t temp_view = view(temp_img); SrcView temp_conv(temp_view); - convolve>( + convolve_1d>( src_view, kernel, temp_view ); diff --git a/test/extension/numeric/CMakeLists.txt b/test/extension/numeric/CMakeLists.txt index 944ed1213a..7f6d3902f0 100644 --- a/test/extension/numeric/CMakeLists.txt +++ b/test/extension/numeric/CMakeLists.txt @@ -16,7 +16,8 @@ foreach(_name kernel matrix3x2 numeric - pixel_numeric_operations) + pixel_numeric_operations + convolve_2d) set(_test t_ext_numeric_${_name}) set(_target test_ext_numeric_${_name}) diff --git a/test/extension/numeric/Jamfile b/test/extension/numeric/Jamfile index 1e2498b996..8e212bccbb 100644 --- a/test/extension/numeric/Jamfile +++ b/test/extension/numeric/Jamfile @@ -26,3 +26,4 @@ compile-fail kernel_1d_fixed_even_size_fail.cpp ; run matrix3x2.cpp ; run numeric.cpp ; run pixel_numeric_operations.cpp ; +run convolve_2d.cpp ; diff --git a/test/extension/numeric/convolve.cpp b/test/extension/numeric/convolve.cpp index f36254dcb7..efadb91863 100644 --- a/test/extension/numeric/convolve.cpp +++ b/test/extension/numeric/convolve.cpp @@ -20,7 +20,7 @@ namespace gil = boost::gil; namespace fixture = boost::gil::test::fixture; -BOOST_AUTO_TEST_SUITE(convolve) +BOOST_AUTO_TEST_SUITE(convolve_1d) BOOST_AUTO_TEST_CASE_TEMPLATE(image_1x1_kernel_1x1_identity, Image, fixture::image_types) { @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(image_1x1_kernel_1x1_identity, Image, fixture::ima using pixel_t = typename Image::value_type; using channel_t = typename gil::channel_type::type; auto const kernel = fixture::create_kernel({1}); - gil::convolve(const_view(img_out), kernel, view(img_out)); + gil::convolve_1d(const_view(img_out), kernel, view(img_out)); // 1x1 kernel reduces convolution to multiplication BOOST_TEST(gil::const_view(img).front() == gil::const_view(img_out).front()); @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(image_1x1_kernel_3x3_identity, Image, fixture::ima using pixel_t = typename Image::value_type; using channel_t = typename gil::channel_type::type; auto const kernel = fixture::create_kernel({0, 0, 0, 0, 1, 0, 0, 0, 0}); - gil::convolve(const_view(img_out), kernel, view(img_out)); + gil::convolve_1d(const_view(img_out), kernel, view(img_out)); BOOST_TEST(gil::const_view(img).front() == gil::const_view(img_out).front()); } @@ -57,7 +57,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(image_3x3_kernel_3x3_identity, Image, fixture::ima Image img_out(img); auto const kernel = fixture::create_kernel({0, 0, 0, 0, 1, 0, 0, 0, 0}); - gil::convolve(const_view(img_out), kernel, view(img_out)); + gil::convolve_1d(const_view(img_out), kernel, view(img_out)); BOOST_TEST(gil::equal_pixels(gil::const_view(img), gil::const_view(img_out))); } @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(image_5x5_kernel_3x3_identity, Image, fixture::ima Image img_out(img); auto const kernel = fixture::create_kernel({0, 0, 0, 0, 1, 0, 0, 0, 0}); - gil::convolve(const_view(img_out), kernel, view(img_out)); + gil::convolve_1d(const_view(img_out), kernel, view(img_out)); // TODO: Test different boundary options BOOST_TEST(gil::equal_pixels(gil::const_view(img), gil::const_view(img_out))); diff --git a/test/extension/numeric/convolve_2d.cpp b/test/extension/numeric/convolve_2d.cpp new file mode 100644 index 0000000000..88757a3885 --- /dev/null +++ b/test/extension/numeric/convolve_2d.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2019 Miral Shah +// +// 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 + +#define BOOST_TEST_MODULE test_ext_convolve_2d +#include "unit_test.hpp" + +namespace gil = boost::gil; + +std::uint8_t img[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 255, 0, 0, 0, 255, 0, 0, + 0, 0, 0, 255, 0, 255, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 0, 0, + 0, 0, 0, 255, 0, 255, 0, 0, 0, + 0, 0, 255, 0, 0, 0, 255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +std::uint8_t output[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 28, 28, 28, 0, 28, 28, 28, 0, + 0, 28, 56, 56, 56, 56, 56, 28, 0, + 0, 28, 56, 85, 85, 85, 56, 28, 0, + 0, 0, 56, 85, 141, 85, 56, 0, 0, + 0, 28, 56, 85, 85, 85, 56, 28, 0, + 0, 28, 56, 56, 56, 56, 56, 28, 0, + 0, 28, 28, 28, 0, 28, 28, 28, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +BOOST_AUTO_TEST_SUITE(convolve_2d) + +BOOST_AUTO_TEST_CASE(convolve_2d_with_normalized_mean_filter) +{ + gil::gray8c_view_t src_view = + gil::interleaved_view(9, 9, reinterpret_cast(img), 9); + + gil::image temp_img(src_view.width(), src_view.height()); + typename gil::image::view_t temp_view = view(temp_img); + gil::gray8_view_t dst_view(temp_view); + + std::vector v(9, 1.0f / 9.0f); + gil::kernel_2d kernel(v.begin(), v.size(), 1, 1); + + gil::convolve_2d(src_view, kernel, dst_view); + + gil::gray8c_view_t out_view = + gil::interleaved_view(9, 9, reinterpret_cast(output), 9); + + BOOST_TEST(gil::equal_pixels(out_view, dst_view)); +} + +BOOST_AUTO_TEST_SUITE_END()