// // 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 #include #include #include #include #include #include #include #include #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) { 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"); //iterate over the image checking 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 /// \addtogroup ImageProcessing /// @{ /// /// \brief Direction of image segmentation. /// The direction specifies which pixels are considered as corresponding to object /// and which pixels correspond to background. enum class threshold_direction { regular, ///< Consider values greater than threshold value inverse ///< Consider values less than or equal to threshold value }; /// \ingroup ImageProcessing /// \brief Method of optimal threshold value calculation. enum class threshold_optimal_value { otsu ///< \todo TODO }; /// \ingroup ImageProcessing /// \brief TODO enum class threshold_truncate_mode { threshold, ///< \todo TODO zero ///< \todo TODO }; enum class threshold_adaptive_method { mean, gaussian }; /// \ingroup ImageProcessing /// \brief Applies fixed threshold to each pixel of image view. /// Performs image binarization by thresholding channel value of each /// pixel of given image view. /// \param src_view - TODO /// \param dst_view - TODO /// \param threshold_value - TODO /// \param max_value - TODO /// \param threshold_direction - if regular, values greater than threshold_value are /// set to max_value else set to 0; if inverse, values greater than threshold_value are /// 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 using source_channel_t = typename channel_type::type; using result_channel_t = typename channel_type::type; 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; }); } } /// \ingroup ImageProcessing /// \brief Applies fixed threshold to each pixel of image view. /// Performs image binarization by thresholding channel value of each /// pixel of given image view. /// This variant of threshold_binary automatically deduces maximum value for each channel /// of pixel based on channel type. /// If direction is regular, values greater than threshold_value will be set to maximum /// numeric limit of channel else 0. /// If direction is inverse, values greater than 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 using result_channel_t = typename channel_type::type; result_channel_t max_value = (std::numeric_limits::max)(); threshold_binary(src_view, dst_view, threshold_value, max_value, direction); } /// \ingroup ImageProcessing /// \brief Applies truncating threshold to each pixel of image view. /// Takes an image view and performs truncating threshold operation on each chennel. /// If mode is threshold and direction is regular: /// values greater than threshold_value will be set to threshold_value else no change /// If mode is threshold and direction is inverse: /// values less than or equal to threshold_value will be set to threshold_value else no change /// If mode is zero and direction is regular: /// values less than or equal to 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 using source_channel_t = typename channel_type::type; using result_channel_t = typename channel_type::type; 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 detail{ template void otsu_impl(SrcView const& src_view, DstView const& dst_view, threshold_direction direction) { //deciding output channel type and creating functor using source_channel_t = typename channel_type::type; std::array histogram{}; //initial value of min is set to maximum possible value to compare histogram data //initial value of max is set to minimum possible value to compare histogram data auto min = (std::numeric_limits::max)(), max = (std::numeric_limits::min)(); if (sizeof(source_channel_t) > 1 || std::is_signed::value) { //iterate over the image to find the min and max pixel values for (std::ptrdiff_t y = 0; y < src_view.height(); y++) { typename SrcView::x_iterator src_it = src_view.row_begin(y); for (std::ptrdiff_t x = 0; x < src_view.width(); x++) { if (src_it[x] < min) min = src_it[x]; if (src_it[x] > min) min = src_it[x]; } } //making histogram for (std::ptrdiff_t y = 0; y < src_view.height(); y++) { typename SrcView::x_iterator src_it = src_view.row_begin(y); for (std::ptrdiff_t x = 0; x < src_view.width(); x++) { histogram[((src_it[x] - min) * 255) / (max - min)]++; } } } else { //making histogram for (std::ptrdiff_t y = 0; y < src_view.height(); y++) { typename SrcView::x_iterator src_it = src_view.row_begin(y); for (std::ptrdiff_t x = 0; x < src_view.width(); x++) { histogram[src_it[x]]++; } } } //histData = histogram data //sum = total (background + foreground) //sumB = sum background //wB = weight background //wf = weight foreground //varMax = tracking the maximum known value of between class variance //mB = mu background //mF = mu foreground //varBeetween = between class variance //http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html //https://www.ipol.im/pub/art/2016/158/ std::ptrdiff_t total_pixel = src_view.height() * src_view.width(); std::ptrdiff_t sum_total = 0, sum_back = 0; std::size_t weight_back = 0, weight_fore = 0, threshold = 0; double var_max = 0, mean_back, mean_fore, var_intra_class; for (std::size_t t = 0; t < 256; t++) { sum_total += t * histogram[t]; } for (int t = 0; t < 256; t++) { weight_back += histogram[t]; // Weight Background if (weight_back == 0) continue; weight_fore = total_pixel - weight_back; // Weight Foreground if (weight_fore == 0) break; sum_back += t * histogram[t]; mean_back = sum_back / weight_back; // Mean Background mean_fore = (sum_total - sum_back) / weight_fore; // Mean Foreground // Calculate Between Class Variance var_intra_class = weight_back * weight_fore * (mean_back - mean_fore) * (mean_back - mean_fore); // Check if new maximum found if (var_intra_class > var_max) { var_max = var_intra_class; threshold = t; } } if (sizeof(source_channel_t) > 1 && std::is_unsigned::value) { threshold_binary(src_view, dst_view, (threshold * (max - min) / 255) + min, direction); } else { threshold_binary(src_view, dst_view, threshold, direction); } } } //namespace detail template void threshold_optimal ( SrcView const& src_view, DstView const& dst_view, threshold_optimal_value mode = threshold_optimal_value::otsu, threshold_direction direction = threshold_direction::regular ) { if (mode == threshold_optimal_value::otsu) { for (std::size_t i = 0; i < src_view.num_channels(); i++) { detail::otsu_impl (nth_channel_view(src_view, i), nth_channel_view(dst_view, i), direction); } } } namespace detail { template < typename SourceChannelT, typename ResultChannelT, typename SrcView, typename DstView, typename Operator > void adaptive_impl ( SrcView const& src_view, SrcView const& convolved_view, DstView const& dst_view, Operator const& threshold_op ) { //template argument validation 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"); //iterate over the image checking 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 SrcView::x_iterator convolved_it = convolved_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], convolved_it[x], dst_it[x], threshold_op); } } } } //namespace boost::gil::detail template void threshold_adaptive ( SrcView const& src_view, DstView const& dst_view, typename channel_type::type max_value, std::size_t kernel_size, threshold_adaptive_method method = threshold_adaptive_method::mean, threshold_direction direction = threshold_direction::regular, typename channel_type::type constant = 0 ) { BOOST_ASSERT_MSG((kernel_size % 2 != 0), "Kernel size must be an odd number"); typedef typename channel_type::type source_channel_t; typedef typename channel_type::type result_channel_t; image temp_img(src_view.width(), src_view.height()); typename image::view_t temp_view = view(temp_img); SrcView temp_conv(temp_view); if (method == threshold_adaptive_method::mean) { std::vector mean_kernel_values(kernel_size, 1.0f/kernel_size); kernel_1d kernel(mean_kernel_values.begin(), kernel_size, kernel_size/2); detail::convolve_1d < pixel >(src_view, kernel, temp_view); } else if (method == threshold_adaptive_method::gaussian) { detail::kernel_2d kernel = generate_gaussian_kernel(kernel_size, 1.0); convolve_2d(src_view, kernel, temp_view); } if (direction == threshold_direction::regular) { detail::adaptive_impl(src_view, temp_conv, dst_view, [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t { return px > (threshold - constant) ? max_value : 0; }); } else { detail::adaptive_impl(src_view, temp_conv, dst_view, [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t { return px > (threshold - constant) ? 0 : max_value; }); } } template void threshold_adaptive ( SrcView const& src_view, DstView const& dst_view, std::size_t kernel_size, threshold_adaptive_method method = threshold_adaptive_method::mean, threshold_direction direction = threshold_direction::regular, int constant = 0 ) { //deciding output channel type and creating functor typedef typename channel_type::type result_channel_t; result_channel_t max_value = (std::numeric_limits::max)(); threshold_adaptive(src_view, dst_view, max_value, kernel_size, method, direction, constant); } /// @} }} //namespace boost::gil #endif //BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP