Line detection in image

9.5k views Asked by At

I am new to image processing and I was trying to detect vertical lines using this code-

image=imread('benzene.jpg');  
BW = im2bw(image);
w1=[-1 2 -1 ; -1 2 -1 ; -1 2 -1];
g=(imfilter(double(BW),w1));
g=abs(g);
T=max(g(:));
g=g>=T;
imshow(g);

This was my image-

enter image description here

And this is what I got after performming the operations- enter image description here

So my question is why am I getting this output?There are 10 vertical lines if vertical double bonds are counted as 2 distinct vertical lines.Also what if I want to get horizontal,vertical,45 and -45 all the lines,how can I use all the 4 masks to get one single output?

3

There are 3 answers

8
rayryeng On BEST ANSWER

One simple suggestion I have is to detect the gradient and determine the orientation of an edge point. Bear in mind that the orientation is in the direction that is perpendicular to the edge. Therefore, if you want to find vertical lines, the direction that is perpendicular to a vertical line is horizontal, which is either 180 degrees or -180 degrees with respect to the Cartesian plane. As such, for each orientation of the edge points that are detected, if the orientation is either -180 degrees or 180 degrees, then set the output of this location to be true, else false. To detect the gradient orientations, use imgradient from the image processing toolbox for that. I'm assuming this is available as you have used both imread and im2bw and they are both part of that toolbox:

im = imread('https://i.stack.imgur.com/bdNOt.png');
tol = 5;
[~,ang] = imgradient(im);
out = (ang >= 180 - tol | ang <= -180 + tol);
imshow(out);

The code uses a variable called tol to define a tolerance in the angles you want to detect to account for noise or edges that look vertical but when the angle is computed, it may not appear to be. Basically, we are looking for any points whose angles are within 180 degrees or -180 degrees.

This is what we get:

enter image description here

As a means of post-processing, you could use bwareaopen to filter out pixel regions whose areas fall below a certain amount. Taking advantage of the fact that the vertical lines have a larger area than the other pixels, you could do something like this:

out_filter = bwareaopen(out, 50);

We get:

enter image description here


Now if you want to detect horizontal lines, you should find gradient orientations that are either -90 or 90 degrees. This makes sense because those lines that are horizontal, the direction perpendicular to a horizontal line is indeed vertical, and that's either -90 or 90 degrees. If you want slanted lines, if you want a left leaning line, look for angles of either 45 degrees or -135 degrees and a right leaning line, either -45 degrees or 135 degrees. I'll let you work out why these angles are indeed representative of those kinds of lines.

You don't have any horizontal lines in the image you provided, so I'll just look for slanted lines:

Left leaning lines

Note: I had to increase the tolerance due to quantization errors.

im = imread('https://i.stack.imgur.com/bdNOt.png');
tol = 20;
[~,ang] = imgradient(im);
out = (ang >= 45 - tol & ang <= 45 + tol) | (ang >= -135 - tol & ang <= -135 + tol);
out_filter = bwareaopen(out, 50);
imshow(out_filter);

enter image description here

Right leaning lines:

Also had to increase the tolerance here as well:

im = imread('https://i.stack.imgur.com/bdNOt.png');
tol = 20;
[~,ang] = imgradient(im);
out = (ang >= 135 - tol & ang <= 135 + tol) | (ang >= -45 - tol & ang <= -45 + tol);
out_filter = bwareaopen(out, 50);
imshow(out_filter);

enter image description here

2
roni On

I am still in the process of doing it. But till now I have got this. I have not used your filter but rather a different one.

I used the first image which you supplied. The filters are described here : image_filters.

enter image description here

image=imread('benzene.png');  
BW = im2bw(image);
w1=(1/3)*[1 0 -1;1 0 -1;1 0 -1];
g=(imfilter(double(BW),w1));
g(g<1)=0;
imshow(g);

The output which I got is this : As you can see the result is not yet complete. I can suggest to you to try two things : use morphological erosion operator to remove the small elements. You can also use connected components to do so.

output

In the meantime try to do what I suggested. If I get the answer I will update it.

5
bla On

A different approach is to use the fact that all the lines that depict bonds have the same aspect ratio and area. after filtering the image leaving it with only the bonds, we can look at the orientation or at the list of indices that compose them to detect if they are vertical or whatnot. All this can be done using regionprops.

image=rgb2gray(imread('benzene.png'));  
d=abs(255-image); % inverse the image
d=im2bw(d);
stat=regionprops(d,'Area', 'Orientation','PixelIdxList'); 
areas=[stat.Area];
hist(areas)

enter image description here

Inspecting the histogram shows where to cut for the lines, the lines have smaller areas than the letters, and they should have approximately the same area. So I cut for areas below 1000 pixels:

idx=find(areas<1000);
angs=round([stat(idx).Orientation]);

now you can use the angs and idx to get which ever type of line you want. For example lets just plot the 30 deg lines:

d2=zeros(size(d));
d2(vertcat(stat(idx(angs==30)).PixelIdxList))=1;
imagesc(d2)

enter image description here

Note that at the time I started answering this question the image I took was the benzene.png file. Now I realize that you have provided a different image than the original one, such that the lines that depict bonds are not separate, rather you have "rings". I'll see later if I can address that as well if you want me to.

EDIT:

To find the relevant line for the new image, where you have rings, the only difference the lines have is that, well, they are straight "lines" and not curved. So I resort to the beloved Hough transform to pick them up:

image=imread('https://i.stack.imgur.com/bdNOt.png');
d=abs(1-image); % inverse the image
BW=im2bw(d);
BW = bwmorph(BW,'skel',1);
[H, T, R] = hough(BW,'Theta',-90:10:80);
P = houghpeaks(H, 100,'NHoodSize',[3 3],'threshold',1);
lines = houghlines(BW, T, R, P, 'FillGap',5, 'MinLength', 35);

Let's obtain the angles of the detected lines:

angs=round([lines.theta]);

you'll see that here angs will generate values of 0,-60 or 60 degrees.

say you want to plot only those that are 0 degrees:

p1=vertcat(lines(angs==0).point1);
p2=vertcat(lines(angs==0).point2);

imshow(BW, 'InitialMag',200, 'Border','tight'), hold on

for k = 1:size(p1,1)
   line([p1(k,1) p2(k,1)],[p1(k,2) p2(k,2)], 'LineWidth',4,...
   'Color',[1 0 0]); hold on
end
hold off

enter image description here