mirror of
https://github.com/vdemydiuk/mtapi.git
synced 2026-08-02 05:27:47 +00:00
Added thirdparty: boost library
This commit is contained in:
Vendored
Executable
+302
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user