Skip to content

Commit d5e0af0

Browse files
committed
1.0.20: threshold: new method Grad
1 parent 0dc4f1e commit d5e0af0

File tree

7 files changed

+192
-40
lines changed

7 files changed

+192
-40
lines changed

src/app/DefaultParamsDialog.cpp

+12-10
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@ DefaultParamsDialog::DefaultParamsDialog(QWidget* parent)
4545
fillingColorBox->addItem(tr("White"), FILL_WHITE);
4646
fillingColorBox->addItem(tr("Black"), FILL_BLACK);
4747

48-
thresholdMethodBox->addItem(tr("Otsu"), OTSU);
49-
thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA);
50-
thresholdMethodBox->addItem(tr("Wolf"), WOLF);
51-
thresholdMethodBox->addItem(tr("Bradley"), BRADLEY);
52-
thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS);
53-
thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV);
54-
thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV);
48+
thresholdMethodBox->addItem(tr("Otsu"), T_OTSU);
49+
thresholdMethodBox->addItem(tr("Sauvola"), T_SAUVOLA);
50+
thresholdMethodBox->addItem(tr("Wolf"), T_WOLF);
51+
thresholdMethodBox->addItem(tr("Bradley"), T_BRADLEY);
52+
thresholdMethodBox->addItem(tr("Grad"), T_GRAD);
53+
thresholdMethodBox->addItem(tr("EdgePlus"), T_EDGEPLUS);
54+
thresholdMethodBox->addItem(tr("BlurDiv"), T_BLURDIV);
55+
thresholdMethodBox->addItem(tr("EdgeDiv"), T_EDGEDIV);
5556

