update boost

This commit is contained in:
2023-11-24 12:56:13 -06:00
parent cfc99971af
commit 19d727037a
9260 changed files with 849256 additions and 299957 deletions

View File

@@ -0,0 +1,426 @@
//
// Copyright 2020 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_EXTENSION_IMAGE_PROCESSING_DIFFUSION_HPP
#define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_DIFFUSION_HPP
#include <boost/gil/detail/math.hpp>
#include <boost/gil/algorithm.hpp>
#include <boost/gil/color_base_algorithm.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/image_view_factory.hpp>
#include <boost/gil/pixel.hpp>
#include <boost/gil/point.hpp>
#include <boost/gil/typedefs.hpp>
#include <functional>
#include <numeric>
#include <vector>
namespace boost { namespace gil {
namespace conductivity {
struct perona_malik_conductivity
{
double kappa;
template <typename Pixel>
Pixel operator()(Pixel input)
{
using channel_type = typename channel_type<Pixel>::type;
// C++11 doesn't seem to capture members
static_transform(input, input, [this](channel_type value) {
value /= kappa;
return std::exp(-std::abs(value));
});
return input;
}
};
struct gaussian_conductivity
{
double kappa;
template <typename Pixel>
Pixel operator()(Pixel input)
{
using channel_type = typename channel_type<Pixel>::type;
// C++11 doesn't seem to capture members
static_transform(input, input, [this](channel_type value) {
value /= kappa;
return std::exp(-value * value);
});
return input;
}
};
struct wide_regions_conductivity
{
double kappa;
template <typename Pixel>
Pixel operator()(Pixel input)
{
using channel_type = typename channel_type<Pixel>::type;
// C++11 doesn't seem to capture members
static_transform(input, input, [this](channel_type value) {
value /= kappa;
return 1.0 / (1.0 + value * value);
});
return input;
}
};
struct more_wide_regions_conductivity
{
double kappa;
template <typename Pixel>
Pixel operator()(Pixel input)
{
using channel_type = typename channel_type<Pixel>::type;
// C++11 doesn't seem to capture members
static_transform(input, input, [this](channel_type value) {
value /= kappa;
return 1.0 / std::sqrt((1.0 + value * value));
});
return input;
}
};
} // namespace diffusion
/**
\brief contains discrete approximations of 2D Laplacian operator
*/
namespace laplace_function {
// The functions assume clockwise enumeration of stencil points, as such
// NW North NE 0 1 2 (-1, -1) (0, -1) (+1, -1)
// West East ===> 7 3 ===> (-1, 0) (+1, 0)
// SW South SE 6 5 4 (-1, +1) (0, +1) (+1, +1)
/**
\brief This function makes sure all Laplace functions enumerate
values in the same order and direction.
The first element is difference North West direction, second in North,
and so on in clockwise manner. Leave element as zero if it is not
to be computed.
*/
inline std::array<gil::point_t, 8> get_directed_offsets()
{
return {point_t{-1, -1}, point_t{0, -1}, point_t{+1, -1}, point_t{+1, 0},
point_t{+1, +1}, point_t{0, +1}, point_t{-1, +1}, point_t{-1, 0}};
}
template <typename PixelType>
using stencil_type = std::array<PixelType, 8>;
/**
\brief 5 point stencil approximation of Laplacian
Only main 4 directions are non-zero, the rest are zero
*/
struct stencil_5points
{
double delta_t = 0.25;
template <typename SubImageView>
stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
point_t origin)
{
auto current = view(origin);
stencil_type<typename SubImageView::value_type> stencil;
using channel_type = typename channel_type<typename SubImageView::value_type>::type;
std::array<gil::point_t, 8> offsets(get_directed_offsets());
typename SubImageView::value_type zero_pixel;
static_fill(zero_pixel, 0);
for (std::size_t index = 0; index < offsets.size(); ++index)
{
if (index % 2 != 0)
{
static_transform(view(origin.x + offsets[index].x, origin.y + offsets[index].y),
current, stencil[index], std::minus<channel_type>{});
}
else
{
stencil[index] = zero_pixel;
}
}
return stencil;
}
template <typename Pixel>
Pixel reduce(const stencil_type<Pixel>& stencil)
{
using channel_type = typename channel_type<Pixel>::type;
auto result = []() {
Pixel zero_pixel;
static_fill(zero_pixel, channel_type(0));
return zero_pixel;
}();
for (std::size_t index : {1u, 3u, 5u, 7u})
{
static_transform(result, stencil[index], result, std::plus<channel_type>{});
}
Pixel delta_t_pixel;
static_fill(delta_t_pixel, delta_t);
static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
return result;
}
};
/**
\brief 9 point stencil approximation of Laplacian
This is full 8 way approximation, though diagonal
elements are halved during reduction.
*/
struct stencil_9points_standard
{
double delta_t = 0.125;
template <typename SubImageView>
stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
point_t origin)
{
stencil_type<typename SubImageView::value_type> stencil;
auto out = stencil.begin();
auto current = view(origin);
using channel_type = typename channel_type<typename SubImageView::value_type>::type;
std::array<gil::point_t, 8> offsets(get_directed_offsets());
for (auto offset : offsets)
{
static_transform(view(origin.x + offset.x, origin.y + offset.y), current, *out++,
std::minus<channel_type>{});
}
return stencil;
}
template <typename Pixel>
Pixel reduce(const stencil_type<Pixel>& stencil)
{
using channel_type = typename channel_type<Pixel>::type;
auto result = []() {
Pixel zero_pixel;
static_fill(zero_pixel, channel_type(0));
return zero_pixel;
}();
for (std::size_t index : {1u, 3u, 5u, 7u})
{
static_transform(result, stencil[index], result, std::plus<channel_type>{});
}
for (std::size_t index : {0u, 2u, 4u, 6u})
{
Pixel half_pixel;
static_fill(half_pixel, channel_type(1 / 2.0));
static_transform(stencil[index], half_pixel, half_pixel,
std::multiplies<channel_type>{});
static_transform(result, half_pixel, result, std::plus<channel_type>{});
}
Pixel delta_t_pixel;
static_fill(delta_t_pixel, delta_t);
static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
return result;
}
};
} // namespace laplace_function
namespace brightness_function {
using laplace_function::stencil_type;
struct identity
{
template <typename Pixel>
stencil_type<Pixel> operator()(const stencil_type<Pixel>& stencil)
{
return stencil;
}
};
// TODO: Figure out how to implement color gradient brightness, as it
// seems to need dx and dy using sobel or scharr kernels
struct rgb_luminance
{
using pixel_type = rgb32f_pixel_t;
stencil_type<pixel_type> operator()(const stencil_type<pixel_type>& stencil)
{
stencil_type<pixel_type> output;
std::transform(stencil.begin(), stencil.end(), output.begin(), [](const pixel_type& pixel) {
float32_t luminance = 0.2126f * pixel[0] + 0.7152f * pixel[1] + 0.0722f * pixel[2];
pixel_type result_pixel;
static_fill(result_pixel, luminance);
return result_pixel;
});
return output;
}
};
} // namespace brightness_function
enum class matlab_connectivity
{
minimal,
maximal
};
enum class matlab_conduction_method
{
exponential,
quadratic
};
template <typename InputView, typename OutputView>
void classic_anisotropic_diffusion(const InputView& input, const OutputView& output,
unsigned int num_iter, double kappa)
{
anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
brightness_function::identity{},
conductivity::perona_malik_conductivity{kappa});
}
template <typename InputView, typename OutputView>
void matlab_anisotropic_diffusion(const InputView& input, const OutputView& output,
unsigned int num_iter, double kappa,
matlab_connectivity connectivity,
matlab_conduction_method conduction_method)
{
if (connectivity == matlab_connectivity::minimal)
{
if (conduction_method == matlab_conduction_method::exponential)
{
anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
brightness_function::identity{},
conductivity::gaussian_conductivity{kappa});
}
else if (conduction_method == matlab_conduction_method::quadratic)
{
anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
brightness_function::identity{},
conductivity::gaussian_conductivity{kappa});
}
else
{
throw std::logic_error("unhandled conduction method found");
}
}
else if (connectivity == matlab_connectivity::maximal)
{
if (conduction_method == matlab_conduction_method::exponential)
{
anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
brightness_function::identity{},
conductivity::gaussian_conductivity{kappa});
}
else if (conduction_method == matlab_conduction_method::quadratic)
{
anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
brightness_function::identity{},
conductivity::gaussian_conductivity{kappa});
}
else
{
throw std::logic_error("unhandled conduction method found");
}
}
else
{
throw std::logic_error("unhandled connectivity found");
}
}
template <typename InputView, typename OutputView>
void default_anisotropic_diffusion(const InputView& input, const OutputView& output,
unsigned int num_iter, double kappa)
{
anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_9points_standard{},
brightness_function::identity{}, conductivity::gaussian_conductivity{kappa});
}
/// \brief Performs diffusion according to Perona-Malik equation
///
/// WARNING: Output channel type must be floating point,
/// otherwise there will be loss in accuracy which most
/// probably will lead to incorrect results (input will be unchanged).
/// Anisotropic diffusion is a smoothing algorithm that respects
/// edge boundaries and can work as an edge detector if suitable
/// iteration count is set and grayscale image view is used
/// as an input
template <typename InputView, typename OutputView,
typename LaplaceStrategy = laplace_function::stencil_9points_standard,
typename BrightnessFunction = brightness_function::identity,
typename DiffusivityFunction = conductivity::gaussian_conductivity>
void anisotropic_diffusion(const InputView& input, const OutputView& output, unsigned int num_iter,
LaplaceStrategy laplace, BrightnessFunction brightness,
DiffusivityFunction diffusivity)
{
using input_pixel_type = typename InputView::value_type;
using pixel_type = typename OutputView::value_type;
using channel_type = typename channel_type<pixel_type>::type;
using computation_image = image<pixel_type>;
const auto width = input.width();
const auto height = input.height();
const auto zero_pixel = []() {
pixel_type pixel;
static_fill(pixel, static_cast<channel_type>(0));
return pixel;
}();
computation_image result_image(width + 2, height + 2, zero_pixel);
auto result = view(result_image);
computation_image scratch_result_image(width + 2, height + 2, zero_pixel);
auto scratch_result = view(scratch_result_image);
transform_pixels(input, subimage_view(result, 1, 1, width, height),
[](const input_pixel_type& pixel) {
pixel_type converted;
for (std::size_t i = 0; i < num_channels<pixel_type>{}; ++i)
{
converted[i] = pixel[i];
}
return converted;
});
for (unsigned int iteration = 0; iteration < num_iter; ++iteration)
{
for (std::ptrdiff_t relative_y = 0; relative_y < height; ++relative_y)
{
for (std::ptrdiff_t relative_x = 0; relative_x < width; ++relative_x)
{
auto x = relative_x + 1;
auto y = relative_y + 1;
auto stencil = laplace.compute_laplace(result, point_t(x, y));
auto brightness_stencil = brightness(stencil);
laplace_function::stencil_type<pixel_type> diffusivity_stencil;
std::transform(brightness_stencil.begin(), brightness_stencil.end(),
diffusivity_stencil.begin(), diffusivity);
laplace_function::stencil_type<pixel_type> product_stencil;
std::transform(stencil.begin(), stencil.end(), diffusivity_stencil.begin(),
product_stencil.begin(), [](pixel_type lhs, pixel_type rhs) {
static_transform(lhs, rhs, lhs, std::multiplies<channel_type>{});
return lhs;
});
static_transform(result(x, y), laplace.reduce(product_stencil),
scratch_result(x, y), std::plus<channel_type>{});
}
}
using std::swap;
swap(result, scratch_result);
}
copy_pixels(subimage_view(result, 1, 1, width, height), output);
}
}} // namespace boost::gil
#endif

