Skip to content

Commit

Permalink
1.0.20: threshold: new method Grad
Browse files Browse the repository at this point in the history
  • Loading branch information
zvezdochiot committed Aug 10, 2024
1 parent 0dc4f1e commit 4e06c82
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 41 deletions.
21 changes: 11 additions & 10 deletions src/app/DefaultParamsDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ DefaultParamsDialog::DefaultParamsDialog(QWidget* parent)
fillingColorBox->addItem(tr("White"), FILL_WHITE);
fillingColorBox->addItem(tr("Black"), FILL_BLACK);

thresholdMethodBox->addItem(tr("Otsu"), OTSU);
thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA);
thresholdMethodBox->addItem(tr("Wolf"), WOLF);
thresholdMethodBox->addItem(tr("Bradley"), BRADLEY);
thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS);
thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV);
thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV);
thresholdMethodBox->addItem(tr("Otsu"), T_OTSU);
thresholdMethodBox->addItem(tr("Sauvola"), T_SAUVOLA);
thresholdMethodBox->addItem(tr("Wolf"), T_WOLF);
thresholdMethodBox->addItem(tr("Bradley"), T_BRADLEY);
thresholdMethodBox->addItem(tr("Grad"), T_GRAD);
thresholdMethodBox->addItem(tr("EdgePlus"), T_EDGEPLUS);
thresholdMethodBox->addItem(tr("BlurDiv"), T_BLURDIV);
thresholdMethodBox->addItem(tr("EdgeDiv"), T_EDGEDIV);

pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE);
pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE);
Expand Down Expand Up @@ -666,10 +667,10 @@ std::unique_ptr<DefaultParams> DefaultParamsDialog::buildParams() const {
blackWhiteOptions.setBinarizationMethod(binarizationMethod);
blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value());
blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value());
if (binarizationMethod == SAUVOLA || binarizationMethod == BRADLEY || binarizationMethod == EDGEPLUS
|| binarizationMethod == BLURDIV || binarizationMethod == EDGEDIV) {
if (binarizationMethod == T_SAUVOLA || binarizationMethod == T_BRADLEY || binarizationMethod == T_GRAD
|| binarizationMethod == T_EDGEPLUS || binarizationMethod == T_BLURDIV || binarizationMethod == T_EDGEDIV) {
blackWhiteOptions.setWindowSize(sauvolaWindowSize->value());
} else if (binarizationMethod == WOLF) {
} else if (binarizationMethod == T_WOLF) {
blackWhiteOptions.setWindowSize(wolfWindowSize->value());
}
blackWhiteOptions.setWolfCoef(wolfCoef->value());
Expand Down
2 changes: 1 addition & 1 deletion src/core/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#ifndef SCANTAILOR_CORE_UTILS_H_
#define SCANTAILOR_CORE_UTILS_H_

#include <QString>
#include <QObject>
#include <QString>
#include <map>
#include <unordered_map>

Expand Down
35 changes: 20 additions & 15 deletions src/core/filters/output/BlackWhiteOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ BlackWhiteOptions::BlackWhiteOptions()
m_wolfLowerBound(1),
m_wolfUpperBound(254),
m_wolfCoef(0.3),
m_binarizationMethod(OTSU) {}
m_binarizationMethod(T_OTSU) {}

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

BinarizationMethod BlackWhiteOptions::parseBinarizationMethod(const QString& str) {
if (str == "wolf") {
return WOLF;
return T_WOLF;
} else if (str == "sauvola") {
return SAUVOLA;
return T_SAUVOLA;
} else if (str == "bradley") {
return BRADLEY;
return T_BRADLEY;
} else if (str == "grad") {
return T_GRAD;
} else if (str == "edgeplus") {
return EDGEPLUS;
return T_EDGEPLUS;
} else if (str == "blurdiv") {
return BLURDIV;
return T_BLURDIV;
} else if (str == "edgediv") {
return EDGEDIV;
return T_EDGEDIV;
} else {
return OTSU;
return T_OTSU;
}
}

QString BlackWhiteOptions::formatBinarizationMethod(BinarizationMethod type) {
QString str = "";
switch (type) {
case OTSU:
case T_OTSU:
str = "otsu";
break;
case SAUVOLA:
case T_SAUVOLA:
str = "sauvola";
break;
case WOLF:
case T_WOLF:
str = "wolf";
break;
case BRADLEY:
case T_BRADLEY:
str = "bradley";
break;
case EDGEPLUS:
case T_GRAD:
str = "grad";
break;
case T_EDGEPLUS:
str = "edgeplus";
break;
case BLURDIV:
case T_BLURDIV:
str = "blurdiv";
break;
case EDGEDIV:
case T_EDGEDIV:
str = "edgediv";
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/filters/output/BlackWhiteOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class QDomDocument;
class QDomElement;

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

class BlackWhiteOptions {
public:
Expand Down
17 changes: 10 additions & 7 deletions src/core/filters/output/OptionsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
colorModeSelector->addItem(tr("Color / Grayscale"), COLOR_GRAYSCALE);
colorModeSelector->addItem(tr("Mixed"), MIXED);

thresholdMethodBox->addItem(tr("Otsu"), OTSU);
thresholdMethodBox->addItem(tr("Sauvola"), SAUVOLA);
thresholdMethodBox->addItem(tr("Wolf"), WOLF);
thresholdMethodBox->addItem(tr("Bradley"), BRADLEY);
thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS);
thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV);
thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV);
thresholdMethodBox->addItem(tr("Otsu"), T_OTSU);
thresholdMethodBox->addItem(tr("Sauvola"), T_SAUVOLA);
thresholdMethodBox->addItem(tr("Wolf"), T_WOLF);
thresholdMethodBox->addItem(tr("Bradley"), T_BRADLEY);
thresholdMethodBox->addItem(tr("Grad"), T_GRAD);
thresholdMethodBox->addItem(tr("EdgePlus"), T_EDGEPLUS);
thresholdMethodBox->addItem(tr("BlurDiv"), T_BLURDIV);
thresholdMethodBox->addItem(tr("EdgeDiv"), T_EDGEDIV);

fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND);
fillingColorBox->addItem(tr("White"), FILL_WHITE);
Expand All @@ -57,6 +58,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
QPointer<BinarizationOptionsWidget> wolfBinarizationOptionsWidget = new WolfBinarizationOptionsWidget(m_settings);
QPointer<BinarizationOptionsWidget> bradleyBinarizationOptionsWidget
= new SauvolaBinarizationOptionsWidget(m_settings);
QPointer<BinarizationOptionsWidget> gradBinarizationOptionsWidget = new SauvolaBinarizationOptionsWidget(m_settings);
QPointer<BinarizationOptionsWidget> edgeplusBinarizationOptionsWidget
= new SauvolaBinarizationOptionsWidget(m_settings);
QPointer<BinarizationOptionsWidget> blurdivBinarizationOptionsWidget
Expand All @@ -71,6 +73,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
addBinarizationOptionsWidget(sauvolaBinarizationOptionsWidget);
addBinarizationOptionsWidget(wolfBinarizationOptionsWidget);
addBinarizationOptionsWidget(bradleyBinarizationOptionsWidget);
addBinarizationOptionsWidget(gradBinarizationOptionsWidget);
addBinarizationOptionsWidget(edgeplusBinarizationOptionsWidget);
addBinarizationOptionsWidget(blurdivBinarizationOptionsWidget);
addBinarizationOptionsWidget(edgedivBinarizationOptionsWidget);
Expand Down
22 changes: 15 additions & 7 deletions src/core/filters/output/OutputGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2294,22 +2294,22 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const {

BinaryImage binarized;
switch (binarizationMethod) {
case OTSU: {
case T_OTSU: {
GrayscaleHistogram hist(image);
const BinaryThreshold bwThresh(BinaryThreshold::otsuThreshold(hist));

binarized = BinaryImage(image, adjustThreshold(bwThresh));
break;
}
case SAUVOLA: {
case T_SAUVOLA: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeSauvola(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
case WOLF: {
case T_WOLF: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound();
Expand All @@ -2319,31 +2319,39 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const {
binarized = binarizeWolf(image, windowsSize, lowerBound, upperBound, thresholdCoef, thresholdDelta);
break;
}
case BRADLEY: {
case T_BRADLEY: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeBradley(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
case EDGEPLUS: {
case T_GRAD: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeGrad(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
case T_EDGEPLUS: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeEdgeDiv(image, windowsSize, thresholdCoef, 0.0, thresholdDelta);
break;
}
case BLURDIV: {
case T_BLURDIV: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeEdgeDiv(image, windowsSize, 0.0, thresholdCoef, thresholdDelta);
break;
}
case EDGEDIV: {
case T_EDGEDIV: {
const double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
const double thresholdCoef = blackWhiteOptions.getSauvolaCoef();
Expand Down
126 changes: 126 additions & 0 deletions src/imageproc/Binarize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ BinaryImage binarizeSauvola(const QImage& src, const QSize windowSize, const dou
}

const QImage gray(toGrayscale(src));
if (gray.isNull()) {
return BinaryImage();
}
const int w = gray.width();
const int h = gray.height();

Expand Down Expand Up @@ -114,6 +117,9 @@ BinaryImage binarizeWolf(const QImage& src,
}

const QImage gray(toGrayscale(src));
if (gray.isNull()) {
return BinaryImage();
}
const int w = gray.width();
const int h = gray.height();

Expand Down Expand Up @@ -211,6 +217,9 @@ BinaryImage binarizeBradley(const QImage& src, const QSize windowSize, const dou
}

QImage gray(toGrayscale(src));
if (gray.isNull()) {
return BinaryImage();
}
const int w = gray.width();
const int h = gray.height();

Expand Down Expand Up @@ -268,6 +277,120 @@ BinaryImage binarizeBradley(const QImage& src, const QSize windowSize, const dou
return bwImg;
} // binarizeBradley

BinaryImage binarizeGrad(const QImage& src, const QSize windowSize, const double k, const double delta) {
if (windowSize.isEmpty()) {
throw std::invalid_argument("binarizeGrad: invalid windowSize");
}

if (src.isNull()) {
return BinaryImage();
}

QImage gray(toGrayscale(src));
if (gray.isNull()) {
return BinaryImage();
}
QImage gmean(toGrayscale(src));
if (gmean.isNull()) {
return BinaryImage();
}
const int w = gray.width();
const int h = gray.height();

const uint8_t* grayLine = gray.bits();
const int grayBpl = gray.bytesPerLine();

uint8_t* gmeanLine = gmean.bits();
const int gmeanBpl = gmean.bytesPerLine();

IntegralImage<uint32_t> integralImage(w, h);

for (int y = 0; y < h; ++y) {
integralImage.beginRow();
for (int x = 0; x < w; ++x) {
const uint32_t pixel = grayLine[x];
integralImage.push(pixel);
}
grayLine += grayBpl;
}

const int windowLowerHalf = windowSize.height() >> 1;
const int windowUpperHalf = windowSize.height() - windowLowerHalf;
const int windowLeftHalf = windowSize.width() >> 1;
const int windowRightHalf = windowSize.width() - windowLeftHalf;

for (int y = 0; y < h; ++y) {
const int top = std::max(0, y - windowLowerHalf);
const int bottom = std::min(h, y + windowUpperHalf); // exclusive
for (int x = 0; x < w; ++x) {
const int left = std::max(0, x - windowLeftHalf);
const int right = std::min(w, x + windowRightHalf); // exclusive
const int area = (bottom - top) * (right - left);
assert(area > 0); // because windowSize > 0 and w > 0 and h > 0
const QRect rect(left, top, right - left, bottom - top);
const double windowSum = integralImage.sum(rect);

const double rArea = 1.0 / area;
const double mean = windowSum * rArea + 0.5;
const int imean = (int) ((mean < 0.0) ? 0.0 : (mean < 255.0) ? mean : 255.0);
gmeanLine[x] = imean;
}
gmeanLine += gmeanBpl;
}

double gvalue = 127.5;
double sum_g = 0.0, sum_gi = 0.0;
grayLine = gray.bits();
gmeanLine = gmean.bits();
for (int y = 0; y < h; y++) {
double sum_gl = 0.0;
double sum_gil = 0.0;
for (int x = 0; x < w; x++) {
double gi = grayLine[x];
double g = gmeanLine[x];
g -= gi;
g = (g < 0.0) ? -g : g;
gi *= g;
sum_gl += g;
sum_gil += gi;
}
sum_g += sum_gl;
sum_gi += sum_gil;
grayLine += grayBpl;
gmeanLine += gmeanBpl;
}
gvalue = (sum_g > 0.0) ? (sum_gi / sum_g) : gvalue;

double const meanGrad = gvalue * (1.0 - k);

BinaryImage bwImg(w, h);
uint32_t* bwLine = bwImg.data();
const int bwWpl = bwImg.wordsPerLine();

grayLine = gray.bits();
gmeanLine = gmean.bits();
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
const double origin = grayLine[x];
const double mean = gmeanLine[x];
const double threshold = meanGrad + mean * k;
const uint32_t msb = uint32_t(1) << 31;
const uint32_t mask = msb >> (x & 31);
if (origin < (threshold + delta)) {
// black
bwLine[x >> 5] |= mask;
} else {
// white
bwLine[x >> 5] &= ~mask;
}
}
grayLine += grayBpl;
gmeanLine += gmeanBpl;
bwLine += bwWpl;
}
return bwImg;
} // binarizeGrad

BinaryImage binarizeEdgeDiv(const QImage& src,
const QSize windowSize,
const double kep,
Expand All @@ -282,6 +405,9 @@ BinaryImage binarizeEdgeDiv(const QImage& src,
}

QImage gray(toGrayscale(src));
if (gray.isNull()) {
return BinaryImage();
}
const int w = gray.width();
const int h = gray.height();

Expand Down
Loading

0 comments on commit 4e06c82

Please sign in to comment.