Boost GIL


diffusion.hpp
1 //
2 // Copyright 2020 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
3 // Copyright 2021 Pranam Lashkari <plashkari628@gmail.com>
4 //
5 // Use, modification and distribution are subject to the Boost Software License,
6 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
7 // http://www.boost.org/LICENSE_1_0.txt)
8 //
9 
10 #ifndef BOOST_GIL_IMAGE_PROCESSING_DIFFUSION_HPP
11 #define BOOST_GIL_IMAGE_PROCESSING_DIFFUSION_HPP
12 
13 #include "boost/gil/detail/math.hpp"
14 #include <boost/gil/algorithm.hpp>
15 #include <boost/gil/color_base_algorithm.hpp>
16 #include <boost/gil/image.hpp>
17 #include <boost/gil/image_view.hpp>
18 #include <boost/gil/image_view_factory.hpp>
19 #include <boost/gil/pixel.hpp>
20 #include <boost/gil/point.hpp>
21 #include <boost/gil/typedefs.hpp>
22 #include <functional>
23 #include <numeric>
24 #include <vector>
25 
26 namespace boost { namespace gil {
27 namespace conductivity {
28 struct perona_malik_conductivity
29 {
30  double kappa;
31  template <typename Pixel>
32  Pixel operator()(Pixel input)
33  {
34  using channel_type = typename channel_type<Pixel>::type;
35  // C++11 doesn't seem to capture members
36  static_transform(input, input, [this](channel_type value) {
37  value /= kappa;
38  return std::exp(-std::abs(value));
39  });
40 
41  return input;
42  }
43 };
44 
45 struct gaussian_conductivity
46 {
47  double kappa;
48  template <typename Pixel>
49  Pixel operator()(Pixel input)
50  {
51  using channel_type = typename channel_type<Pixel>::type;
52  // C++11 doesn't seem to capture members
53  static_transform(input, input, [this](channel_type value) {
54  value /= kappa;
55  return std::exp(-value * value);
56  });
57 
58  return input;
59  }
60 };
61 
62 struct wide_regions_conductivity
63 {
64  double kappa;
65  template <typename Pixel>
66  Pixel operator()(Pixel input)
67  {
68  using channel_type = typename channel_type<Pixel>::type;
69  // C++11 doesn't seem to capture members
70  static_transform(input, input, [this](channel_type value) {
71  value /= kappa;
72  return 1.0 / (1.0 + value * value);
73  });
74 
75  return input;
76  }
77 };
78 
79 struct more_wide_regions_conductivity
80 {
81  double kappa;
82  template <typename Pixel>
83  Pixel operator()(Pixel input)
84  {
85  using channel_type = typename channel_type<Pixel>::type;
86  // C++11 doesn't seem to capture members
87  static_transform(input, input, [this](channel_type value) {
88  value /= kappa;
89  return 1.0 / std::sqrt((1.0 + value * value));
90  });
91 
92  return input;
93  }
94 };
95 } // namespace diffusion
96 
100 namespace laplace_function {
101 // The functions assume clockwise enumeration of stencil points, as such
102 // NW North NE 0 1 2 (-1, -1) (0, -1) (+1, -1)
103 // West East ===> 7 3 ===> (-1, 0) (+1, 0)
104 // SW South SE 6 5 4 (-1, +1) (0, +1) (+1, +1)
105 
114 inline std::array<gil::point_t, 8> get_directed_offsets()
115 {
116  return {point_t{-1, -1}, point_t{0, -1}, point_t{+1, -1}, point_t{+1, 0},
117  point_t{+1, +1}, point_t{0, +1}, point_t{-1, +1}, point_t{-1, 0}};
118 }
119 
120 template <typename PixelType>
121 using stencil_type = std::array<PixelType, 8>;
122 
129 {
130  double delta_t = 0.25;
131 
132  template <typename SubImageView>
133  stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
134  point_t origin)
135  {
136  auto current = view(origin);
137  stencil_type<typename SubImageView::value_type> stencil;
139  std::array<gil::point_t, 8> offsets(get_directed_offsets());
140  typename SubImageView::value_type zero_pixel;
141  static_fill(zero_pixel, 0);
142  for (std::size_t index = 0; index < offsets.size(); ++index)
143  {
144  if (index % 2 != 0)
145  {
146  static_transform(view(origin.x + offsets[index].x, origin.y + offsets[index].y),
147  current, stencil[index], std::minus<channel_type>{});
148  }
149  else
150  {
151  stencil[index] = zero_pixel;
152  }
153  }
154  return stencil;
155  }
156 
157  template <typename Pixel>
158  Pixel reduce(const stencil_type<Pixel>& stencil)
159  {
160  auto first = stencil.begin();
161  auto last = stencil.end();
162  using channel_type = typename channel_type<Pixel>::type;
163  auto result = []() {
164  Pixel zero_pixel;
165  static_fill(zero_pixel, channel_type(0));
166  return zero_pixel;
167  }();
168 
169  for (std::size_t index : {1u, 3u, 5u, 7u})
170  {
171  static_transform(result, stencil[index], result, std::plus<channel_type>{});
172  }
173  Pixel delta_t_pixel;
174  static_fill(delta_t_pixel, delta_t);
175  static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
176 
177  return result;
178  }
179 };
180 
188 {
189  double delta_t = 0.125;
190 
191  template <typename SubImageView>
192  stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
193  point_t origin)
194  {
195  stencil_type<typename SubImageView::value_type> stencil;
196  auto out = stencil.begin();
197  auto current = view(origin);
199  std::array<gil::point_t, 8> offsets(get_directed_offsets());
200  for (auto offset : offsets)
201  {
202  static_transform(view(origin.x + offset.x, origin.y + offset.y), current, *out++,
203  std::minus<channel_type>{});
204  }
205 
206  return stencil;
207  }
208 
209  template <typename Pixel>
210  Pixel reduce(const stencil_type<Pixel>& stencil)
211  {
212  using channel_type = typename channel_type<Pixel>::type;
213  auto result = []() {
214  Pixel zero_pixel;
215  static_fill(zero_pixel, channel_type(0));
216  return zero_pixel;
217  }();
218  for (std::size_t index : {1u, 3u, 5u, 7u})
219  {
220  static_transform(result, stencil[index], result, std::plus<channel_type>{});
221  }
222 
223  for (std::size_t index : {0u, 2u, 4u, 6u})
224  {
225  Pixel half_pixel;
226  static_fill(half_pixel, channel_type(1 / 2.0));
227  static_transform(stencil[index], half_pixel, half_pixel,
228  std::multiplies<channel_type>{});
229  static_transform(result, half_pixel, result, std::plus<channel_type>{});
230  }
231 
232  Pixel delta_t_pixel;
233  static_fill(delta_t_pixel, delta_t);
234  static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
235 
236  return result;
237  }
238 };
239 } // namespace laplace_function
240 
241 namespace brightness_function {
242 using laplace_function::stencil_type;
243 struct identity
244 {
245  template <typename Pixel>
246  stencil_type<Pixel> operator()(const stencil_type<Pixel>& stencil)
247  {
248  return stencil;
249  }
250 };
251 
252 // TODO: Figure out how to implement color gradient brightness, as it
253 // seems to need dx and dy using sobel or scharr kernels
254 
255 struct rgb_luminance
256 {
257  using pixel_type = rgb32f_pixel_t;
258  stencil_type<pixel_type> operator()(const stencil_type<pixel_type>& stencil)
259  {
260  stencil_type<pixel_type> output;
261  std::transform(stencil.begin(), stencil.end(), output.begin(), [](const pixel_type& pixel) {
262  float32_t luminance = 0.2126f * pixel[0] + 0.7152f * pixel[1] + 0.0722f * pixel[2];
263  pixel_type result_pixel;
264  static_fill(result_pixel, luminance);
265  return result_pixel;
266  });
267  return output;
268  }
269 };
270 
271 } // namespace brightness_function
272 
273 enum class matlab_connectivity
274 {
275  minimal,
276  maximal
277 };
278 
279 enum class matlab_conduction_method
280 {
281  exponential,
282  quadratic
283 };
284 
285 template <typename InputView, typename OutputView>
286 void classic_anisotropic_diffusion(const InputView& input, const OutputView& output,
287  unsigned int num_iter, double kappa)
288 {
289  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
290  brightness_function::identity{},
291  conductivity::perona_malik_conductivity{kappa});
292 }
293 
294 template <typename InputView, typename OutputView>
295 void matlab_anisotropic_diffusion(const InputView& input, const OutputView& output,
296  unsigned int num_iter, double kappa,
297  matlab_connectivity connectivity,
298  matlab_conduction_method conduction_method)
299 {
300  if (connectivity == matlab_connectivity::minimal)
301  {
302  if (conduction_method == matlab_conduction_method::exponential)
303  {
304  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
305  brightness_function::identity{},
306  conductivity::gaussian_conductivity{kappa});
307  }
308  else if (conduction_method == matlab_conduction_method::quadratic)
309  {
310  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
311  brightness_function::identity{},
312  conductivity::gaussian_conductivity{kappa});
313  }
314  else
315  {
316  throw std::logic_error("unhandled conduction method found");
317  }
318  }
319  else if (connectivity == matlab_connectivity::maximal)
320  {
321  if (conduction_method == matlab_conduction_method::exponential)
322  {
323  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
324  brightness_function::identity{},
325  conductivity::gaussian_conductivity{kappa});
326  }
327  else if (conduction_method == matlab_conduction_method::quadratic)
328  {
329  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
330  brightness_function::identity{},
331  conductivity::gaussian_conductivity{kappa});
332  }
333  else
334  {
335  throw std::logic_error("unhandled conduction method found");
336  }
337  }
338  else
339  {
340  throw std::logic_error("unhandled connectivity found");
341  }
342 }
343 
344 template <typename InputView, typename OutputView>
345 void default_anisotropic_diffusion(const InputView& input, const OutputView& output,
346  unsigned int num_iter, double kappa)
347 {
348  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_9points_standard{},
349  brightness_function::identity{}, conductivity::gaussian_conductivity{kappa});
350 }
351 
361 template <typename InputView, typename OutputView,
362  typename LaplaceStrategy = laplace_function::stencil_9points_standard,
363  typename BrightnessFunction = brightness_function::identity,
364  typename DiffusivityFunction = conductivity::gaussian_conductivity>
365 void anisotropic_diffusion(const InputView& input, const OutputView& output, unsigned int num_iter,
366  LaplaceStrategy laplace, BrightnessFunction brightness,
367  DiffusivityFunction diffusivity)
368 {
369  using input_pixel_type = typename InputView::value_type;
370  using pixel_type = typename OutputView::value_type;
371  using channel_type = typename channel_type<pixel_type>::type;
372  using computation_image = image<pixel_type>;
373  const auto width = input.width();
374  const auto height = input.height();
375  const point_t dims(width, height);
376  const auto zero_pixel = []() {
377  pixel_type pixel;
378  static_fill(pixel, static_cast<channel_type>(0));
379 
380  return pixel;
381  }();
382  computation_image result_image(width + 2, height + 2, zero_pixel);
383  auto result = view(result_image);
384  computation_image scratch_result_image(width + 2, height + 2, zero_pixel);
385  auto scratch_result = view(scratch_result_image);
386  transform_pixels(input, subimage_view(result, 1, 1, width, height),
387  [](const input_pixel_type& pixel) {
388  pixel_type converted;
389  for (std::size_t i = 0; i < num_channels<pixel_type>{}; ++i)
390  {
391  converted[i] = pixel[i];
392  }
393  return converted;
394  });
395 
396  for (unsigned int iteration = 0; iteration < num_iter; ++iteration)
397  {
398  for (std::ptrdiff_t relative_y = 0; relative_y < height; ++relative_y)
399  {
400  for (std::ptrdiff_t relative_x = 0; relative_x < width; ++relative_x)
401  {
402  auto x = relative_x + 1;
403  auto y = relative_y + 1;
404  auto stencil = laplace.compute_laplace(result, point_t(x, y));
405  auto brightness_stencil = brightness(stencil);
406  laplace_function::stencil_type<pixel_type> diffusivity_stencil;
407  std::transform(brightness_stencil.begin(), brightness_stencil.end(),
408  diffusivity_stencil.begin(), diffusivity);
409  laplace_function::stencil_type<pixel_type> product_stencil;
410  std::transform(stencil.begin(), stencil.end(), diffusivity_stencil.begin(),
411  product_stencil.begin(), [](pixel_type lhs, pixel_type rhs) {
412  static_transform(lhs, rhs, lhs, std::multiplies<channel_type>{});
413  return lhs;
414  });
415  static_transform(result(x, y), laplace.reduce(product_stencil),
416  scratch_result(x, y), std::plus<channel_type>{});
417  }
418  }
419  using std::swap;
420  swap(result, scratch_result);
421  }
422 
423  copy_pixels(subimage_view(result, 1, 1, width, height), output);
424 }
425 
426 }} // namespace boost::gil
427 
428 #endif
boost::gil::laplace_function::stencil_9points_standard
9 point stencil approximation of Laplacian
Definition: diffusion.hpp:187
boost::gil::laplace_function::stencil_5points
5 point stencil approximation of Laplacian
Definition: diffusion.hpp:128
std::swap
void swap(boost::gil::packed_channel_reference< BF, FB, NB, M > const x, R &y)
swap for packed_channel_reference
Definition: channel.hpp:529
boost::gil::transform_pixels
BOOST_FORCEINLINE F transform_pixels(const View1 &src, const View2 &dst, F fun)
std::transform for image views
Definition: algorithm.hpp:1116
boost::gil::copy_pixels
BOOST_FORCEINLINE void copy_pixels(const View1 &src, const View2 &dst)
std::copy for image views
Definition: algorithm.hpp:282
boost::gil::view
const image< Pixel, IsPlanar, Alloc >::view_t & view(image< Pixel, IsPlanar, Alloc > &img)
Returns the non-constant-pixel view of an image.
Definition: image.hpp:548
boost::gil::point< std::ptrdiff_t >
boost::gil::laplace_function::get_directed_offsets
std::array< gil::point_t, 8 > get_directed_offsets()
This function makes sure all Laplace functions enumerate values in the same order and direction.
Definition: diffusion.hpp:114
boost::gil::channel_type
Definition: color_convert.hpp:31
boost::gil::subimage_view
View subimage_view(View const &src, typename View::point_t const &topleft, typename View::point_t const &dimensions)
Definition: image_view_factory.hpp:254