Skip to content

Commit d3fc0d7

Browse files
zvezdochiottrufanov-nok
authored andcommitted
0.2.15: threshold: new adjustment method and method Window
1 parent a1c628c commit d3fc0d7

File tree

7 files changed

+237
-15
lines changed

7 files changed

+237
-15
lines changed

src/core/filters/output/BlackWhiteOptions.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ BlackWhiteOptions::parseThresholdMethod(QString const& str)
107107
{
108108
return T_WOLF;
109109
}
110+
else if (str == "window")
111+
{
112+
return T_WINDOW;
113+
}
110114
else if (str == "bradley")
111115
{
112116
return T_BRADLEY;
@@ -148,6 +152,9 @@ BlackWhiteOptions::formatThresholdMethod(ThresholdFilter type)
148152
case T_WOLF:
149153
str = "wolf";
150154
break;
155+
case T_WINDOW:
156+
str = "window";
157+
break;
151158
case T_BRADLEY:
152159
str = "bradley";
153160
break;

src/core/filters/output/BlackWhiteOptions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class QDomElement;
2626
namespace output
2727
{
2828

29-
enum ThresholdFilter { T_OTSU, T_SAUVOLA, T_WOLF, T_BRADLEY, T_GRAD, T_EDGEPLUS, T_BLURDIV, T_EDGEDIV };
29+
enum ThresholdFilter { T_OTSU, T_SAUVOLA, T_WOLF, T_WINDOW, T_BRADLEY, T_GRAD, T_EDGEPLUS, T_BLURDIV, T_EDGEDIV };
3030

3131
class BlackWhiteOptions
3232
{

src/core/filters/output/OptionsWidget.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ OptionsWidget::OptionsWidget(
6666
thresholdMethodSelector->addItem(tr("Otsu"), T_OTSU);
6767
thresholdMethodSelector->addItem(tr("Sauvola"), T_SAUVOLA);
6868
thresholdMethodSelector->addItem(tr("Wolf"), T_WOLF);
69+
thresholdMethodSelector->addItem(tr("Window"), T_WINDOW);
6970
thresholdMethodSelector->addItem(tr("Bradley"), T_BRADLEY);
7071
thresholdMethodSelector->addItem(tr("Grad"), T_GRAD);
7172
thresholdMethodSelector->addItem(tr("EdgePlus"), T_EDGEPLUS);

src/core/filters/output/OutputGenerator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,6 +2076,11 @@ OutputGenerator::binarize(QImage const& image, BinaryImage const& mask, const in
20762076
binarized = binarizeWolf(image, window_size, 1, 254, threshold_coef, threshold_delta);
20772077
break;
20782078
}
2079+
case T_WINDOW:
2080+
{
2081+
binarized = binarizeWindow(image, window_size, 1, 254, threshold_coef, threshold_delta);
2082+
break;
2083+
}
20792084
case T_BRADLEY:
20802085
{
20812086
binarized = binarizeBradley(image, window_size, threshold_coef, threshold_delta);

src/imageproc/Binarize.cpp

Lines changed: 203 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,17 @@ BinaryImage binarizeMokji(
5252
return BinaryImage(src, threshold);
5353
}
5454

55-
BinaryImage binarizeSauvola(QImage const& src, QSize const window_size, double const k, int const delta)
55+
/*
56+
* sauvola = mean * (1.0 - k * (1.0 - stderr / 128.0)), k = 0.34
57+
* modification by zvezdochiot:
58+
* sauvola = base * (1.0 - k * (1.0 - (frac_s + frac_d))), k = 0.34, delta = 0
59+
* base = mean, frac_s = stderr / range, frac_d = delta / range, range = 128.0
60+
*/
61+
BinaryImage binarizeSauvola(
62+
QImage const& src,
63+
QSize const window_size,
64+
double const k,
65+
int const delta)
5666
{
5767
if (window_size.isEmpty()) {
5868
throw std::invalid_argument("binarizeSauvola: invalid window_size");
@@ -102,6 +112,8 @@ BinaryImage binarizeSauvola(QImage const& src, QSize const window_size, double c
102112
uint32_t* bw_line = bw_img.data();
103113
int const bw_wpl = bw_img.wordsPerLine();
104114

115+
double const range = 128.0;
116+
double const frac_d = (double) delta / range;
105117
uint32_t const msb = uint32_t(1) << 31;
106118
gray_line = gray.bits();
107119
for (int y = 0; y < h; ++y) {
@@ -124,12 +136,13 @@ BinaryImage binarizeSauvola(QImage const& src, QSize const window_size, double c
124136

125137
long double const variance = sqmean - mean * mean;
126138
long double const deviation = sqrt(fabs(variance));
139+
long double const frac_s = deviation / range;
127140

128-
long double const threshold = mean * (1.0 + k * (deviation / 128.0 - 1.0));
141+
long double const threshold = mean * (1.0 - k * (1.0 - (frac_s + frac_d)));
129142

130143
uint32_t const mask = msb >> (x & 31);
131144
int const origin = gray_line[x];
132-
if (origin < (threshold + delta))
145+
if (origin < threshold)
133146
{
134147
// black
135148
bw_line[x >> 5] |= mask;
@@ -147,10 +160,19 @@ BinaryImage binarizeSauvola(QImage const& src, QSize const window_size, double c
147160
return bw_img;
148161
}
149162

163+
/*
164+
* wolf = mean - k * (mean - min_v) * (1.0 - stderr / stdmax), k = 0.3
165+
* modification by zvezdochiot:
166+
* wolf = base * (1.0 - k * (1.0 - (frac_sn + frac_d))) + min_v, k = 0.3, delta = 0
167+
* base = mean - min_v, frac_sn = stderr / stdmax, frac_d = delta / range, range = 128.0
168+
*/
150169
BinaryImage binarizeWolf(
151-
QImage const& src, QSize const window_size,
152-
unsigned char const lower_bound, unsigned char const upper_bound,
153-
double const k, int const delta)
170+
QImage const& src,
171+
QSize const window_size,
172+
unsigned char const lower_bound,
173+
unsigned char const upper_bound,
174+
double const k,
175+
int const delta)
154176
{
155177
if (window_size.isEmpty())
156178
{
@@ -199,7 +221,7 @@ BinaryImage binarizeWolf(
199221
std::vector<float> means(w * h, 0);
200222
std::vector<float> deviations(w * h, 0);
201223

202-
long double max_deviation = 0;
224+
long double max_deviation = 1.0;
203225

204226
for (int y = 0; y < h; y++)
205227
{
@@ -239,6 +261,8 @@ BinaryImage binarizeWolf(
239261
uint32_t* bw_line = bw_img.data();
240262
int const bw_wpl = bw_img.wordsPerLine();
241263

264+
double const range = 128.0;
265+
double const frac_d = (double) delta / range;
242266
uint32_t const msb = uint32_t(1) << 31;
243267
gray_line = gray.bits();
244268
for (int y = 0; y < h; y++)
@@ -247,12 +271,171 @@ BinaryImage binarizeWolf(
247271
{
248272
float const mean = means[y * w + x];
249273
float const deviation = deviations[y * w + x];
250-
long double const a = 1.0 - deviation / max_deviation;
251-
long double const threshold = mean - k * a * (mean - min_gray_level);
274+
275+
long double const base = mean - min_gray_level;
276+
long double const frac_sn = deviation / max_deviation;
277+
long double const threshold = base * (1.0 - k * (1.0 - (frac_sn + frac_d))) + min_gray_level;
278+
279+
uint32_t const mask = msb >> (x & 31);
280+
unsigned char const origin = gray_line[x];
281+
if (origin < lower_bound || (origin <= upper_bound && (int) origin < threshold))
282+
{
283+
// black
284+
bw_line[x >> 5] |= mask;
285+
} else {
286+
// white
287+
bw_line[x >> 5] &= ~mask;
288+
}
289+
}
290+
gray_line += gray_bpl;
291+
bw_line += bw_wpl;
292+
}
293+
294+
return bw_img;
295+
}
296+
297+
/*
298+
* window = mean * (1 - k * md / kd), k = 1.0
299+
* where:
300+
* md = (mean + 1) / (meanFull + deviation + 1)
301+
* kd = 1 + kdm * kds
302+
* kdm = (2 * meanFull + 1) / (deviation + 1)
303+
* deviationD = deviationMax - deviationMin
304+
* kds = (deviation - deviationMin) / deviationD if deviationD > 0, 1 if other
305+
* modification by zvezdochiot:
306+
* md = (mean + 1 - delta) / (meanFull + deviation + 1), delta = 0
307+
*/
308+
BinaryImage binarizeWindow(
309+
QImage const& src,
310+
QSize const window_size,
311+
unsigned char const lower_bound,
312+
unsigned char const upper_bound,
313+
double const k,
314+
int const delta)
315+
{
316+
if (window_size.isEmpty())
317+
{
318+
throw std::invalid_argument("binarizeWolf: invalid window_size");
319+
}
320+
321+
if (src.isNull())
322+
{
323+
return BinaryImage();
324+
}
325+
int const w = src.width();
326+
int const h = src.height();
327+
328+
QImage gray(toGrayscale(src));
329+
if (gray.isNull())
330+
{
331+
return BinaryImage();
332+
}
333+
uint8_t const* gray_line = gray.bits();
334+
int const gray_bpl = gray.bytesPerLine();
335+
336+
IntegralImage<uint32_t> integral_image(w, h);
337+
IntegralImage<uint64_t> integral_sqimage(w, h);
338+
339+
uint32_t min_gray_level = 255;
340+
341+
for (int y = 0; y < h; y++)
342+
{
343+
integral_image.beginRow();
344+
integral_sqimage.beginRow();
345+
for (int x = 0; x < w; x++)
346+
{
347+
uint32_t const pixel = gray_line[x];
348+
integral_image.push(pixel);
349+
integral_sqimage.push(pixel * pixel);
350+
min_gray_level = std::min(min_gray_level, pixel);
351+
}
352+
gray_line += gray_bpl;
353+
}
354+
355+
int const window_lower_half = window_size.height() >> 1;
356+
int const window_upper_half = window_size.height() - window_lower_half;
357+
int const window_left_half = window_size.width() >> 1;
358+
int const window_right_half = window_size.width() - window_left_half;
359+
360+
int const areaFull = w * h;
361+
assert(areaFull > 0); // because w > 0 and h > 0
362+
long double const meanFull = integral_image.sum(QRect(0, 0, w, h)) / areaFull;
363+
long double deviationMax = 0.0;
364+
long double deviationMin = 256.0;
365+
366+
for (int y = 0; y < h; y++)
367+
{
368+
int const top = (y > window_lower_half) ? (y -window_lower_half) : 0;;
369+
int const bottom = ((y + window_upper_half) < h) ? (y + window_upper_half) : h;
370+
371+
for (int x = 0; x < w; x++)
372+
{
373+
int const left = (x > window_left_half) ? (x - window_left_half) : 0;;
374+
int const right = ((x + window_right_half) < w) ? (x + window_right_half) : w;
375+
int const area = (bottom - top) * (right - left);
376+
assert(area > 0); // because window_size > 0 and w > 0 and h > 0
252377

378+
QRect const rect(left, top, right - left, bottom - top);
379+
long double const window_sum = integral_image.sum(rect);
380+
long double const window_sqsum = integral_sqimage.sum(rect);
381+
382+
long double const r_area = 1.0 / area;
383+
long double const mean = window_sum * r_area;
384+
long double const sqmean = window_sqsum * r_area;
385+
386+
long double const variance = sqmean - mean * mean;
387+
long double const deviation = sqrt(fabs(variance));
388+
389+
deviationMax = (deviation > deviationMax) ? deviation : deviationMax;
390+
deviationMin = (deviation < deviationMin) ? deviation : deviationMin;
391+
}
392+
}
393+
394+
long double deviationD = (deviationMax > deviationMin) ? (deviationMax - deviationMin) : 1.0;
395+
396+
BinaryImage bw_img(w, h);
397+
if (bw_img.isNull())
398+
{
399+
return BinaryImage();
400+
}
401+
uint32_t* bw_line = bw_img.data();
402+
int const bw_wpl = bw_img.wordsPerLine();
403+
404+
uint32_t const msb = uint32_t(1) << 31;
405+
gray_line = gray.bits();
406+
for (int y = 0; y < h; y++)
407+
{
408+
int const top = (y > window_lower_half) ? (y -window_lower_half) : 0;;
409+
int const bottom = ((y + window_upper_half) < h) ? (y + window_upper_half) : h;
410+
411+
for (int x = 0; x < w; x++)
412+
{
413+
int const left = (x > window_left_half) ? (x - window_left_half) : 0;;
414+
int const right = ((x + window_right_half) < w) ? (x + window_right_half) : w;
415+
int const area = (bottom - top) * (right - left);
416+
assert(area > 0); // because window_size > 0 and w > 0 and h > 0
417+
418+
QRect const rect(left, top, right - left, bottom - top);
419+
long double const window_sum = integral_image.sum(rect);
420+
long double const window_sqsum = integral_sqimage.sum(rect);
421+
422+
long double const r_area = 1.0 / area;
423+
long double const mean = window_sum * r_area;
424+
long double const sqmean = window_sqsum * r_area;
425+
426+
long double const variance = sqmean - mean * mean;
427+
long double const deviation = sqrt(fabs(variance));
428+
429+
long double const md = (mean + 1.0 - delta) / (meanFull + deviation + 1.0);
430+
long double const kdm = (meanFull + meanFull + 1.0) / (deviation + 1.0);
431+
long double const kds = (deviation - deviationMin) / deviationD;
432+
long double const kd = 1.0 + kdm * kds;
433+
434+
long double const threshold = mean * (1.0 - k * 3.0 * md / kd);
435+
253436
uint32_t const mask = msb >> (x & 31);
254437
unsigned char const origin = gray_line[x];
255-
if (origin < lower_bound || (origin <= upper_bound && (int) origin < (threshold + delta)))
438+
if (origin < lower_bound || (origin <= upper_bound && (int) origin < threshold))
256439
{
257440
// black
258441
bw_line[x >> 5] |= mask;
@@ -354,6 +537,11 @@ BinaryImage binarizeBradley(
354537
return bw_img;
355538
} // binarizeBradley
356539

540+
/*
541+
* grad = mean * k + meanG * (1.0 - k), meanG = mean(I * G) / mean(G), G = |I - mean|, k = 0.75
542+
* modification by zvezdochiot:
543+
* mean = mean + delta, delta = 0
544+
*/
357545
BinaryImage binarizeGrad(
358546
QImage const& src, QSize const window_size,
359547
double const k, int const delta)
@@ -418,7 +606,7 @@ BinaryImage binarizeGrad(
418606
double const window_sum = integral_image.sum(rect);
419607

420608
double const r_area = 1.0 / area;
421-
double const mean = window_sum * r_area + 0.5;
609+
double const mean = window_sum * r_area + 0.5 + delta;
422610
int const imean = (int) ((mean < 0.0) ? 0.0 : (mean < 255.0) ? mean : 255.0);
423611
gmean_line[x] = imean;
424612
}
@@ -471,7 +659,7 @@ BinaryImage binarizeGrad(
471659
double const mean = gmean_line[x];
472660
double const threshold = mean_grad + mean * k;
473661
uint32_t const mask = msb >> (x & 31);
474-
if (origin < (threshold + delta))
662+
if (origin < threshold)
475663
{
476664
// black
477665
bw_line[x >> 5] |= mask;
@@ -489,6 +677,9 @@ BinaryImage binarizeGrad(
489677
return bw_img;
490678
} // binarizeGrad
491679

680+
/*
681+
* edgediv == EdgeDiv image prefilter before the Otsu threshold
682+
*/
492683
BinaryImage binarizeEdgeDiv(
493684
QImage const& src, QSize const window_size,
494685
double const kep, double const kbd, int const delta)

src/imageproc/Binarize.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ BinaryImage binarizeWolf(
8080
unsigned char lower_bound = 1, unsigned char upper_bound = 254,
8181
double k = 0.30, int delta = 0);
8282

83+
/**
84+
* \brief Image binarization using Dynamic Window based thresholding method.
85+
*
86+
* Bataineh, B., Abdullah, S. N. H. S., & Omar, K. (2011).
87+
* An adaptive local binarization method for document images based
88+
* on a novel thresholding method and dynamic windows.
89+
* Pattern Recognition Letters, 32(14), 1805–1813.
90+
*
91+
* \param src The image to binarize.
92+
* \param window_size The dimensions of a pixel neighborhood to consider.
93+
* \param lower_bound The minimum possible gray level that can be made white.
94+
* \param upper_bound The maximum possible gray level that can be made black.
95+
*/
96+
BinaryImage binarizeWindow(
97+
QImage const& src, QSize window_size,
98+
unsigned char lower_bound = 1, unsigned char upper_bound = 254,
99+
double k = 0.33, int delta = 0);
100+
83101
/**
84102
* \brief Image binarization using Bradley's adaptive thresholding method.
85103
*

version.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
#define BUILD_MONTH ((BUILD_DATE_IS_BAD) ? 00 : COMPUTE_BUILD_MONTH)
7373
#define BUILD_DAY ((BUILD_DATE_IS_BAD) ? 00 : COMPUTE_BUILD_DAY)
7474

75-
#define VERSION "0.2.14"
76-
#define VERSION_QUAD "0.2.14.0"
75+
#define VERSION "0.2.15"
76+
#define VERSION_QUAD "0.2.15.0"
7777

7878
#endif

0 commit comments

Comments
 (0)