Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AdOtsu #35

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Doxa.Test/BinarizationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ namespace Doxa::UnitTests
TestUtilities::AssertImageFile(imageOtsu, projFolder + "2JohnC1V3-Otsu.pbm");
}

TEST_F(BinarizationTests, BinarizationAdOtsuTest)
{
// DEMO! Raw AdOtsu
Image imageAdOtsu = AdOtsu::ToBinaryImage(image);
PNM::Write(imageAdOtsu, projFolder + "2JohnC1V3-AdOtsu.pbm");

// DEMO! AdOtsu /w Multi-Scale
Image imageAdOtsuMSG = AdOtsuMS::ToBinaryImage(image);
PNM::Write(imageAdOtsuMSG, projFolder + "2JohnC1V3-AdOtsuMS.pbm");
}

TEST_F(BinarizationTests, BinarizationBatainehTest)
{
Image imageBataineh = Bataineh::ToBinaryImage(image);
Expand Down
2 changes: 2 additions & 0 deletions Doxa.Test/GrayscaleTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace Doxa::UnitTests

EXPECT_EQ(125, Grayscale::Luster(50, 100, 200));

EXPECT_EQ(150, Grayscale::MinAvg(100, 200, 300));

// Assert that all weights sum to exactly 1
EXPECT_NEAR(1.0, Grayscale::BT601(1.0, 1.0, 1.0), 0.001);
EXPECT_NEAR(1.0, Grayscale::BT709(1.0, 1.0, 1.0), 0.001);
Expand Down
1 change: 1 addition & 0 deletions Doxa.Test/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <Wolf.hpp>
#include <Gatos.hpp>
#include <Nick.hpp>
#include <AdOtsu.hpp>
#include <TRSingh.hpp>
#include <Wan.hpp>
#include <Su.hpp>
Expand Down
81 changes: 81 additions & 0 deletions Doxa/AdOtsu.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Δoxa Binarization Framework
// License: CC0 2022, "Freely you have received; freely give." - Matt 10:8
#ifndef ADOTSU_HPP
#define ADOTSU_HPP

#include "Algorithm.hpp"
#include "LocalWindow.hpp"
#include "Otsu.hpp"
#include "MultiScale.hpp"
#include <cmath>


namespace Doxa
{
/// <summary>
/// The AdOtsu Algorithm, v2010: Reza Farrahi Moghaddam, Mohamed Cheriet
///
/// This is the core, non-multi-scale grid based, AdOtsu algorithm referenced in (5) of their paper.
/// It should be noted that the paper uses a special color to grayscale algorithm in order to create a
/// "non-sensitive gray-value image." This would be "GrayscaleConversion::MinAvg".
///
/// The second iteration of this algorithm was introduced a year later in their work:
/// "AdOtsu: An adaptive and parameterless generalization of Otsu’s method for document image binarization"
///
/// This later work builds on top of their earlier work. Our implementation features their earlier work
/// which will act as a base for future improvements.
/// </summary>
/// <remarks>"A multi-scale framework for adaptive binarization of degraded document images", 2010.</remarks>
class AdOtsu : public Algorithm<AdOtsu>
{
public:
static const int HISTOGRAM_SIZE = 256;

void ToBinary(Image& binaryImageOut, const Parameters& parameters = Parameters())
{
// Read parameters, utilizing defaults
const int windowSize = parameters.Get("window", 75);
const double k = parameters.Get("k", 1.0);
const double R = parameters.Get("R", 0.1);

Otsu otsu;
const Pixel8 globalThreshold = otsu.Threshold(Algorithm::grayScaleImageIn);

LocalWindow::Process(binaryImageOut, Algorithm::grayScaleImageIn, windowSize, [&](const Region& window, const int&) {

const double localThreshold = k * LocalThreshold(otsu, Algorithm::grayScaleImageIn, window);

const double u = (std::abs((double)globalThreshold - localThreshold) / R);

// Apply the Unit Step Function
return (u < 255) ? localThreshold : -1;
});
}

Pixel8 LocalThreshold(const Otsu& otsu, const Image& grayScaleImage, const Region& window)
{
// Create Local Histogram
unsigned int histogram[HISTOGRAM_SIZE]; // Placed on stack for performance. This shouldn't be too large.
memset(histogram, 0, (HISTOGRAM_SIZE) * sizeof(unsigned int));

// Initialize Histogram from Local Window
LocalWindow::Iterate(grayScaleImage.width, window, [&](const int& windowPosition)
{
++histogram[grayScaleImage.data[windowPosition]];
});

return otsu.Algorithm(histogram, window.Area());
}
};

/// <summary>
/// A multi-scale local adaptive Otsu binarization algorithm
///
/// TODO: Change this so AdOtsuMSG when Grid support is available.
/// This should greatly improve performance.
/// </summary>
typedef MultiScale<AdOtsu> AdOtsuMS;
}


#endif //ADOTSU_HPP
9 changes: 7 additions & 2 deletions Doxa/BinarizationFactory.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Δoxa Binarization Framework
// License: CC0 2021, "Freely you have received; freely give." - Matt 10:8
// License: CC0 2022, "Freely you have received; freely give." - Matt 10:8
#ifndef BINARIZATIONFACTORY_HPP
#define BINARIZATIONFACTORY_HPP

Expand All @@ -16,6 +16,7 @@
#include "Wolf.hpp"
#include "Su.hpp"
#include "Gatos.hpp"
#include "AdOtsu.hpp"


namespace Doxa
Expand All @@ -33,7 +34,8 @@ namespace Doxa
BATAINEH = 8,
ISAUVOLA = 9,
WAN = 10,
GATOS = 11
GATOS = 11,
ADOTSU = 12
};