5657
pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE);
5758
pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE);
@@ -666,10 +667,11 @@ std::unique_ptr<DefaultParams> DefaultParamsDialog::buildParams() const {
666667
blackWhiteOptions.setBinarizationMethod(binarizationMethod);
667668
blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value());
668669
blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value());
669-
if (binarizationMethod == SAUVOLA || binarizationMethod == BRADLEY || binarizationMethod == EDGEPLUS
670-
|| binarizationMethod == BLURDIV || binarizationMethod == EDGEDIV) {
670+
if (binarizationMethod == T_SAUVOLA || binarizationMethod == T_BRADLEY
671+
|| binarizationMethod == T_GRAD || binarizationMethod == T_EDGEPLUS
672+
|| binarizationMethod == T_BLURDIV || binarizationMethod == T_EDGEDIV) {
671673
blackWhiteOptions.setWindowSize(sauvolaWindowSize->value());
672-
} else if (binarizationMethod == WOLF) {
674+
} else if (binarizationMethod == T_WOLF) {
673675
blackWhiteOptions.setWindowSize(wolfWindowSize->value());
674676
}
675677
blackWhiteOptions.setWolfCoef(wolfCoef->value());

src/core/filters/output/BlackWhiteOptions.cpp

+20-15
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ BlackWhiteOptions::BlackWhiteOptions()
2121
m_wolfLowerBound(1),
2222
m_wolfUpperBound(254),
2323
m_wolfCoef(0.3),
24-
m_binarizationMethod(OTSU) {}
24+
m_binarizationMethod(T_OTSU) {}
2525

2626
BlackWhiteOptions::BlackWhiteOptions(const QDomElement& el)
2727
: m_thresholdAdjustment(el.attribute("thresholdAdj").toInt()),
@@ -69,44 +69,49 @@ bool BlackWhiteOptions::operator!=(const BlackWhiteOptions& other) const {
6969

7070
BinarizationMethod BlackWhiteOptions::parseBinarizationMethod(const QString& str) {
7171
if (str == "wolf") {
72-
return WOLF;
72+
return T_WOLF;
7373
} else if (str == "sauvola") {
74-
return SAUVOLA;
74+
return T_SAUVOLA;
7575
} else if (str == "bradley") {
76-
return BRADLEY;
76+
return T_BRADLEY;
77+
} else if (str == "grad") {
78+
return T_GRAD;
7779
} else if (str == "edgeplus") {
78-
return EDGEPLUS;
80+
return T_EDGEPLUS;
7981
} else if (str == "blurdiv") {
80-
return BLURDIV;
82+
return T_BLURDIV;
8183
} else if (str == "edgediv") {
82-
return EDGEDIV;
84+
return T_EDGEDIV;
8385
} else {
84-
return OTSU;
86+
return T_OTSU;
8587
}
8688
}
8789

8890
QString BlackWhiteOptions::formatBinarizationMethod(BinarizationMethod type) {
8991
QString str = "";
9092
switch (type) {
91-
case OTSU:
93+
case T_OTSU:
9294
str = "otsu";
9395
break;
94-
case SAUVOLA:
96+
case T_SAUVOLA:
9597
str = "sauvola";
9698
break;
97-
case WOLF:
99+
case T_WOLF:
98100
str = "wolf";
99101
break;
100-
case BRADLEY:
102+
case T_BRADLEY:
101103
str = "bradley";
102104
break;
103-
case EDGEPLUS:
105+
case T_GRAD:
106+
str = "grad";
107+
break;
108+
case T_EDGEPLUS:
104109
str = "edgeplus";
105110
break;
106-
case BLURDIV:
111+
case T_BLURDIV:
107112
str = "blurdiv";
108113
break;
109-
case EDGEDIV:
114+
case T_EDGEDIV:
110115
str = "edgediv";
111116
break;
112117
}

src/core/filters/output/BlackWhiteOptions.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class QDomDocument;
99
class QDomElement;
1010

1111
namespace output {
12-
enum BinarizationMethod { OTSU, SAUVOLA, WOLF, BRADLEY, EDGEPLUS, BLURDIV, EDGEDIV };
12+
enum BinarizationMethod { T_OTSU, T_SAUVOLA, T_WOLF, T_BRADLEY, T_GRAD, T_EDGEPLUS, T_BLURDIV, T_EDGEDIV };
1313

1414
class BlackWhiteOptions {
1515
public:

src/core/filters/output/OptionsWidget.cpp

+11-7
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
3939
colorModeSelector->addItem(tr("Color / Grayscale"), COLOR_GRAYSCALE);
4040
colorModeSelector->addItem(tr("Mixed"), MIXED);
4141

42-
thresholdMethodBox->addItem(tr("Otsu"), OTSU);
43-
thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA);
44-
thresholdMethodBox->addItem(tr("Wolf"), WOLF);
45-
thresholdMethodBox->addItem(tr("Bradley"), BRADLEY);
46-
thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS);
47-
thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV);
48-
thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV);
42+
thresholdMethodBox->addItem(tr("Otsu"), T_OTSU);
43+
thresholdMethodBox->addItem(tr("Sauvola"), T_SAUVOLA);
44+
thresholdMethodBox->addItem(tr("Wolf"), T_WOLF);
45+
thresholdMethodBox->addItem(tr("Bradley"), T_BRADLEY);
46+
thresholdMethodBox->addItem(tr("Grad"), T_GRAD);
47+
thresholdMethodBox->addItem(tr("EdgePlus"), T_EDGEPLUS);
48+
thresholdMethodBox->addItem(tr("BlurDiv"), T_BLURDIV);
49+
thresholdMethodBox->addItem(tr("EdgeDiv"), T_EDGEDIV);
4950

5051
fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND);
5152
fillingColorBox->addItem(tr("White"), FILL_WHITE);
@@ -57,6 +58,8 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
5758
QPointer<BinarizationOptionsWidget> wolfBinarizationOptionsWidget = new WolfBinarizationOptionsWidget(m_settings);
5859
QPointer<BinarizationOptionsWidget> bradleyBinarizationOptionsWidget
5960
= new SauvolaBinarizationOptionsWidget(m_settings);
61+
QPointer<BinarizationOptionsWidget> gradBinarizationOptionsWidget
62+
= new SauvolaBinarizationOptionsWidget(m_settings);
6063
QPointer<BinarizationOptionsWidget> edgeplusBinarizationOptionsWidget
6164
= new SauvolaBinarizationOptionsWidget(m_settings);
6265
QPointer<BinarizationOptionsWidget> blurdivBinarizationOptionsWidget
@@ -71,6 +74,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
7174
addBinarizationOptionsWidget(sauvolaBinarizationOptionsWidget);
7275
addBinarizationOptionsWidget(wolfBinarizationOptionsWidget);
7376
addBinarizationOptionsWidget(bradleyBinarizationOptionsWidget);
77+
addBinarizationOptionsWidget(gradBinarizationOptionsWidget);
7478
addBinarizationOptionsWidget(edgeplusBinarizationOptionsWidget);
7579
addBinarizationOptionsWidget(blurdivBinarizationOptionsWidget);
7680
addBinarizationOptionsWidget(edgedivBinarizationOptionsWidget);

