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.
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 Image
s 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"];
el = Switch[OptionValue["Mask"],
"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]];
padim =
ArrayPad[ImageData[im, "Real"], Floor[Dimensions[el]/2],
"Reversed"];
padim =
Developer`PartitionMap[(NiblackKernel[Flatten[#, 1][[flatelpos]],
sdc]) &, padim, Dimensions[el], {1, 1},
Ceiling[Dimensions[el]/2]];
Image[ArrayPad[padim, -Floor[Dimensions[el]/2]], "Bit"]
];
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];
el = Switch[OptionValue["Mask"],
"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]];
padim =
ArrayPad[ImageData[im, "Real"], Floor[Dimensions[el]/2],
"Reversed"];
padim =
Developer`PartitionMap[(SauvolaKernel[Flatten[#, 1][[flatelpos]],
sdc, dr]) &, padim, Dimensions[el], {1, 1},
Ceiling[Dimensions[el]/2]];
Image[ArrayPad[padim, -Floor[Dimensions[el]/2]], "Bit"]
];
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.
-
$\begingroup$ 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}]
$\endgroup$ – Dragutin Jul 9 '16 at 21:21 -
$\begingroup$ @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
$\endgroup$ – UDB Jul 10 '16 at 11:40 -
$\begingroup$ 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. $\endgroup$ – J. M.'s ennui♦ Jul 10 '16 at 13:13
Binarize[]
at all? There are lots of options. $\endgroup$ – dr.blochwave Jul 9 '16 at 12:17LocalAdaptiveBinarize[img, 25, {.95, 0, .05}]
$\endgroup$ – dr.blochwave Jul 9 '16 at 12:26LocalAdaptiveBinarize
and it seems OK. $\endgroup$ – Dragutin Jul 9 '16 at 12:27