Added thirdparty: boost library

This commit is contained in:
Viacheslav Demydiuk
2024-01-06 19:55:56 +02:00
parent bf49f439e1
commit bccd1e7051
15683 changed files with 3239840 additions and 0 deletions
@@ -0,0 +1,302 @@
//
// Copyright 2020 Debabrata Mandal <mandaldebabrata123@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_ADAPTIVE_HISTOGRAM_EQUALIZATION_HPP
#define BOOST_GIL_IMAGE_PROCESSING_ADAPTIVE_HISTOGRAM_EQUALIZATION_HPP
#include <boost/gil/algorithm.hpp>
#include <boost/gil/histogram.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_processing/histogram_equalization.hpp>
#include <boost/gil/image_view_factory.hpp>
#include <cmath>
#include <map>
#include <vector>
namespace boost { namespace gil {
/////////////////////////////////////////
/// Adaptive Histogram Equalization(AHE)
/////////////////////////////////////////
/// \defgroup AHE AHE
/// \brief Contains implementation and description of the algorithm used to compute
/// adaptive histogram equalization of input images. Naming for the AHE functions
/// are done in the following way
/// <feature-1>_<feature-2>_.._<feature-n>ahe
/// For example, for AHE done using local (non-overlapping) tiles/blocks and
/// final output interpolated among tiles , it is called
/// non_overlapping_interpolated_clahe
///
namespace detail {
/// \defgroup AHE-helpers AHE-helpers
/// \brief AHE helper functions
/// \fn double actual_clip_limit
/// \ingroup AHE-helpers
/// \brief Computes the actual clip limit given a clip limit value using binary search.
/// Reference - Adaptive Histogram Equalization and Its Variations
/// (http://www.cs.unc.edu/techreports/86-013.pdf, Pg - 15)
///
template <typename SrcHist>
double actual_clip_limit(SrcHist const& src_hist, double cliplimit = 0.03)
{
double epsilon = 1.0;
using value_t = typename SrcHist::value_type;
double sum = src_hist.sum();
std::size_t num_bins = src_hist.size();
cliplimit = sum * cliplimit;
long low = 0, high = cliplimit, middle = low;
while (high - low >= 1)
{
middle = (low + high + 1) >> 1;
long excess = 0;
std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
if (v.second > middle)
excess += v.second - middle;
});
if (std::abs(excess - (cliplimit - middle) * num_bins) < epsilon)
break;
else if (excess > (cliplimit - middle) * num_bins)
high = middle - 1;
else
low = middle + 1;
}
return middle / sum;
}
/// \fn void clip_and_redistribute
/// \ingroup AHE-helpers
/// \brief Clips and redistributes excess pixels based on the actual clip limit value
/// obtained from the other helper function actual_clip_limit
/// Reference - Graphic Gems 4, Pg. 474
/// (http://cas.xav.free.fr/Graphics%20Gems%204%20-%20Paul%20S.%20Heckbert.pdf)
///
template <typename SrcHist, typename DstHist>
void clip_and_redistribute(SrcHist const& src_hist, DstHist& dst_hist, double clip_limit = 0.03)
{
using value_t = typename SrcHist::value_type;
double sum = src_hist.sum();
double actual_clip_value = detail::actual_clip_limit(src_hist, clip_limit);
// double actual_clip_value = clip_limit;
long actual_clip_limit = actual_clip_value * sum;
double excess = 0;
std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
if (v.second > actual_clip_limit)
excess += v.second - actual_clip_limit;
});
std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
if (v.second >= actual_clip_limit)
dst_hist[dst_hist.key_from_tuple(v.first)] = clip_limit * sum;
else
dst_hist[dst_hist.key_from_tuple(v.first)] = v.second + excess / src_hist.size();
});
long rem = long(excess) % src_hist.size();
if (rem == 0)
return;
long period = round(src_hist.size() / rem);
std::size_t index = 0;
while (rem)
{
if (dst_hist(index) >= clip_limit * sum)
{
index = (index + 1) % src_hist.size();
}
dst_hist(index)++;
rem--;
index = (index + period) % src_hist.size();
}
}
} // namespace detail
/// \fn void non_overlapping_interpolated_clahe
/// \ingroup AHE
/// @param src_view Input Source image view
/// @param dst_view Output Output image view
/// @param tile_width_x Input Tile width along x-axis to apply HE
/// @param tile_width_y Input Tile width along x-axis to apply HE
/// @param clip_limit Input Clipping limit to be applied
/// @param bin_width Input Bin widths for histogram
/// @param mask Input Specify if mask is to be used
/// @param src_mask Input Mask on input image to ignore specified pixels
/// \brief Performs local histogram equalization on tiles of size (tile_width_x, tile_width_y)
/// Then uses the clip limit to redistribute excess pixels above the limit uniformly to
/// other bins. The clip limit is specified as a fraction i.e. a bin's value is clipped
/// if bin_value >= clip_limit * (Total number of pixels in the tile)
///
template <typename SrcView, typename DstView>
void non_overlapping_interpolated_clahe(
SrcView const& src_view,
DstView const& dst_view,
std::ptrdiff_t tile_width_x = 20,
std::ptrdiff_t tile_width_y = 20,
double clip_limit = 0.03,
std::size_t bin_width = 1.0,
bool mask = false,
std::vector<std::vector<bool>> src_mask = {})
{
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 same color space");
using source_channel_t = typename channel_type<SrcView>::type;
using dst_channel_t = typename channel_type<DstView>::type;
using coord_t = typename SrcView::x_coord_t;
std::size_t const channels = num_channels<SrcView>::value;
coord_t const width = src_view.width();
coord_t const height = src_view.height();
// Find control points
std::vector<coord_t> sample_x;
coord_t sample_x1 = tile_width_x / 2;
coord_t sample_y1 = tile_width_y / 2;
auto extend_left = tile_width_x;
auto extend_top = tile_width_y;
auto extend_right = (tile_width_x - width % tile_width_x) % tile_width_x + tile_width_x;
auto extend_bottom = (tile_width_y - height % tile_width_y) % tile_width_y + tile_width_y;
auto new_width = width + extend_left + extend_right;
auto new_height = height + extend_top + extend_bottom;
image<typename SrcView::value_type> padded_img(new_width, new_height);
auto top_left_x = tile_width_x;
auto top_left_y = tile_width_y;
auto bottom_right_x = tile_width_x + width;
auto bottom_right_y = tile_width_y + height;
copy_pixels(src_view, subimage_view(view(padded_img), top_left_x, top_left_y, width, height));
for (std::size_t k = 0; k < channels; k++)
{
std::vector<histogram<source_channel_t>> prev_row(new_width / tile_width_x),
next_row((new_width / tile_width_x));
std::vector<std::map<source_channel_t, source_channel_t>> prev_map(
new_width / tile_width_x),
next_map((new_width / tile_width_x));
coord_t prev = 0, next = 1;
auto channel_view = nth_channel_view(view(padded_img), k);
for (std::ptrdiff_t i = top_left_y; i < bottom_right_y; ++i)
{
if ((i - sample_y1) / tile_width_y >= next || i == top_left_y)
{
if (i != top_left_y)
{
prev = next;
next++;
}
prev_row = next_row;
prev_map = next_map;
for (std::ptrdiff_t j = sample_x1; j < new_width; j += tile_width_x)
{
auto img_view = subimage_view(
channel_view, j - sample_x1, next * tile_width_y,
std::max<int>(
std::min<int>(tile_width_x + j - sample_x1, bottom_right_x) -
(j - sample_x1),
0),
std::max<int>(
std::min<int>((next + 1) * tile_width_y, bottom_right_y) -
next * tile_width_y,
0));
fill_histogram(
img_view, next_row[(j - sample_x1) / tile_width_x], bin_width, false,
false);
detail::clip_and_redistribute(
next_row[(j - sample_x1) / tile_width_x],
next_row[(j - sample_x1) / tile_width_x], clip_limit);
next_map[(j - sample_x1) / tile_width_x] =
histogram_equalization(next_row[(j - sample_x1) / tile_width_x]);
}
}
bool prev_row_mask = 1, next_row_mask = 1;
if (prev == 0)
prev_row_mask = false;
else if (next + 1 == new_height / tile_width_y)
next_row_mask = false;
for (std::ptrdiff_t j = top_left_x; j < bottom_right_x; ++j)
{
bool prev_col_mask = true, next_col_mask = true;
if ((j - sample_x1) / tile_width_x == 0)
prev_col_mask = false;
else if ((j - sample_x1) / tile_width_x + 1 == new_width / tile_width_x - 1)
next_col_mask = false;
// Bilinear interpolation
point_t top_left(
(j - sample_x1) / tile_width_x * tile_width_x + sample_x1,
prev * tile_width_y + sample_y1);
point_t top_right(top_left.x + tile_width_x, top_left.y);
point_t bottom_left(top_left.x, top_left.y + tile_width_y);
point_t bottom_right(top_left.x + tile_width_x, top_left.y + tile_width_y);
long double x_diff = top_right.x - top_left.x;
long double y_diff = bottom_left.y - top_left.y;
long double x1 = (j - top_left.x) / x_diff;
long double x2 = (top_right.x - j) / x_diff;
long double y1 = (i - top_left.y) / y_diff;
long double y2 = (bottom_left.y - i) / y_diff;
if (prev_row_mask == 0)
y1 = 1;
else if (next_row_mask == 0)
y2 = 1;
if (prev_col_mask == 0)
x1 = 1;
else if (next_col_mask == 0)
x2 = 1;
long double numerator =
((prev_row_mask & prev_col_mask) * x2 *
prev_map[(top_left.x - sample_x1) / tile_width_x][channel_view(j, i)] +
(prev_row_mask & next_col_mask) * x1 *
prev_map[(top_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) *
y2 +
((next_row_mask & prev_col_mask) * x2 *
next_map[(bottom_left.x - sample_x1) / tile_width_x][channel_view(j, i)] +
(next_row_mask & next_col_mask) * x1 *
next_map[(bottom_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) *
y1;
if (mask && !src_mask[i - top_left_y][j - top_left_x])
{
dst_view(j - top_left_x, i - top_left_y) =
channel_convert<dst_channel_t>(
static_cast<source_channel_t>(channel_view(i, j)));
}
else
{
dst_view(j - top_left_x, i - top_left_y) =
channel_convert<dst_channel_t>(static_cast<source_channel_t>(numerator));
}
}
}
}
}
}} //namespace boost::gil
#endif
+445
View File
@@ -0,0 +1,445 @@
//
// Copyright 2005-2007 Adobe Systems Incorporated
// Copyright 2019 Miral Shah <miralshah2211@gmail.com>
// Copyright 2019-2021 Pranam Lashkari <plashkari628@gmail.com>
//
// Distributed under 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_CONVOLVE_HPP
#define BOOST_GIL_IMAGE_PROCESSING_CONVOLVE_HPP
#include <boost/gil/image_processing/kernel.hpp>
#include <boost/gil/algorithm.hpp>
#include <boost/gil/image_view_factory.hpp>
#include <boost/gil/metafunctions.hpp>
#include <boost/gil/pixel_numeric_operations.hpp>
#include <boost/assert.hpp>
#include <algorithm>
#include <cstddef>
#include <functional>
#include <type_traits>
#include <vector>
namespace boost { namespace gil {
// 2D spatial seperable convolutions and cross-correlations
namespace detail {
/// \brief Computes the cross-correlation of 1D kernel with rows of an image.
/// \tparam PixelAccum - Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView - Specifies the type of gil view of source image which is to be row correlated
/// with the kernel.
/// \tparam Kernel - Specifies the type of 1D kernel which will be row correlated with source image.
/// \tparam DstView - Specifies the type of gil view which will store the result of row
/// correlation between source image and kernel.
/// \tparam Correlator - Specifies the type of correlator which should be used for performing
/// correlation.
/// \param src_view - Gil view of source image used in correlation.
/// \param kernel - 1D kernel which will be correlated with source image.
/// \param dst_view - Gil view which will store the result of row correlation between "src_view"
/// and "kernel".
/// \param option - Specifies the manner in which boundary pixels of "dst_view" should be computed.
/// \param correlator - Correlator which will be used for performing correlation.
template
<
typename PixelAccum,
typename SrcView,
typename Kernel,
typename DstView,
typename Correlator
>
void correlate_rows_impl(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option,
Correlator correlator)
{
BOOST_ASSERT(src_view.dimensions() == dst_view.dimensions());
BOOST_ASSERT(kernel.size() != 0);
if(kernel.size() == 1)
{
// Reduces to a multiplication
view_multiplies_scalar<PixelAccum>(src_view, *kernel.begin(), dst_view);
return;
}
using src_pixel_ref_t = typename pixel_proxy<typename SrcView::value_type>::type;
using dst_pixel_ref_t = typename pixel_proxy<typename DstView::value_type>::type;
using x_coord_t = typename SrcView::x_coord_t;
using y_coord_t = typename SrcView::y_coord_t;
x_coord_t const width = src_view.width();
y_coord_t const height = src_view.height();
if (width == 0)
return;
PixelAccum acc_zero;
pixel_zeros_t<PixelAccum>()(acc_zero);
if (option == boundary_option::output_ignore || option == boundary_option::output_zero)
{
typename DstView::value_type dst_zero;
pixel_assigns_t<PixelAccum, dst_pixel_ref_t>()(acc_zero, dst_zero);
if (width < static_cast<x_coord_t>(kernel.size()))
{
if (option == boundary_option::output_zero)
fill_pixels(dst_view, dst_zero);
}
else
{
std::vector<PixelAccum> buffer(width);
for (y_coord_t y = 0; y < height; ++y)
{
assign_pixels(src_view.row_begin(y), src_view.row_end(y), &buffer.front());
typename DstView::x_iterator it_dst = dst_view.row_begin(y);
if (option == boundary_option::output_zero)
std::fill_n(it_dst, kernel.left_size(), dst_zero);
it_dst += kernel.left_size();
correlator(&buffer.front(), &buffer.front() + width + 1 - kernel.size(),
kernel.begin(), it_dst);
it_dst += width + 1 - kernel.size();
if (option == boundary_option::output_zero)
std::fill_n(it_dst, kernel.right_size(), dst_zero);
}
}
}
else
{
std::vector<PixelAccum> buffer(width + kernel.size() - 1);
for (y_coord_t y = 0; y < height; ++y)
{
PixelAccum *it_buffer = &buffer.front();
if (option == boundary_option::extend_padded)
{
assign_pixels(
src_view.row_begin(y) - kernel.left_size(),
src_view.row_end(y) + kernel.right_size(),
it_buffer);
}
else if (option == boundary_option::extend_zero)
{
std::fill_n(it_buffer, kernel.left_size(), acc_zero);
it_buffer += kernel.left_size();
assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer);
it_buffer += width;
std::fill_n(it_buffer, kernel.right_size(), acc_zero);
}
else if (option == boundary_option::extend_constant)
{
PixelAccum filler;
pixel_assigns_t<src_pixel_ref_t, PixelAccum>()(*src_view.row_begin(y), filler);
std::fill_n(it_buffer, kernel.left_size(), filler);
it_buffer += kernel.left_size();
assign_pixels(src_view.row_begin(y), src_view.row_end(y), it_buffer);
it_buffer += width;
pixel_assigns_t<src_pixel_ref_t, PixelAccum>()(src_view.row_end(y)[-1], filler);
std::fill_n(it_buffer, kernel.right_size(), filler);
}
correlator(
&buffer.front(), &buffer.front() + width,
kernel.begin(),
dst_view.row_begin(y));
}
}
}
/// \brief Provides functionality for performing 1D correlation between the kernel and a buffer
/// storing row pixels of source image. Kernel size is to be provided through constructor for all
/// instances.
template <typename PixelAccum>
class correlator_n
{
public:
correlator_n(std::size_t size) : size_(size) {}
template <typename SrcIterator, typename KernelIterator, typename DstIterator>
void operator()(
SrcIterator src_begin,
SrcIterator src_end,
KernelIterator kernel_begin,
DstIterator dst_begin)
{
correlate_pixels_n<PixelAccum>(src_begin, src_end, kernel_begin, size_, dst_begin);
}
private:
std::size_t size_{0};
};
/// \brief Provides functionality for performing 1D correlation between the kernel and a buffer
/// storing row pixels of source image. Kernel size is a template parameter and must be
/// compulsorily specified while using.
template <std::size_t Size, typename PixelAccum>
struct correlator_k
{
template <typename SrcIterator, typename KernelIterator, typename DstIterator>
void operator()(
SrcIterator src_begin,
SrcIterator src_end,
KernelIterator kernel_begin,
DstIterator dst_begin)
{
correlate_pixels_k<Size, PixelAccum>(src_begin, src_end, kernel_begin, dst_begin);
}
};
} // namespace detail
/// \ingroup ImageAlgorithms
/// \brief Correlate 1D variable-size kernel along the rows of image.
/// \tparam PixelAccum Specifies tha data type which will be used while creating buffer container
/// which is utilized for holding source image pixels after applying appropriate boundary
/// manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be row correlated with source image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void correlate_rows(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
detail::correlate_rows_impl<PixelAccum>(
src_view, kernel, dst_view, option, detail::correlator_n<PixelAccum>(kernel.size()));
}
/// \ingroup ImageAlgorithms
/// \brief Correlates 1D variable-size kernel along the columns of image.
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be column correlated with source
/// image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void correlate_cols(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
correlate_rows<PixelAccum>(
transposed_view(src_view), kernel, transposed_view(dst_view), option);
}
/// \ingroup ImageAlgorithms
/// \brief Convolves 1D variable-size kernel along the rows of image.
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be row convoluted with source image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void convolve_rows(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
correlate_rows<PixelAccum>(src_view, reverse_kernel(kernel), dst_view, option);
}
/// \ingroup ImageAlgorithms
/// \brief Convolves 1D variable-size kernel along the columns of image.
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be column convoluted with source
/// image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void convolve_cols(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
convolve_rows<PixelAccum>(
transposed_view(src_view), kernel, transposed_view(dst_view), option);
}
/// \ingroup ImageAlgorithms
/// \brief Correlate 1D fixed-size kernel along the rows of image.
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be row correlated with source image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void correlate_rows_fixed(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
using correlator = detail::correlator_k<Kernel::static_size, PixelAccum>;
detail::correlate_rows_impl<PixelAccum>(src_view, kernel, dst_view, option, correlator{});
}
/// \ingroup ImageAlgorithms
/// \brief Correlate 1D fixed-size kernel along the columns of image
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be column correlated with source
/// image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum,typename SrcView,typename Kernel,typename DstView>
BOOST_FORCEINLINE
void correlate_cols_fixed(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
correlate_rows_fixed<PixelAccum>(
transposed_view(src_view), kernel, transposed_view(dst_view), option);
}
/// \ingroup ImageAlgorithms
/// \brief Convolve 1D fixed-size kernel along the rows of image
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be row convolved with source image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void convolve_rows_fixed(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
correlate_rows_fixed<PixelAccum>(src_view, reverse_kernel(kernel), dst_view, option);
}
/// \ingroup ImageAlgorithms
/// \brief Convolve 1D fixed-size kernel along the columns of image
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be column convolved with source
/// image.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void convolve_cols_fixed(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
convolve_rows_fixed<PixelAccum>(
transposed_view(src_view), kernel, transposed_view(dst_view), option);
}
namespace detail
{
/// \ingroup ImageAlgorithms
/// \brief Convolve 1D variable-size kernel along both rows and columns of image
/// \tparam PixelAccum Specifies tha data type which will be used for creating buffer container
/// utilized for holding source image pixels after applying appropriate boundary manipulations.
/// \tparam SrcView Models ImageViewConcept
/// \tparam Kernel Specifies the type of 1D kernel which will be used for 1D row and column
/// convolution.
/// \tparam DstView Models MutableImageViewConcept
template <typename PixelAccum, typename SrcView, typename Kernel, typename DstView>
BOOST_FORCEINLINE
void convolve_1d(
SrcView const& src_view,
Kernel const& kernel,
DstView const& dst_view,
boundary_option option = boundary_option::extend_zero)
{
convolve_rows<PixelAccum>(src_view, kernel, dst_view, option);
convolve_cols<PixelAccum>(dst_view, kernel, dst_view, option);
}
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_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())
{
aux_total +=
src_view(col_boundary, row_boundary)[0] *
kernel.at(flip_ker_row, flip_ker_col);
}
}
}
dst_view(view_col, view_row) = aux_total;
}
}
}
/// \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 Specifies the type of 2D kernel which will be used while convolution.
/// \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::detail
#endif
+139
View File
@@ -0,0 +1,139 @@
//
// Copyright 2019 Miral Shah <miralshah2211@gmail.com>
// Copyright 2021 Pranam Lashkari <plashkari628@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_FILTER_HPP
#define BOOST_GIL_IMAGE_PROCESSING_FILTER_HPP
#include <boost/gil/image_processing/kernel.hpp>
#include <boost/gil/image_processing/convolve.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/algorithm.hpp>
#include <cstddef>
#include <vector>
namespace boost { namespace gil {
template <typename SrcView, typename DstView>
void box_filter(
SrcView const& src_view,
DstView const& dst_view,
std::size_t kernel_size,
long int anchor = -1,
bool normalize=true,
boundary_option option = boundary_option::extend_zero
)
{
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");
std::vector<float> kernel_values;
if (normalize) { kernel_values.resize(kernel_size, 1.0f / float(kernel_size)); }
else { kernel_values.resize(kernel_size, 1.0f); }
if (anchor == -1) anchor = static_cast<int>(kernel_size / 2);
kernel_1d<float> kernel(kernel_values.begin(), kernel_size, anchor);
detail::convolve_1d
<
pixel<float, typename SrcView::value_type::layout_t>
>(src_view, kernel, dst_view, option);
}
template <typename SrcView, typename DstView>
void blur(
SrcView const& src_view,
DstView const& dst_view,
std::size_t kernel_size,
long int anchor = -1,
boundary_option option = boundary_option::extend_zero
)
{
box_filter(src_view, dst_view, kernel_size, anchor, true, option);
}
namespace detail
{
template <typename SrcView, typename DstView>
void filter_median_impl(SrcView const& src_view, DstView const& dst_view, std::size_t kernel_size)
{
std::size_t half_kernel_size = kernel_size / 2;
// deciding output channel type and creating functor
using src_channel_t = typename channel_type<SrcView>::type;
std::vector<src_channel_t> values;
values.reserve(kernel_size * kernel_size);
for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
{
typename DstView::x_iterator dst_it = dst_view.row_begin(y);
for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
{
auto sub_view = subimage_view(
src_view,
x - half_kernel_size, y - half_kernel_size,
kernel_size,
kernel_size
);
values.assign(sub_view.begin(), sub_view.end());
std::nth_element(values.begin(), values.begin() + (values.size() / 2), values.end());
dst_it[x] = values[values.size() / 2];
}
}
}
} // namespace detail
template <typename SrcView, typename DstView>
void median_filter(SrcView const& src_view, DstView const& dst_view, std::size_t kernel_size)
{
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");
std::size_t half_kernel_size = kernel_size / 2;
auto extended_img = extend_boundary(
src_view,
half_kernel_size,
boundary_option::extend_constant
);
auto extended_view = subimage_view(
view(extended_img),
half_kernel_size,
half_kernel_size,
src_view.width(),
src_view.height()
);
for (std::size_t channel = 0; channel < extended_view.num_channels(); channel++)
{
detail::filter_median_impl(
nth_channel_view(extended_view, channel),
nth_channel_view(dst_view, channel),
kernel_size
);
}
}
}} //namespace boost::gil
#endif // !BOOST_GIL_IMAGE_PROCESSING_FILTER_HPP
+82
View File
@@ -0,0 +1,82 @@
//
// Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_HARRIS_HPP
#define BOOST_GIL_IMAGE_PROCESSING_HARRIS_HPP
#include <boost/gil/image_view.hpp>
#include <boost/gil/typedefs.hpp>
#include <boost/gil/image_processing/kernel.hpp>
namespace boost { namespace gil {
/// \defgroup CornerDetectionAlgorithms
/// \brief Algorithms that are used to find corners in an image
///
/// These algorithms are used to find spots from which
/// sliding the window will produce large intensity change
/// \brief function to record Harris responses
/// \ingroup CornerDetectionAlgorithms
///
/// This algorithm computes Harris responses
/// for structure tensor represented by m11, m12_21, m22 views.
/// Note that m12_21 represents both entries (1, 2) and (2, 1).
/// Window length represents size of a window which is slided around
/// to compute sum of corresponding entries. k is a discrimination
/// constant against edges (usually in range 0.04 to 0.06).
/// harris_response is an out parameter that will contain the Harris responses.
template <typename T, typename Allocator>
void compute_harris_responses(
boost::gil::gray32f_view_t m11,
boost::gil::gray32f_view_t m12_21,
boost::gil::gray32f_view_t m22,
boost::gil::detail::kernel_2d<T, Allocator> weights,
float k,
boost::gil::gray32f_view_t harris_response)
{
if (m11.dimensions() != m12_21.dimensions() || m12_21.dimensions() != m22.dimensions()) {
throw std::invalid_argument("m prefixed arguments must represent"
" tensor from the same image");
}
std::ptrdiff_t const window_length = weights.size();
auto const width = m11.width();
auto const height = m11.height();
auto const half_length = window_length / 2;
for (auto y = half_length; y < height - half_length; ++y)
{
for (auto x = half_length; x < width - half_length; ++x)
{
float ddxx = 0;
float dxdy = 0;
float ddyy = 0;
for (gil::gray32f_view_t::coord_t y_kernel = 0;
y_kernel < window_length;
++y_kernel) {
for (gil::gray32f_view_t::coord_t x_kernel = 0;
x_kernel < window_length;
++x_kernel) {
ddxx += m11(x + x_kernel - half_length, y + y_kernel - half_length)
.at(std::integral_constant<int, 0>{}) * weights.at(x_kernel, y_kernel);
dxdy += m12_21(x + x_kernel - half_length, y + y_kernel - half_length)
.at(std::integral_constant<int, 0>{}) * weights.at(x_kernel, y_kernel);
ddyy += m22(x + x_kernel - half_length, y + y_kernel - half_length)
.at(std::integral_constant<int, 0>{}) * weights.at(x_kernel, y_kernel);
}
}
auto det = (ddxx * ddyy) - dxdy * dxdy;
auto trace = ddxx + ddyy;
auto harris_value = det - k * trace * trace;
harris_response(x, y).at(std::integral_constant<int, 0>{}) = harris_value;
}
}
}
}} //namespace boost::gil
#endif
+80
View File
@@ -0,0 +1,80 @@
//
// Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
// Copyright 2021 Scramjet911 <36035352+Scramjet911@users.noreply.github.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)
#ifndef BOOST_GIL_IMAGE_PROCESSING_HESSIAN_HPP
#define BOOST_GIL_IMAGE_PROCESSING_HESSIAN_HPP
#include <boost/gil/image_view.hpp>
#include <boost/gil/typedefs.hpp>
#include <boost/gil/image_processing/kernel.hpp>
#include <stdexcept>
namespace boost { namespace gil {
/// \brief Computes Hessian response
///
/// Computes Hessian response based on computed entries of Hessian matrix, e.g. second order
/// derivates in x and y, and derivatives in both x, y.
/// d stands for derivative, and x or y stand for derivative direction. For example,
/// ddxx means taking two derivatives (gradients) in horizontal direction.
/// Weights change perception of surroinding pixels.
/// Additional filtering is strongly advised.
template <typename GradientView, typename T, typename Allocator, typename OutputView>
inline void compute_hessian_responses(
GradientView ddxx,
GradientView dxdy,
GradientView ddyy,
const detail::kernel_2d<T, Allocator>& weights,
OutputView dst)
{
if (ddxx.dimensions() != ddyy.dimensions()
|| ddyy.dimensions() != dxdy.dimensions()
|| dxdy.dimensions() != dst.dimensions()
|| weights.center_x() != weights.center_y())
{
throw std::invalid_argument("dimensions of views are not the same"
" or weights don't have equal width and height"
" or weights' dimensions are not odd");
}
// Use pixel type of output, as values will be written to output
using pixel_t = typename std::remove_reference<decltype(std::declval<OutputView>()(0, 0))>::type;
using channel_t = typename std::remove_reference
<
decltype(std::declval<pixel_t>().at(std::integral_constant<int, 0>{}))
>::type;
auto center = weights.center_y();
for (auto y = center; y < dst.height() - center; ++y)
{
for (auto x = center; x < dst.width() - center; ++x)
{
auto ddxx_i = channel_t();
auto ddyy_i = channel_t();
auto dxdy_i = channel_t();
for (typename OutputView::coord_t w_y = 0; w_y < static_cast<std::ptrdiff_t>(weights.size()); ++w_y)
{
for (typename OutputView::coord_t w_x = 0; w_x < static_cast<std::ptrdiff_t>(weights.size()); ++w_x)
{
ddxx_i += ddxx(x + w_x - center, y + w_y - center)
.at(std::integral_constant<int, 0>{}) * weights.at(w_x, w_y);
ddyy_i += ddyy(x + w_x - center, y + w_y - center)
.at(std::integral_constant<int, 0>{}) * weights.at(w_x, w_y);
dxdy_i += dxdy(x + w_x - center, y + w_y - center)
.at(std::integral_constant<int, 0>{}) * weights.at(w_x, w_y);
}
}
auto determinant = ddxx_i * ddyy_i - dxdy_i * dxdy_i;
dst(x, y).at(std::integral_constant<int, 0>{}) = determinant;
}
}
}
}} // namespace boost::gil
#endif
+150
View File
@@ -0,0 +1,150 @@
//
// Copyright 2020 Debabrata Mandal <mandaldebabrata123@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_EQUALIZATION_HPP
#define BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_EQUALIZATION_HPP
#include <boost/gil/histogram.hpp>
#include <boost/gil/image.hpp>
#include <cmath>
#include <map>
#include <vector>
namespace boost { namespace gil {
/////////////////////////////////////////
/// Histogram Equalization(HE)
/////////////////////////////////////////
/// \defgroup HE HE
/// \brief Contains implementation and description of the algorithm used to compute
/// global histogram equalization of input images.
///
/// Algorithm :-
/// 1. If histogram A is to be equalized compute the cumulative histogram of A.
/// 2. Let CFD(A) refer to the cumulative histogram of A
/// 3. For a uniform histogram A', CDF(A') = A'
/// 4. We need to transfrom A to A' such that
/// 5. CDF(A') = CDF(A) => A' = CDF(A)
/// 6. Hence the pixel transform , px => histogram_of_ith_channel[px].
///
/// \fn histogram_equalization
/// \ingroup HE
/// \tparam SrcKeyType Key Type of input histogram
/// @param src_hist INPUT Input source histogram
/// \brief Overload for histogram equalization algorithm, takes in a single source histogram
/// and returns the color map used for histogram equalization.
///
template <typename SrcKeyType>
auto histogram_equalization(histogram<SrcKeyType> const& src_hist)
-> std::map<SrcKeyType, SrcKeyType>
{
histogram<SrcKeyType> dst_hist;
return histogram_equalization(src_hist, dst_hist);
}
/// \overload histogram_equalization
/// \ingroup HE
/// \tparam SrcKeyType Key Type of input histogram
/// \tparam DstKeyType Key Type of output histogram
/// @param src_hist INPUT source histogram
/// @param dst_hist OUTPUT Output histogram
/// \brief Overload for histogram equalization algorithm, takes in both source histogram &
/// destination histogram and returns the color map used for histogram equalization
/// as well as transforming the destination histogram.
///
template <typename SrcKeyType, typename DstKeyType>
auto histogram_equalization(histogram<SrcKeyType> const& src_hist, histogram<DstKeyType>& dst_hist)
-> std::map<SrcKeyType, DstKeyType>
{
static_assert(
std::is_integral<SrcKeyType>::value &&
std::is_integral<DstKeyType>::value,
"Source and destination histogram types are not appropriate");
using value_t = typename histogram<SrcKeyType>::value_type;
dst_hist.clear();
double sum = src_hist.sum();
SrcKeyType min_key = std::numeric_limits<DstKeyType>::min();
SrcKeyType max_key = std::numeric_limits<DstKeyType>::max();
auto cumltv_srchist = cumulative_histogram(src_hist);
std::map<SrcKeyType, DstKeyType> color_map;
std::for_each(cumltv_srchist.begin(), cumltv_srchist.end(), [&](value_t const& v) {
DstKeyType trnsfrmd_key =
static_cast<DstKeyType>((v.second * (max_key - min_key)) / sum + min_key);
color_map[std::get<0>(v.first)] = trnsfrmd_key;
});
std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
dst_hist[color_map[std::get<0>(v.first)]] += v.second;
});
return color_map;
}
/// \overload histogram_equalization
/// \ingroup HE
/// @param src_view INPUT source image view
/// @param dst_view OUTPUT Output image view
/// @param bin_width INPUT Histogram bin width
/// @param mask INPUT Specify is mask is to be used
/// @param src_mask INPUT Mask vector over input image
/// \brief Overload for histogram equalization algorithm, takes in both source & destination
/// image views and histogram equalizes the input image.
///
template <typename SrcView, typename DstView>
void histogram_equalization(
SrcView const& src_view,
DstView const& dst_view,
std::size_t bin_width = 1,
bool mask = false,
std::vector<std::vector<bool>> src_mask = {})
{
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 same color space");
// Defining channel type
using source_channel_t = typename channel_type<SrcView>::type;
using dst_channel_t = typename channel_type<DstView>::type;
using coord_t = typename SrcView::x_coord_t;
std::size_t const channels = num_channels<SrcView>::value;
coord_t const width = src_view.width();
coord_t const height = src_view.height();
std::size_t pixel_max = std::numeric_limits<dst_channel_t>::max();
std::size_t pixel_min = std::numeric_limits<dst_channel_t>::min();
for (std::size_t i = 0; i < channels; i++)
{
histogram<source_channel_t> h;
fill_histogram(nth_channel_view(src_view, i), h, bin_width, false, false, mask, src_mask);
h.normalize();
auto h2 = cumulative_histogram(h);
for (std::ptrdiff_t src_y = 0; src_y < height; ++src_y)
{
auto src_it = nth_channel_view(src_view, i).row_begin(src_y);
auto dst_it = nth_channel_view(dst_view, i).row_begin(src_y);
for (std::ptrdiff_t src_x = 0; src_x < width; ++src_x)
{
if (mask && !src_mask[src_y][src_x])
dst_it[src_x][0] = channel_convert<dst_channel_t>(src_it[src_x][0]);
else
dst_it[src_x][0] = static_cast<dst_channel_t>(
h2[src_it[src_x][0]] * (pixel_max - pixel_min) + pixel_min);
}
}
}
}
}} //namespace boost::gil
#endif
+205
View File
@@ -0,0 +1,205 @@
//
// Copyright 2020 Debabrata Mandal <mandaldebabrata123@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_MATCHING_HPP
#define BOOST_GIL_IMAGE_PROCESSING_HISTOGRAM_MATCHING_HPP
#include <boost/gil/algorithm.hpp>
#include <boost/gil/histogram.hpp>
#include <boost/gil/image.hpp>
#include <algorithm>
#include <cmath>
#include <map>
#include <vector>
namespace boost { namespace gil {
/////////////////////////////////////////
/// Histogram Matching(HM)
/////////////////////////////////////////
/// \defgroup HM HM
/// \brief Contains implementation and description of the algorithm used to compute
/// global histogram matching of input images.
///
/// Algorithm :-
/// 1. Calculate histogram A(pixel) of input image and G(pixel) of reference image.
/// 2. Compute the normalized cumulative(CDF) histograms of A and G.
/// 3. Match the histograms using transofrmation => CDF(A(px)) = CDF(G(px'))
/// => px' = Inv-CDF (CDF(px))
///
/// \fn histogram_matching
/// \ingroup HM
/// \tparam SrcKeyType Key Type of input histogram
/// @param src_hist INPUT Input source histogram
/// @param ref_hist INPUT Input reference histogram
/// \brief Overload for histogram matching algorithm, takes in a single source histogram &
/// reference histogram and returns the color map used for histogram matching.
///
template <typename SrcKeyType, typename RefKeyType>
auto histogram_matching(histogram<SrcKeyType> const& src_hist, histogram<RefKeyType> const& ref_hist)
-> std::map<SrcKeyType, SrcKeyType>
{
histogram<SrcKeyType> dst_hist;
return histogram_matching(src_hist, ref_hist, dst_hist);
}
/// \overload histogram_matching
/// \ingroup HM
/// \tparam SrcKeyType Key Type of input histogram
/// \tparam RefKeyType Key Type of reference histogram
/// \tparam DstKeyType Key Type of output histogram
/// @param src_hist INPUT source histogram
/// @param ref_hist INPUT reference histogram
/// @param dst_hist OUTPUT Output histogram
/// \brief Overload for histogram matching algorithm, takes in source histogram, reference
/// histogram & destination histogram and returns the color map used for histogram
/// matching as well as transforming the destination histogram.
///
template <typename SrcKeyType, typename RefKeyType, typename DstKeyType>
auto histogram_matching(
histogram<SrcKeyType> const& src_hist,
histogram<RefKeyType> const& ref_hist,
histogram<DstKeyType>& dst_hist)
-> std::map<SrcKeyType, DstKeyType>
{
static_assert(
std::is_integral<SrcKeyType>::value &&
std::is_integral<RefKeyType>::value &&
std::is_integral<DstKeyType>::value,
"Source, Refernce or Destination histogram type is not appropriate.");
using value_t = typename histogram<SrcKeyType>::value_type;
dst_hist.clear();
double src_sum = src_hist.sum();
double ref_sum = ref_hist.sum();
auto cumltv_srchist = cumulative_histogram(src_hist);
auto cumltv_refhist = cumulative_histogram(ref_hist);
std::map<SrcKeyType, RefKeyType> inverse_mapping;
std::vector<typename histogram<RefKeyType>::key_type> src_keys, ref_keys;
src_keys = src_hist.sorted_keys();
ref_keys = ref_hist.sorted_keys();
std::ptrdiff_t start = ref_keys.size() - 1;
RefKeyType ref_max;
if (start >= 0)
ref_max = std::get<0>(ref_keys[start]);
for (std::ptrdiff_t j = src_keys.size() - 1; j >= 0; --j)
{
double src_val = (cumltv_srchist[src_keys[j]] * ref_sum) / src_sum;
while (cumltv_refhist[ref_keys[start]] > src_val && start > 0)
{
start--;
}
if (std::abs(cumltv_refhist[ref_keys[start]] - src_val) >
std::abs(cumltv_refhist(std::min<RefKeyType>(ref_max, std::get<0>(ref_keys[start + 1]))) -
src_val))
{
inverse_mapping[std::get<0>(src_keys[j])] =
std::min<RefKeyType>(ref_max, std::get<0>(ref_keys[start + 1]));
}
else
{
inverse_mapping[std::get<0>(src_keys[j])] = std::get<0>(ref_keys[start]);
}
if (j == 0)
break;
}
std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
dst_hist[inverse_mapping[std::get<0>(v.first)]] += v.second;
});
return inverse_mapping;
}
/// \overload histogram_matching
/// \ingroup HM
/// @param src_view INPUT source image view
/// @param ref_view INPUT Reference image view
/// @param dst_view OUTPUT Output image view
/// @param bin_width INPUT Histogram bin width
/// @param mask INPUT Specify is mask is to be used
/// @param src_mask INPUT Mask vector over input image
/// @param ref_mask INPUT Mask vector over reference image
/// \brief Overload for histogram matching algorithm, takes in both source, reference &
/// destination image views and histogram matches the input image using the
/// reference image.
///
template <typename SrcView, typename ReferenceView, typename DstView>
void histogram_matching(
SrcView const& src_view,
ReferenceView const& ref_view,
DstView const& dst_view,
std::size_t bin_width = 1,
bool mask = false,
std::vector<std::vector<bool>> src_mask = {},
std::vector<std::vector<bool>> ref_mask = {})
{
gil_function_requires<ImageViewConcept<SrcView>>();
gil_function_requires<ImageViewConcept<ReferenceView>>();
gil_function_requires<MutableImageViewConcept<DstView>>();
static_assert(
color_spaces_are_compatible<
typename color_space_type<SrcView>::type,
typename color_space_type<ReferenceView>::type>::value,
"Source and reference view must have same color space");
static_assert(
color_spaces_are_compatible<
typename color_space_type<SrcView>::type,
typename color_space_type<DstView>::type>::value,
"Source and destination view must have same color space");
// Defining channel type
using source_channel_t = typename channel_type<SrcView>::type;
using ref_channel_t = typename channel_type<ReferenceView>::type;
using dst_channel_t = typename channel_type<DstView>::type;
using coord_t = typename SrcView::x_coord_t;
std::size_t const channels = num_channels<SrcView>::value;
coord_t const width = src_view.width();
coord_t const height = src_view.height();
source_channel_t src_pixel_min = std::numeric_limits<source_channel_t>::min();
source_channel_t src_pixel_max = std::numeric_limits<source_channel_t>::max();
ref_channel_t ref_pixel_min = std::numeric_limits<ref_channel_t>::min();
ref_channel_t ref_pixel_max = std::numeric_limits<ref_channel_t>::max();
for (std::size_t i = 0; i < channels; i++)
{
histogram<source_channel_t> src_histogram;
histogram<ref_channel_t> ref_histogram;
fill_histogram(
nth_channel_view(src_view, i), src_histogram, bin_width, false, false, mask, src_mask,
std::tuple<source_channel_t>(src_pixel_min),
std::tuple<source_channel_t>(src_pixel_max), true);
fill_histogram(
nth_channel_view(ref_view, i), ref_histogram, bin_width, false, false, mask, ref_mask,
std::tuple<ref_channel_t>(ref_pixel_min), std::tuple<ref_channel_t>(ref_pixel_max),
true);
auto inverse_mapping = histogram_matching(src_histogram, ref_histogram);
for (std::ptrdiff_t src_y = 0; src_y < height; ++src_y)
{
auto src_it = nth_channel_view(src_view, i).row_begin(src_y);
auto dst_it = nth_channel_view(dst_view, i).row_begin(src_y);
for (std::ptrdiff_t src_x = 0; src_x < width; ++src_x)
{
if (mask && !src_mask[src_y][src_x])
dst_it[src_x][0] = src_it[src_x][0];
else
dst_it[src_x][0] =
static_cast<dst_channel_t>(inverse_mapping[src_it[src_x][0]]);
}
}
}
}
}} //namespace boost::gil
#endif
+362
View File
@@ -0,0 +1,362 @@
//
// Copyright 2005-2007 Adobe Systems Incorporated
// Copyright 2019 Miral Shah <miralshah2211@gmail.com>
// Copyright 2022 Pranam Lashkari <plashkari628@gmail.com>
//
// Distributed under 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_KERNEL_HPP
#define BOOST_GIL_IMAGE_PROCESSING_KERNEL_HPP
#include <boost/gil/utilities.hpp>
#include <boost/gil/point.hpp>
#include <boost/assert.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <memory>
#include <vector>
#include <cmath>
#include <stdexcept>
namespace boost { namespace gil {
// Definitions of 1D fixed-size and variable-size kernels and related operations
namespace detail {
/// \brief kernel adaptor for one-dimensional cores
/// Core needs to provide size(),begin(),end(),operator[],
/// value_type,iterator,const_iterator,reference,const_reference
template <typename Core>
class kernel_1d_adaptor : public Core
{
public:
kernel_1d_adaptor() = default;
explicit kernel_1d_adaptor(std::size_t center)
: center_(center)
{
BOOST_ASSERT(center_ < this->size());
}
kernel_1d_adaptor(std::size_t size, std::size_t center)
: Core(size) , center_(center)
{
BOOST_ASSERT(this->size() > 0);
BOOST_ASSERT(center_ < this->size()); // also implies `size() > 0`
}
kernel_1d_adaptor(kernel_1d_adaptor const& other)
: Core(other), center_(other.center_)
{
BOOST_ASSERT(this->size() > 0);
BOOST_ASSERT(center_ < this->size()); // also implies `size() > 0`
}
kernel_1d_adaptor& operator=(kernel_1d_adaptor const& other)
{
Core::operator=(other);
center_ = other.center_;
return *this;
}
std::size_t left_size() const
{
BOOST_ASSERT(center_ < this->size());
return center_;
}
std::size_t right_size() const
{
BOOST_ASSERT(center_ < this->size());
return this->size() - center_ - 1;
}
auto center() -> std::size_t&
{
BOOST_ASSERT(center_ < this->size());
return center_;
}
auto center() const -> std::size_t const&
{
BOOST_ASSERT(center_ < this->size());
return center_;
}
private:
std::size_t center_{0};
};
} // namespace detail
/// \brief variable-size kernel
template <typename T, typename Allocator = std::allocator<T> >
class kernel_1d : public detail::kernel_1d_adaptor<std::vector<T, Allocator>>
{
using parent_t = detail::kernel_1d_adaptor<std::vector<T, Allocator>>;
public:
kernel_1d() = default;
kernel_1d(std::size_t size, std::size_t center) : parent_t(size, center) {}
template <typename FwdIterator>
kernel_1d(FwdIterator elements, std::size_t size, std::size_t center)
: parent_t(size, center)
{
detail::copy_n(elements, size, this->begin());
}
kernel_1d(kernel_1d const& other) : parent_t(other) {}
kernel_1d& operator=(kernel_1d const& other) = default;
};
/// \brief static-size kernel
template <typename T,std::size_t Size>
class kernel_1d_fixed : public detail::kernel_1d_adaptor<std::array<T, Size>>
{
using parent_t = detail::kernel_1d_adaptor<std::array<T, Size>>;
public:
static constexpr std::size_t static_size = Size;
static_assert(static_size > 0, "kernel must have size greater than 0");
static_assert(static_size % 2 == 1, "kernel size must be odd to ensure validity at the center");
kernel_1d_fixed() = default;
explicit kernel_1d_fixed(std::size_t center) : parent_t(center) {}
template <typename FwdIterator>
explicit kernel_1d_fixed(FwdIterator elements, std::size_t center)
: parent_t(center)
{
detail::copy_n(elements, Size, this->begin());
}
kernel_1d_fixed(kernel_1d_fixed const& other) : parent_t(other) {}
kernel_1d_fixed& operator=(kernel_1d_fixed const& other) = default;
};
// TODO: This data member is odr-used and definition at namespace scope
// is required by C++11. Redundant and deprecated in C++17.
template <typename T,std::size_t Size>
constexpr std::size_t kernel_1d_fixed<T, Size>::static_size;
/// \brief reverse a kernel
template <typename Kernel>
inline Kernel reverse_kernel(Kernel const& kernel)
{
Kernel result(kernel);
result.center() = kernel.right_size();
std::reverse(result.begin(), result.end());
return result;
}
namespace detail {
template <typename Core>
class kernel_2d_adaptor : public Core
{
public:
kernel_2d_adaptor() = default;
explicit kernel_2d_adaptor(std::size_t center_y, std::size_t center_x)
: center_(center_x, center_y)
{
BOOST_ASSERT(center_.y < this->size() && center_.x < this->size());
}
kernel_2d_adaptor(std::size_t size, std::size_t center_y, std::size_t center_x)
: Core(size * size), square_size(size), center_(center_x, center_y)
{
BOOST_ASSERT(this->size() > 0);
BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); // implies `size() > 0`
}
kernel_2d_adaptor(kernel_2d_adaptor const& other)
: Core(other), square_size(other.square_size), center_(other.center_.x, other.center_.y)
{
BOOST_ASSERT(this->size() > 0);
BOOST_ASSERT(center_.y < this->size() && center_.x < this->size()); // implies `size() > 0`
}
kernel_2d_adaptor& operator=(kernel_2d_adaptor const& other)
{
Core::operator=(other);
center_.y = other.center_.y;
center_.x = other.center_.x;
square_size = other.square_size;
return *this;
}
std::size_t upper_size() const
{
BOOST_ASSERT(center_.y < this->size());
return center_.y;
}
std::size_t lower_size() const
{
BOOST_ASSERT(center_.y < this->size());
return this->size() - center_.y - 1;
}
std::size_t left_size() const
{
BOOST_ASSERT(center_.x < this->size());
return center_.x;
}
std::size_t right_size() const
{
BOOST_ASSERT(center_.x < this->size());
return this->size() - center_.x - 1;
}
auto center_y() -> std::size_t&
{
BOOST_ASSERT(center_.y < this->size());
return center_.y;
}
auto center_y() const -> std::size_t const&
{
BOOST_ASSERT(center_.y < this->size());
return center_.y;
}
auto center_x() -> std::size_t&
{
BOOST_ASSERT(center_.x < this->size());
return center_.x;
}
auto center_x() const -> std::size_t const&
{
BOOST_ASSERT(center_.x < this->size());
return center_.x;
}
std::size_t size() const
{
return square_size;
}
typename Core::value_type at(std::size_t x, std::size_t y) const
{
if (x >= this->size() || y >= this->size())
{
throw std::out_of_range("Index out of range");
}
return this->begin()[y * this->size() + x];
}
protected:
std::size_t square_size{0};
private:
point<std::size_t> center_{0, 0};
};
/// \brief variable-size kernel
template
<
typename T,
typename Allocator = std::allocator<T>
>
class kernel_2d : public detail::kernel_2d_adaptor<std::vector<T, Allocator>>
{
using parent_t = detail::kernel_2d_adaptor<std::vector<T, Allocator>>;
public:
kernel_2d() = default;
kernel_2d(std::size_t size,std::size_t center_y, std::size_t center_x)
: parent_t(size, center_y, center_x)
{}
template <typename FwdIterator>
kernel_2d(FwdIterator elements, std::size_t size, std::size_t center_y, std::size_t center_x)
: parent_t(static_cast<int>(std::sqrt(size)), center_y, center_x)
{
detail::copy_n(elements, size, this->begin());
}
kernel_2d(kernel_2d const& other) : parent_t(other) {}
kernel_2d& operator=(kernel_2d const& other) = default;
};
/// \brief static-size kernel
template <typename T, std::size_t Size>
class kernel_2d_fixed :
public detail::kernel_2d_adaptor<std::array<T, Size * Size>>
{
using parent_t = detail::kernel_2d_adaptor<std::array<T, Size * Size>>;
public:
static constexpr std::size_t static_size = Size;
static_assert(static_size > 0, "kernel must have size greater than 0");
static_assert(static_size % 2 == 1, "kernel size must be odd to ensure validity at the center");
kernel_2d_fixed()
{
this->square_size = Size;
}
explicit kernel_2d_fixed(std::size_t center_y, std::size_t center_x) :
parent_t(center_y, center_x)
{
this->square_size = Size;
}
template <typename FwdIterator>
explicit kernel_2d_fixed(FwdIterator elements, std::size_t center_y, std::size_t center_x)
: parent_t(center_y, center_x)
{
this->square_size = Size;
detail::copy_n(elements, Size * Size, this->begin());
}
kernel_2d_fixed(kernel_2d_fixed const& other) : parent_t(other) {}
kernel_2d_fixed& operator=(kernel_2d_fixed const& other) = default;
};
// TODO: This data member is odr-used and definition at namespace scope
// is required by C++11. Redundant and deprecated in C++17.
template <typename T, std::size_t Size>
constexpr std::size_t kernel_2d_fixed<T, Size>::static_size;
template <typename Kernel>
inline Kernel reverse_kernel_2d(Kernel const& kernel)
{
Kernel result(kernel);
result.center_x() = kernel.lower_size();
result.center_y() = kernel.right_size();
std::reverse(result.begin(), result.end());
return result;
}
/// \brief reverse a kernel_2d
template<typename T, typename Allocator>
inline kernel_2d<T, Allocator> reverse_kernel(kernel_2d<T, Allocator> const& kernel)
{
return reverse_kernel_2d(kernel);
}
/// \brief reverse a kernel_2d
template<typename T, std::size_t Size>
inline kernel_2d_fixed<T, Size> reverse_kernel(kernel_2d_fixed<T, Size> const& kernel)
{
return reverse_kernel_2d(kernel);
}
} //namespace detail
}} // namespace boost::gil
#endif
+298
View File
@@ -0,0 +1,298 @@
//
// Copyright 2021 Prathamesh Tagore <prathameshtagore@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP
#define BOOST_GIL_IMAGE_PROCESSING_MORPHOLOGY_HPP
#include <boost/gil/image_processing/kernel.hpp>
#include <boost/gil/gray.hpp>
#include <boost/gil/image_processing/threshold.hpp>
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 <typename SrcView, typename DstView, typename Kernel>
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<typename SrcView::value_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 <typename SrcView, typename DstView, typename Kernel>
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<ImageViewConcept<SrcView>>();
gil_function_requires<MutableImageViewConcept<DstView>>();
gil_function_requires<ColorSpacesCompatibleConcept<typename color_space_type<SrcView>::type,
typename color_space_type<DstView>::type>>();
gil::image<typename DstView::value_type> 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 <typename SrcView, typename DiffView>
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 <typename SrcView, typename DiffView>
void difference(SrcView const& src_view1, SrcView const& src_view2, DiffView const& diff_view)
{
gil_function_requires<ImageViewConcept<SrcView>>();
gil_function_requires<MutableImageViewConcept<DiffView>>();
gil_function_requires<ColorSpacesCompatibleConcept<
typename color_space_type<SrcView>::type, typename color_space_type<DiffView>::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 <typename SrcView, typename IntOpView, typename Kernel>
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 <typename SrcView, typename IntOpView, typename Kernel>
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 <typename SrcView, typename IntOpView, typename Kernel>
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 <typename SrcView, typename IntOpView, typename Kernel>
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 <typename SrcView, typename DstView, typename Kernel>
void morphological_gradient(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat)
{
using namespace boost::gil;
gil::image<typename DstView::value_type> 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 <typename SrcView, typename DstView, typename Kernel>
void top_hat(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat)
{
using namespace boost::gil;
gil::image<typename DstView::value_type> 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 <typename SrcView, typename DstView, typename Kernel>
void black_hat(SrcView const& src_view, DstView const& dst_view, Kernel const& ker_mat)
{
using namespace boost::gil;
gil::image<typename DstView::value_type> 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
+307
View File
@@ -0,0 +1,307 @@
//
// Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
// Copyright 2021 Pranam Lashkari <plashkari628@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_NUMERIC_HPP
#define BOOST_GIL_IMAGE_PROCESSING_NUMERIC_HPP
#include <boost/gil/image_processing/kernel.hpp>
#include <boost/gil/image_processing/convolve.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/typedefs.hpp>
#include <boost/gil/detail/math.hpp>
// fixes ambigious call to std::abs, https://stackoverflow.com/a/30084734/4593721
#include <cstdlib>
#include <cmath>
namespace boost { namespace gil {
/// \defgroup ImageProcessingMath
/// \brief Math operations for IP algorithms
///
/// This is mostly handful of mathemtical operations that are required by other
/// image processing algorithms
///
/// \brief Normalized cardinal sine
/// \ingroup ImageProcessingMath
///
/// normalized_sinc(x) = sin(pi * x) / (pi * x)
///
inline double normalized_sinc(double x)
{
return std::sin(x * boost::gil::detail::pi) / (x * boost::gil::detail::pi);
}
/// \brief Lanczos response at point x
/// \ingroup ImageProcessingMath
///
/// Lanczos response is defined as:
/// x == 0: 1
/// -a < x && x < a: 0
/// otherwise: normalized_sinc(x) / normalized_sinc(x / a)
inline double lanczos(double x, std::ptrdiff_t a)
{
// means == but <= avoids compiler warning
if (0 <= x && x <= 0)
return 1;
if (static_cast<double>(-a) < x && x < static_cast<double>(a))
return normalized_sinc(x) / normalized_sinc(x / static_cast<double>(a));
return 0;
}
#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)
#pragma warning(push)
#pragma warning(disable:4244) // 'argument': conversion from 'const Channel' to 'BaseChannelValue', possible loss of data
#endif
inline void compute_tensor_entries(
boost::gil::gray16s_view_t dx,
boost::gil::gray16s_view_t dy,
boost::gil::gray32f_view_t m11,
boost::gil::gray32f_view_t m12_21,
boost::gil::gray32f_view_t m22)
{
for (std::ptrdiff_t y = 0; y < dx.height(); ++y) {
for (std::ptrdiff_t x = 0; x < dx.width(); ++x) {
auto dx_value = dx(x, y);
auto dy_value = dy(x, y);
m11(x, y) = dx_value * dx_value;
m12_21(x, y) = dx_value * dy_value;
m22(x, y) = dy_value * dy_value;
}
}
}
#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)
#pragma warning(pop)
#endif
/// \brief Generate mean kernel
/// \ingroup ImageProcessingMath
///
/// Fills supplied view with normalized mean
/// in which all entries will be equal to
/// \code 1 / (dst.size()) \endcode
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_normalized_mean(std::size_t side_length)
-> detail::kernel_2d<T, Allocator>
{
if (side_length % 2 != 1)
throw std::invalid_argument("kernel dimensions should be odd and equal");
const float entry = 1.0f / static_cast<float>(side_length * side_length);
detail::kernel_2d<T, Allocator> result(side_length, side_length / 2, side_length / 2);
for (auto& cell: result) {
cell = entry;
}
return result;
}
/// \brief Generate kernel with all 1s
/// \ingroup ImageProcessingMath
///
/// Fills supplied view with 1s (ones)
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_unnormalized_mean(std::size_t side_length)
-> detail::kernel_2d<T, Allocator>
{
if (side_length % 2 != 1)
throw std::invalid_argument("kernel dimensions should be odd and equal");
detail::kernel_2d<T, Allocator> result(side_length, side_length / 2, side_length / 2);
for (auto& cell: result) {
cell = 1.0f;
}
return result;
}
/// \brief Generate Gaussian kernel
/// \ingroup ImageProcessingMath
///
/// Fills supplied view with values taken from Gaussian distribution. See
/// https://en.wikipedia.org/wiki/Gaussian_blur
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_gaussian_kernel(std::size_t side_length, double sigma)
-> detail::kernel_2d<T, Allocator>
{
if (side_length % 2 != 1)
throw std::invalid_argument("kernel dimensions should be odd and equal");
const double denominator = 2 * boost::gil::detail::pi * sigma * sigma;
auto middle = side_length / 2;
std::vector<T, Allocator> values(side_length * side_length);
for (std::size_t y = 0; y < side_length; ++y)
{
for (std::size_t x = 0; x < side_length; ++x)
{
const auto delta_x = middle > x ? middle - x : x - middle;
const auto delta_y = middle > y ? middle - y : y - middle;
const double power = (delta_x * delta_x + delta_y * delta_y) / (2 * sigma * sigma);
const double nominator = std::exp(-power);
const float value = static_cast<float>(nominator / denominator);
values[y * side_length + x] = value;
}
}
return detail::kernel_2d<T, Allocator>(values.begin(), values.size(), middle, middle);
}
/// \brief Generates Sobel operator in horizontal direction
/// \ingroup ImageProcessingMath
///
/// Generates a kernel which will represent Sobel operator in
/// horizontal direction of specified degree (no need to convolve multiple times
/// to obtain the desired degree).
/// https://www.researchgate.net/publication/239398674_An_Isotropic_3_3_Image_Gradient_Operator
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_dx_sobel(unsigned int degree = 1)
-> detail::kernel_2d<T, Allocator>
{
switch (degree)
{
case 0:
{
return detail::get_identity_kernel<T, Allocator>();
}
case 1:
{
detail::kernel_2d<T, Allocator> result(3, 1, 1);
std::copy(detail::dx_sobel.begin(), detail::dx_sobel.end(), result.begin());
return result;
}
default:
throw std::logic_error("not supported yet");
}
//to not upset compiler
throw std::runtime_error("unreachable statement");
}
/// \brief Generate Scharr operator in horizontal direction
/// \ingroup ImageProcessingMath
///
/// Generates a kernel which will represent Scharr operator in
/// horizontal direction of specified degree (no need to convolve multiple times
/// to obtain the desired degree).
/// https://www.researchgate.net/profile/Hanno_Scharr/publication/220955743_Optimal_Filters_for_Extended_Optical_Flow/links/004635151972eda98f000000/Optimal-Filters-for-Extended-Optical-Flow.pdf
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_dx_scharr(unsigned int degree = 1)
-> detail::kernel_2d<T, Allocator>
{
switch (degree)
{
case 0:
{
return detail::get_identity_kernel<T, Allocator>();
}
case 1:
{
detail::kernel_2d<T, Allocator> result(3, 1, 1);
std::copy(detail::dx_scharr.begin(), detail::dx_scharr.end(), result.begin());
return result;
}
default:
throw std::logic_error("not supported yet");
}
//to not upset compiler
throw std::runtime_error("unreachable statement");
}
/// \brief Generates Sobel operator in vertical direction
/// \ingroup ImageProcessingMath
///
/// Generates a kernel which will represent Sobel operator in
/// vertical direction of specified degree (no need to convolve multiple times
/// to obtain the desired degree).
/// https://www.researchgate.net/publication/239398674_An_Isotropic_3_3_Image_Gradient_Operator
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_dy_sobel(unsigned int degree = 1)
-> detail::kernel_2d<T, Allocator>
{
switch (degree)
{
case 0:
{
return detail::get_identity_kernel<T, Allocator>();
}
case 1:
{
detail::kernel_2d<T, Allocator> result(3, 1, 1);
std::copy(detail::dy_sobel.begin(), detail::dy_sobel.end(), result.begin());
return result;
}
default:
throw std::logic_error("not supported yet");
}
//to not upset compiler
throw std::runtime_error("unreachable statement");
}
/// \brief Generate Scharr operator in vertical direction
/// \ingroup ImageProcessingMath
///
/// Generates a kernel which will represent Scharr operator in
/// vertical direction of specified degree (no need to convolve multiple times
/// to obtain the desired degree).
/// https://www.researchgate.net/profile/Hanno_Scharr/publication/220955743_Optimal_Filters_for_Extended_Optical_Flow/links/004635151972eda98f000000/Optimal-Filters-for-Extended-Optical-Flow.pdf
template <typename T = float, typename Allocator = std::allocator<T>>
inline auto generate_dy_scharr(unsigned int degree = 1)
-> detail::kernel_2d<T, Allocator>
{
switch (degree)
{
case 0:
{
return detail::get_identity_kernel<T, Allocator>();
}
case 1:
{
detail::kernel_2d<T, Allocator> result(3, 1, 1);
std::copy(detail::dy_scharr.begin(), detail::dy_scharr.end(), result.begin());
return result;
}
default:
throw std::logic_error("not supported yet");
}
//to not upset compiler
throw std::runtime_error("unreachable statement");
}
/// \brief Compute xy gradient, and second order x and y gradients
/// \ingroup ImageProcessingMath
///
/// Hessian matrix is defined as a matrix of partial derivates
/// for 2d case, it is [[ddxx, dxdy], [dxdy, ddyy].
/// d stands for derivative, and x or y stand for direction.
/// For example, dx stands for derivative (gradient) in horizontal
/// direction, and ddxx means second order derivative in horizon direction
/// https://en.wikipedia.org/wiki/Hessian_matrix
template <typename GradientView, typename OutputView>
inline void compute_hessian_entries(
GradientView dx,
GradientView dy,
OutputView ddxx,
OutputView dxdy,
OutputView ddyy)
{
auto sobel_x = generate_dx_sobel();
auto sobel_y = generate_dy_sobel();
detail::convolve_2d(dx, sobel_x, ddxx);
detail::convolve_2d(dx, sobel_y, dxdy);
detail::convolve_2d(dy, sobel_y, ddyy);
}
}} // namespace boost::gil
#endif
+115
View File
@@ -0,0 +1,115 @@
//
// Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_SCALING_HPP
#define BOOST_GIL_IMAGE_PROCESSING_SCALING_HPP
#include <boost/gil/image_view.hpp>
#include <boost/gil/rgb.hpp>
#include <boost/gil/pixel.hpp>
#include <boost/gil/image_processing/numeric.hpp>
namespace boost { namespace gil {
/// \defgroup ScalingAlgorithms
/// \brief Algorthims suitable for rescaling
///
/// These algorithms are used to improve image quality after image resizing is made.
///
/// \defgroup DownScalingAlgorithms
/// \ingroup ScalingAlgorithms
/// \brief Algorthims suitable for downscaling
///
/// These algorithms provide best results when used for downscaling. Using for upscaling will
/// probably provide less than good results.
///
/// \brief a single step of lanczos downscaling
/// \ingroup DownScalingAlgorithms
///
/// Use this algorithm to scale down source image into a smaller image with reasonable quality.
/// Do note that having a look at the output once is a good idea, since it might have ringing
/// artifacts.
template <typename ImageView>
void lanczos_at(
ImageView input_view,
ImageView output_view,
typename ImageView::x_coord_t source_x,
typename ImageView::y_coord_t source_y,
typename ImageView::x_coord_t target_x,
typename ImageView::y_coord_t target_y,
std::ptrdiff_t a)
{
using x_coord_t = typename ImageView::x_coord_t;
using y_coord_t = typename ImageView::y_coord_t;
using pixel_t = typename std::remove_reference<decltype(std::declval<ImageView>()(0, 0))>::type;
// C++11 doesn't allow auto in lambdas
using channel_t = typename std::remove_reference
<
decltype(std::declval<pixel_t>().at(std::integral_constant<int, 0>{}))
>::type;
pixel_t result_pixel;
static_transform(result_pixel, result_pixel, [](channel_t) {
return static_cast<channel_t>(0);
});
auto x_zero = static_cast<x_coord_t>(0);
auto x_one = static_cast<x_coord_t>(1);
auto y_zero = static_cast<y_coord_t>(0);
auto y_one = static_cast<y_coord_t>(1);
for (y_coord_t y_i = (std::max)(source_y - static_cast<y_coord_t>(a) + y_one, y_zero);
y_i <= (std::min)(source_y + static_cast<y_coord_t>(a), input_view.height() - y_one);
++y_i)
{
for (x_coord_t x_i = (std::max)(source_x - static_cast<x_coord_t>(a) + x_one, x_zero);
x_i <= (std::min)(source_x + static_cast<x_coord_t>(a), input_view.width() - x_one);
++x_i)
{
double lanczos_response = lanczos(source_x - x_i, a) * lanczos(source_y - y_i, a);
auto op = [lanczos_response](channel_t prev, channel_t next)
{
return static_cast<channel_t>(prev + next * lanczos_response);
};
static_transform(result_pixel, input_view(source_x, source_y), result_pixel, op);
}
}
output_view(target_x, target_y) = result_pixel;
}
/// \brief Complete Lanczos algorithm
/// \ingroup DownScalingAlgorithms
///
/// This algorithm does full pass over resulting image and convolves pixels from
/// original image. Do note that it might be a good idea to have a look at test
/// output as there might be ringing artifacts.
/// Based on wikipedia article:
/// https://en.wikipedia.org/wiki/Lanczos_resampling
/// with standardinzed cardinal sin (sinc)
template <typename ImageView>
void scale_lanczos(ImageView input_view, ImageView output_view, std::ptrdiff_t a)
{
double scale_x = (static_cast<double>(output_view.width()))
/ static_cast<double>(input_view.width());
double scale_y = (static_cast<double>(output_view.height()))
/ static_cast<double>(input_view.height());
using x_coord_t = typename ImageView::x_coord_t;
using y_coord_t = typename ImageView::y_coord_t;
for (y_coord_t y = 0; y < output_view.height(); ++y)
{
for (x_coord_t x = 0; x < output_view.width(); ++x)
{
lanczos_at(input_view, output_view, x / scale_x, y / scale_y, x, y, a);
}
}
}
}} // namespace boost::gil
#endif
+465
View File
@@ -0,0 +1,465 @@
//
// Copyright 2019 Miral Shah <miralshah2211@gmail.com>
// Copyright 2021 Pranam Lashkari <plashkari628@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)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
#define BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
#include <limits>
#include <array>
#include <type_traits>
#include <cstddef>
#include <algorithm>
#include <vector>
#include <cmath>
#include <boost/assert.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_processing/kernel.hpp>
#include <boost/gil/image_processing/convolve.hpp>
#include <boost/gil/image_processing/numeric.hpp>
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<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");
//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 <typename SrcView, typename DstView>
void threshold_binary(
SrcView const& src_view,
DstView const& dst_view,
typename channel_type<DstView>::type threshold_value,
typename channel_type<DstView>::type max_value,
threshold_direction direction = threshold_direction::regular
)
{
//deciding output channel type and creating functor
using source_channel_t = typename channel_type<SrcView>::type;
using result_channel_t = typename channel_type<DstView>::type;
if (direction == threshold_direction::regular)
{
detail::threshold_impl<source_channel_t, result_channel_t>(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<source_channel_t, result_channel_t>(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 <typename SrcView, typename DstView>
void threshold_binary(
SrcView const& src_view,
DstView const& dst_view,
typename channel_type<DstView>::type threshold_value,
threshold_direction direction = threshold_direction::regular
)
{
//deciding output channel type and creating functor
using result_channel_t = typename channel_type<DstView>::type;
result_channel_t max_value = (std::numeric_limits<result_channel_t>::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 <typename SrcView, typename DstView>
void threshold_truncate(
SrcView const& src_view,
DstView const& dst_view,
typename channel_type<DstView>::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<SrcView>::type;
using result_channel_t = typename channel_type<DstView>::type;
std::function<result_channel_t(source_channel_t)> threshold_logic;
if (mode == threshold_truncate_mode::threshold)
{
if (direction == threshold_direction::regular)
{
detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
[threshold_value](source_channel_t px) -> result_channel_t {
return px > threshold_value ? threshold_value : px;
});
}
else
{
detail::threshold_impl<source_channel_t, result_channel_t>(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<source_channel_t, result_channel_t>(src_view, dst_view,
[threshold_value](source_channel_t px) -> result_channel_t {
return px > threshold_value ? px : 0;
});
}
else
{
detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
[threshold_value](source_channel_t px) -> result_channel_t {
return px > threshold_value ? 0 : px;
});
}
}
}
namespace detail{
template <typename SrcView, typename DstView>
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<SrcView>::type;
std::array<std::size_t, 256> 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<source_channel_t>::max)(),
max = (std::numeric_limits<source_channel_t>::min)();
if (sizeof(source_channel_t) > 1 || std::is_signed<source_channel_t>::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<source_channel_t>::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 <typename SrcView, typename DstView>
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<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");
//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 <typename SrcView, typename DstView>
void threshold_adaptive
(
SrcView const& src_view,
DstView const& dst_view,
typename channel_type<DstView>::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<DstView>::type constant = 0
)
{
BOOST_ASSERT_MSG((kernel_size % 2 != 0), "Kernel size must be an odd number");
typedef typename channel_type<SrcView>::type source_channel_t;
typedef typename channel_type<DstView>::type result_channel_t;
image<typename SrcView::value_type> temp_img(src_view.width(), src_view.height());
typename image<typename SrcView::value_type>::view_t temp_view = view(temp_img);
SrcView temp_conv(temp_view);
if (method == threshold_adaptive_method::mean)
{
std::vector<float> mean_kernel_values(kernel_size, 1.0f/kernel_size);
kernel_1d<float> kernel(mean_kernel_values.begin(), kernel_size, kernel_size/2);
detail::convolve_1d
<
pixel<float, typename SrcView::value_type::layout_t>
>(src_view, kernel, temp_view);
}
else if (method == threshold_adaptive_method::gaussian)
{
detail::kernel_2d<float> kernel = generate_gaussian_kernel(kernel_size, 1.0);
convolve_2d(src_view, kernel, temp_view);
}
if (direction == threshold_direction::regular)
{
detail::adaptive_impl<source_channel_t, result_channel_t>(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<source_channel_t, result_channel_t>(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 <typename SrcView, typename DstView>
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<DstView>::type result_channel_t;
result_channel_t max_value = (std::numeric_limits<result_channel_t>::max)();
threshold_adaptive(src_view, dst_view, max_value, kernel_size, method, direction, constant);
}
/// @}
}} //namespace boost::gil
#endif //BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP