Boost GIL


threshold.hpp
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
12#include <limits>
13#include <array>
14#include <type_traits>
15#include <cstddef>
16#include <algorithm>
17#include <vector>
18#include <cmath>
19
20#include <boost/assert.hpp>
21
22#include <boost/gil/image.hpp>
23#include <boost/gil/image_processing/kernel.hpp>
24#include <boost/gil/image_processing/convolve.hpp>
25#include <boost/gil/image_processing/numeric.hpp>
26
27namespace boost { namespace gil {
28
29namespace detail {
30
31template
32<
33 typename SourceChannelT,
34 typename ResultChannelT,
35 typename SrcView,
36 typename DstView,
37 typename Operator
38>
39void threshold_impl(SrcView const& src_view, DstView const& dst_view, Operator const& threshold_op)
40{
41 gil_function_requires<ImageViewConcept<SrcView>>();
42 gil_function_requires<MutableImageViewConcept<DstView>>();
43 static_assert(color_spaces_are_compatible
44 <
45 typename color_space_type<SrcView>::type,
46 typename color_space_type<DstView>::type
47 >::value, "Source and destination views must have pixels with the same color space");
48
49 //iterate over the image checking each pixel value for the threshold
50 for (std::ptrdiff_t y = 0; y < src_view.height(); y++)
51 {
52 typename SrcView::x_iterator src_it = src_view.row_begin(y);
53 typename DstView::x_iterator dst_it = dst_view.row_begin(y);
54
55 for (std::ptrdiff_t x = 0; x < src_view.width(); x++)
56 {
57 static_transform(src_it[x], dst_it[x], threshold_op);
58 }
59 }
60}
61
62} //namespace boost::gil::detail
63
71{
72 regular,
73 inverse
74};
75
79{
80 otsu
81};
82
86{
87 threshold,
88 zero
89};
90
91enum class threshold_adaptive_method
92{
93 mean,
94 gaussian
95};
96
108template <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
147template <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
173template <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
224namespace detail{
225
226template <typename SrcView, typename DstView>
227void 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
329template <typename SrcView, typename DstView>
330void threshold_optimal
331(
332 SrcView const& src_view,
333 DstView const& dst_view,
336)
337{
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
348namespace detail {
349
350template
351<
352 typename SourceChannelT,
353 typename ResultChannelT,
354 typename SrcView,
355 typename DstView,
356 typename Operator
357>
358void 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 checking 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
391template <typename SrcView, typename DstView>
392void 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 detail::convolve_1d
418 <
419 pixel<float, typename SrcView::value_type::layout_t>
420 >(src_view, kernel, temp_view);
421 }
422 else if (method == threshold_adaptive_method::gaussian)
423 {
424 detail::kernel_2d<float> kernel = generate_gaussian_kernel(kernel_size, 1.0);
425 convolve_2d(src_view, kernel, temp_view);
426 }
427
428 if (direction == threshold_direction::regular)
429 {
430 detail::adaptive_impl<source_channel_t, result_channel_t>(src_view, temp_conv, dst_view,
431 [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t
432 { return px > (threshold - constant) ? max_value : 0; });
433 }
434 else
435 {
436 detail::adaptive_impl<source_channel_t, result_channel_t>(src_view, temp_conv, dst_view,
437 [max_value, constant](source_channel_t px, source_channel_t threshold) -> result_channel_t
438 { return px > (threshold - constant) ? 0 : max_value; });
439 }
440}
441
442template <typename SrcView, typename DstView>
443void threshold_adaptive
444(
445 SrcView const& src_view,
446 DstView const& dst_view,
447 std::size_t kernel_size,
448 threshold_adaptive_method method = threshold_adaptive_method::mean,
450 int constant = 0
451)
452{
453 //deciding output channel type and creating functor
454 typedef typename channel_type<DstView>::type result_channel_t;
455
456 result_channel_t max_value = (std::numeric_limits<result_channel_t>::max)();
457
458 threshold_adaptive(src_view, dst_view, max_value, kernel_size, method, direction, constant);
459}
460
462
463}} //namespace boost::gil
464
465#endif //BOOST_GIL_IMAGE_PROCESSING_THRESHOLD_HPP
auto view(image< Pixel, IsPlanar, Alloc > &img) -> typename image< Pixel, IsPlanar, Alloc >::view_t const &
Returns the non-constant-pixel view of an image.
Definition image.hpp:565
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 performs truncating...
Definition threshold.hpp:174
void threshold_binary(SrcView const &src_view, DstView const &dst_view, typename channel_type< DstView >::type threshold_value, typename channel_type< DstView >::type max_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:109
threshold_optimal_value
Method of optimal threshold value calculation.
Definition threshold.hpp:79
threshold_truncate_mode
TODO.
Definition threshold.hpp:86
threshold_direction
Definition threshold.hpp:71
@ inverse
Consider values less than or equal to threshold value.
@ regular
Consider values greater than threshold value.
auto generate_gaussian_kernel(std::size_t side_length, double sigma) -> detail::kernel_2d< T, Allocator >
Generate Gaussian kernel.
Definition numeric.hpp:132
defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE)
Definition algorithm.hpp:36
Definition color_convert.hpp:31