# What are the best algorithms for document image thresholding in this example?

I'm trying to implement various binarization algorithms to the image shown:

Here's the code:

clc;
clear;
x=imread('n2.jpg');     %load original image


% Now we resize the images so that computational work becomes easier later onwards for us.

size(x);
x=imresize(x,[500 800]);
figure;
imshow(x);
title('original image');

z=rgb2hsv(x);       %extract the value part of hsv plane
v=z(:,:,3);
v=imadjust(v);


%now we find the mean and standard deviation required for niblack and %sauvola algorithms

m = mean(v(:))
s=std(v(:))
k=-.4;
value=m+ k*s;
temp=v;


% implementing niblack thresholding algorithm:

for p=1:1:500
for q=1:1:800
pixel=temp(p,q);
if(pixel>value)
temp(p,q)=1;
else
temp(p,q)=0;
end
end
end
figure;
imshow(temp);
title('result by niblack');
k=kittlerMet(g);
figure;
imshow(k);
title('result by kittlerMet');


% implementing sauvola thresholding algorithm:

val2=m*(1+.1*((s/128)-1));
t2=v;
for p=1:1:500
for q=1:1:800
pixel=t2(p,q);
if(pixel>value)
t2(p,q)=1;
else
t2(p,q)=0;
end
end


end

figure;
imshow(t2);
title('result by sauvola');


The results I obtained are as shown:

As you can see the resultant images are degraded at the darker spots.Could someone please suggest how to optimize my result??

-
Can you use color information to throw away the background instead of brightness only? – endolith Apr 5 '12 at 16:02
Respected Sir/madam. I am doing project on image processing.i am interesting binarization concept..please check and correct the coding... I take the coding and run the program.But some error occur this coding... Undefined function or variable 'g'. and another one is Error in msp (line 31) k=kittlerMet(g);.. How to solve it... Please correct the coding... – muthu Mar 20 '15 at 9:31

## 5 Answers

Your image doesn't have uniform brightness,so you shouldn't work with a uniform threshold. You need an adaptive threshold. This can be implemented by preprocessing the image to make the brightness more uniform across the image (code written in Mathematica, you'll have to implement the Matlab version for yourself):

A simple way to make the brightness uniform is to remove the actual text from the image using a closing filter:

white = Closing[src, DiskMatrix[5]]


The filter size should be chosen larger than the font stroke width and smaller than the size of the stains you're trying to remove.

EDIT: I was asked in the comments to explain what a closing operation does. It's a morphological dilation followed by a morphological erosion. The dilation essentially moves the structuring element at every position in the image, and picks the brightest pixel under the mask, thus :

• removing dark structures smaller than the structuring element
• shrinking larger dark structures by the size of the structuring element
• enlarging bright structures

The erosion operation does the opposite (it picks the darkest pixel under inside the structuring element), so if you apply it on the dilated image:

• the dark structures that were removed because they're smaller than the structuring element are still gone
• the darker structures that were shrunk are enlarged again to their original size (though their shape will be smoother)
• the bright structures are reduced to their original size

So the closing operation removes small dark objects with only minor changes to larger dark objects and bright objects.

Here's an example with different structuring element sizes:

As the size of the structuring element increases, more and more of the characters is removed. At radius=5, all of the characters are removed. If the radius is increased further, the smaller stains are removed, too:

Now you just divide the original image by this "white image" to get an image of (nearly) uniform brightness:

whiteAdjusted = Image[ImageData[src]/ImageData[white]*0.85]


This image can now be binarized with a constant threshold:

Binarize[whiteAdjusted , 0.6]


-
Wow! This is really cool! Huge +1! – Phonon Apr 4 '12 at 14:03
@nikie +1 Very nice - what do you mean exactly by closing filter must be "chosen larger than the font stroke"? Width or length of any letter? Furthermore, what is a closing filter 'really' doing? Thanks! – Mohammad Apr 4 '12 at 16:02
@Mohammad: I've added a little explanation to my answer. And yes, these are non-linear operations. The common heading is morphological image processing. – nikie Apr 4 '12 at 20:15
@nikie Never mind, the white is the max, not the black. :-) – Mohammad Apr 4 '12 at 20:33
@gdelfino: I usually try to avoid it by using a large enough mask, and use Clip[ImageData[white],{eps,Infinity}] where eps is a small number, to be safe. – nikie Nov 22 '12 at 19:53

