threshold.hpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. //
  2. // Copyright 2019 Miral Shah <miralshah2211@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_IMAGE_PROCESSING_THRESHOLD_HPP
  10. #define BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
  11. #include <limits>
  12. #include <array>
  13. #include <type_traits>
  14. #include <cstddef>
  15. #include <algorithm>
  16. #include <vector>
  17. #include <cmath>
  18. #include <boost/assert.hpp>
  19. #include <boost/gil/image.hpp>
  20. #include <boost/gil/image_processing/kernel.hpp>
  21. #include <boost/gil/image_processing/convolve.hpp>
  22. #include <boost/gil/image_processing/numeric.hpp>
  23. namespace boost { namespace gil {
  24. namespace detail {
  25. template
  26. <
  27. typename SourceChannelT,
  28. typename ResultChannelT,
  29. typename SrcView,
  30. typename DstView,
  31. typename Operator
  32. >
  33. void threshold_impl(SrcView const& src_view, DstView const& dst_view, Operator const& threshold_op)
  34. {
  35. gil_function_requires<ImageViewConcept<SrcView>>();
  36. gil_function_requires<MutableImageViewConcept<DstView>>();
  37. static_assert(color_spaces_are_compatible
  38. <
  39. typename color_space_type<SrcView>::type,
  40. typename color_space_type<DstView>::type
  41. >::value, "Source and destination views must have pixels with the same color space");
  42. //iterate over the image checking each pixel value for the threshold
  43. for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
  44. {
  45. typename SrcView::x_iterator src_it = src_view.row_begin(y);
  46. typename DstView::x_iterator dst_it = dst_view.row_begin(y);
  47. for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
  48. {
  49. static_transform(src_it[x], dst_it[x], threshold_op);
  50. }
  51. }
  52. }
  53. } //namespace boost::gil::detail
  54. /// \addtogroup ImageProcessing
  55. /// @{
  56. ///
  57. /// \brief Direction of image segmentation.
  58. /// The direction specifies which pixels are considered as corresponding to object
  59. /// and which pixels correspond to background.
  60. enum class threshold_direction
  61. {
  62. regular, ///< Consider values greater than threshold value
  63. inverse ///< Consider values less than or equal to threshold value
  64. };
  65. /// \ingroup ImageProcessing
  66. /// \brief Method of optimal threshold value calculation.
  67. enum class threshold_optimal_value
  68. {
  69. otsu ///< \todo TODO
  70. };
  71. /// \ingroup ImageProcessing
  72. /// \brief TODO
  73. enum class threshold_truncate_mode
  74. {
  75. threshold, ///< \todo TODO
  76. zero ///< \todo TODO
  77. };
  78. enum class threshold_adaptive_method
  79. {
  80. mean,
  81. gaussian
  82. };
  83. /// \ingroup ImageProcessing
  84. /// \brief Applies fixed threshold to each pixel of image view.
  85. /// Performs image binarization by thresholding channel value of each
  86. /// pixel of given image view.
  87. /// \param src_view - TODO
  88. /// \param dst_view - TODO
  89. /// \param threshold_value - TODO
  90. /// \param max_value - TODO
  91. /// \param threshold_direction - if regular, values greater than threshold_value are
  92. /// set to max_value else set to 0; if inverse, values greater than threshold_value are
  93. /// set to 0 else set to max_value.
  94. template <typename SrcView, typename DstView>
  95. void threshold_binary(
  96. SrcView const& src_view,
  97. DstView const& dst_view,
  98. typename channel_type<DstView>::type threshold_value,
  99. typename channel_type<DstView>::type max_value,
  100. threshold_direction direction = threshold_direction::regular
  101. )
  102. {
  103. //deciding output channel type and creating functor
  104. using source_channel_t = typename channel_type<SrcView>::type;
  105. using result_channel_t = typename channel_type<DstView>::type;
  106. if (direction == threshold_direction::regular)
  107. {
  108. detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
  109. [threshold_value, max_value](source_channel_t px) -> result_channel_t {
  110. return px > threshold_value ? max_value : 0;
  111. });
  112. }
  113. else
  114. {
  115. detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
  116. [threshold_value, max_value](source_channel_t px) -> result_channel_t {
  117. return px > threshold_value ? 0 : max_value;
  118. });
  119. }
  120. }
  121. /// \ingroup ImageProcessing
  122. /// \brief Applies fixed threshold to each pixel of image view.
  123. /// Performs image binarization by thresholding channel value of each
  124. /// pixel of given image view.
  125. /// This variant of threshold_binary automatically deduces maximum value for each channel
  126. /// of pixel based on channel type.
  127. /// If direction is regular, values greater than threshold_value will be set to maximum
  128. /// numeric limit of channel else 0.
  129. /// If direction is inverse, values greater than threshold_value will be set to 0 else maximum
  130. /// numeric limit of channel.
  131. template <typename SrcView, typename DstView>
  132. void threshold_binary(
  133. SrcView const& src_view,
  134. DstView const& dst_view,
  135. typename channel_type<DstView>::type threshold_value,
  136. threshold_direction direction = threshold_direction::regular
  137. )
  138. {
  139. //deciding output channel type and creating functor
  140. using result_channel_t = typename channel_type<DstView>::type;
  141. result_channel_t max_value = (std::numeric_limits<result_channel_t>::max)();
  142. threshold_binary(src_view, dst_view, threshold_value, max_value, direction);
  143. }
  144. /// \ingroup ImageProcessing
  145. /// \brief Applies truncating threshold to each pixel of image view.
  146. /// Takes an image view and performs truncating threshold operation on each chennel.
  147. /// If mode is threshold and direction is regular:
  148. /// values greater than threshold_value will be set to threshold_value else no change
  149. /// If mode is threshold and direction is inverse:
  150. /// values less than or equal to threshold_value will be set to threshold_value else no change
  151. /// If mode is zero and direction is regular:
  152. /// values less than or equal to threshold_value will be set to 0 else no change
  153. /// If mode is zero and direction is inverse:
  154. /// values more than threshold_value will be set to 0 else no change
  155. template <typename SrcView, typename DstView>
  156. void threshold_truncate(
  157. SrcView const& src_view,
  158. DstView const& dst_view,
  159. typename channel_type<DstView>::type threshold_value,
  160. threshold_truncate_mode mode = threshold_truncate_mode::threshold,
  161. threshold_direction direction = threshold_direction::regular
  162. )
  163. {
  164. //deciding output channel type and creating functor
  165. using source_channel_t = typename channel_type<SrcView>::type;
  166. using result_channel_t = typename channel_type<DstView>::type;
  167. std::function<result_channel_t(source_channel_t)> threshold_logic;
  168. if (mode == threshold_truncate_mode::threshold)
  169. {
  170. if (direction == threshold_direction::regular)
  171. {
  172. detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
  173. [threshold_value](source_channel_t px) -> result_channel_t {
  174. return px > threshold_value ? threshold_value : px;
  175. });
  176. }
  177. else
  178. {
  179. detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
  180. [threshold_value](source_channel_t px) -> result_channel_t {
  181. return px > threshold_value ? px : threshold_value;
  182. });
  183. }
  184. }
  185. else
  186. {
  187. if (direction == threshold_direction::regular)
  188. {
  189. detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
  190. [threshold_value](source_channel_t px) -> result_channel_t {
  191. return px > threshold_value ? px : 0;
  192. });
  193. }
  194. else
  195. {
  196. detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
  197. [threshold_value](source_channel_t px) -> result_channel_t {
  198. return px > threshold_value ? 0 : px;
  199. });
  200. }
  201. }
  202. }
  203. namespace detail{
  204. template <typename SrcView, typename DstView>
  205. void otsu_impl(SrcView const& src_view, DstView const& dst_view, threshold_direction direction)
  206. {
  207. //deciding output channel type and creating functor
  208. using source_channel_t = typename channel_type<SrcView>::type;
  209. std::array<std::size_t, 256> histogram{};
  210. //initial value of min is set to maximum possible value to compare histogram data
  211. //initial value of max is set to minimum possible value to compare histogram data
  212. auto min = (std::numeric_limits<source_channel_t>::max)(),
  213. max = (std::numeric_limits<source_channel_t>::min)();
  214. if (sizeof(source_channel_t) > 1 || std::is_signed<source_channel_t>::value)
  215. {
  216. //iterate over the image to find the min and max pixel values
  217. for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
  218. {
  219. typename SrcView::x_iterator src_it = src_view.row_begin(y);
  220. for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
  221. {
  222. if (src_it[x] < min) min = src_it[x];
  223. if (src_it[x] > min) min = src_it[x];
  224. }
  225. }
  226. //making histogram
  227. for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
  228. {
  229. typename SrcView::x_iterator src_it = src_view.row_begin(y);
  230. for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
  231. {
  232. histogram[((src_it[x] - min) * 255) / (max - min)]++;
  233. }
  234. }
  235. }
  236. else
  237. {
  238. //making histogram
  239. for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
  240. {
  241. typename SrcView::x_iterator src_it = src_view.row_begin(y);
  242. for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
  243. {
  244. histogram[src_it[x]]++;
  245. }
  246. }
  247. }
  248. //histData = histogram data
  249. //sum = total (background + foreground)
  250. //sumB = sum background
  251. //wB = weight background
  252. //wf = weight foreground
  253. //varMax = tracking the maximum known value of between class variance
  254. //mB = mu background
  255. //mF = mu foreground
  256. //varBeetween = between class variance
  257. //http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html
  258. //https://www.ipol.im/pub/art/2016/158/
  259. std::ptrdiff_t total_pixel = src_view.height() * src_view.width();
  260. std::ptrdiff_t sum_total = 0, sum_back = 0;
  261. std::size_t weight_back = 0, weight_fore = 0, threshold = 0;
  262. double var_max = 0, mean_back, mean_fore, var_intra_class;
  263. for (std::size_t t = 0; t < 256; t++)
  264. {
  265. sum_total += t * histogram[t];
  266. }
  267. for (int t = 0; t < 256; t++)
  268. {
  269. weight_back += histogram[t]; // Weight Background
  270. if (weight_back == 0) continue;
  271. weight_fore = total_pixel - weight_back; // Weight Foreground
  272. if (weight_fore == 0) break;
  273. sum_back += t * histogram[t];
  274. mean_back = sum_back / weight_back; // Mean Background
  275. mean_fore = (sum_total - sum_back) / weight_fore; // Mean Foreground
  276. // Calculate Between Class Variance
  277. var_intra_class = weight_back * weight_fore * (mean_back - mean_fore) * (mean_back - mean_fore);
  278. // Check if new maximum found
  279. if (var_intra_class > var_max) {
  280. var_max = var_intra_class;
  281. threshold = t;
  282. }
  283. }
  284. if (sizeof(source_channel_t) > 1 && std::is_unsigned<source_channel_t>::value)
  285. {
  286. threshold_binary(src_view, dst_view, (threshold * (max - min) / 255) + min, direction);
  287. }
  288. else {
  289. threshold_binary(src_view, dst_view, threshold, direction);
  290. }
  291. }
  292. } //namespace detail
  293. template <typename SrcView, typename DstView>
  294. void threshold_optimal
  295. (
  296. SrcView const& src_view,
  297. DstView const& dst_view,
  298. threshold_optimal_value mode = threshold_optimal_value::otsu,
  299. threshold_direction direction = threshold_direction::regular
  300. )
  301. {
  302. if (mode == threshold_optimal_value::otsu)
  303. {
  304. for (std::size_t i = 0; i < src_view.num_channels(); i++)
  305. {
  306. detail::otsu_impl
  307. (nth_channel_view(src_view, i), nth_channel_view(dst_view, i), direction);
  308. }
  309. }
  310. }
  311. namespace detail {
  312. template
  313. <
  314. typename SourceChannelT,
  315. typename ResultChannelT,
  316. typename SrcView,
  317. typename DstView,
  318. typename Operator
  319. >
  320. void adaptive_impl
  321. (
  322. SrcView const& src_view,
  323. SrcView const& convolved_view,
  324. DstView const& dst_view,
  325. Operator const& threshold_op
  326. )
  327. {
  328. //template argument validation
  329. gil_function_requires<ImageViewConcept<SrcView>>();
  330. gil_function_requires<MutableImageViewConcept<DstView>>();
  331. static_assert(color_spaces_are_compatible
  332. <
  333. typename color_space_type<SrcView>::type,
  334. typename color_space_type<DstView>::type
  335. >::value, "Source and destination views must have pixels with the same color space");
  336. //iterate over the image checking each pixel value for the threshold
  337. for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
  338. {
  339. typename SrcView::x_iterator src_it = src_view.row_begin(y);
  340. typename SrcView::x_iterator convolved_it = convolved_view.row_begin(y);
  341. typename DstView::x_iterator dst_it = dst_view.row_begin(y);
  342. for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
  343. {
  344. static_transform(src_it[x], convolved_it[x], dst_it[x], threshold_op);
  345. }
  346. }
  347. }
  348. } //namespace boost::gil::detail
  349. template <typename SrcView, typename DstView>
  350. void threshold_adaptive
  351. (
  352. SrcView const& src_view,
  353. DstView const& dst_view,
  354. typename channel_type<DstView>::type max_value,
  355. std::size_t kernel_size,
  356. threshold_adaptive_method method = threshold_adaptive_method::mean,
  357. threshold_direction direction = threshold_direction::regular,
  358. typename channel_type<DstView>::type constant = 0
  359. )
  360. {
  361. BOOST_ASSERT_MSG((kernel_size % 2 != 0), "Kernel size must be an odd number");
  362. typedef typename channel_type<SrcView>::type source_channel_t;
  363. typedef typename channel_type<DstView>::type result_channel_t;
  364. image<typename SrcView::value_type> temp_img(src_view.width(), src_view.height());
  365. typename image<typename SrcView::value_type>::view_t temp_view = view(temp_img);
  366. SrcView temp_conv(temp_view);
  367. if (method == threshold_adaptive_method::mean)
  368. {
  369. std::vector<float> mean_kernel_values(kernel_size, 1.0f/kernel_size);
  370. kernel_1d<float> kernel(mean_kernel_values.begin(), kernel_size, kernel_size/2);
  371. detail::convolve_1d
  372. <
  373. pixel<float, typename SrcView::value_type::layout_t>
  374. >(src_view, kernel, temp_view);
  375. }
  376. else if (method == threshold_adaptive_method::gaussian)
  377. {
  378. detail::kernel_2d<float> kernel = generate_gaussian_kernel(kernel_size, 1.0);
  379. convolve_2d(src_view, kernel, temp_view);
  380. }
  381. if (direction == threshold_direction::regular)
  382. {
  383. detail::adaptive_impl<source_channel_t, result_channel_t>(src_view, temp_conv, dst_view,
  384. [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t
  385. { return px > (threshold - constant) ? max_value : 0; });
  386. }
  387. else
  388. {
  389. detail::adaptive_impl<source_channel_t, result_channel_t>(src_view, temp_conv, dst_view,
  390. [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t
  391. { return px > (threshold - constant) ? 0 : max_value; });
  392. }
  393. }
  394. template <typename SrcView, typename DstView>
  395. void threshold_adaptive
  396. (
  397. SrcView const& src_view,
  398. DstView const& dst_view,
  399. std::size_t kernel_size,
  400. threshold_adaptive_method method = threshold_adaptive_method::mean,
  401. threshold_direction direction = threshold_direction::regular,
  402. int constant = 0
  403. )
  404. {
  405. //deciding output channel type and creating functor
  406. typedef typename channel_type<DstView>::type result_channel_t;
  407. result_channel_t max_value = (std::numeric_limits<result_channel_t>::max)();
  408. threshold_adaptive(src_view, dst_view, max_value, kernel_size, method, direction, constant);
  409. }
  410. /// @}
  411. }} //namespace boost::gil
  412. #endif //BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP