681 lines
22 KiB
C++
681 lines
22 KiB
C++
// 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_
|
|
|