Nikie's answer seems best and also seems to be working and producing results. So it's a clear winner.

However, just to documentation i adding one more reference, that could just be very fast.

This technique is called Adaptive thresholding which doesn't require to learn the background explicitly.

Essentially, instead of finding the most suitable global threshold - we can partition the image in a local window (say about 7x7 or appropriate) and find thresholds that changes as the window traverses.

The reference below details the exact method. http://homepages.inf.ed.ac.uk/rbf/HIPR2/adpthrsh.htm

This method would be relatively computationally faster.

-
Aren't those two things essentially the same? Namely estimating the local mean of the signal prior to thresholding? – Maurits Apr 9 '12 at 11:12
@Maurits It looks like the main differences are the ordering and the statistics used. For example, in the opening/closing operators, (which are made up of dilation and erosion but in a different order), a window is raster scanned and max is taken. (Among other things). However in the adaptive threshold the mean/median can be taken instead of the max. – Mohammad Apr 9 '12 at 23:17
OP asked it on SO as well, which I answered. But in principle, I don't think there is any difference between the answers, one is always estimating local statistics. If you do adapative thresholding, you also learn the background in the process. – Maurits Apr 10 '12 at 7:54

Another way using a bandpass filter (in MATLAB). Playing around with the difference of Gaussian parameters may give better results. The process is basically bandpass filter the image to remove the low frequency background blobs, normalise to [0,1] required for 'graythresh' command, threshold image.

Load image and convert to grayscale double:

I = imread('hw.jpg');
I = rgb2gray(I);
I = double(I);


Filter using difference of Gaussian kernel and normalise:

J = imgaussian(I,1.5) - imgaussian(I,0.5);
J = J - min(J(:));
J = J / max(J(:));


Calculate threshold and make 010101:

T = J > graythresh(J);


-

This is a good Matlab code for adaptive thresholding: http://www.mathworks.com/matlabcentral/fileexchange/8647-local-adaptive-thresholding

-
Argh! Requires the image processing toolbox though. :-/ – Mohammad Apr 9 '12 at 23:00
Indeed. Sorry if you do not have it. But DIPImage is a free Image Tolbox for Matlab. diplib.org/documentation It has a few methods for thresholding (check segmentation section) and you can also do all the morphological operations like closing. The developer also has a blog cb.uu.se/~cris/blog/index.php/archives/tag/matlab – MyCarta Apr 10 '12 at 14:15

I Will try this coding.But i don't have a correct answer...

clc;
clear;
x=imread('base2.jpg');
size(x);
x=imresize(x,[500 800]);
figure;
imshow(x);
title('original image');
z=rgb2hsv(x);       %extract the value part of hsv plane
v=z(:,:,3);
v=imadjust(v);
m = mean(v(:))
s=std(v(:))
k=-2;
value=m+ k*s;
temp=v;
for p=1:1:500
for q=1:1:800
pixel=temp(p,q);
if(pixel>value)
temp(p,q)=1;
else
temp(p,q)=0;
end
end
end
figure;
imshow(temp);
title('result by niblack');
% k=kittlerMet(g);
% figure;
% imshow(k);
% title('result by kittlerMet');

val2=m*(1+.1*((s/128)-1));
t2=v;
for p=1:1:500
for q=1:1:800
pixel=t2(p,q);
if(pixel>value)
t2(p,q)=1;
else
t2(p,q)=0;
end
end

end
figure;
imshow(t2);
title('result by sauvola');


-
If this is meant to answer the question, please explain what you're doing and why. – Matt L. Mar 20 '15 at 11:37