// Copyright (C) 2006 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_THRESHOLDINg_ #define DLIB_THRESHOLDINg_ #include "../pixel.h" #include "thresholding_abstract.h" #include "equalize_histogram.h" #include "../enable_if.h" #include <vector> namespace dlib { // ---------------------------------------------------------------------------------------- const unsigned char on_pixel = 255; const unsigned char off_pixel = 0; // ---------------------------------------------------------------------------------------- namespace impl { template < typename U, typename V, typename basic_pixel_type > void partition_pixels_float_work ( unsigned long begin, unsigned long end, U&& cumsum, V&& sorted, basic_pixel_type& pix_thresh, unsigned long& int_thresh ) { auto histsum = [&](long begin, long end) { return end-begin; }; auto histsumi = [&](long begin, long end) { return cumsum[end]-cumsum[begin]; }; // If we split the pixels into two groups, those < thresh (the left group) and // those >= thresh (the right group), what would the sum of absolute deviations of // each pixel from the mean of its group be? total_abs(thresh) computes that // value. unsigned long left_idx = 0; unsigned long right_idx = 0; auto total_abs = [&](unsigned long thresh) { auto left_avg = histsumi(begin,thresh); auto tmp = histsum(begin,thresh); if (tmp != 0) left_avg /= tmp; auto right_avg = histsumi(thresh,end); tmp = histsum(thresh,end); if (tmp != 0) right_avg /= tmp; while(left_idx+1 < sorted.size() && sorted[left_idx] <= left_avg) ++left_idx; while(right_idx+1 < sorted.size() && sorted[right_idx] <= right_avg) ++right_idx; double score = 0; score += left_avg*histsum(begin,left_idx) - histsumi(begin,left_idx); score -= left_avg*histsum(left_idx,thresh) - histsumi(left_idx,thresh); score += right_avg*histsum(thresh,right_idx) - histsumi(thresh,right_idx); score -= right_avg*histsum(right_idx,end) - histsumi(right_idx,end); return score; }; int_thresh = begin; double min_sad = std::numeric_limits<double>::infinity(); for (unsigned long i = begin; i < end; ++i) { // You can't drop a threshold in-between pixels with identical values. So // skip thresholds corresponding to this degenerate case. if (i > 0 && sorted[i-1]==sorted[i]) continue; double sad = total_abs(i); if (sad <= min_sad) { min_sad = sad; int_thresh = i; } } pix_thresh = sorted[int_thresh]; } template < typename U, typename V, typename basic_pixel_type > void recursive_partition_pixels_float ( unsigned long begin, unsigned long end, U&& cumsum, V&& sorted, basic_pixel_type& pix_thresh ) { unsigned long int_thresh; partition_pixels_float_work(begin, end, cumsum, sorted, pix_thresh, int_thresh); } template < typename U, typename V, typename basic_pixel_type, typename ...T > void recursive_partition_pixels_float ( unsigned long begin, unsigned long end, U&& cumsum, V&& sorted, basic_pixel_type& pix_thresh, T&& ...more_thresholds ) { unsigned long int_thresh; partition_pixels_float_work(begin, end, cumsum, sorted, pix_thresh, int_thresh); recursive_partition_pixels_float(int_thresh, end, cumsum, sorted, more_thresholds...); } template < typename image_type, typename ...T > void partition_pixels_float ( const image_type& img_, typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type& pix_thresh, T&& ...more_thresholds ) { /* This is a version of partition_pixels() that doesn't use the histogram to perform a radix sort but rather uses std::sort() as the first processing step. It is therefor useful in cases where the range of possible pixels is too large for the faster histogram version. */ COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<image_type>::pixel_type>::has_alpha == false ); typedef typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type basic_pixel_type; const_image_view<image_type> img(img_); std::vector<basic_pixel_type> sorted; sorted.reserve(img.size()); for (long r = 0; r < img.nr(); ++r) { for (long c = 0; c < img.nc(); ++c) sorted.emplace_back(get_pixel_intensity(img[r][c])); } std::sort(sorted.begin(), sorted.end()); std::vector<double> cumsum; cumsum.reserve(sorted.size()+1); // create integral array cumsum.emplace_back(0); for (auto& v : sorted) cumsum.emplace_back(cumsum.back()+v); recursive_partition_pixels_float(0, img.size(), cumsum, sorted, pix_thresh, more_thresholds...); } template <typename image_type> struct is_u16img_or_less { typedef typename image_traits<image_type>::pixel_type pixel_type; typedef typename pixel_traits<pixel_type>::basic_pixel_type basic_pixel_type; const static bool value = sizeof(basic_pixel_type) <= 2 && pixel_traits<pixel_type>::is_unsigned; }; } template < typename image_type, typename ...T > typename disable_if<impl::is_u16img_or_less<image_type>>::type partition_pixels ( const image_type& img, typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type& pix_thresh, T&& ...more_thresholds ) { impl::partition_pixels_float(img, pix_thresh, more_thresholds...); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- namespace impl { template < typename U, typename basic_pixel_type > void partition_pixels_work ( unsigned long begin, unsigned long end, U&& total_abs, basic_pixel_type& pix_thresh, unsigned long& int_thresh ) { int_thresh = begin; double min_sad = std::numeric_limits<double>::infinity(); for (unsigned long i = begin; i < end; ++i) { double sad = total_abs(begin, i); if (sad <= min_sad) { min_sad = sad; int_thresh = i; } } pix_thresh = int_thresh; } template < typename U, typename basic_pixel_type > void recursive_partition_pixels ( unsigned long begin, unsigned long end, U&& total_abs, basic_pixel_type& pix_thresh ) { unsigned long int_thresh; partition_pixels_work(begin, end, total_abs, pix_thresh, int_thresh); } template < typename U, typename basic_pixel_type, typename ...T > void recursive_partition_pixels ( unsigned long begin, unsigned long end, U&& total_abs, basic_pixel_type& pix_thresh, T&& ...more_thresholds ) { unsigned long int_thresh; partition_pixels_work(begin, end, total_abs, pix_thresh, int_thresh); recursive_partition_pixels(int_thresh, end, total_abs, more_thresholds...); } } template < typename image_type, typename ...T > typename enable_if<impl::is_u16img_or_less<image_type>>::type partition_pixels ( const image_type& img, typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type& pix_thresh, T&& ...more_thresholds ) { COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<image_type>::pixel_type>::is_unsigned == true ); matrix<unsigned long,1> hist; get_histogram(img,hist); // create integral histograms matrix<double,1> cum_hist(hist.size()+1), cum_histi(hist.size()+1); cum_hist(0) = 0; cum_histi(0) = 0; for (long i = 0; i < hist.size(); ++i) { cum_hist(i+1) = cum_hist(i) + hist(i); cum_histi(i+1) = cum_histi(i) + hist(i)*(double)i; } auto histsum = [&](long begin, long end) { return cum_hist(end)-cum_hist(begin); }; auto histsumi = [&](long begin, long end) { return cum_histi(end)-cum_histi(begin); }; // If we split the pixels into two groups, those < thresh (the left group) and // those >= thresh (the right group), what would the sum of absolute deviations of // each pixel from the mean of its group be? total_abs(thresh) computes that // value. auto total_abs = [&](unsigned long begin, unsigned long thresh) { auto left_avg = histsumi(begin,thresh); auto tmp = histsum(begin,thresh); if (tmp != 0) left_avg /= tmp; auto right_avg = histsumi(thresh,hist.size()); tmp = histsum(thresh,hist.size()); if (tmp != 0) right_avg /= tmp; const long left_idx = (long)std::ceil(left_avg); const long right_idx = (long)std::ceil(right_avg); double score = 0; score += left_avg*histsum(begin,left_idx) - histsumi(begin,left_idx); score -= left_avg*histsum(left_idx,thresh) - histsumi(left_idx,thresh); score += right_avg*histsum(thresh,right_idx) - histsumi(thresh,right_idx); score -= right_avg*histsum(right_idx,hist.size()) - histsumi(right_idx,hist.size()); return score; }; impl::recursive_partition_pixels(0, hist.size(), total_abs, pix_thresh, more_thresholds...); } // ---------------------------------------------------------------------------------------- template < typename image_type > typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type partition_pixels ( const image_type& img ) { typedef typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type basic_pixel_type; basic_pixel_type thresh; partition_pixels(img, thresh); return thresh; } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template < typename in_image_type, typename out_image_type > void threshold_image ( const in_image_type& in_img_, out_image_type& out_img_, typename pixel_traits<typename image_traits<in_image_type>::pixel_type>::basic_pixel_type thresh ) { COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<in_image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<out_image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT(pixel_traits<typename image_traits<out_image_type>::pixel_type>::grayscale); const_image_view<in_image_type> in_img(in_img_); image_view<out_image_type> out_img(out_img_); // if there isn't any input image then don't do anything if (in_img.size() == 0) { out_img.clear(); return; } out_img.set_size(in_img.nr(),in_img.nc()); for (long r = 0; r < in_img.nr(); ++r) { for (long c = 0; c < in_img.nc(); ++c) { if (get_pixel_intensity(in_img[r][c]) >= thresh) assign_pixel(out_img[r][c], on_pixel); else assign_pixel(out_img[r][c], off_pixel); } } } // ---------------------------------------------------------------------------------------- template < typename in_image_type, typename out_image_type > void threshold_image ( const in_image_type& in_img, out_image_type& out_img ) { threshold_image(in_img,out_img,partition_pixels(in_img)); } template < typename image_type > void threshold_image ( image_type& img, typename pixel_traits<typename image_traits<image_type>::pixel_type>::basic_pixel_type thresh ) { threshold_image(img,img,thresh); } // ---------------------------------------------------------------------------------------- template < typename image_type > void threshold_image ( image_type& img ) { threshold_image(img,img,partition_pixels(img)); } // ---------------------------------------------------------------------------------------- template < typename in_image_type, typename out_image_type > void auto_threshold_image ( const in_image_type& in_img_, out_image_type& out_img_ ) { COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<in_image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<out_image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<in_image_type>::pixel_type>::is_unsigned == true ); COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<out_image_type>::pixel_type>::is_unsigned == true ); COMPILE_TIME_ASSERT(pixel_traits<typename image_traits<out_image_type>::pixel_type>::grayscale); image_view<out_image_type> out_img(out_img_); // if there isn't any input image then don't do anything if (image_size(in_img_) == 0) { out_img.clear(); return; } unsigned long thresh; // find the threshold we should use matrix<unsigned long,1> hist; get_histogram(in_img_,hist); const_image_view<in_image_type> in_img(in_img_); // Start our two means (a and b) out at the ends of the histogram long a = 0; long b = hist.size()-1; bool moved_a = true; bool moved_b = true; while (moved_a || moved_b) { moved_a = false; moved_b = false; // catch the degenerate case where the histogram is empty if (a >= b) break; if (hist(a) == 0) { ++a; moved_a = true; } if (hist(b) == 0) { --b; moved_b = true; } } // now do k-means clustering with k = 2 on the histogram. moved_a = true; moved_b = true; while (moved_a || moved_b) { moved_a = false; moved_b = false; int64 a_hits = 0; int64 b_hits = 0; int64 a_mass = 0; int64 b_mass = 0; for (long i = 0; i < hist.size(); ++i) { // if i is closer to a if (std::abs(i-a) < std::abs(i-b)) { a_mass += hist(i)*i; a_hits += hist(i); } else // if i is closer to b { b_mass += hist(i)*i; b_hits += hist(i); } } long new_a = (a_mass + a_hits/2)/a_hits; long new_b = (b_mass + b_hits/2)/b_hits; if (new_a != a) { moved_a = true; a = new_a; } if (new_b != b) { moved_b = true; b = new_b; } } // put the threshold between the two means we found thresh = (a + b)/2; // now actually apply the threshold threshold_image(in_img_,out_img_,thresh); } template < typename image_type > void auto_threshold_image ( image_type& img ) { auto_threshold_image(img,img); } // ---------------------------------------------------------------------------------------- template < typename in_image_type, typename out_image_type > void hysteresis_threshold ( const in_image_type& in_img_, out_image_type& out_img_, typename pixel_traits<typename image_traits<in_image_type>::pixel_type>::basic_pixel_type lower_thresh, typename pixel_traits<typename image_traits<in_image_type>::pixel_type>::basic_pixel_type upper_thresh ) { COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<in_image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT( pixel_traits<typename image_traits<out_image_type>::pixel_type>::has_alpha == false ); COMPILE_TIME_ASSERT(pixel_traits<typename image_traits<out_image_type>::pixel_type>::grayscale); DLIB_ASSERT(is_same_object(in_img_, out_img_) == false, "\tvoid hysteresis_threshold(in_img_, out_img_, lower_thresh, upper_thresh)" << "\n\tis_same_object(in_img_,out_img_): " << is_same_object(in_img_,out_img_) ); const_image_view<in_image_type> in_img(in_img_); image_view<out_image_type> out_img(out_img_); // if there isn't any input image then don't do anything if (in_img.size() == 0) { out_img.clear(); return; } out_img.set_size(in_img.nr(),in_img.nc()); assign_all_pixels(out_img, off_pixel); std::vector<std::pair<long,long>> stack; using std::make_pair; // now do the thresholding for (long r = 0; r < in_img.nr(); ++r) { for (long c = 0; c < in_img.nc(); ++c) { typename pixel_traits<typename image_traits<in_image_type>::pixel_type>::basic_pixel_type p; assign_pixel(p,in_img[r][c]); if (p >= upper_thresh) { // now do line following for pixels >= lower_thresh. // set the stack position to 0. stack.push_back(make_pair(r,c)); while (stack.size() > 0) { const long r = stack.back().first; const long c = stack.back().second; stack.pop_back(); // This is the base case of our recursion. We want to stop if we hit a // pixel we have already visited. if (out_img[r][c] == on_pixel) continue; out_img[r][c] = on_pixel; // put the neighbors of this pixel on the stack if they are bright enough if (r-1 >= 0) { if (get_pixel_intensity(in_img[r-1][c]) >= lower_thresh) stack.push_back(make_pair(r-1, c)); if (c-1 >= 0 && get_pixel_intensity(in_img[r-1][c-1]) >= lower_thresh) stack.push_back(make_pair(r-1, c-1)); if (c+1 < in_img.nc() && get_pixel_intensity(in_img[r-1][c+1]) >= lower_thresh) stack.push_back(make_pair(r-1, c+1)); } if (c-1 >= 0 && get_pixel_intensity(in_img[r][c-1]) >= lower_thresh) stack.push_back(make_pair(r,c-1)); if (c+1 < in_img.nc() && get_pixel_intensity(in_img[r][c+1]) >= lower_thresh) stack.push_back(make_pair(r,c+1)); if (r+1 < in_img.nr()) { if (get_pixel_intensity(in_img[r+1][c]) >= lower_thresh) stack.push_back(make_pair(r+1,c)); if (c-1 >= 0 && get_pixel_intensity(in_img[r+1][c-1]) >= lower_thresh) stack.push_back(make_pair(r+1,c-1)); if (c+1 < in_img.nc() && get_pixel_intensity(in_img[r+1][c+1]) >= lower_thresh) stack.push_back(make_pair(r+1,c+1)); } } // end while (stack.size() > 0) } } } } template < typename in_image_type, typename out_image_type > void hysteresis_threshold ( const in_image_type& in_img, out_image_type& out_img ) { using basic_pixel_type = typename pixel_traits<typename image_traits<in_image_type>::pixel_type>::basic_pixel_type; basic_pixel_type t1, t2; partition_pixels(in_img, t1, t2); hysteresis_threshold(in_img, out_img, t1, t2); } // ---------------------------------------------------------------------------------------- } #endif // DLIB_THRESHOLDINg_