diff --git a/src/app/DefaultParamsDialog.cpp b/src/app/DefaultParamsDialog.cpp index ffa239535..c9c1c2f6a 100644 --- a/src/app/DefaultParamsDialog.cpp +++ b/src/app/DefaultParamsDialog.cpp @@ -48,6 +48,7 @@ DefaultParamsDialog::DefaultParamsDialog(QWidget* parent) 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); @@ -665,8 +666,8 @@ std::unique_ptr DefaultParamsDialog::buildParams() const { blackWhiteOptions.setBinarizationMethod(binarizationMethod); blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value()); blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value()); - if (binarizationMethod == SAUVOLA || binarizationMethod == EDGEPLUS || binarizationMethod == BLURDIV - || binarizationMethod == EDGEDIV) { + if (binarizationMethod == SAUVOLA || binarizationMethod == BRADLEY || binarizationMethod == EDGEPLUS + || binarizationMethod == BLURDIV || binarizationMethod == EDGEDIV) { blackWhiteOptions.setWindowSize(sauvolaWindowSize->value()); } else if (binarizationMethod == WOLF) { blackWhiteOptions.setWindowSize(wolfWindowSize->value()); diff --git a/src/core/filters/output/BlackWhiteOptions.cpp b/src/core/filters/output/BlackWhiteOptions.cpp index 235789cfe..88e8551bf 100644 --- a/src/core/filters/output/BlackWhiteOptions.cpp +++ b/src/core/filters/output/BlackWhiteOptions.cpp @@ -72,6 +72,8 @@ BinarizationMethod BlackWhiteOptions::parseBinarizationMethod(const QString& str return WOLF; } else if (str == "sauvola") { return SAUVOLA; + } else if (str == "bradley") { + return BRADLEY; } else if (str == "edgeplus") { return EDGEPLUS; } else if (str == "blurdiv") { @@ -95,6 +97,9 @@ QString BlackWhiteOptions::formatBinarizationMethod(BinarizationMethod type) { case WOLF: str = "wolf"; break; + case BRADLEY: + str = "bradley"; + break; case EDGEPLUS: str = "edgeplus"; break; diff --git a/src/core/filters/output/BlackWhiteOptions.h b/src/core/filters/output/BlackWhiteOptions.h index 2709b6428..171da6ef4 100644 --- a/src/core/filters/output/BlackWhiteOptions.h +++ b/src/core/filters/output/BlackWhiteOptions.h @@ -9,7 +9,7 @@ class QDomDocument; class QDomElement; namespace output { -enum BinarizationMethod { OTSU, SAUVOLA, WOLF, EDGEPLUS, BLURDIV, EDGEDIV }; +enum BinarizationMethod { OTSU, SAUVOLA, WOLF, BRADLEY, EDGEPLUS, BLURDIV, EDGEDIV }; class BlackWhiteOptions { public: diff --git a/src/core/filters/output/OptionsWidget.cpp b/src/core/filters/output/OptionsWidget.cpp index 9111a7c28..cd19356e2 100644 --- a/src/core/filters/output/OptionsWidget.cpp +++ b/src/core/filters/output/OptionsWidget.cpp @@ -42,6 +42,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr settings, const PageSelec 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); @@ -54,6 +55,8 @@ OptionsWidget::OptionsWidget(std::shared_ptr settings, const PageSelec QPointer sauvolaBinarizationOptionsWidget = new SauvolaBinarizationOptionsWidget(m_settings); QPointer wolfBinarizationOptionsWidget = new WolfBinarizationOptionsWidget(m_settings); + QPointer bradleyBinarizationOptionsWidget + = new SauvolaBinarizationOptionsWidget(m_settings); QPointer edgeplusBinarizationOptionsWidget = new SauvolaBinarizationOptionsWidget(m_settings); QPointer blurdivBinarizationOptionsWidget @@ -67,6 +70,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr settings, const PageSelec addBinarizationOptionsWidget(otsuBinarizationOptionsWidget); addBinarizationOptionsWidget(sauvolaBinarizationOptionsWidget); addBinarizationOptionsWidget(wolfBinarizationOptionsWidget); + addBinarizationOptionsWidget(bradleyBinarizationOptionsWidget); addBinarizationOptionsWidget(edgeplusBinarizationOptionsWidget); addBinarizationOptionsWidget(blurdivBinarizationOptionsWidget); addBinarizationOptionsWidget(edgedivBinarizationOptionsWidget); diff --git a/src/core/filters/output/OutputGenerator.cpp b/src/core/filters/output/OutputGenerator.cpp index 0d396b1a1..27265ed4d 100644 --- a/src/core/filters/output/OutputGenerator.cpp +++ b/src/core/filters/output/OutputGenerator.cpp @@ -2306,6 +2306,14 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const { binarized = binarizeWolf(image, windowsSize, lowerBound, upperBound, thresholdCoef, thresholdDelta); break; } + case 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: { const double thresholdDelta = blackWhiteOptions.thresholdAdjustment(); const QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize()); diff --git a/src/imageproc/Binarize.cpp b/src/imageproc/Binarize.cpp index e816490d7..1ecb5d9bf 100644 --- a/src/imageproc/Binarize.cpp +++ b/src/imageproc/Binarize.cpp @@ -201,6 +201,73 @@ BinaryImage binarizeWolf(const QImage& src, return bwImg; } // binarizeWolf +BinaryImage binarizeBradley(const QImage& src, const QSize windowSize, const double k, const double delta) { + if (windowSize.isEmpty()) { + throw std::invalid_argument("binarizeBradley: invalid windowSize"); + } + + if (src.isNull()) { + return BinaryImage(); + } + + QImage gray(toGrayscale(src)); + const int w = gray.width(); + const int h = gray.height(); + + IntegralImage integralImage(w, h); + + uint8_t* grayLine = gray.bits(); + const int grayBpl = gray.bytesPerLine(); + + 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; + + BinaryImage bwImg(w, h); + uint32_t* bwLine = bwImg.data(); + const int bwWpl = bwImg.wordsPerLine(); + + grayLine = gray.bits(); + 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; + const double threshold = (k < 1.0) ? (mean * (1.0 - k)) : 0; + const uint32_t msb = uint32_t(1) << 31; + const uint32_t mask = msb >> (x & 31); + if (int(grayLine[x]) < (threshold + delta)) { + // black + bwLine[x >> 5] |= mask; + } else { + // white + bwLine[x >> 5] &= ~mask; + } + } + grayLine += grayBpl; + bwLine += bwWpl; + } + return bwImg; +} // binarizeBradley + BinaryImage binarizeEdgeDiv(const QImage& src, const QSize windowSize, const double kep, diff --git a/src/imageproc/Binarize.h b/src/imageproc/Binarize.h index aabe9d204..0adc332cf 100644 --- a/src/imageproc/Binarize.h +++ b/src/imageproc/Binarize.h @@ -61,6 +61,14 @@ BinaryImage binarizeWolf(const QImage& src, double k = 0.3, double delta = 0.0); +/** + * \brief Image binarization using Bradley's adaptive thresholding method. + * + * Derek Bradley, Gerhard Roth. 2005. "Adaptive Thresholding Using the Integral Image". + * http://www.scs.carleton.ca/~roth/iit-publications-iti/docs/gerh-50002.pdf + */ +BinaryImage binarizeBradley(const QImage& src, QSize windowSize, double k = 0.34, double delta = 0.0); + /** * \brief Image binarization using EdgeDiv (EdgePlus & BlurDiv) local/global thresholding method. *