Boost GIL


threshold.hpp
1 //
2 // Copyright 2019 Miral Shah <miralshah2211@gmail.com>
3 //
4 // Use, modification and distribution are subject to the Boost Software License,
5 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt)
7 //
8 #ifndef BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
9 #define BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
10 
11 #include <limits>
12 #include <array>
13 #include <type_traits>
14 #include <cstddef>
15 #include <algorithm>
16 #include <vector>
17 #include <cmath>
18 
19 #include <boost/assert.hpp>
20 
21 #include <boost/gil/image.hpp>
22 #include <boost/gil/extension/numeric/kernel.hpp>
23 #include <boost/gil/extension/numeric/convolve.hpp>
24 #include <boost/gil/image_processing/numeric.hpp>
25 
26 namespace boost { namespace gil {
27 
28 namespace detail {
29 
30 template
31 <
32  typename SourceChannelT,
33  typename ResultChannelT,
34  typename SrcView,
35  typename DstView,
36  typename Operator
37 >
38 void threshold_impl(SrcView const& src_view, DstView const& dst_view, Operator const& threshold_op)
39 {
40  gil_function_requires<ImageViewConcept<SrcView>>();
41  gil_function_requires<MutableImageViewConcept<DstView>>();
42  static_assert(color_spaces_are_compatible
43  <
44  typename color_space_type<SrcView>::type,
45  typename color_space_type<DstView>::type
46  >::value, "Source and destination views must have pixels with the same color space");
47 
48  //iterate over the image chaecking each pixel value for the threshold
49  for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
50  {
51  typename SrcView::x_iterator src_it = src_view.row_begin(y);
52  typename DstView::x_iterator dst_it = dst_view.row_begin(y);
53 
54  for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
55  {
56  static_transform(src_it[x], dst_it[x], threshold_op);
57  }
58  }
59 }
60 
61 } //namespace boost::gil::detail
62 
70 {
71  regular,
72  inverse
73 };
74 
78 {
79  otsu,
80  triangle
81 };
82 
86 {
87  threshold,
88  zero
89 };
90 
91 enum class threshold_adaptive_method
92 {
93  mean,
94  gaussian
95 };
96 
108 template <typename SrcView, typename DstView>
110  SrcView const& src_view,
111  DstView const& dst_view,
112  typename channel_type<DstView>::type threshold_value,
113  typename channel_type<DstView>::type max_value,
115 )
116 {
117  //deciding output channel type and creating functor
118  using source_channel_t = typename channel_type<SrcView>::type;
119  using result_channel_t = typename channel_type<DstView>::type;
120 
121  if (direction == threshold_direction::regular)
122  {
123  detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
124  [threshold_value, max_value](source_channel_t px) -> result_channel_t {
125  return px > threshold_value ? max_value : 0;
126  });
127  }
128  else
129  {
130  detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
131  [threshold_value, max_value](source_channel_t px) -> result_channel_t {
132  return px > threshold_value ? 0 : max_value;
133  });
134  }
135 }
136 
147 template <typename SrcView, typename DstView>
149  SrcView const& src_view,
150  DstView const& dst_view,
151  typename channel_type<DstView>::type threshold_value,
153 )
154 {
155  //deciding output channel type and creating functor
156  using result_channel_t = typename channel_type<DstView>::type;
157 
158  result_channel_t max_value = (std::numeric_limits<result_channel_t>::max)();
159  threshold_binary(src_view, dst_view, threshold_value, max_value, direction);
160 }
161 
173 template <typename SrcView, typename DstView>
175  SrcView const& src_view,
176  DstView const& dst_view,
177  typename channel_type<DstView>::type threshold_value,
180 )
181 {
182  //deciding output channel type and creating functor
183  using source_channel_t = typename channel_type<SrcView>::type;
184  using result_channel_t = typename channel_type<DstView>::type;
185 
186  std::function<result_channel_t(source_channel_t)> threshold_logic;
187 
189  {
190  if (direction == threshold_direction::regular)
191  {
192  detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
193  [threshold_value](source_channel_t px) -> result_channel_t {
194  return px > threshold_value ? threshold_value : px;
195  });
196  }
197  else
198  {
199  detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
200  [threshold_value](source_channel_t px) -> result_channel_t {
201  return px > threshold_value ? px : threshold_value;
202  });
203  }
204  }
205  else
206  {
207  if (direction == threshold_direction::regular)
208  {
209  detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
210  [threshold_value](source_channel_t px) -> result_channel_t {
211  return px > threshold_value ? px : 0;
212  });
213  }
214  else
215  {
216  detail::threshold_impl<source_channel_t, result_channel_t>(src_view, dst_view,
217  [threshold_value](source_channel_t px) -> result_channel_t {
218  return px > threshold_value ? 0 : px;
219  });
220  }
221  }
222 }
223 
224 namespace detail{
225 
226 template <typename SrcView, typename DstView>
227 void otsu_impl(SrcView const& src_view, DstView const& dst_view, threshold_direction direction)
228 {
229  //deciding output channel type and creating functor
230  using source_channel_t = typename channel_type<SrcView>::type;
231 
232  std::array<std::size_t, 256> histogram{};
233  //initial value of min is set to maximum possible value to compare histogram data
234  //initial value of max is set to minimum possible value to compare histogram data
235  auto min = (std::numeric_limits<source_channel_t>::max)(),
236  max = (std::numeric_limits<source_channel_t>::min)();
237 
238  if (sizeof(source_channel_t) > 1 || std::is_signed<source_channel_t>::value)
239  {
240  //iterate over the image to find the min and max pixel values
241  for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
242  {
243  typename SrcView::x_iterator src_it = src_view.row_begin(y);
244  for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
245  {
246  if (src_it[x] < min) min = src_it[x];
247  if (src_it[x] > min) min = src_it[x];
248  }
249  }
250 
251  //making histogram
252  for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
253  {
254  typename SrcView::x_iterator src_it = src_view.row_begin(y);
255 
256  for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
257  {
258  histogram[((src_it[x] - min) * 255) / (max - min)]++;
259  }
260  }
261  }
262  else
263  {
264  //making histogram
265  for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
266  {
267  typename SrcView::x_iterator src_it = src_view.row_begin(y);
268 
269  for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
270  {
271  histogram[src_it[x]]++;
272  }
273  }
274  }
275 
276  //histData = histogram data
277  //sum = total (background + foreground)
278  //sumB = sum background
279  //wB = weight background
280  //wf = weight foreground
281  //varMax = tracking the maximum known value of between class variance
282  //mB = mu background
283  //mF = mu foreground
284  //varBeetween = between class variance
285  //http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html
286  //https://www.ipol.im/pub/art/2016/158/
287  std::ptrdiff_t total_pixel = src_view.height() * src_view.width();
288  std::ptrdiff_t sum_total = 0, sum_back = 0;
289  std::size_t weight_back = 0, weight_fore = 0, threshold = 0;
290  double var_max = 0, mean_back, mean_fore, var_intra_class;
291 
292  for (std::size_t t = 0; t < 256; t++)
293  {
294  sum_total += t * histogram[t];
295  }
296 
297  for (int t = 0; t < 256; t++)
298  {
299  weight_back += histogram[t]; // Weight Background
300  if (weight_back == 0) continue;
301 
302  weight_fore = total_pixel - weight_back; // Weight Foreground
303  if (weight_fore == 0) break;
304 
305  sum_back += t * histogram[t];
306 
307  mean_back = sum_back / weight_back; // Mean Background
308  mean_fore = (sum_total - sum_back) / weight_fore; // Mean Foreground
309 
310  // Calculate Between Class Variance
311  var_intra_class = weight_back * weight_fore * (mean_back - mean_fore) * (mean_back - mean_fore);
312 
313  // Check if new maximum found
314  if (var_intra_class > var_max) {
315  var_max = var_intra_class;
316  threshold = t;
317  }
318  }
319  if (sizeof(source_channel_t) > 1 && std::is_unsigned<source_channel_t>::value)
320  {
321  threshold_binary(src_view, dst_view, (threshold * (max - min) / 255) + min, direction);
322  }
323  else {
324  threshold_binary(src_view, dst_view, threshold, direction);
325  }
326 }
327 } //namespace detail
328 
329 template <typename SrcView, typename DstView>
330 void threshold_optimal
331 (
332  SrcView const& src_view,
333  DstView const& dst_view,
336 )
337 {
338  if (mode == threshold_optimal_value::otsu)
339  {
340  for (std::size_t i = 0; i < src_view.num_channels(); i++)
341  {
342  detail::otsu_impl
343  (nth_channel_view(src_view, i), nth_channel_view(dst_view, i), direction);
344  }
345  }
346 }
347 
348 namespace detail {
349 
350 template
351 <
352  typename SourceChannelT,
353  typename ResultChannelT,
354  typename SrcView,
355  typename DstView,
356  typename Operator
357 >
358 void adaptive_impl
359 (
360  SrcView const& src_view,
361  SrcView const& convolved_view,
362  DstView const& dst_view,
363  Operator const& threshold_op
364 )
365 {
366  //template argument validation
367  gil_function_requires<ImageViewConcept<SrcView>>();
368  gil_function_requires<MutableImageViewConcept<DstView>>();
369 
370  static_assert(color_spaces_are_compatible
371  <
372  typename color_space_type<SrcView>::type,
373  typename color_space_type<DstView>::type
374  >::value, "Source and destination views must have pixels with the same color space");
375 
376  //iterate over the image chaecking each pixel value for the threshold
377  for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
378  {
379  typename SrcView::x_iterator src_it = src_view.row_begin(y);
380  typename SrcView::x_iterator convolved_it = convolved_view.row_begin(y);
381  typename DstView::x_iterator dst_it = dst_view.row_begin(y);
382 
383  for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
384  {
385  static_transform(src_it[x], convolved_it[x], dst_it[x], threshold_op);
386  }
387  }
388 }
389 } //namespace boost::gil::detail
390 
391 template <typename SrcView, typename DstView>
392 void threshold_adaptive
393 (
394  SrcView const& src_view,
395  DstView const& dst_view,
396  typename channel_type<DstView>::type max_value,
397  std::size_t kernel_size,
398  threshold_adaptive_method method = threshold_adaptive_method::mean,
400  typename channel_type<DstView>::type constant = 0
401 )
402 {
403  BOOST_ASSERT_MSG((kernel_size % 2 != 0), "Kernel size must be an odd number");
404 
405  typedef typename channel_type<SrcView>::type source_channel_t;
406  typedef typename channel_type<DstView>::type result_channel_t;
407 
408  image<typename SrcView::value_type> temp_img(src_view.width(), src_view.height());
409  typename image<typename SrcView::value_type>::view_t temp_view = view(temp_img);
410  SrcView temp_conv(temp_view);
411 
412  if (method == threshold_adaptive_method::mean)
413  {
414  std::vector<float> mean_kernel_values(kernel_size, 1.0f/kernel_size);
415  kernel_1d<float> kernel(mean_kernel_values.begin(), kernel_size, kernel_size/2);
416 
417  convolve_1d<pixel<float, typename SrcView::value_type::layout_t>>(
418  src_view, kernel, temp_view
419  );
420  }
421  else if (method == threshold_adaptive_method::gaussian)
422  {
423  gray32f_image_t gaussian_kernel_values(kernel_size, kernel_size);
424  generate_gaussian_kernel(view(gaussian_kernel_values), 1.0);
425 
426  gray32f_view_t gaussian_kernel_view = view(gaussian_kernel_values);
427  kernel_2d<float> kernel(
428  kernel_size,
429  kernel_size / 2,
430  kernel_size / 2
431  );
432 
433  std::transform(gaussian_kernel_view.begin(), gaussian_kernel_view.end(), kernel.begin(),
434  [](gray32f_pixel_t pixel) -> float {return pixel.at(std::integral_constant<int, 0>{}); }
435  );
436 
437  convolve_2d(src_view, kernel, temp_view);
438  }
439 
440  if (direction == threshold_direction::regular)
441  {
442  detail::adaptive_impl<source_channel_t, result_channel_t>(src_view, temp_conv, dst_view,
443  [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t
444  { return px > (threshold - constant) ? max_value : 0; });
445  }
446  else
447  {
448  detail::adaptive_impl<source_channel_t, result_channel_t>(src_view, temp_conv, dst_view,
449  [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t
450  { return px > (threshold - constant) ? 0 : max_value; });
451  }
452 }
453 
454 template <typename SrcView, typename DstView>
455 void threshold_adaptive
456 (
457  SrcView const& src_view,
458  DstView const& dst_view,
459  std::size_t kernel_size,
460  threshold_adaptive_method method = threshold_adaptive_method::mean,
462  int constant = 0
463 )
464 {
465  //deciding output channel type and creating functor
466  typedef typename channel_type<DstView>::type result_channel_t;
467 
468  result_channel_t max_value = (std::numeric_limits<result_channel_t>::max)();
469 
470  threshold_adaptive(src_view, dst_view, max_value, kernel_size, method, direction, constant);
471 }
472 
474 
475 }} //namespace boost::gil
476 
477 #endif //BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
Definition: algorithm.hpp:30
Represents a pixel value (a container of channels). Models: HomogeneousColorBaseValueConcept, PixelValueConcept, HomogeneousPixelBasedConcept.
Definition: metafunctions.hpp:23
void threshold_binary(SrcView const &src_view, DstView const &dst_view, typename channel_type< DstView >::type threshold_value, threshold_direction direction=threshold_direction::regular)
Applies fixed threshold to each pixel of image view. Performs image binarization by thresholding chan...
Definition: threshold.hpp:148
threshold_truncate_mode
TODO.
Definition: threshold.hpp:85
threshold_direction
Definition: threshold.hpp:69
threshold_optimal_value
Method of optimal threshold value calculation.
Definition: threshold.hpp:77
Consider values less than or equal to threshold value.
container interface over image view. Models ImageConcept, PixelBasedConcept
Definition: image.hpp:40
Definition: color_convert.hpp:31
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:443
void threshold_truncate(SrcView const &src_view, DstView const &dst_view, typename channel_type< DstView >::type threshold_value, threshold_truncate_mode mode=threshold_truncate_mode::threshold, threshold_direction direction=threshold_direction::regular)
Applies truncating threshold to each pixel of image view. Takes an image view and performes truncatin...
Definition: threshold.hpp:174
Consider values greater than threshold value.
void generate_gaussian_kernel(boost::gil::gray32f_view_t dst, double sigma)
Generate Gaussian kernelFills supplied view with values taken from Gaussian distribution. See https://en.wikipedia.org/wiki/Gaussian_blur.
Definition: numeric.hpp:185