How to find intersections in binary image lines? - matlab

I have a binary image with curved lines as shown below, but I would like to know how I can find where they would intersect if they are extended.
So could you give me some ides on how i could:
extend the line endpoints in the same direction,
how to find the intersections?
I have thought about using hough transform to find lines, then intersection, but in some images my line endpoints are not exactly straight. Is there a way to maybe only find the direction of the line at the end of it instead of over the whole line, as it is a binary image?
Thanks for any help

Applying a dilation and then an erosion will extend your endpoints like this:
(*Code in Mathematica*)
Erosion[Dilation[i, 10], 10]
A full solution could be something like this:
r = Dilation[MorphologicalBranchPoints[
Thinning[Binarize#Erosion[Dilation[i, 10], 10], 10]] // Colorize, 3]
ImageAdd[i, r]

I think you should take a look at Hough transform. It calculates lines equations from binary reprentation (usually output of edge detector). Once you have this, it is a piece of cake to calculate intersections.

You could try to fit three corresponding curves and then solve the equation for the two intersections explicitely.
There exist some established models for curve fitting.

Here is what I came up with using Hough Transform:
%# read and binarize image
I = imread('http://i.stack.imgur.com/XlxmL.jpg');
BW = im2bw(rgb2gray(I));
%# hough transform, detect peaks, then get lines segments
[H T R] = hough(BW, 'Theta',-10:10); %# specific theta range
P = houghpeaks(H, 5);
lines = houghlines(BW, T, R, P);
%# overlay detected lines over image
figure, imshow(BW), hold on
for k = 1:length(lines)
xy = [lines(k).point1; lines(k).point2];
plot(xy(:,1), xy(:,2), 'g.-', 'LineWidth',2);
end
%# find endpoints (intersections) and show them
xy = [vertcat(lines.point1);vertcat(lines.point2)];
[~,idx1] = min(xy(:,2));
[~,idx2] = max(xy(:,2));
xy = xy([idx1;idx2],:); %# intersection points
plot(xy(:,1),xy(:,2), 'ro', 'LineWidth',3, 'MarkerSize',12)
hold off

Simply looking at your lines, they are more or less straight lines (not concave/convex curves) In my humble opinion, there's an easier way and more obvious way, since, you know either end points of the three lines. You can always get the intersection by solving x and y respectively.
http://zonalandeducation.com/mmts/intersections/intersectionOfTwoLines1/intersectionOfTwoLines1.html
gd luck

fill contours into std::vector<std::vector<cv::Point> > , using findContours function from OpenCV library, then for any two contours which don't intersect ( case of intersection i'll explane later) do the following:
first contour is the sequence of 2D points A1 A2 .... An and second contour is B1, B2, .., Bm, fix some i > 0 && i < n , j >0 && j < m and do extrapolation using (A1, ..., Ai) for finding extend from first endpoint of first contour than extrapolate (An-i, ... ,An) for finding extendion of the first contour from second endpoint: do this similarly for second contour (B1, ... ,Bj) &&(Bm-j, ... , Bm) :
now you can extend your contours till borders of image and check are their intersect or not.
i hope you know how to find intersection of the segments in 2D space. You must use this for all [Ai Ai+1] and [Bj Bj+1] i = 1,... ,n-1 && j = 1,...,m-1

Related

MATLAB - 3D volume fill based on inequalities

I have three variables x, y and z. I have inequalities of the form
x >= a, y>= b, z>=c, x+y>=d, y+z>=e, x+z>=f, x+y+z>=g
where a to g are positive numbers. On a 3D plot with axes x, y and z, this is an open volume. I would like to fill the open side (i.e. away from 0) shape with color and show it in a plot. What is the way to do this on MATLAB?
I attempted to use fill3 and a mesh but the result was not very good
[x,y,z] = meshgrid(0:0.01:2,0:0.01:2,0:0.01:2);
ineq = (x>=1)& (y>0.5)&(z>=0.25)&(x+y>1.25)&(y+z>0.6)&(x+z>1.1)&(x+y+z>1.6);
fill3(x(:),y(:),z(:), 'r')
box on
grid on
Using plot3 also was not very good. Is there any other way to generate a nice 3D figure on MATLAB?
Mathematica does this using RegionPlot3D. I was hoping for a similar resultant image.
First of all, be careful when using 3D meshes, the one you defined contains 8M+ points.
Assuming your shape is convex, you can use convhull and trisurf:
Not that the option 'Simplify' is set as true to reduce the number of elements accounted for in the convex hull.
[x,y,z] = meshgrid(0:0.1:2,0:0.1:2,0:0.1:2);
ineq = (x>=1)& (y>0.5)&(z>=0.25)&(x+y>1.25)&(y+z>0.6)&(x+z>1.1)&(x+y+z>1.6);
figure;
x_ineq = x(ineq);
y_ineq = y(ineq);
z_ineq = z(ineq);
id_cvhl = convhull(x_ineq,y_ineq,z_ineq,'Simplify',true);
trisurf(id_cvhl,x_ineq,y_ineq,z_ineq,'FaceColor','cyan','edgecolor','none')
xlim([0 2])
ylim([0 2])
zlim([0 2])
In case you want the result to look a bit more than RegionPlot3D, don't use Simplify, and plot the edges (Be careful not too have a mesh with too many points!).
id_cvhl = convhull(x_ineq,y_ineq,z_ineq);
trisurf(id_cvhl,x_ineq,y_ineq,z_ineq,'Facecolor','yellow')

curve fitting: a number of curves with different length/number of points into one curve in 3D in Matlab

Say I've got a number of curves with different length (number of points in each curve and points distance are all vary). Could I find a curve in 3D space that fit best for this group of lines?
Code example in Matlab would be appreciated.
example data set:
the 1st curve has 10 points.
18.5860 18.4683 18.3576 18.2491 18.0844 17.9016 17.7709 17.6401 17.4617 17.2726
91.6178 91.5711 91.5580 91.5580 91.5701 91.6130 91.5746 91.5050 91.3993 91.2977
90.6253 91.1090 91.5964 92.0845 92.5565 93.0199 93.5010 93.9785 94.4335 94.8851
the 2nd curve has 8 points.
15.2091 15.0894 14.9765 14.8567 14.7360 14.6144 14.4695 14.3017
90.1138 89.9824 89.8683 89.7716 89.6889 89.6040 89.4928 89.3624
99.4393 99.9066 100.3802 100.8559 101.3340 101.8115 102.2770 102.7296
a desired curve is one that could represent these two exist curves.
I have thinking of make these curves as points scatters and fit a line out of them. But only a straight line can I get from many code snippet online.
So did I missing something or could someone provide some hint. Thanks.
Hard to come up with a bulletproof solution without more details, but here's an approach that works for the sample data provided. I found the line of best fit for all the points, and then parameterized all the points along that line of best fit. Then I did least-squares polynomial fitting for each dimension separately. This produced a three-dimensional parametric curve that seems to fit the data just fine.
Note that curve fitting approaches other than polynomial least-squares might be better suited to some cases---just substitute the preferred fitting function for polyfit and polyval.
Hope this is helpful!
clear;
close all;
pts1=[18.5860 18.4683 18.3576 18.2491 18.0844 17.9016 17.7709 17.6401 17.4617 17.2726;
91.6178 91.5711 91.5580 91.5580 91.5701 91.6130 91.5746 91.5050 91.3993 91.2977;
90.6253 91.1090 91.5964 92.0845 92.5565 93.0199 93.5010 93.9785 94.4335 94.8851]';
pts2=[ 15.2091 15.0894 14.9765 14.8567 14.7360 14.6144 14.4695 14.3017;
90.1138 89.9824 89.8683 89.7716 89.6889 89.6040 89.4928 89.3624;
99.4393 99.9066 100.3802 100.8559 101.3340 101.8115 102.2770 102.7296]';
%Combine all of our curves into a single point cloud
X = [pts1;pts2];
%=======================================================
%We want to first find the line of best fit
%This line will provide a parameterization of the points
%See accepted answer to http://stackoverflow.com/questions/10878167/plot-3d-line-matlab
% calculate centroid
x0 = mean(X)';
% form matrix A of translated points
A = [(X(:, 1) - x0(1)) (X(:, 2) - x0(2)) (X(:, 3) - x0(3))];
% calculate the SVD of A
[~, S, V] = svd(A, 0);
% find the largest singular value in S and extract from V the
% corresponding right singular vector
[s, i] = max(diag(S));
a = V(:, i);
%=======================================================
a=a / norm(a);
%OK now 'a' is a unit vector pointing along the line of best fit.
%Now we need to compute a new variable, 't', for each point in the cloud
%This 't' value will parameterize the curve of best fit.
%Essentially what we're doing here is taking the dot product of each
%shifted point (contained in A) with the normal vector 'a'
t = A * a;
tMin = min(t);
tMax = max(t);
%This variable represents the order of our polynomial fit
%Use the smallest number that produces a satisfactory result
polyOrder = 8;
%Polynomial fit all three dimensions separately against t
pX = polyfit(t,X(:,1),polyOrder);
pY = polyfit(t,X(:,2),polyOrder);
pZ = polyfit(t,X(:,3),polyOrder);
%And that's our curve fit: (pX(t),pY(t),pZ(t))
%Now let's plot it.
tFine = tMin:.01:tMax;
fitXFine = polyval(pX,tFine);
fitYFine = polyval(pY,tFine);
fitZFine = polyval(pZ,tFine);
figure;
scatter3(X(:,1),X(:,2),X(:,3));
hold on;
plot3(fitXFine,fitYFine,fitZFine);
hold off;

Extend a line through 3 points matlab

Im just trying to draw a line through the following points in matlab. Currently the line extends only to the points. I need to to extend and intercept the x axis. The code is below
A = [209.45 198.066 162.759];
B = [1.805 1.637 1.115];
plot(A,B,'*');
axis([0 210 0 2]);
hold on
line(A,B)
hold off
If you want to augment your points with a corresponding y==0 point, I suggest using interp1 to obtain the x-intercept:
A = [209.45 198.066 162.759];
B = [1.805 1.637 1.115];
x0 = interp1(B,A,0,'linear','extrap'); %extrapolate (y,x) at y==0 to get x0
[newA, inds] = sort([x0 A]); %insert x0 where it belongs
newB = [0 B];
newB = newB(inds); %keep the same order with B
plot(A,B,'b*',newA,newB,'b-');
This will use interp1 to perform a linear interpolant, with extrapolation switched on. By interpolating (B,A) pairs, we in effect invert your linear function.
Next we add the (x0,0) point to the data, but since matlab draws lines in the order of the points, we have to sort the vector according to x component. The sorting order is then used to keep the same order in the extended B vector.
Finally the line is plotted. I made use of plot with a linespec of '-' to draw the line in the same command as the points themselves. If it doesn't bother you that the (x0,0) point is also indicated, you can plot both markers and lines together using plot(newA,newB,'*-'); which ensures that the colors match up (in the above code I manually set the same blue colour on both plots).

How to select maximum intensity in Hough transform in MATLAB?

After doing the Hough transform in MATLAB, how do I pick the lines so that I can compare between two or more images?
I followed the example given by Amro and actually what I wanted to detect is the two lines in the first picture. However, what I got is the one in the second picture. How can I do this?
I think you meant the goal to be to detect lines in an image, not comparing two images (?).
Anyway, to find the maximum intensities in the Hough transform matrix generated by the hough function, we use the houghpeaks function, and pass it the desired number of peaks to detect.
EDIT1:
I figured I would add an example to show the procedure:
%# Load image, process it, find edges
I = rgb2gray( imread('pillsetc.png') );
I = imcrop(I, [30 30 450 350]);
J = imfilter(I, fspecial('gaussian', [17 17], 5), 'symmetric');
BW = edge(J, 'canny');
%# Perform Hough transform and show matrix
[H,T,R] = hough(BW);
imshow(imadjust(mat2gray(H)), [], 'XData',T, 'YData',R, ...
'InitialMagnification','fit')
xlabel('\theta (degrees)'), ylabel('\rho')
axis on, axis normal, hold on
colormap(hot), colorbar
%# Detect peaks
P = houghpeaks(H, 4);
plot(T(P(:,2)), R(P(:,1)), 'gs', 'LineWidth',2);
%# Detect lines and overlay on top of image
lines = houghlines(BW, T, R, P);
figure, imshow(I), hold on
for k = 1:length(lines)
xy = [lines(k).point1; lines(k).point2];
plot(xy(:,1), xy(:,2), 'g.-', 'LineWidth',2);
end
hold off
EDIT2:
Following your recent update, I managed to detect the lines by only making a few changes to the same above code:
I cropped the region to: [200 70 160 140]
I used an 11x11 Gaussian filter with sigma=3
Note: You will have to add the offset to get the position of the lines in the original image uncropped. Also, if you want more accurate results, you might want to detect four lines and get the lines in the middle as shown below:

Connect points and compute area

thats my first post, so please be kind.
I have a matrix with 3~10 coordinates and I want to connect these points to become a polygone with maximum size.
I tried fill() [1] to generate a plot but how do I calculate the area of this plot? Is there a way of converting the plot back to an matrix?
What would you reccomend me?
Thank you in advance!
[1]
x1 = [ 0.0, 0.5, 0.5 ];
y1 = [ 0.5, 0.5, 1.0 ];
fill ( x1, y1, 'r' );
[update]
Thank you for your answer MatlabDoug, but I think I did not formulate my question clear enough. I want to connect all of these points to become a polygone with maximum size.
Any new ideas?
x1 = rand(1,10)
y1 = rand(1,10)
vi = convhull(x1,y1)
polyarea(x1(vi),y1(vi))
fill ( x1(vi), y1(vi), 'r' );
hold on
plot(x1,y1,'.')
hold off
What is happening here is that CONVHULL is telling us which verticies (vi) are on the convex hull (the smallest polygon that encloses all the points). Knowing which ones are on the convex hull, we ask MATLAB for the area with POLYAREA.
Finally, we use your FILL command to draw the polygon, then PLOT to place the points on there for confirmation.
I second groovingandi's suggestion of trying all polygons; you just have to be sure to check the validity of the polygon (no self-intersections, etc).
Now, if you want to work with lots of points... As MatlabDoug pointed out, the convex hull is a good place to start. Notice that the convex hull gives a polygon whose area is the maximum possible. The problem, of course, is that there could be points in the interior of the hull that are not part of the polygon. I propose the following greedy algorithm, but I am not sure if it guarantees THE maximum area polygon.
The basic idea is to start with the convex hull as a candidate final polygon, and carve out triangles corresponding to the unused points until all the points belong to the final polygon. At each stage, the smallest possible triangle is removed.
Given: Points P = {p1, ... pN}, convex hull H = {h1, ..., hM}
where each h is a point that lies on the convex hull.
H is a subset of P, and it is also ordered such that adjacent
points in the list of H are edges of the convex hull, and the
first and last points form an edge.
Let Q = H
while(Q.size < P.size)
% For each point, compute minimum area triangle
T = empty heap of triangles with value of their area
For each P not in Q
For each edge E of Q
If triangle formed by P and E does not contain any other point
Add triangle(P,E) with value area(triangle(P,E))
% Modify the current polygon Q to carve out the triangle
Let t=(P,E) be the element of T with minimum area
Find the ordered pair of points that form the edge E within Q
(denote them Pa and Pb)
Replace the pair (Pa,Pb) with (Pa,E,Pb)
Now, in practice you don't need a heap for T, just append the data to four lists: one for P, one for Pa, one for Pb, and one for the area. To test if a point lies within a triangle, you only need to test each point against the lines forming the sides of the triangle, and you only need to test points not already in Q. Finally, to compute the area of the final polygon, you can triangulate it (like with the delaunay function, and sum up the areas of each triangle in the triangulation), or you can find the area of the convex hull, and subtract out the areas of the triangles as you carve them out.
Again, I don't know if this greedy algorithm is guaranteed to find the maximum area polygon, but I think it should work most of the time, and is interesting nonetheless.
You said you only have 3...10 points to connect. In this case, I suggest you just take all possible combinations, compute the areas with polyarea and take the biggest one.
Only if your number of points increases or if you have to compute it frequently so that compuation time matters, it's worth investing some time in a better algorithm. However I think it's difficult to come up with an algorithm and prove its completeness.
Finding the right order for the points is the hard part, as Amro commented. Does this function suffice?
function [idx] = Polyfy(x, y)
% [idx] = Polyfy(x, y)
% Given vectors x and y that contain pairs of points, find the order that
% joins them into a polygon. fill(x(idx),y(idx),'r') should show no holes.
%ensure column vectors
if (size(x,1) == 1)
x = x';
end
if (size(y,1) == 1)
y = y';
end
% vectors from centroid of points to each point
vx = x - mean(x);
vy = y - mean(y);
% unit vectors from centroid towards each point
v = (vx + 1i*vy)./abs(vx + 1i*vy);
vx = real(v);
vy = imag(v);
% rotate all unit vectors by first
rot = [vx(1) vy(1) ; -vy(1) vx(1)];
v = (rot*[vx vy]')';
% find angles from first vector to each vector
angles = atan2(v(:,2), v(:,1));
[angles, idx] = sort(angles);
end
The idea is to find the centroid of the points, then find vectors from the centroid to each point. You can think of these vectors as sides of triangles. The polygon is made up the set of triangles where each vector is used as the "left" and "right" only once, and no vectors are skipped. This boils down to ordering the vectors by angle around the centroid.
I chose to do this by normalizing the vectors to unit length, choosing one of them as a rotation vector, and rotating the rest. This allowed me to simply use atan2 to find the angles. There's probably a faster and/or more elegant way to do this, but I was confusing myself with trig identities. Finally, sorting those angles provides the correct order for the points to form the desired polygon.
This is the test function:
function [x, y] = TestPolyArea(N)
x = rand(N,1);
y = rand(N,1);
[indexes] = Polyfy(x, y);
x2 = x(indexes);
y2 = y(indexes);
a = polyarea(x2, y2);
disp(num2str(a));
fill(x2, y2, 'r');
hold on
plot(x2, y2, '.');
hold off
end
You can get some pretty wild pictures by passing N = 100 or so!