I'm currently learning image processing with Matlab. What I'm trying to do is to find in the following image all the letter a's and remove all the rest.
First of all I convert the image to a binary one. Then I try to get rid of the noise by using a median filter. There are some gaps in some of tha a's borders, which I fill by opening the image. But then I get stuck, I found some things on the internet (which I do not completely understand) with which I am able to select the gaps from all the a's.
What should I do next?
My code is as follows:
text = imread('http://i.stack.imgur.com/N4nCm.png');
text = mat2gray(text);
% find threshold and chance to binary image
border = graythresh(text);
textbw = im2bw(text, border);
imshow(textbw);
% remove noise with median filter
textfilt = medfilt2(textbw);
% remove small holes in the border of the a
se = strel('disk', 4);
texter = imopen(textfilt, se);
% find holes
s = regionprops(texter, 'BoundingBox');
bb = round(reshape([s.BoundingBox], 4, []).');
% show original image with holes
imshow(textbw);
for idx = 1 : numel(s)
rectangle('Position', bb(idx,:), 'edgecolor', 'red');
end
example. BW2 = bwareafilt( BW , range ) extracts all connected components (objects) from the binary image BW , where the area of the objects is in the specified range , producing another binary image BW2 . bwareafilt returns a binary image BW2 containing only those objects that meet the criteria.
bwpropfilt returns a binary image BW2 containing only those objects that meet the criteria. example. BW2 = bwpropfilt( BW , prop , n ) sorts the objects based on the value of the specified property, prop , returning a binary image that contains only the top n largest objects.
example. stats = regionprops( BW , properties ) returns measurements for the set of properties for each 8-connected component (object) in the binary image, BW . You can use regionprops on contiguous regions and discontiguous regions (see More About). Note.
example. L = bwlabel( BW ) returns the label matrix L that contains labels for the 8-connected objects found in BW . L = bwlabel( BW , conn ) returns a label matrix, where conn specifies the connectivity.
That is a nice problem to tackle. Here is an approach you could use, but I admit that it is by no means perfect and might not be that robust. Hopefully it will give you ideas...
What I did is basically filter the image with a median filter (as you did) and removed small elements using bwareaopen
. Then I called regionprops
to get a bunch of properties, among which the most important are the area
and eccentricity
. The idea was that all letters "a" should have a somewhat similar eccentricity, therefore once we know the eccentricity of one letter we can find the other letters that have about the same. You could probably make the code more robust using additional properties that make the letters stand out from the rest; maybe the ratio MajorAxisLength/MinorAxisLength
for instance. I'll leave that part up to you :)
So the easiest way to pick a letter in this case was to select the object with the largest area, which is the big a at the center of your image. Once we have its eccentricity, we can apply some threshold and select only those objects found using regionprops
that have an eccentricity about similar. The median filter and call to bwareaopen
applied earlier are important here because the kind of noise in the 4 boxes on the right can complicate things if they are not removed, as a few of the random spots are likely to have an eccentricity similar to our dear letter "a".
That being said, here is the commented code. Note that I changed the name of your text
variable to textIm
since text
is a Matlab function.
clc
clear
close all
textIm = imread('http://i.stack.imgur.com/N4nCm.png');
%// find threshold and change to binary image
border = graythresh(textIm);
%// =========== NEW \\ ===========
%// NOTICE the use of ~im2bw(...)
textbw = ~im2bw(textIm, border);
%// remove noise with median filter
%// =========== NEW \\ ===========
textfilt = medfilt2(textbw,[7 7]);
textfilt = bwareaopen(textfilt,8);
%// =========== NEW \\ ===========
%// Use an absurdely large line structuring element oriented at 25 degrees
%// to make the a's stand out
se = strel('line', 20 ,25);
textfilt = imclose(textfilt, se);
%// Get a couple properties. Note the "Eccentricity"
S = regionprops(textfilt, 'Area','Eccentricity','Centroid','BoundingBox');
All_areas = vertcat(S.Area);
%// Find the largest element (i.e. the big a). We will use it to get its
%// eccentricity and fetch other a's.
[MaxArea, MaxAreaIdx] = (max(All_areas(:)));
%// Get eccentricity of largest letter.
RefEcc = S(MaxAreaIdx).Eccentricity
Here the eccentricity of the large "a" is 0.6654. An eccentricity of 0 means a circle and an eccentricity of 1 means a line.
%// Just concatenate everything. Easier to work with.
All_Ecc = vertcat(S.Eccentricity);
All_Centroids = vertcat(S.Centroid);
All_BB = vertcat(S.BoundingBox)
%// Find elements that have the approximate eccentricity of the large a
%// found earlier. You can be more/less stringent and add more conditions here.
PotA = find(All_Ecc > RefEcc*.8 & All_Ecc < RefEcc*1.2)
%// Display output with centroids and bounding boxes.
imshow(textIm)
hold on
scatter(All_Centroids(PotA,1),All_Centroids(PotA,2),60,'r','filled');
for k = 1:numel(PotA)
rectangle('Position',All_BB(PotA(k),:),'EdgeColor','y','LineWidth',2)
end
And output, with centroids as red dots and bounding boxes as yellow rectangles:
That was fun! Hope I could help somehow. You might need to adapt the code for other letters or if you have other circle-ish objects in your image, but I guess that's a start.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With