From 716fe9ad8cdb0dfdeef2f7d19b17df1a87ec1f7a Mon Sep 17 00:00:00 2001 From: MIRAL SHAH <37846212+miralshah365@users.noreply.github.com> Date: Wed, 19 Jun 2019 01:12:43 +0530 Subject: [PATCH] Implement algorithms for binary and binary inverse thresholding (#313) Add public functions threshold_binary and threshold_truncate. Add tests and example. Closes #310 --- example/threshold.cpp | 29 +++ include/boost/gil.hpp | 1 + .../boost/gil/image_processing/threshold.hpp | 184 ++++++++++++++++++ test/CMakeLists.txt | 1 - test/core/CMakeLists.txt | 1 + test/core/Jamfile | 1 + test/core/image_processing/CMakeLists.txt | 26 +++ test/core/image_processing/Jamfile | 17 ++ test/core/image_processing/binary.cpp | 71 +++++++ test/core/image_processing/truncate.cpp | 118 +++++++++++ 10 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 example/threshold.cpp create mode 100644 include/boost/gil/image_processing/threshold.hpp create mode 100644 test/core/image_processing/CMakeLists.txt create mode 100644 test/core/image_processing/Jamfile create mode 100644 test/core/image_processing/binary.cpp create mode 100644 test/core/image_processing/truncate.cpp diff --git a/example/threshold.cpp b/example/threshold.cpp new file mode 100644 index 000000000..565ff1ca1 --- /dev/null +++ b/example/threshold.cpp @@ -0,0 +1,29 @@ +// +// 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 + +using namespace boost::gil; + +int main() +{ + rgb8_image_t img; + read_image("test.jpg",img, jpeg_tag{}); + rgb8_image_t img_out(img.dimensions()); + +// performing binary threshold on each channel of the image +// if the pixel value is more than 150 than it will be set to 255 else to 0 + boost::gil::threshold_binary(const_view(img), view(img_out), 150, 255); + write_view("out-threshold-binary.jpg", view(img_out), jpeg_tag{}); + +// if the pixel value is more than 150 than it will be set to 150 else no change + boost::gil::threshold_truncate(const_view(img), view(img_out), 150, threshold_truncate_mode::threshold); + write_view("out-threshold-binary_inv.jpg", view(img_out), jpeg_tag{}); + + return 0; +} diff --git a/include/boost/gil.hpp b/include/boost/gil.hpp index baf431eec..b64f89a33 100644 --- a/include/boost/gil.hpp +++ b/include/boost/gil.hpp @@ -44,5 +44,6 @@ #include #include #include +#include #endif diff --git a/include/boost/gil/image_processing/threshold.hpp b/include/boost/gil/image_processing/threshold.hpp new file mode 100644 index 000000000..61e25e862 --- /dev/null +++ b/include/boost/gil/image_processing/threshold.hpp @@ -0,0 +1,184 @@ +// +// 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) +// +#ifndef BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP +#define BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP + +#include +#include + +namespace boost { namespace gil { + + +namespace detail { + +template +< + typename SourceChannelT, + typename ResultChannelT, + typename SrcView, + typename DstView, + typename Operator +> +void threshold_impl +( + SrcView const& src_view, + DstView const& dst_view, + Operator const& threshold_op +) +{ + //template argument validation + gil_function_requires>(); + gil_function_requires>(); + gil_function_requires::type, + typename color_space_type::type> + >(); + + //iterate over the image chaecking each pixel value for the threshold + for (std::ptrdiff_t y = 0; y < src_view.height(); y++) + { + typename SrcView::x_iterator src_it = src_view.row_begin(y); + typename DstView::x_iterator dst_it = dst_view.row_begin(y); + + for (std::ptrdiff_t x = 0; x < src_view.width(); x++) + { + static_transform(src_it[x], dst_it[x], threshold_op); + } + } +} + +} //namespace boost::gil::detail + +enum class threshold_direction { regular, inverse }; +enum class threshold_optimal_value { otsu, triangle }; +enum class threshold_truncate_mode { threshold, zero }; + +/*! +Takes an image view and performes binary thresholding operation on each chennel. +If direction is regular: +values greater than or equal to threshold_value will be set to max_value else set to 0 +If direction is inverse: +values greater than or equal to threshold_value will be set to 0 else set to max_value +*/ +template +void threshold_binary +( + SrcView const& src_view, + DstView const& dst_view, + typename channel_type::type threshold_value, + typename channel_type::type max_value, + threshold_direction direction = threshold_direction::regular +) +{ + //deciding output channel type and creating functor + typedef typename channel_type::type source_channel_t; + typedef typename channel_type::type result_channel_t; + + + if (direction == threshold_direction::regular) + { + detail::threshold_impl(src_view, dst_view, + [threshold_value, max_value](source_channel_t px) -> + result_channel_t { return px >= threshold_value ? max_value : 0; }); + } + else + { + detail::threshold_impl(src_view, dst_view, + [threshold_value, max_value](source_channel_t px) -> + result_channel_t { return px >= threshold_value ? 0 : max_value; }); + } +} + +/*! +Takes an image view and performes binary thresholding operation on each chennel. +If direction is regular: +values more or equal to threshold_value will be set to maximum numeric limit of channel else 0 +If direction is inverse: +values more than or equal to threshold_value will be set to 0 else maximum numeric limit of channel +*/ +template +void threshold_binary +( + SrcView const& src_view, + DstView const& dst_view, + typename channel_type::type threshold_value, + threshold_direction direction = threshold_direction::regular +) +{ + //deciding output channel type and creating functor + typedef typename channel_type::type source_channel_t; + typedef typename channel_type::type result_channel_t; + + result_channel_t max_value = std::numeric_limits::max(); + + threshold_binary(src_view, dst_view, threshold_value, max_value, direction); +} + +/*! +Takes an image view and performes truncating threshold operation on each chennel. +If mode is truncate and direction is regular: +values greater than threshold_value will be set to threshold_value else no change +If mode is truncate and direction is inverse: +values less than threshold_value will be set to threshold_value else no change +If mode is zeo and direction is regular: +values less than threshold_value will be set to 0 else no change +If mode is zero and direction is inverse: +values more than threshold_value will be set to 0 else no change +*/ +template +void threshold_truncate +( + SrcView const& src_view, + DstView const& dst_view, + typename channel_type::type threshold_value, + threshold_truncate_mode mode = threshold_truncate_mode::threshold, + threshold_direction direction = threshold_direction::regular +) +{ + //deciding output channel type and creating functor + typedef typename channel_type::type source_channel_t; + typedef typename channel_type::type result_channel_t; + + std::function threshold_logic; + + if (mode == threshold_truncate_mode::threshold) + { + if (direction == threshold_direction::regular) + { + detail::threshold_impl(src_view, dst_view, + [threshold_value](source_channel_t px) -> result_channel_t + { return px >= threshold_value ? threshold_value : px; }); + } + else + { + detail::threshold_impl(src_view, dst_view, + [threshold_value](source_channel_t px) -> result_channel_t + { return px >= threshold_value ? px : threshold_value; }); + } + } + else + { + if (direction == threshold_direction::regular) + { + detail::threshold_impl(src_view, dst_view, + [threshold_value](source_channel_t px) -> result_channel_t + { return px >= threshold_value ? px : 0; }); + } + else + { + detail::threshold_impl(src_view, dst_view, + [threshold_value](source_channel_t px) -> result_channel_t + { return px >= threshold_value ? 0 : px; }); + } + } +} + +}} //namespace boost::gil + +#endif //BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2934673f7..a0a2364ae 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,4 +15,3 @@ add_subdirectory(extension) # if(GIL_BUILD_HEADERS_TESTS) # add_subdirectory(headers) # endif() - diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 2cabce2eb..c119300d9 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -37,3 +37,4 @@ add_subdirectory(locator) add_subdirectory(image) add_subdirectory(image_view) add_subdirectory(algorithm) +add_subdirectory(image_processing) diff --git a/test/core/Jamfile b/test/core/Jamfile index 6467b0bf0..958ba324b 100644 --- a/test/core/Jamfile +++ b/test/core/Jamfile @@ -28,5 +28,6 @@ build-project locator ; build-project image ; build-project image_view ; build-project algorithm ; +build-project image_processing ; run promote_integral.cpp /boost/test//boost_unit_test_framework : : : shared:BOOST_TEST_DYN_LINK=1 ; diff --git a/test/core/image_processing/CMakeLists.txt b/test/core/image_processing/CMakeLists.txt new file mode 100644 index 000000000..564fb4548 --- /dev/null +++ b/test/core/image_processing/CMakeLists.txt @@ -0,0 +1,26 @@ +# Boost.GIL (Generic Image Library) - tests +# +# 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) +# +foreach(_name + binary + truncate) + set(_target test_threshold_${_name}) + + add_executable(${_target} "") + target_sources(${_target} PRIVATE ${_name}.cpp) + target_link_libraries(${_target} + PRIVATE + gil_compile_options + gil_include_directories + gil_dependencies) + target_compile_definitions(${_target} PRIVATE BOOST_GIL_USE_CONCEPT_CHECK) + add_test(NAME test.threshold.${_name} COMMAND ${_target}) + + unset(_name) + unset(_target) +endforeach() diff --git a/test/core/image_processing/Jamfile b/test/core/image_processing/Jamfile new file mode 100644 index 000000000..a93a7a4f8 --- /dev/null +++ b/test/core/image_processing/Jamfile @@ -0,0 +1,17 @@ +# Boost.GIL (Generic Image Library) - tests +# +# 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) +# +import testing ; + +project + : requirements + .. + ; + +run binary.cpp ; +run truncate.cpp ; diff --git a/test/core/image_processing/binary.cpp b/test/core/image_processing/binary.cpp new file mode 100644 index 000000000..4e0e60246 --- /dev/null +++ b/test/core/image_processing/binary.cpp @@ -0,0 +1,71 @@ +// +// 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 +#include +#include + + +namespace gil = boost::gil; + +int main() +{ + int height = 4; + int width= 4; + gil::gray8_image_t original(width, height), threshold(width, height), + expected(width, height); + + //filling original view's upper half part with gray pixels of value 50 + //filling original view's lower half part with gray pixels of value 150 + gil::fill_pixels(gil::subimage_view(gil::view(original), 0, 0, original.width(), + original.height()/2), gil::gray8_pixel_t(50)); + gil::fill_pixels(gil::subimage_view(gil::view(original), 0, original.height()/2, + original.width(), original.height()/2), gil::gray8_pixel_t(150)); + + + /*------------------------------Threshold Binary-----------------------------------*/ + + //expected view after thresholding of the original view with threshold value of 100 + //filling expected view's upper half part with gray pixels of value 0 + //filling expected view's lower half part with gray pixels of value 255 + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(0)); + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(255)); + + gil::threshold_binary(gil::view(original), gil::view(threshold), 100); + + //comparing threshold view generated by the function with the expected view + BOOST_TEST(gil::equal_pixels(gil::view(threshold), gil::view(expected))); + + + /*-----------------------Threshold Binary with inverse----------------------------*/ + + //expected view after thresholding of the original view with threshold value of 100 + //filling expected view's upper half part with gray pixels of value 200 + //filling expected view's lower half part with gray pixels of value 0 + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(200)); + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(0)); + + gil::threshold_binary + ( + gil::view(original), + gil::view(threshold), + 100, + 200, + gil::threshold_direction::inverse + ); + + //comparing threshold view generated by the function with the expected view + BOOST_TEST(gil::equal_pixels(gil::view(threshold), gil::view(expected))); + + return boost::report_errors(); +} diff --git a/test/core/image_processing/truncate.cpp b/test/core/image_processing/truncate.cpp new file mode 100644 index 000000000..4ad048c44 --- /dev/null +++ b/test/core/image_processing/truncate.cpp @@ -0,0 +1,118 @@ +// +// 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 +#include +#include + + +namespace gil = boost::gil; + +int main() +{ + int height = 4; + int width = 4; + gil::gray8_image_t original(width, height), threshold(width, height), + expected(width, height); + + //filling original view's upper half part with gray pixels of value 50 + //filling original view's lower half part with gray pixels of value 150 + gil::fill_pixels(gil::subimage_view(gil::view(original), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(50)); + gil::fill_pixels(gil::subimage_view(gil::view(original), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(150)); + + + /*--------------------------------Mode Threshold------------------------------------*/ + /*-------------------------------Direction Regular----------------------------------*/ + + //expected view after thresholding of the original view with threshold value of 100 + //filling expected view's upper half part with gray pixels of value 50 + //filling expected view's lower half part with gray pixels of value 100 + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(50)); + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(100)); + + gil::threshold_truncate(gil::view(original), gil::view(threshold), 100); + + //comparing threshold view generated by the function with the expected view + BOOST_TEST(gil::equal_pixels(gil::view(threshold), gil::view(expected))); + + /*--------------------------------Mode Threshold------------------------------------*/ + /*-------------------------------Direction Inverse----------------------------------*/ + + //expected view after thresholding of the original view with threshold value of 100 + //filling expected view's upper half part with gray pixels of value 100 + //filling expected view's lower half part with gray pixels of value 150 + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(100)); + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(150)); + + gil::threshold_truncate + ( + gil::view(original), + gil::view(threshold), + 100, + gil::threshold_truncate_mode::threshold, + gil::threshold_direction::inverse + ); + + //comparing threshold view generated by the function with the expected view + BOOST_TEST(gil::equal_pixels(gil::view(threshold), gil::view(expected))); + + /*-----------------------------------Mode Zero--------------------------------------*/ + /*-------------------------------Direction Regular----------------------------------*/ + + //expected view after thresholding of the original view with threshold value of 100 + //filling expected view's upper half part with gray pixels of value 0 + //filling expected view's lower half part with gray pixels of value 150 + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(0)); + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(150)); + + gil::threshold_truncate + ( + gil::view(original), + gil::view(threshold), + 100, + gil::threshold_truncate_mode::zero, + gil::threshold_direction::regular + ); + + //comparing threshold view generated by the function with the expected view + BOOST_TEST(gil::equal_pixels(gil::view(threshold), gil::view(expected))); + + /*-----------------------------------Mode Zero--------------------------------------*/ + /*-------------------------------Direction Inverse----------------------------------*/ + + //expected view after thresholding of the original view with threshold value of 100 + //filling expected view's upper half part with gray pixels of value 50 + //filling expected view's lower half part with gray pixels of value 0 + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, 0, original.width(), + original.height() / 2), gil::gray8_pixel_t(50)); + gil::fill_pixels(gil::subimage_view(gil::view(expected), 0, original.height() / 2, + original.width(), original.height() / 2), gil::gray8_pixel_t(0)); + + gil::threshold_truncate + ( + gil::view(original), + gil::view(threshold), + 100, + gil::threshold_truncate_mode::zero, + gil::threshold_direction::inverse + ); + + //comparing threshold view generated by the function with the expected view + BOOST_TEST(gil::equal_pixels(gil::view(threshold), gil::view(expected))); + + return boost::report_errors(); +}