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  using channel_type = typename channel_type<Pixel>::type;
161  auto result = []() {
162  Pixel zero_pixel;
163  static_fill(zero_pixel, channel_type(0));
164  return zero_pixel;
165  }();
166 
167  for (std::size_t index : {1u, 3u, 5u, 7u})
168  {
169  static_transform(result, stencil[index], result, std::plus<channel_type>{});
170  }
171  Pixel delta_t_pixel;
172  static_fill(delta_t_pixel, delta_t);
173  static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
174 
175  return result;
176  }
177 };
178 
186 {
187  double delta_t = 0.125;
188 
189  template <typename SubImageView>
190  stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
191  point_t origin)
192  {
193  stencil_type<typename SubImageView::value_type> stencil;
194  auto out = stencil.begin();
195  auto current = view(origin);
197  std::array<gil::point_t, 8> offsets(get_directed_offsets());
198  for (auto offset : offsets)
199  {
200  static_transform(view(origin.x + offset.x, origin.y + offset.y), current, *out++,
201  std::minus<channel_type>{});
202  }
203 
204  return stencil;
205  }
206 
207  template <typename Pixel>
208  Pixel reduce(const stencil_type<Pixel>& stencil)
209  {
210  using channel_type = typename channel_type<Pixel>::type;
211  auto result = []() {
212  Pixel zero_pixel;
213  static_fill(zero_pixel, channel_type(0));
214  return zero_pixel;
215  }();
216  for (std::size_t index : {1u, 3u, 5u, 7u})
217  {
218  static_transform(result, stencil[index], result, std::plus<channel_type>{});
219  }
220 
221  for (std::size_t index : {0u, 2u, 4u, 6u})
222  {
223  Pixel half_pixel;
224  static_fill(half_pixel, channel_type(1 / 2.0));
225  static_transform(stencil[index], half_pixel, half_pixel,
226  std::multiplies<channel_type>{});
227  static_transform(result, half_pixel, result, std::plus<channel_type>{});
228  }
229 
230  Pixel delta_t_pixel;
231  static_fill(delta_t_pixel, delta_t);
232  static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
233 
234  return result;
235  }
236 };
237 } // namespace laplace_function
238 
239 namespace brightness_function {
240 using laplace_function::stencil_type;
241 struct identity
242 {
243  template <typename Pixel>
244  stencil_type<Pixel> operator()(const stencil_type<Pixel>& stencil)
245  {
246  return stencil;
247  }
248 };
249 
250 // TODO: Figure out how to implement color gradient brightness, as it
251 // seems to need dx and dy using sobel or scharr kernels
252 
253 struct rgb_luminance
254 {
255  using pixel_type = rgb32f_pixel_t;
256  stencil_type<pixel_type> operator()(const stencil_type<pixel_type>& stencil)
257  {
258  stencil_type<pixel_type> output;
259  std::transform(stencil.begin(), stencil.end(), output.begin(), [](const pixel_type& pixel) {
260  float32_t luminance = 0.2126f * pixel[0] + 0.7152f * pixel[1] + 0.0722f * pixel[2];
261  pixel_type result_pixel;
262  static_fill(result_pixel, luminance);
263  return result_pixel;
264  });
265  return output;
266  }
267 };
268 
269 } // namespace brightness_function
270 
271 enum class matlab_connectivity
272 {
273  minimal,
274  maximal
275 };
276 
277 enum class matlab_conduction_method
278 {
279  exponential,
280  quadratic
281 };
282 
283 template <typename InputView, typename OutputView>
284 void classic_anisotropic_diffusion(const InputView& input, const OutputView& output,
285  unsigned int num_iter, double kappa)
286 {
287  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
288  brightness_function::identity{},
289  conductivity::perona_malik_conductivity{kappa});
290 }
291 
292 template <typename InputView, typename OutputView>
293 void matlab_anisotropic_diffusion(const InputView& input, const OutputView& output,
294  unsigned int num_iter, double kappa,
295  matlab_connectivity connectivity,
296  matlab_conduction_method conduction_method)
297 {
298  if (connectivity == matlab_connectivity::minimal)
299  {
300  if (conduction_method == matlab_conduction_method::exponential)
301  {
302  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
303  brightness_function::identity{},
304  conductivity::gaussian_conductivity{kappa});
305  }
306  else if (conduction_method == matlab_conduction_method::quadratic)
307  {
308  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
309  brightness_function::identity{},
310  conductivity::gaussian_conductivity{kappa});
311  }
312  else
313  {
314  throw std::logic_error("unhandled conduction method found");
315  }
316  }
317  else if (connectivity == matlab_connectivity::maximal)
318  {
319  if (conduction_method == matlab_conduction_method::exponential)
320  {
321  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
322  brightness_function::identity{},
323  conductivity::gaussian_conductivity{kappa});
324  }
325  else if (conduction_method == matlab_conduction_method::quadratic)
326  {
327  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
328  brightness_function::identity{},
329  conductivity::gaussian_conductivity{kappa});
330  }
331  else
332  {
333  throw std::logic_error("unhandled conduction method found");
334  }
335  }
336  else
337  {
338  throw std::logic_error("unhandled connectivity found");
339  }
340 }
341 
342 template <typename InputView, typename OutputView>
343 void default_anisotropic_diffusion(const InputView& input, const OutputView& output,
344  unsigned int num_iter, double kappa)
345 {
346  anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_9points_standard{},
347  brightness_function::identity{}, conductivity::gaussian_conductivity{kappa});
348 }
349 
359 template <typename InputView, typename OutputView,
360  typename LaplaceStrategy = laplace_function::stencil_9points_standard,
361  typename BrightnessFunction = brightness_function::identity,
362  typename DiffusivityFunction = conductivity::gaussian_conductivity>
363 void anisotropic_diffusion(const InputView& input, const OutputView& output, unsigned int num_iter,
364  LaplaceStrategy laplace, BrightnessFunction brightness,
365  DiffusivityFunction diffusivity)
366 {
367  using input_pixel_type = typename InputView::value_type;
368  using pixel_type = typename OutputView::value_type;
369  using channel_type = typename channel_type<pixel_type>::type;
370  using computation_image = image<pixel_type>;
371  const auto width = input.width();
372  const auto height = input.height();
373  const point_t dims(width, height);
374  const auto zero_pixel = []() {
375  pixel_type pixel;
376  static_fill(pixel, static_cast<channel_type>(0));
377 
378  return pixel;
379  }();
380  computation_image result_image(width + 2, height + 2, zero_pixel);
381  auto result = view(result_image);
382  computation_image scratch_result_image(width + 2, height + 2, zero_pixel);
383  auto scratch_result = view(scratch_result_image);
384  transform_pixels(input, subimage_view(result, 1, 1, width, height),
385  [](const input_pixel_type& pixel) {
386  pixel_type converted;
387  for (std::size_t i = 0; i < num_channels<pixel_type>{}; ++i)
388  {
389  converted[i] = pixel[i];
390  }
391  return converted;
392  });
393 
394  for (unsigned int iteration = 0; iteration < num_iter; ++iteration)
395  {
396  for (std::ptrdiff_t relative_y = 0; relative_y < height; ++relative_y)
397  {
398  for (std::ptrdiff_t relative_x = 0; relative_x < width; ++relative_x)
399  {
400  auto x = relative_x + 1;
401  auto y = relative_y + 1;
402  auto stencil = laplace.compute_laplace(result, point_t(x, y));
403  auto brightness_stencil = brightness(stencil);
404  laplace_function::stencil_type<pixel_type> diffusivity_stencil;
405  std::transform(brightness_stencil.begin(), brightness_stencil.end(),
406  diffusivity_stencil.begin(), diffusivity);
407  laplace_function::stencil_type<pixel_type> product_stencil;
408  std::transform(stencil.begin(), stencil.end(), diffusivity_stencil.begin(),
409  product_stencil.begin(), [](pixel_type lhs, pixel_type rhs) {
410  static_transform(lhs, rhs, lhs, std::multiplies<channel_type>{});
411  return lhs;
412  });
413  static_transform(result(x, y), laplace.reduce(product_stencil),
414  scratch_result(x, y), std::plus<channel_type>{});
415  }
416  }
417  using std::swap;
418  swap(result, scratch_result);
419  }
420 
421  copy_pixels(subimage_view(result, 1, 1, width, height), output);
422 }
423 
424 }} // namespace boost::gil
425 
426 #endif
boost::gil::laplace_function::stencil_9points_standard
9 point stencil approximation of Laplacian
Definition: diffusion.hpp:185
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:1123
boost::gil::copy_pixels
BOOST_FORCEINLINE void copy_pixels(const View1 &src, const View2 &dst)
std::copy for image views
Definition: algorithm.hpp:288
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:549
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