update boost
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user