src/core/filters/output/OutputGenerator.cpp

+15-7
Original file line numberDiff line numberDiff line change
@@ -2294,22 +2294,22 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const {
22942294

22952295
BinaryImage binarized;
22962296
switch (binarizationMethod) {
2297-
case OTSU: {
2297+
case T_OTSU: {
22982298
GrayscaleHistogram hist(image);
22992299
const BinaryThreshold bwThresh(BinaryThreshold::otsuThreshold(hist));
23002300

23012301
binarized = BinaryImage(image, adjustThreshold(bwThresh));
23022302
break;
23032303
}
2304-
case SAUVOLA: {
2304+
case T_SAUVOLA: {
23052305
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
23062306
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
23072307
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();
23082308

23092309
binarized = binarizeSauvola(image, windowsSize, thresholdCoef, thresholdDelta);
23102310
break;
23112311
}
2312-
case WOLF: {
2312+
case T_WOLF: {
23132313
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
23142314
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
23152315
const auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound();
@@ -2319,31 +2319,39 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const {
23192319
binarized = binarizeWolf(image, windowsSize, lowerBound, upperBound, thresholdCoef, thresholdDelta);
23202320
break;
23212321
}
2322-
case BRADLEY: {
2322+
case T_BRADLEY: {
23232323
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
23242324
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
23252325
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();
23262326

23272327
binarized = binarizeBradley(image, windowsSize, thresholdCoef, thresholdDelta);
23282328
break;
23292329
}
2330-
case EDGEPLUS: {
2330+
case T_GRAD: {
2331+
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
2332+
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
2333+
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();
2334+
2335+
binarized = binarizeGrad(image, windowsSize, thresholdCoef, thresholdDelta);
2336+
break;
2337+
}
2338+
case T_EDGEPLUS: {
23312339
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
23322340
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
23332341
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();
23342342

23352343
binarized = binarizeEdgeDiv(image, windowsSize, thresholdCoef, 0.0, thresholdDelta);
23362344
break;
23372345
}
2338-
case BLURDIV: {
2346+
case T_BLURDIV: {
23392347
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
23402348
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
23412349
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();
23422350

23432351
binarized = binarizeEdgeDiv(image, windowsSize, 0.0, thresholdCoef, thresholdDelta);
23442352
break;
23452353
}
2346-
case EDGEDIV: {
2354+
case T_EDGEDIV: {
23472355
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
23482356
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
23492357
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

src/imageproc/Binarize.cpp

+126
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ BinaryImage binarizeSauvola(const QImage& src, const QSize windowSize, const dou
3232
}
3333

3434
const QImage gray(toGrayscale(src));
35+
if (gray.isNull()) {
36+
return BinaryImage();
37+
}
3538
const int w = gray.width();
3639
const int h = gray.height();
3740

@@ -114,6 +117,9 @@ BinaryImage binarizeWolf(const QImage& src,
114117
}
115118

116119
const QImage gray(toGrayscale(src));
120+
if (gray.isNull()) {
121+
return BinaryImage();
122+
}
117123
const int w = gray.width();
118124
const int h = gray.height();
119125

@@ -211,6 +217,9 @@ BinaryImage binarizeBradley(const QImage& src, const QSize windowSize, const dou
211217
}
212218

213219
QImage gray(toGrayscale(src));
220+
if (gray.isNull()) {
221+
return BinaryImage();
222+
}
214223
const int w = gray.width();
215224
const int h = gray.height();
216225

@@ -268,6 +277,120 @@ BinaryImage binarizeBradley(const QImage& src, const QSize windowSize, const dou
268277
return bwImg;
269278
} // binarizeBradley
270279

280+
BinaryImage binarizeGrad(const QImage& src, const QSize windowSize, const double k, const double delta) {
281+
if (windowSize.isEmpty()) {
282+
throw std::invalid_argument("binarizeGrad: invalid windowSize");
283+
}
284+
285+
if (src.isNull()) {
286+
return BinaryImage();
287+
}
288+
289+
QImage gray(toGrayscale(src));
290+
if (gray.isNull()) {
291+
return BinaryImage();
292+
}
293+
QImage gmean(toGrayscale(src));
294+
if (gmean.isNull()) {
295+
return BinaryImage();
296+
}
297+
const int w = gray.width();
298+
const int h = gray.height();
299+
300+
const uint8_t* grayLine = gray.bits();
301+
const int grayBpl = gray.bytesPerLine();
302+
303+
uint8_t* gmeanLine = gmean.bits();
304+
const int gmeanBpl = gmean.bytesPerLine();
305+
306+
IntegralImage<uint32_t> integralImage(w, h);
307+
308+
for (int y = 0; y < h; ++y) {
309+
integralImage.beginRow();
310+
for (int x = 0; x < w; ++x) {
311+
const uint32_t pixel = grayLine[x];
312+
integralImage.push(pixel);
313+
}
314+
grayLine += grayBpl;
315+
}
316+
317+
const int windowLowerHalf = windowSize.height() >> 1;
318+
const int windowUpperHalf = windowSize.height() - windowLowerHalf;
319+
const int windowLeftHalf = windowSize.width() >> 1;
320+
const int windowRightHalf = windowSize.width() - windowLeftHalf;
321+
322+
for (int y = 0; y < h; ++y) {
323+
const int top = std::max(0, y - windowLowerHalf);
324+
const int bottom = std::min(h, y + windowUpperHalf); // exclusive
325+
for (int x = 0; x < w; ++x) {
326+
const int left = std::max(0, x - windowLeftHalf);
327+
const int right = std::min(w, x + windowRightHalf); // exclusive
328+
const int area = (bottom - top) * (right - left);
329+
assert(area > 0); // because windowSize > 0 and w > 0 and h > 0
330+
const QRect rect(left, top, right - left, bottom - top);
331+
const double windowSum = integralImage.sum(rect);
332+
333+
const double rArea = 1.0 / area;
334+
const double mean = windowSum * rArea + 0.5;
335+
const int imean = (int)((mean < 0.0) ? 0.0 : (mean < 255.0) ? mean : 255.0);
336+
gmeanLine[x] = imean;
337+
}
338+
gmeanLine += gmeanBpl;
339+
}
340+
341+
double gvalue = 127.5;
342+
double sum_g = 0.0, sum_gi = 0.0;
343+
grayLine = gray.bits();
344+
gmeanLine = gmean.bits();
345+
for (int y = 0; y < h; y++){
346+
double sum_gl = 0.0;
347+
double sum_gil = 0.0;
348+
for (int x = 0; x < w; x++){
349+
double gi = grayLine[x];
350+
double g = gmeanLine[x];
351+
g -= gi;
352+
g = (g < 0.0) ? -g : g;
353+
gi *= g;
354+
sum_gl += g;
355+
sum_gil += gi;
356+
}
357+
sum_g += sum_gl;
358+
sum_gi += sum_gil;
359+
grayLine += grayBpl;
360+
gmeanLine += gmeanBpl;
361+
}
362+
gvalue = (sum_g > 0.0) ? (sum_gi / sum_g) : gvalue;
363+
364+
double const meanGrad = gvalue * (1.0 - k);
365+
366+
BinaryImage bwImg(w, h);
367+
uint32_t* bwLine = bwImg.data();
368+
const int bwWpl = bwImg.wordsPerLine();
369+
370+
grayLine = gray.bits();
371+
gmeanLine = gmean.bits();
372+
for (int y = 0; y < h; ++y) {
373+
for (int x = 0; x < w; ++x) {
374+
const double origin = grayLine[x];
375+
const double mean = gmeanLine[x];
376+
const double threshold = meanGrad + mean * k;
377+
const uint32_t msb = uint32_t(1) << 31;
378+
const uint32_t mask = msb >> (x & 31);
379+
if (origin < (threshold + delta)) {
380+
// black
381+
bwLine[x >> 5] |= mask;
382+
} else {
383+
// white
384+
bwLine[x >> 5] &= ~mask;
385+
}
386+
}
387+
grayLine += grayBpl;
388+
gmeanLine += gmeanBpl;
389+
bwLine += bwWpl;
390+
}
391+
return bwImg;
392+
} // binarizeGrad
393+
271394
BinaryImage binarizeEdgeDiv(const QImage& src,
272395
const QSize windowSize,
273396
const double kep,
@@ -282,6 +405,9 @@ BinaryImage binarizeEdgeDiv(const QImage& src,
282405
}
283406

284407
QImage gray(toGrayscale(src));
408+
if (gray.isNull()) {
409+
return BinaryImage();
410+
}
285411
const int w = gray.width();
286412
const int h = gray.height();
287413

0 commit comments

Comments
 (0)