Skip to content

Commit

Permalink
Added 2D convolution definitions to numeric extension (#367)
Browse files Browse the repository at this point in the history
2D convolution tests added

`convolve` function renamed to `convolve_1d`

closes #356
  • Loading branch information
miralshah365 committed Aug 8, 2019
1 parent 499d30f commit ca696ce
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 8 deletions.
41 changes: 41 additions & 0 deletions example/convolve2d.cpp
@@ -0,0 +1,41 @@
#include <vector>
#include <iostream>
#include <boost/gil/extension/numeric/kernel.hpp>
#include <boost/gil/extension/numeric/convolve.hpp>
#include <boost/gil/extension/io/png.hpp>

#include <boost/gil/extension/io/jpeg.hpp>
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<float> v(9, 1.0f / 9.0f);
kernel_2d<float> 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<float> v1(3, 1.0f / 3.0f);
kernel_1d<float> kernel1(v1.begin(), v1.size(), 1);

convolve_1d<gray32f_pixel_t>(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;
}
76 changes: 75 additions & 1 deletion include/boost/gil/extension/numeric/convolve.hpp
Expand Up @@ -274,7 +274,7 @@ void convolve_cols(
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void convolve(
void convolve_1d(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
Expand Down Expand Up @@ -355,6 +355,80 @@ void convolve_cols_fixed(
transposed_view(src_view), kernel, transposed_view(dst_view), option);
}

namespace detail
{

template <typename SrcView, typename DstView, typename Kernel>
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 <typename SrcView, typename DstView, typename Kernel>
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<ImageViewConcept<SrcView>>();
gil_function_requires<MutableImageViewConcept<DstView>>();
static_assert(color_spaces_are_compatible
<
typename color_space_type<SrcView>::type,
typename color_space_type<DstView>::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
2 changes: 1 addition & 1 deletion include/boost/gil/image_processing/threshold.hpp
Expand Up @@ -412,7 +412,7 @@ void threshold_adaptive
typename image<typename SrcView::value_type>::view_t temp_view = view(temp_img);
SrcView temp_conv(temp_view);

convolve<pixel<float, typename SrcView::value_type::layout_t>>(
convolve_1d<pixel<float, typename SrcView::value_type::layout_t>>(
src_view, kernel, temp_view
);

Expand Down
3 changes: 2 additions & 1 deletion test/extension/numeric/CMakeLists.txt
Expand Up @@ -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})

Expand Down
1 change: 1 addition & 0 deletions test/extension/numeric/Jamfile
Expand Up @@ -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 ;
10 changes: 5 additions & 5 deletions test/extension/numeric/convolve.cpp
Expand Up @@ -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)
{
Expand All @@ -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<pixel_t>::type;
auto const kernel = fixture::create_kernel<channel_t>({1});
gil::convolve<pixel_t>(const_view(img_out), kernel, view(img_out));
gil::convolve_1d<pixel_t>(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());
Expand All @@ -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<pixel_t>::type;
auto const kernel = fixture::create_kernel<channel_t>({0, 0, 0, 0, 1, 0, 0, 0, 0});
gil::convolve<pixel_t>(const_view(img_out), kernel, view(img_out));
gil::convolve_1d<pixel_t>(const_view(img_out), kernel, view(img_out));

BOOST_TEST(gil::const_view(img).front() == gil::const_view(img_out).front());
}
Expand All @@ -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<channel_t>({0, 0, 0, 0, 1, 0, 0, 0, 0});
gil::convolve<pixel_t>(const_view(img_out), kernel, view(img_out));
gil::convolve_1d<pixel_t>(const_view(img_out), kernel, view(img_out));

BOOST_TEST(gil::equal_pixels(gil::const_view(img), gil::const_view(img_out)));
}
Expand All @@ -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<channel_t>({0, 0, 0, 0, 1, 0, 0, 0, 0});
gil::convolve<pixel_t>(const_view(img_out), kernel, view(img_out));
gil::convolve_1d<pixel_t>(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)));
Expand Down
67 changes: 67 additions & 0 deletions test/extension/numeric/convolve_2d.cpp
@@ -0,0 +1,67 @@
//
// Copyright 2019 Miral Shah <miralshah2211@gmail.com>
//
// 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 <cstddef>

#include <boost/gil.hpp>
#include <boost/gil/extension/numeric/convolve.hpp>

#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<const gil::gray8_pixel_t*>(img), 9);

gil::image<gil::gray8_pixel_t> temp_img(src_view.width(), src_view.height());
typename gil::image<gil::gray8_pixel_t>::view_t temp_view = view(temp_img);
gil::gray8_view_t dst_view(temp_view);

std::vector<float> v(9, 1.0f / 9.0f);
gil::kernel_2d<float> 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<const gil::gray8_pixel_t*>(output), 9);

BOOST_TEST(gil::equal_pixels(out_view, dst_view));
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit ca696ce

Please sign in to comment.