View File

@@ -0,0 +1,113 @@
// Boost.GIL (Generic Image Library) - tests
//
// Copyright 2020 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_EXTENSION_IMAGE_PROCESSING_HOUGH_PARAMETER_HPP
#define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_HOUGH_PARAMETER_HPP
#include "boost/gil/point.hpp"
#include <cmath>
#include <cstddef>
namespace boost
{
namespace gil
{
/// \ingroup HoughTransform
/// \brief A type to encapsulate Hough transform parameter range
///
/// This type provides a way to express value range for a parameter
/// as well as some factory functions to simplify initialization
template <typename T>
struct hough_parameter
{
T start_point;
T step_size;
std::size_t step_count;
/// \ingroup HoughTransform
/// \brief Create Hough parameter from value neighborhood and step count
///
/// This function will take start_point as middle point, and in both
/// directions will try to walk half_step_count times until distance of
/// neighborhood is reached
static hough_parameter<T> from_step_count(T start_point, T neighborhood,
std::size_t half_step_count)
{
T step_size = neighborhood / half_step_count;
std::size_t step_count = half_step_count * 2 + 1;
// explicitly fill out members, as aggregate init will error out with narrowing
hough_parameter<T> parameter;
parameter.start_point = start_point - neighborhood;
parameter.step_size = step_size;
parameter.step_count = step_count;
return parameter;
}
/// \ingroup HoughTransform
/// \brief Create Hough parameter from value neighborhood and step size
///
/// This function will take start_point as middle point, and in both
/// directions will try to walk step_size at a time until distance of
/// neighborhood is reached
static hough_parameter<T> from_step_size(T start_point, T neighborhood, T step_size)
{
std::size_t step_count =
2 * static_cast<std::size_t>(std::floor(neighborhood / step_size)) + 1;
// do not use step_size - neighborhood, as step_size might not allow
// landing exactly on that value when starting from start_point
// also use parentheses on step_count / 2 because flooring is exactly
// what we want
// explicitly fill out members, as aggregate init will error out with narrowing
hough_parameter<T> parameter;
parameter.start_point = start_point - step_size * (step_count / 2);
parameter.step_size = step_size;
parameter.step_count = step_count;
return parameter;
}
};
/// \ingroup HoughTransform
/// \brief Calculate minimum angle which would be observable if walked on a circle
///
/// When drawing a circle or moving around a point in circular motion, it is
/// important to not do too many steps, but also to not have disconnected
/// trajectory. This function will calculate the minimum angle that is observable
/// when walking on a circle or tilting a line.
/// WARNING: do keep in mind IEEE 754 quirks, e.g. no-associativity,
/// no-commutativity and precision. Do not expect expressions that are
/// mathematically the same to produce the same values
inline double minimum_angle_step(point_t dimensions)
{
auto longer_dimension = dimensions.x > dimensions.y ? dimensions.x : dimensions.y;
return std::atan2(1, longer_dimension);
}
/// \ingroup HoughTransform
/// \brief Create a Hough transform parameter with optimal angle step
///
/// Due to computational intensity and noise sensitivity of Hough transform,
/// having any candidates missed or computed again is problematic. This function
/// will properly encapsulate optimal value range around approx_angle with amplitude of
/// neighborhood in each direction.
/// WARNING: do keep in mind IEEE 754 quirks, e.g. no-associativity,
/// no-commutativity and precision. Do not expect expressions that are
/// mathematically the same to produce the same values
inline auto make_theta_parameter(double approx_angle, double neighborhood, point_t dimensions)
-> hough_parameter<double>
{
auto angle_step = minimum_angle_step(dimensions);
// std::size_t step_count =
// 2 * static_cast<std::size_t>(std::floor(neighborhood / angle_step)) + 1;
// return {approx_angle - angle_step * (step_count / 2), angle_step, step_count};
return hough_parameter<double>::from_step_size(approx_angle, neighborhood, angle_step);
}
}} // namespace boost::gil
#endif

