diffusion.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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. #ifndef BOOST_GIL_EXTENSION_IMAGE_PROCESSING_DIFFUSION_HPP
  10. #define BOOST_GIL_EXTENSION_IMAGE_PROCESSING_DIFFUSION_HPP
  11. #include <boost/gil/detail/math.hpp>
  12. #include <boost/gil/algorithm.hpp>
  13. #include <boost/gil/color_base_algorithm.hpp>
  14. #include <boost/gil/image.hpp>
  15. #include <boost/gil/image_view.hpp>
  16. #include <boost/gil/image_view_factory.hpp>
  17. #include <boost/gil/pixel.hpp>
  18. #include <boost/gil/point.hpp>
  19. #include <boost/gil/typedefs.hpp>
  20. #include <functional>
  21. #include <numeric>
  22. #include <vector>
  23. namespace boost { namespace gil {
  24. namespace conductivity {
  25. struct perona_malik_conductivity
  26. {
  27. double kappa;
  28. template <typename Pixel>
  29. Pixel operator()(Pixel input)
  30. {
  31. using channel_type = typename channel_type<Pixel>::type;
  32. // C++11 doesn't seem to capture members
  33. static_transform(input, input, [this](channel_type value) {
  34. value /= kappa;
  35. return std::exp(-std::abs(value));
  36. });
  37. return input;
  38. }
  39. };
  40. struct gaussian_conductivity
  41. {
  42. double kappa;
  43. template <typename Pixel>
  44. Pixel operator()(Pixel input)
  45. {
  46. using channel_type = typename channel_type<Pixel>::type;
  47. // C++11 doesn't seem to capture members
  48. static_transform(input, input, [this](channel_type value) {
  49. value /= kappa;
  50. return std::exp(-value * value);
  51. });
  52. return input;
  53. }
  54. };
  55. struct wide_regions_conductivity
  56. {
  57. double kappa;
  58. template <typename Pixel>
  59. Pixel operator()(Pixel input)
  60. {
  61. using channel_type = typename channel_type<Pixel>::type;
  62. // C++11 doesn't seem to capture members
  63. static_transform(input, input, [this](channel_type value) {
  64. value /= kappa;
  65. return 1.0 / (1.0 + value * value);
  66. });
  67. return input;
  68. }
  69. };
  70. struct more_wide_regions_conductivity
  71. {
  72. double kappa;
  73. template <typename Pixel>
  74. Pixel operator()(Pixel input)
  75. {
  76. using channel_type = typename channel_type<Pixel>::type;
  77. // C++11 doesn't seem to capture members
  78. static_transform(input, input, [this](channel_type value) {
  79. value /= kappa;
  80. return 1.0 / std::sqrt((1.0 + value * value));
  81. });
  82. return input;
  83. }
  84. };
  85. } // namespace diffusion
  86. /**
  87. \brief contains discrete approximations of 2D Laplacian operator
  88. */
  89. namespace laplace_function {
  90. // The functions assume clockwise enumeration of stencil points, as such
  91. // NW North NE 0 1 2 (-1, -1) (0, -1) (+1, -1)
  92. // West East ===> 7 3 ===> (-1, 0) (+1, 0)
  93. // SW South SE 6 5 4 (-1, +1) (0, +1) (+1, +1)
  94. /**
  95. \brief This function makes sure all Laplace functions enumerate
  96. values in the same order and direction.
  97. The first element is difference North West direction, second in North,
  98. and so on in clockwise manner. Leave element as zero if it is not
  99. to be computed.
  100. */
  101. inline std::array<gil::point_t, 8> get_directed_offsets()
  102. {
  103. return {point_t{-1, -1}, point_t{0, -1}, point_t{+1, -1}, point_t{+1, 0},
  104. point_t{+1, +1}, point_t{0, +1}, point_t{-1, +1}, point_t{-1, 0}};
  105. }
  106. template <typename PixelType>
  107. using stencil_type = std::array<PixelType, 8>;
  108. /**
  109. \brief 5 point stencil approximation of Laplacian
  110. Only main 4 directions are non-zero, the rest are zero
  111. */
  112. struct stencil_5points
  113. {
  114. double delta_t = 0.25;
  115. template <typename SubImageView>
  116. stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
  117. point_t origin)
  118. {
  119. auto current = view(origin);
  120. stencil_type<typename SubImageView::value_type> stencil;
  121. using channel_type = typename channel_type<typename SubImageView::value_type>::type;
  122. std::array<gil::point_t, 8> offsets(get_directed_offsets());
  123. typename SubImageView::value_type zero_pixel;
  124. static_fill(zero_pixel, 0);
  125. for (std::size_t index = 0; index < offsets.size(); ++index)
  126. {
  127. if (index % 2 != 0)
  128. {
  129. static_transform(view(origin.x + offsets[index].x, origin.y + offsets[index].y),
  130. current, stencil[index], std::minus<channel_type>{});
  131. }
  132. else
  133. {
  134. stencil[index] = zero_pixel;
  135. }
  136. }
  137. return stencil;
  138. }
  139. template <typename Pixel>
  140. Pixel reduce(const stencil_type<Pixel>& stencil)
  141. {
  142. using channel_type = typename channel_type<Pixel>::type;
  143. auto result = []() {
  144. Pixel zero_pixel;
  145. static_fill(zero_pixel, channel_type(0));
  146. return zero_pixel;
  147. }();
  148. for (std::size_t index : {1u, 3u, 5u, 7u})
  149. {
  150. static_transform(result, stencil[index], result, std::plus<channel_type>{});
  151. }
  152. Pixel delta_t_pixel;
  153. static_fill(delta_t_pixel, delta_t);
  154. static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
  155. return result;
  156. }
  157. };
  158. /**
  159. \brief 9 point stencil approximation of Laplacian
  160. This is full 8 way approximation, though diagonal
  161. elements are halved during reduction.
  162. */
  163. struct stencil_9points_standard
  164. {
  165. double delta_t = 0.125;
  166. template <typename SubImageView>
  167. stencil_type<typename SubImageView::value_type> compute_laplace(SubImageView view,
  168. point_t origin)
  169. {
  170. stencil_type<typename SubImageView::value_type> stencil;
  171. auto out = stencil.begin();
  172. auto current = view(origin);
  173. using channel_type = typename channel_type<typename SubImageView::value_type>::type;
  174. std::array<gil::point_t, 8> offsets(get_directed_offsets());
  175. for (auto offset : offsets)
  176. {
  177. static_transform(view(origin.x + offset.x, origin.y + offset.y), current, *out++,
  178. std::minus<channel_type>{});
  179. }
  180. return stencil;
  181. }
  182. template <typename Pixel>
  183. Pixel reduce(const stencil_type<Pixel>& stencil)
  184. {
  185. using channel_type = typename channel_type<Pixel>::type;
  186. auto result = []() {
  187. Pixel zero_pixel;
  188. static_fill(zero_pixel, channel_type(0));
  189. return zero_pixel;
  190. }();
  191. for (std::size_t index : {1u, 3u, 5u, 7u})
  192. {
  193. static_transform(result, stencil[index], result, std::plus<channel_type>{});
  194. }
  195. for (std::size_t index : {0u, 2u, 4u, 6u})
  196. {
  197. Pixel half_pixel;
  198. static_fill(half_pixel, channel_type(1 / 2.0));
  199. static_transform(stencil[index], half_pixel, half_pixel,
  200. std::multiplies<channel_type>{});
  201. static_transform(result, half_pixel, result, std::plus<channel_type>{});
  202. }
  203. Pixel delta_t_pixel;
  204. static_fill(delta_t_pixel, delta_t);
  205. static_transform(result, delta_t_pixel, result, std::multiplies<channel_type>{});
  206. return result;
  207. }
  208. };
  209. } // namespace laplace_function
  210. namespace brightness_function {
  211. using laplace_function::stencil_type;
  212. struct identity
  213. {
  214. template <typename Pixel>
  215. stencil_type<Pixel> operator()(const stencil_type<Pixel>& stencil)
  216. {
  217. return stencil;
  218. }
  219. };
  220. // TODO: Figure out how to implement color gradient brightness, as it
  221. // seems to need dx and dy using sobel or scharr kernels
  222. struct rgb_luminance
  223. {
  224. using pixel_type = rgb32f_pixel_t;
  225. stencil_type<pixel_type> operator()(const stencil_type<pixel_type>& stencil)
  226. {
  227. stencil_type<pixel_type> output;
  228. std::transform(stencil.begin(), stencil.end(), output.begin(), [](const pixel_type& pixel) {
  229. float32_t luminance = 0.2126f * pixel[0] + 0.7152f * pixel[1] + 0.0722f * pixel[2];
  230. pixel_type result_pixel;
  231. static_fill(result_pixel, luminance);
  232. return result_pixel;
  233. });
  234. return output;
  235. }
  236. };
  237. } // namespace brightness_function
  238. enum class matlab_connectivity
  239. {
  240. minimal,
  241. maximal
  242. };
  243. enum class matlab_conduction_method
  244. {
  245. exponential,
  246. quadratic
  247. };
  248. template <typename InputView, typename OutputView>
  249. void classic_anisotropic_diffusion(const InputView& input, const OutputView& output,
  250. unsigned int num_iter, double kappa)
  251. {
  252. anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
  253. brightness_function::identity{},
  254. conductivity::perona_malik_conductivity{kappa});
  255. }
  256. template <typename InputView, typename OutputView>
  257. void matlab_anisotropic_diffusion(const InputView& input, const OutputView& output,
  258. unsigned int num_iter, double kappa,
  259. matlab_connectivity connectivity,
  260. matlab_conduction_method conduction_method)
  261. {
  262. if (connectivity == matlab_connectivity::minimal)
  263. {
  264. if (conduction_method == matlab_conduction_method::exponential)
  265. {
  266. anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
  267. brightness_function::identity{},
  268. conductivity::gaussian_conductivity{kappa});
  269. }
  270. else if (conduction_method == matlab_conduction_method::quadratic)
  271. {
  272. anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
  273. brightness_function::identity{},
  274. conductivity::gaussian_conductivity{kappa});
  275. }
  276. else
  277. {
  278. throw std::logic_error("unhandled conduction method found");
  279. }
  280. }
  281. else if (connectivity == matlab_connectivity::maximal)
  282. {
  283. if (conduction_method == matlab_conduction_method::exponential)
  284. {
  285. anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
  286. brightness_function::identity{},
  287. conductivity::gaussian_conductivity{kappa});
  288. }
  289. else if (conduction_method == matlab_conduction_method::quadratic)
  290. {
  291. anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_5points{},
  292. brightness_function::identity{},
  293. conductivity::gaussian_conductivity{kappa});
  294. }
  295. else
  296. {
  297. throw std::logic_error("unhandled conduction method found");
  298. }
  299. }
  300. else
  301. {
  302. throw std::logic_error("unhandled connectivity found");
  303. }
  304. }
  305. template <typename InputView, typename OutputView>
  306. void default_anisotropic_diffusion(const InputView& input, const OutputView& output,
  307. unsigned int num_iter, double kappa)
  308. {
  309. anisotropic_diffusion(input, output, num_iter, laplace_function::stencil_9points_standard{},
  310. brightness_function::identity{}, conductivity::gaussian_conductivity{kappa});
  311. }
  312. /// \brief Performs diffusion according to Perona-Malik equation
  313. ///
  314. /// WARNING: Output channel type must be floating point,
  315. /// otherwise there will be loss in accuracy which most
  316. /// probably will lead to incorrect results (input will be unchanged).
  317. /// Anisotropic diffusion is a smoothing algorithm that respects
  318. /// edge boundaries and can work as an edge detector if suitable
  319. /// iteration count is set and grayscale image view is used
  320. /// as an input
  321. template <typename InputView, typename OutputView,
  322. typename LaplaceStrategy = laplace_function::stencil_9points_standard,
  323. typename BrightnessFunction = brightness_function::identity,
  324. typename DiffusivityFunction = conductivity::gaussian_conductivity>
  325. void anisotropic_diffusion(const InputView& input, const OutputView& output, unsigned int num_iter,
  326. LaplaceStrategy laplace, BrightnessFunction brightness,
  327. DiffusivityFunction diffusivity)
  328. {
  329. using input_pixel_type = typename InputView::value_type;
  330. using pixel_type = typename OutputView::value_type;
  331. using channel_type = typename channel_type<pixel_type>::type;
  332. using computation_image = image<pixel_type>;
  333. const auto width = input.width();
  334. const auto height = input.height();
  335. const auto zero_pixel = []() {
  336. pixel_type pixel;
  337. static_fill(pixel, static_cast<channel_type>(0));
  338. return pixel;
  339. }();
  340. computation_image result_image(width + 2, height + 2, zero_pixel);
  341. auto result = view(result_image);
  342. computation_image scratch_result_image(width + 2, height + 2, zero_pixel);
  343. auto scratch_result = view(scratch_result_image);
  344. transform_pixels(input, subimage_view(result, 1, 1, width, height),
  345. [](const input_pixel_type& pixel) {
  346. pixel_type converted;
  347. for (std::size_t i = 0; i < num_channels<pixel_type>{}; ++i)
  348. {
  349. converted[i] = pixel[i];
  350. }
  351. return converted;
  352. });
  353. for (unsigned int iteration = 0; iteration < num_iter; ++iteration)
  354. {
  355. for (std::ptrdiff_t relative_y = 0; relative_y < height; ++relative_y)
  356. {
  357. for (std::ptrdiff_t relative_x = 0; relative_x < width; ++relative_x)
  358. {
  359. auto x = relative_x + 1;
  360. auto y = relative_y + 1;
  361. auto stencil = laplace.compute_laplace(result, point_t(x, y));
  362. auto brightness_stencil = brightness(stencil);
  363. laplace_function::stencil_type<pixel_type> diffusivity_stencil;
  364. std::transform(brightness_stencil.begin(), brightness_stencil.end(),
  365. diffusivity_stencil.begin(), diffusivity);
  366. laplace_function::stencil_type<pixel_type> product_stencil;
  367. std::transform(stencil.begin(), stencil.end(), diffusivity_stencil.begin(),
  368. product_stencil.begin(), [](pixel_type lhs, pixel_type rhs) {
  369. static_transform(lhs, rhs, lhs, std::multiplies<channel_type>{});
  370. return lhs;
  371. });
  372. static_transform(result(x, y), laplace.reduce(product_stencil),
  373. scratch_result(x, y), std::plus<channel_type>{});
  374. }
  375. }
  376. using std::swap;
  377. swap(result, scratch_result);
  378. }
  379. copy_pixels(subimage_view(result, 1, 1, width, height), output);
  380. }
  381. }} // namespace boost::gil
  382. #endif