Expand Down Expand Up @@ -85,6 +87,9 @@ namespace Doxa
case GATOS:
algorithmPtr = new Gatos();
break;
case ADOTSU:
algorithmPtr = new AdOtsuMS();
break;
}

return algorithmPtr;
Expand Down
2 changes: 2 additions & 0 deletions Doxa/Doxa.vcxitems
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ProjectCapability Include="SourceItemsFromImports" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)AdOtsu.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Algorithm.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Bataineh.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Bernsen.hpp" />
Expand All @@ -30,6 +31,7 @@
<ClInclude Include="$(MSBuildThisFileDirectory)ISauvola.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)LocalWindow.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Morphology.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)MultiScale.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Niblack.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Nick.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)Otsu.hpp" />
Expand Down
13 changes: 12 additions & 1 deletion Doxa/Grayscale.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Δoxa Binarization Framework
// License: CC0 2018, "Freely you have received; freely give." - Matt 10:8
// License: CC0 2022, "Freely you have received; freely give." - Matt 10:8
#ifndef GRAYSCALE_HPP
#define GRAYSCALE_HPP

Expand Down Expand Up @@ -160,6 +160,17 @@ namespace Doxa
return (std::max({ r, g, b }) + std::min({ r, g, b })) / 2;
}

/// <summary>
/// The purpose of MinAvg is to produce a grayscale image whose values are less sensitive to multi color text.
/// It was introduced for the first AdOtsu algorithm.
/// </summary>
/// <remarks>"A multi-scale framework for adaptive binarization of degraded document images", 2010</remarks>
template<typename T>
static inline constexpr T MinAvg(T r, T g, T b)
{
return (Mean(r, g, b) + std::min({ r, g, b })) / 2;
}

/// <summary>
/// CIELAB & CIELUV L Value. Calculates Lightness when RGB are Linear.
/// Notice that this has built in gamma correction.
Expand Down
79 changes: 79 additions & 0 deletions Doxa/MultiScale.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Δoxa Binarization Framework
// License: CC0 2022, "Freely you have received; freely give." - Matt 10:8
#ifndef MULTISCALE_HPP
#define MULTISCALE_HPP

#include "Algorithm.hpp"
#include "Morphology.hpp"


namespace Doxa
{
/// <summary>
/// Multi-Scale Algorithm: Reza Farrahi Moghaddam, Mohamed Cheriet
///
/// This algorithm takes in an initial scale, aka the window size, and stitches together a final binarized output
/// based on the output for binarized images taken at different scales. These images are eroded and any connecting
/// pixels are assigned to the final output image.
///
/// This algorithm, in the paper, is paired with a Grid image processor. Depending on the number of scales, a
/// grid-based approach may be necessary in order to reduce the overall runtime of the underlying algorithm.
///
/// Note:
/// This should not be confused with "Efficient Multiscale Sauvola’s Binarization" by Guillaume Lazzara and Thierry Géraud.
///
/// </summary>
/// <remarks>"A multi-scale framework for adaptive binarization of degraded document images", 2010.</remarks>
template<class BinarizationClass>
class MultiScale : public Algorithm<MultiScale<BinarizationClass>>
{
public:

void ToBinary(Image& binaryImageOut, const Parameters& parameters = Parameters())
{
const int Shigh = parameters.Get("window", 75);

const int Slow = 9;
double k = parameters.Get("k", 1.0);

// TODO: Ensure binaryImageOut is not the same as grayScaleImageIn!!!


Image binarizedMap(MultiScale::grayScaleImageIn.width, MultiScale::grayScaleImageIn.height);
Image binarizedMask(MultiScale::grayScaleImageIn.width, MultiScale::grayScaleImageIn.height);

// Seed our binary output
BinarizationClass algorithm;
algorithm.Initialize(MultiScale::grayScaleImageIn);
algorithm.ToBinary(binaryImageOut, parameters);

// Create our first binarized mask
Morphology::Erode(binarizedMask, binaryImageOut, Shigh / 4);

// Iterate over multiple window scales
for (int S = Shigh / 2; S >= Slow; S = S / 2, k = k / 2)
{
// Get scaled map
algorithm.ToBinary(binarizedMap, Parameters({ {"window", S}, {"k", k } }));

// Apply mask
for (int idx = 0; idx < binarizedMask.size; ++idx)
{
if (binarizedMask.data[idx] == Palette::Black && binarizedMap.data[idx] == Palette::Black)
{
binaryImageOut.data[idx] = Palette::Black;
}
}

// Avoid calculating the next Mask if we aren't going to use it
if (S / 2 < Slow) break;

// Obtain the next Mask
Morphology::Erode(binarizedMask, binarizedMap, S / 4);
}
}
};
}