View File

@@ -0,0 +1,138 @@
// Boost.GIL (Generic Image Library) - tests
//
// Copyright 2020 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_EXTENSION_IMAGE_PROCESSING_HOUGH_TRANSFORM_HPP
#define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_HOUGH_TRANSFORM_HPP
#include <boost/gil/extension/image_processing/hough_parameter.hpp>
#include <boost/gil/extension/rasterization/circle.hpp>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <vector>
namespace boost { namespace gil {
/// \defgroup HoughTransform
/// \brief A family of shape detectors that are specified by equation
///
/// Hough transform is a method of mapping (voting) an object which can be described by
/// equation to single point in accumulator array (also called parameter space).
/// Each set pixel in edge map votes for every shape it can be part of.
/// Circle and ellipse transforms are very costly to brute force, while
/// non-brute-forcing algorithms tend to gamble on probabilities.
/// \ingroup HoughTransform
/// \brief Vote for best fit of a line in parameter space
///
/// The input must be an edge map with grayscale pixels. Be aware of overflow inside
/// accumulator array. The theta parameter is best computed through factory function
/// provided in hough_parameter.hpp
template <typename InputView, typename OutputView>
void hough_line_transform(InputView const& input_view, OutputView const& accumulator_array,
hough_parameter<double> const& theta,
hough_parameter<std::ptrdiff_t> const& radius)
{
std::ptrdiff_t r_lower_bound = radius.start_point;
std::ptrdiff_t r_upper_bound = r_lower_bound + radius.step_size * (radius.step_count - 1);
for (std::ptrdiff_t y = 0; y < input_view.height(); ++y)
{
for (std::ptrdiff_t x = 0; x < input_view.width(); ++x)
{
if (!input_view(x, y)[0])
{
continue;
}
for (std::size_t theta_index = 0; theta_index < theta.step_count; ++theta_index)
{
double theta_current =
theta.start_point + theta.step_size * static_cast<double>(theta_index);
std::ptrdiff_t current_r =
std::llround(static_cast<double>(x) * std::cos(theta_current) +
static_cast<double>(y) * std::sin(theta_current));
if (current_r < r_lower_bound || current_r > r_upper_bound)
{
continue;
}
std::size_t r_index = static_cast<std::size_t>(
std::llround((current_r - radius.start_point) / radius.step_size));
// one more safety guard to not get out of bounds
if (r_index < radius.step_count)
{
accumulator_array(theta_index, r_index)[0] += 1;
}
}
}
}
}
/// \ingroup HoughTransform
/// \brief Vote for best fit of a circle in parameter space according to rasterizer
///
/// The input must be an edge map with grayscale pixels. Be aware of overflow inside
/// accumulator array. Rasterizer is used to rasterize a circle for voting. The circle
/// then is translated for every origin (x, y) in x y parameter space. For available
/// circle rasterizers, please look at rasterization/circle.hpp
template <typename ImageView, typename ForwardIterator, typename Rasterizer>
void hough_circle_transform_brute(ImageView const& input,
hough_parameter<std::ptrdiff_t> const& radius_parameter,
hough_parameter<std::ptrdiff_t> const& x_parameter,
hough_parameter<std::ptrdiff_t> const& y_parameter,
ForwardIterator d_first, Rasterizer rasterizer)
{
for (std::size_t radius_index = 0; radius_index < radius_parameter.step_count; ++radius_index)
{
const auto radius = radius_parameter.start_point +
radius_parameter.step_size * static_cast<std::ptrdiff_t>(radius_index);
Rasterizer rasterizer{point_t{}, radius};
std::vector<point_t> circle_points(rasterizer.point_count());
rasterizer(circle_points.begin());
// sort by scanline to improve cache coherence for row major images
std::sort(circle_points.begin(), circle_points.end(),
[](point_t const& lhs, point_t const& rhs) { return lhs.y < rhs.y; });
const auto translate = [](std::vector<point_t>& points, point_t offset) {
std::transform(points.begin(), points.end(), points.begin(), [offset](point_t point) {
return point_t(point.x + offset.x, point.y + offset.y);
});
};
// in case somebody passes iterator to likes of std::vector<bool>
typename std::iterator_traits<ForwardIterator>::reference current_image = *d_first;
// the algorithm has to traverse over parameter space and look at input, instead
// of vice versa, as otherwise it will call translate too many times, as input
// is usually bigger than the coordinate portion of parameter space.
// This might cause extensive cache misses
for (std::size_t x_index = 0; x_index < x_parameter.step_count; ++x_index)
{
for (std::size_t y_index = 0; y_index < y_parameter.step_count; ++y_index)
{
const std::ptrdiff_t x = x_parameter.start_point + x_index * x_parameter.step_size;
const std::ptrdiff_t y = y_parameter.start_point + y_index * y_parameter.step_size;
auto translated_circle = circle_points;
translate(translated_circle, {x, y});
for (const auto& point : translated_circle)
{
if (input(point))
{
++current_image(x_index, y_index)[0];
}
}
}
}
++d_first;
}
}
}} // namespace boost::gil
#endif