Skip to content

Commit

Permalink
1.0.18: feature #8: binarizeEdgeDiv (EdgePlus & BlurDiv)
Browse files Browse the repository at this point in the history
  • Loading branch information
zvezdochiot committed Jul 12, 2023
1 parent 648ad39 commit ae131a0
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 21 deletions.
3 changes: 2 additions & 1 deletion src/app/DefaultParamsDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ DefaultParamsDialog::DefaultParamsDialog(QWidget* parent)
thresholdMethodBox->addItem(tr("Wolf"), WOLF);
thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS);
thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV);
thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV);

pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE);
pictureShapeSelector->addItem(tr("Free"), FREE_SHAPE);
Expand Down Expand Up @@ -664,7 +665,7 @@ std::unique_ptr<DefaultParams> DefaultParamsDialog::buildParams() const {
blackWhiteOptions.setBinarizationMethod(binarizationMethod);
blackWhiteOptions.setThresholdAdjustment(thresholdSlider->value());
blackWhiteOptions.setSauvolaCoef(sauvolaCoef->value());
if (binarizationMethod == SAUVOLA || binarizationMethod == EDGEPLUS || binarizationMethod == BLURDIV) {
if (binarizationMethod == SAUVOLA || binarizationMethod == EDGEPLUS || binarizationMethod == BLURDIV || binarizationMethod == EDGEDIV) {
blackWhiteOptions.setWindowSize(sauvolaWindowSize->value());
} else if (binarizationMethod == WOLF) {
blackWhiteOptions.setWindowSize(wolfWindowSize->value());
Expand Down
5 changes: 5 additions & 0 deletions src/core/filters/output/BlackWhiteOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ BinarizationMethod BlackWhiteOptions::parseBinarizationMethod(const QString& str
return EDGEPLUS;
} else if (str == "blurdiv") {
return BLURDIV;
} else if (str == "edgediv") {
return EDGEDIV;
} else {
return OTSU;
}
Expand All @@ -99,6 +101,9 @@ QString BlackWhiteOptions::formatBinarizationMethod(BinarizationMethod type) {
case BLURDIV:
str = "blurdiv";
break;
case EDGEDIV:
str = "edgediv";
break;
}
return str;
}
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, EDGEPLUS, BLURDIV };
enum BinarizationMethod { OTSU, SAUVOLA, WOLF, EDGEPLUS, BLURDIV, EDGEDIV };

class BlackWhiteOptions {
public:
Expand Down
4 changes: 4 additions & 0 deletions src/core/filters/output/OptionsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
thresholdMethodBox->addItem(tr("Wolf"), WOLF);
thresholdMethodBox->addItem(tr("EdgePlus"), EDGEPLUS);
thresholdMethodBox->addItem(tr("BlurDiv"), BLURDIV);
thresholdMethodBox->addItem(tr("EdgeDiv"), EDGEDIV);

fillingColorBox->addItem(tr("Background"), FILL_BACKGROUND);
fillingColorBox->addItem(tr("White"), FILL_WHITE);
Expand All @@ -57,6 +58,8 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
= new SauvolaBinarizationOptionsWidget(m_settings);
QPointer<BinarizationOptionsWidget> blurdivBinarizationOptionsWidget
= new SauvolaBinarizationOptionsWidget(m_settings);
QPointer<BinarizationOptionsWidget> edgedivBinarizationOptionsWidget
= new SauvolaBinarizationOptionsWidget(m_settings);

while (binarizationOptions->count() != 0) {
binarizationOptions->removeWidget(binarizationOptions->widget(0));
Expand All @@ -66,6 +69,7 @@ OptionsWidget::OptionsWidget(std::shared_ptr<Settings> settings, const PageSelec
addBinarizationOptionsWidget(wolfBinarizationOptionsWidget);
addBinarizationOptionsWidget(edgeplusBinarizationOptionsWidget);
addBinarizationOptionsWidget(blurdivBinarizationOptionsWidget);
addBinarizationOptionsWidget(edgedivBinarizationOptionsWidget);
updateBinarizationOptionsDisplay(binarizationOptions->currentIndex());

pictureShapeSelector->addItem(tr("Off"), OFF_SHAPE);
Expand Down
32 changes: 20 additions & 12 deletions src/core/filters/output/OutputGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2219,37 +2219,45 @@ BinaryImage OutputGenerator::Processor::binarize(const QImage& image) const {
break;
}
case SAUVOLA: {
double sauvolaDelta = blackWhiteOptions.thresholdAdjustment();
double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
double sauvolaCoef = blackWhiteOptions.getSauvolaCoef();
double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeSauvola(image, windowsSize, sauvolaCoef, sauvolaDelta);
binarized = binarizeSauvola(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
case WOLF: {
double wolfDelta = blackWhiteOptions.thresholdAdjustment();
double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
auto lowerBound = (unsigned char) blackWhiteOptions.getWolfLowerBound();
auto upperBound = (unsigned char) blackWhiteOptions.getWolfUpperBound();
double wolfCoef = blackWhiteOptions.getWolfCoef();
double thresholdCoef = blackWhiteOptions.getWolfCoef();

binarized = binarizeWolf(image, windowsSize, lowerBound, upperBound, wolfCoef, wolfDelta);
binarized = binarizeWolf(image, windowsSize, lowerBound, upperBound, thresholdCoef, thresholdDelta);
break;
}
case EDGEPLUS: {
double sauvolaDelta = blackWhiteOptions.thresholdAdjustment();
double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
double sauvolaCoef = blackWhiteOptions.getSauvolaCoef();
double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeEdgePlus(image, windowsSize, sauvolaCoef, sauvolaDelta);
binarized = binarizeEdgePlus(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
case BLURDIV: {
double sauvolaDelta = blackWhiteOptions.thresholdAdjustment();
double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
double sauvolaCoef = blackWhiteOptions.getSauvolaCoef();
double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeBlurDiv(image, windowsSize, sauvolaCoef, sauvolaDelta);
binarized = binarizeBlurDiv(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
case EDGEDIV: {
double thresholdDelta = blackWhiteOptions.thresholdAdjustment();
QSize windowsSize = QSize(blackWhiteOptions.getWindowSize(), blackWhiteOptions.getWindowSize());
double thresholdCoef = blackWhiteOptions.getSauvolaCoef();

binarized = binarizeEdgeDiv(image, windowsSize, thresholdCoef, thresholdDelta);
break;
}
}
Expand Down
87 changes: 81 additions & 6 deletions src/imageproc/Binarize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ BinaryImage binarizeSauvola(const QImage& src, const QSize windowSize, const dou
const uint8_t* grayLine = gray.bits();
const int grayBpl = gray.bytesPerLine();

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

const int windowLowerHalf = windowSize.height() >> 1;
Expand Down Expand Up @@ -92,7 +93,6 @@ BinaryImage binarizeSauvola(const QImage& src, const QSize windowSize, const dou
bwLine[x >> 5] &= ~mask;
}
}

grayLine += grayBpl;
bwLine += bwWpl;
}
Expand Down Expand Up @@ -125,7 +125,7 @@ BinaryImage binarizeWolf(const QImage& src,

uint32_t minGrayLevel = 255;

for (int y = 0; y < h; ++y, grayLine += grayBpl) {
for (int y = 0; y < h; ++y) {
integralImage.beginRow();
integralSqimage.beginRow();
for (int x = 0; x < w; ++x) {
Expand All @@ -134,6 +134,7 @@ BinaryImage binarizeWolf(const QImage& src,
integralSqimage.push(pixel * pixel);
minGrayLevel = std::min(minGrayLevel, pixel);
}
grayLine += grayBpl;
}

const int windowLowerHalf = windowSize.height() >> 1;
Expand Down Expand Up @@ -177,7 +178,7 @@ BinaryImage binarizeWolf(const QImage& src,
const int bwWpl = bwImg.wordsPerLine();

grayLine = gray.bits();
for (int y = 0; y < h; ++y, grayLine += grayBpl, bwLine += bwWpl) {
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
const float mean = means[y * w + x];
const float deviation = deviations[y * w + x];
Expand All @@ -194,6 +195,8 @@ BinaryImage binarizeWolf(const QImage& src,
bwLine[x >> 5] &= ~mask;
}
}
grayLine += grayBpl;
bwLine += bwWpl;
}
return bwImg;
} // binarizeWolf
Expand All @@ -216,12 +219,13 @@ BinaryImage binarizeEdgePlus(const QImage& src, const QSize windowSize, const do
uint8_t* grayLine = gray.bits();
const int grayBpl = gray.bytesPerLine();

for (int y = 0; y < h; ++y, grayLine += grayBpl) {
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;
Expand Down Expand Up @@ -277,12 +281,13 @@ BinaryImage binarizeBlurDiv(const QImage& src, const QSize windowSize, const dou
uint8_t* grayLine = gray.bits();
const int grayBpl = gray.bytesPerLine();

for (int y = 0; y < h; ++y, grayLine += grayBpl) {
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;
Expand Down Expand Up @@ -320,6 +325,76 @@ BinaryImage binarizeBlurDiv(const QImage& src, const QSize windowSize, const dou
return BinaryImage(gray, (BinaryThreshold::otsuThreshold(gray) + delta));
} // binarizeBlurDiv

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

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

QImage gray(toGrayscale(src));
const int w = gray.width();
const int h = gray.height();

IntegralImage<uint32_t> 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;

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 origin = grayLine[x];
// EdgePlus
// edge = I / blur (shift = -0.5) {0.0 .. >1.0}, mean value = 0.5
const double edge = (origin + 1) / (mean + 1) - 0.5;
// edgeplus = I * edge, mean value = 0.5 * mean(I)
const double edgeplus = origin * edge;
// return k * edgeplus + (1 - k) * I
double retval = k * edgeplus + (1.0 - k) * origin;
// BlurDiv
// edge = blur / I (shift = -0.5) {0.0 .. >1.0}, mean value = 0.5
const double edgeinv = (mean + 1) / (retval + 1) - 0.5;
// edgenorm = edge * k + max * (1 - k), mean value = {0.5 .. 1.0} * mean(I)
const double edgenorm = k * edgeinv + (1.0 - k);
// return I / edgenorm
retval = (edgenorm > 0.0) ? (origin / edgenorm) : origin;
// trim value {0..255}
retval = (retval < 0.0) ? 0.0 : (retval < 255.0) ? retval : 255.0;
grayLine[x] = (int) retval;
}
grayLine += grayBpl;
}
return BinaryImage(gray, (BinaryThreshold::otsuThreshold(gray) + delta));
} // binarizeBlurDiv

BinaryImage peakThreshold(const QImage& image) {
return BinaryImage(image, BinaryThreshold::peakThreshold(image));
}
Expand Down
7 changes: 6 additions & 1 deletion src/imageproc/Binarize.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,19 @@ BinaryImage binarizeWolf(const QImage& src,
*/
BinaryImage binarizeEdgePlus(const QImage& src, QSize windowSize, double k = 0.34, double delta = 0.0);


/**
* \brief Image binarization using BlurDiv local/global thresholding method.
*
* BlurDiv, zvezdochiot 2023. "Adaptive/global document image binarization".
*/
BinaryImage binarizeBlurDiv(const QImage& src, QSize windowSize, double k = 0.34, double delta = 0.0);

/**
* \brief Image binarization using EdgeDiv (EdgePlus & BlurDiv) local/global thresholding method.
*
* EdgeDiv, zvezdochiot 2023. "Adaptive/global document image binarization".
*/
BinaryImage binarizeEdgeDiv(const QImage& src, QSize windowSize, double k = 0.34, double delta = 0.0);

BinaryImage peakThreshold(const QImage& image);
} // namespace imageproc
Expand Down

0 comments on commit ae131a0

Please sign in to comment.