#endif //MUTISCALEGRID_HPP
43 changes: 26 additions & 17 deletions Doxa/Otsu.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Δoxa Binarization Framework
// License: CC0 2018, "Freely you have received; freely give." - Matt 10:8
// License: CC0 2022, "Freely you have received; freely give." - Matt 10:8
#ifndef OTSU_HPP
#define OTSU_HPP

Expand All @@ -24,34 +24,23 @@ namespace Doxa
public:
static const int HISTOGRAM_SIZE = 256;

Pixel8 Threshold(const Image& grayScaleImage, const Parameters& parameters = Parameters())
Pixel8 Algorithm(const unsigned int* histogram, const int N) const
{
// Compute number of pixels
long int N = grayScaleImage.size;
Pixel8 threshold = 0;

// Compute threshold
// Init variables
int sum = 0;
int sumB = 0;
int q1 = 0;
double max = 0;

// Create Histogram
unsigned int histogram[HISTOGRAM_SIZE]; // Placed on stack for performance. This shouldn't be too large.
memset(histogram, 0, (HISTOGRAM_SIZE) * sizeof(unsigned int));
for (int idx = 0; idx < grayScaleImage.size; ++idx)
{
++histogram[grayScaleImage.data[idx]];
}

// Calculate sum
for (int idx = 0; idx < HISTOGRAM_SIZE; ++idx)
for (int idx = 0; idx < HISTOGRAM_SIZE; ++idx)
{
sum += idx * histogram[idx];
}

for (int idx = 0; idx < HISTOGRAM_SIZE; ++idx)
for (int idx = 0; idx < HISTOGRAM_SIZE; ++idx)
{
// q1 = Weighted Background
q1 += histogram[idx];
Expand All @@ -65,15 +54,15 @@ namespace Doxa

sumB += (idx * histogram[idx]);

const double m1m2 =
const double m1m2 =
(double)sumB / q1 - // Mean Background
(double)(sum - sumB) / q2; // Mean Forground

// Note - There is an insidious casting situation going on here.
// If one were to multiple by q1 or q2 first, an explicit cast would be required!
const double between = m1m2 * m1m2 * q1 * q2;

if (between > max)
if (between > max)
{
threshold = idx;
max = between;
Expand All @@ -82,6 +71,26 @@ namespace Doxa

return threshold;
}

/// <summary>
/// Get the Global Threshold for an image using the Otsu algorithm
/// </summary>
/// <param name="grayScaleImage">Grayscale input image</param>
/// <param name="parameters">This algorithm does not take parameters</param>
/// <returns>Global Threshold</returns>
Pixel8 Threshold(const Image& grayScaleImage, const Parameters& parameters = Parameters())
{
const int N = grayScaleImage.size;
unsigned int histogram[HISTOGRAM_SIZE]; // Placed on stack for performance. This shouldn't be too large.
memset(histogram, 0, (HISTOGRAM_SIZE) * sizeof(unsigned int));

for (int idx = 0; idx < N; ++idx)
{
++histogram[grayScaleImage.data[idx]];
}

return Algorithm(histogram, N);
}
};
}

Expand Down
8 changes: 6 additions & 2 deletions Doxa/PNM.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Δoxa Binarization Framework
// License: CC0 2018, "Freely you have received; freely give." - Matt 10:8
// License: CC0 2022, "Freely you have received; freely give." - Matt 10:8
#ifndef PNM_HPP
#define PNM_HPP

Expand Down Expand Up @@ -53,7 +53,8 @@ namespace Doxa
BT2100,
Value,
Luster,
Lightness
Lightness,
MinAvg
};

/// <summary>
Expand Down Expand Up @@ -388,6 +389,9 @@ namespace Doxa
// This requires a change in colorspace. sRGB is assumed.
algorithm = Grayscale::sRgbToLightness;
break;
case GrayscaleConversion::MinAvg:
algorithm = Grayscale::MinAvg<Pixel8>;
break;
}

return algorithm;
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ It is written in C++ but supports multiple language bindings.
* Wolf - "Extraction and Recognition of Artificial Text in Multimedia Documents", 2003.
* Gatos - "Adaptive degraded document image binarization", 2005. (Partial)
* NICK - "Comparison of Niblack inspired Binarization methods for ancient documents", 2009.
* AdOtsu - "A multi-scale framework for adaptive binarization of degraded document images", 2010.
* Su - "Binarization of Historical Document Images Using the Local Maximum and Minimum", 2010.
* T.R. Singh - "A New local Adaptive Thresholding Technique in Binarization", 2011.
* Bataineh - "An adaptive local binarization method for document images based on a novel thresholding method and dynamic windows", 2011. (unreproducible)
Expand Down
Loading