# Is there Sauvola's or Niblack's threshold binarization algorithms?

It seems that there are no Sauvola's or Niblack's built-in algorithms for image binarization. Maybe someone has ready to use math-code that implement one of these algorithms. I need one of these algorithms as a starting point in next steps in binarisation procedure of handwritten old documents, as is described in: Ntirogiannis,Gatos,Pratikakis "A combined approach for the binarization of handwritten document images" If someone can suggest another code for segmeting handwritten text from image like this, I will appreciate this.

• Have you tried any methods with Binarize[] at all? There are lots of options. – dr.blochwave Jul 9 '16 at 12:17
• e.g. LocalAdaptiveBinarize[img, 25, {.95, 0, .05}] – dr.blochwave Jul 9 '16 at 12:26
• Yes, I have looked but nothing is satisfying. But, just now I tried LocalAdaptiveBinarize and it seems OK. – Dragutin Jul 9 '16 at 12:27

Please enjoy my own implementations of the two requested algorithms. Feel free to replace the "WVM" with "C" in case you have an accepted C compiler installed. Please note that your example image is of extraordinarily bad quality, so once you have found appropriate parameters for the two algorithms please let us know what you have obtained.

Please note that my implementations lack any Image3D functionality. Also, multichannel Images cannot be applied, so in other words, only scalar single-channel Image data can be processed so far. However, a specialty is the option to select a circular approximation of the filter kernel. In an update below I also add a sketch of the two algorithms using ImageFilter. By this the restrictions mentioned before can be overcome, but then the kernel will have box shape...

Niblack 1986

Assumes white text on black background

Clear[NiblackKernel];
NiblackKernel = Compile[{{list, _Real, 1}, {sdc, _Real}},
Module[{mean, stddev, thr},
mean = Mean[list];
stddev = StandardDeviation[list];
thr = mean + sdc*stddev;
UnitStep[list[[Ceiling[Length[list]/2]]] - thr]
], CompilationTarget -> "WVM"];

Clear[NiblackFilter];
Options[NiblackFilter] = {"StdDevCoefficient" -> 0.2,
"WindowHalfWidth" -> 15, "Mask" -> "Box"};
NiblackFilter[im_Image, OptionsPattern[]] :=
Module[{flatelpos, padim, whw, sdc, el},
whw = Round[OptionValue["WindowHalfWidth"]];
sdc = OptionValue["StdDevCoefficient"];
"Box", Table[1, {2 whw + 1}, {2 whw + 1}],
"Circle",
Ceiling[Rescale[
Sign[Table[(x^2 + y^2), {x, -whw, whw}, {y, -whw, whw}] -
whw*(whw + 1/2)], {1, -1}]],
_, Table[1, {2 whw + 1}, {2 whw + 1}]
];
flatelpos = Flatten[Position[Flatten[el], 1]];
"Reversed"];
DeveloperPartitionMap[(NiblackKernel[Flatten[#, 1][[flatelpos]],
sdc]) &, padim, Dimensions[el], {1, 1},
Ceiling[Dimensions[el]/2]];
];


Sauvola and Pietikäinen 2000

Assumes black text on white background

Clear[SauvolaKernel];
SauvolaKernel =
Compile[{{list, _Real, 1}, {sdc, _Real}, {dr, _Real}},
Module[{mean, stddev, thr},
mean = Mean[list];
stddev = StandardDeviation[list];
thr = mean*(1. + sdc*(stddev/dr - 1.));
UnitStep[list[[Ceiling[Length[list]/2]]] - thr]
], CompilationTarget -> "WVM"];

Clear[SauvolaFilter];
Options[SauvolaFilter] = {"StdDevCoefficient" -> 0.2,
"DynamicRange" -> 128, "WindowHalfWidth" -> 15, "Mask" -> "Box"};
SauvolaFilter[im_Image, OptionsPattern[]] :=
Module[{flatelpos, padim, whw, sdc, dr, el},
whw = Round[OptionValue["WindowHalfWidth"]];
sdc = OptionValue["StdDevCoefficient"];
dr = N[OptionValue["DynamicRange"]/255];
"Box", Table[1, {2 whw + 1}, {2 whw + 1}],
"Circle",
Ceiling[Rescale[
Sign[Table[(x^2 + y^2), {x, -whw, whw}, {y, -whw, whw}] -
whw*(whw + 1/2)], {1, -1}]],
_, Table[1, {2 whw + 1}, {2 whw + 1}]
];
flatelpos = Flatten[Position[Flatten[el], 1]];
"Reversed"];
DeveloperPartitionMap[(SauvolaKernel[Flatten[#, 1][[flatelpos]],
sdc, dr]) &, padim, Dimensions[el], {1, 1},
Ceiling[Dimensions[el]/2]];
];


Update

I add some sketches using ImageFilter (without some good handling of the margins as above, sorry). I expect them working also for multichannel images as well as for 3D images:

Niblack

whw = 15;
sdc = 0.2;
image = Import[...];
center = Sequence @@ Table[whw + 1, {Length[ImageDimensions[image]]}];
ImageFilter[
UnitStep[#[[center]] - (Mean[Flatten[#]] +
sdc*StandardDeviation[Flatten[#]])] &,
image, whw, Padding -> "Fixed"]


whw is the kernel radius, sdc is the stand. dev. coefficient

Sauvola

whw = 15;
sdc = 0.05;
dr = 128./255.;
image = Import[...];
center = Sequence @@ Table[whw + 1, {Length[ImageDimensions[image]]}];
ImageFilter[
UnitStep[#[[center]] - (Mean[
Flatten[#]]*(1. +
sdc*(StandardDeviation[Flatten[#]]/dr - 1.)))] &,
image, whw, Padding -> "Fixed"]


whw is the kernel radius, sdc is the stand. dev. coefficient, dr is the dynamic range (rf. to 8 bit, so normalized to 255)

Hope everything is correct now.

• Tank you very much. It may seem that Niblack's result is poor, but this is only for creating Inpaint Mask for procedure described in Ntirogiannis. When I finish the process I will post results. In meantime I experimented, and I get this result with LocalAdaptiveBinarize[image, 72, {0.928, 0., 0.034}] – Dragutin Jul 9 '16 at 21:21
• @Dragutin: have corrected a few minor errors in my code; also have added comments, plus some attempts to implement the algorithms using in-mathematica means, i.e. ImageFilter – UDB Jul 10 '16 at 11:40
• I wanted to add a link to Niblack, but it seems he described the procedure in a book, and I can't seem to access that in Google Books. – J. M. is away Jul 10 '16 at 13:13