Related
I am working on images to detect and fit the largest possible circle in any of the free areas of an image containing distributed particles:
(able to detect the location of particle).
One direction is to define a circle touching any 3-point combination, checking if the circle is empty, then finding the largest circle among all empty circles. However, it leads to a huge number of combination i.e. C(n,3), where n is the total number of particles in the image.
I would appreciate if anyone can provide me any hint or alternate method that I can explore.
Lets do some maths my friend, as maths will always get to the end!
Wikipedia:
In mathematics, a Voronoi diagram is a partitioning of a plane into
regions based on distance to points in a specific subset of the plane.
For example:
rng(1)
x=rand(1,100)*5;
y=rand(1,100)*5;
voronoi(x,y);
The nice thing about this diagram is that if you notice, all the edges/vertices of those blue areas are all to equal distance to the points around them. Thus, if we know the location of the vertices, and compute the distances to the closest points, then we can choose the vertex with highest distance as our center of the circle.
Interestingly, the edges of a Voronoi regions are also defined as the circumcenters of the triangles generated by a Delaunay triangulation.
So if we compute the Delaunay triangulation of the area, and their circumcenters
dt=delaunayTriangulation([x;y].');
cc=circumcenter(dt); %voronoi edges
And compute the distances between the circumcenters and any of the points that define each triangle:
for ii=1:size(cc,1)
if cc(ii,1)>0 && cc(ii,1)<5 && cc(ii,2)>0 && cc(ii,2)<5
point=dt.Points(dt.ConnectivityList(ii,1),:); %the first one, or any other (they are the same distance)
distance(ii)=sqrt((cc(ii,1)-point(1)).^2+(cc(ii,2)-point(2)).^2);
end
end
Then we have the center (cc) and radius (distance) of all possible circles that have no point inside them. We just need the biggest one!
[r,ind]=max(distance); %Tada!
Now lets plot
hold on
ang=0:0.01:2*pi;
xp=r*cos(ang);
yp=r*sin(ang);
point=cc(ind,:);
voronoi(x,y)
triplot(dt,'color','r','linestyle',':')
plot(point(1)+xp,point(2)+yp,'k');
plot(point(1),point(2),'g.','markersize',20);
Notice how the center of the circle is on one vertex of the Voronoi diagram.
NOTE: this will find the center inside [0-5],[0-5]. you can easily modify it to change this constrain. You can also try to find the circle that fits on its entirety inside the interested area (as opposed to just the center). This would require a small addition in the end where the maximum is obtained.
I'd like to propose another solution based on a grid search with refinement. It's not as advanced as Ander's or as short as rahnema1's, but it should be very easy to follow and understand. Also, it runs quite fast.
The algorithm contains several stages:
We generate an evenly-spaced grid.
We find the minimal distances of points in the grid to all provided points.
We discard all points whose distances are below a certain percentile (e.g. 95th).
We choose the region which contains the largest distance (this should contain the correct center if my initial grid is fine enough).
We create a new meshgrid around the chosen region and find distances again (this part is clearly sub-optimal, because the distances are computed to all points, including far and irrelevant ones).
We iterate the refinement within the region, while keeping an eye on the variance of the top 5% of values -> if it drops below some preset threshold we break.
Several notes:
I have made the assumption that circles cannot go beyond the scattered points' extent (i.e. the bounding square of the scatter acts as an "invisible wall").
The appropriate percentile depends on how fine the initial grid is. This will also affect the amount of while iterations, and the optimal initial value for cnt.
function [xBest,yBest,R] = q42806059
rng(1)
x=rand(1,100)*5;
y=rand(1,100)*5;
%% Find the approximate region(s) where there exists a point farthest from all the rest:
xExtent = linspace(min(x),max(x),numel(x));
yExtent = linspace(min(y),max(y),numel(y)).';
% Create a grid:
[XX,YY] = meshgrid(xExtent,yExtent);
% Compute pairwise distance from grid points to free points:
D = reshape(min(pdist2([XX(:),YY(:)],[x(:),y(:)]),[],2),size(XX));
% Intermediate plot:
% figure(); plot(x,y,'.k'); hold on; contour(XX,YY,D); axis square; grid on;
% Remove irrelevant candidates:
D(D<prctile(D(:),95)) = NaN;
D(D > xExtent | D > yExtent | D > yExtent(end)-yExtent | D > xExtent(end)-xExtent) = NaN;
%% Keep only the region with the largest distance
L = bwlabel(~isnan(D));
[~,I] = max(table2array(regionprops('table',L,D,'MaxIntensity')));
D(L~=I) = NaN;
% surf(XX,YY,D,'EdgeColor','interp','FaceColor','interp');
%% Iterate until sufficient precision:
xExtent = xExtent(~isnan(min(D,[],1,'omitnan')));
yExtent = yExtent(~isnan(min(D,[],2,'omitnan')));
cnt = 1; % increase or decrease according to the nature of the problem
while true
% Same ideas as above, so no explanations:
xExtent = linspace(xExtent(1),xExtent(end),20);
yExtent = linspace(yExtent(1),yExtent(end),20).';
[XX,YY] = meshgrid(xExtent,yExtent);
D = reshape(min(pdist2([XX(:),YY(:)],[x(:),y(:)]),[],2),size(XX));
D(D<prctile(D(:),95)) = NaN;
I = find(D == max(D(:)));
xBest = XX(I);
yBest = YY(I);
if nanvar(D(:)) < 1E-10 || cnt == 10
R = D(I);
break
end
xExtent = (1+[-1 +1]*10^-cnt)*xBest;
yExtent = (1+[-1 +1]*10^-cnt)*yBest;
cnt = cnt+1;
end
% Finally:
% rectangle('Position',[xBest-R,yBest-R,2*R,2*R],'Curvature',[1 1],'EdgeColor','r');
The result I'm getting for Ander's example data is [x,y,r] = [0.7832, 2.0694, 0.7815] (which is the same). The execution time is about half of Ander's solution.
Here are the intermediate plots:
Contour of the largest (clear) distance from a point to the set of all provided points:
After considering distance from the boundary, keeping only the top 5% of distant points, and considering only the region which contains the largest distance (the piece of surface represents the kept values):
And finally:
You can use bwdist from Image Processing Toolbox to compute the distance transform of the image. This can be regarded as a method to create voronoi diagram that well explained in #AnderBiguri's answer.
img = imread('AbmxL.jpg');
%convert the image to a binary image
points = img(:,:,3)<200;
%compute the distance transform of the binary image
dist = bwdist(points);
%find the circle that has maximum radius
radius = max(dist(:));
%find position of the circle
[x y] = find(dist == radius);
imshow(dist,[]);
hold on
plot(y,x,'ro');
The fact that this problem can be solved using a "direct search" (as can be seen in another answer) means one can look at this as a global optimization problem. There exist various ways to solve such problems, each appropriate for certain scenarios. Out of my personal curiosity I have decided to solve this using a genetic algorithm.
Generally speaking, such an algorithm requires us to think of the solution as a set of "genes" subject to "evolution" under a certain "fitness function". As it happens, it's quite easy to identify the genes and the fitness function in this problem:
Genes: x , y, r.
Fitness function: technically, maximum area of circle, but this is equivalent to the maximum r (or minimum -r, since the algorithm requires a function to minimize).
Special constraint - if r is larger than the euclidean distance to the closest of the provided points (that is, the circle contains a point), the organism "dies".
Below is a basic implementation of such an algorithm ("basic" because it's completely unoptimized, and there is lot of room for optimizationno pun intended in this problem).
function [x,y,r] = q42806059b(cloudOfPoints)
% Problem setup
if nargin == 0
rng(1)
cloudOfPoints = rand(100,2)*5; % equivalent to Ander's initialization.
end
%{
figure(); plot(cloudOfPoints(:,1),cloudOfPoints(:,2),'.w'); hold on; axis square;
set(gca,'Color','k'); plot(0.7832,2.0694,'ro'); plot(0.7832,2.0694,'r*');
%}
nVariables = 3;
options = optimoptions(#ga,'UseVectorized',true,'CreationFcn',#gacreationuniform,...
'PopulationSize',1000);
S = max(cloudOfPoints,[],1); L = min(cloudOfPoints,[],1); % Find geometric bounds:
% In R2017a: use [S,L] = bounds(cloudOfPoints,1);
% Here we also define distance-from-boundary constraints.
g = ga(#(g)vectorized_fitness(g,cloudOfPoints,[L;S]), nVariables,...
[],[], [],[], [L 0],[S min(S-L)], [], options);
x = g(1); y = g(2); r = g(3);
%{
plot(x,y,'ro'); plot(x,y,'r*');
rectangle('Position',[x-r,y-r,2*r,2*r],'Curvature',[1 1],'EdgeColor','r');
%}
function f = vectorized_fitness(genes,pts,extent)
% genes = [x,y,r]
% extent = [Xmin Ymin; Xmax Ymax]
% f, the fitness, is the largest radius.
f = min(pdist2(genes(:,1:2), pts, 'euclidean'), [], 2);
% Instant death if circle contains a point:
f( f < genes(:,3) ) = Inf;
% Instant death if circle is too close to boundary:
f( any( genes(:,3) > genes(:,1:2) - extent(1,:) | ...
genes(:,3) > extent(2,:) - genes(:,1:2), 2) ) = Inf;
% Note: this condition may possibly be specified using the A,b inputs of ga().
f(isfinite(f)) = -genes(isfinite(f),3);
%DEBUG:
%{
scatter(genes(:,1),genes(:,2),10 ,[0, .447, .741] ,'o'); % All
z = ~isfinite(f); scatter(genes(z,1),genes(z,2),30,'r','x'); % Killed
z = isfinite(f); scatter(genes(z,1),genes(z,2),30,'g','h'); % Surviving
[~,I] = sort(f); scatter(genes(I(1:5),1),genes(I(1:5),2),30,'y','p'); % Elite
%}
And here's a "time-lapse" plot of 47 generations of a typical run:
(Where blue points are the current generation, red crosses are "insta-killed" organisms, green hexagrams are the "non-insta-killed" organisms, and the red circle marks the destination).
I'm not used to image processing, so it's just an Idea:
Implement something like a gaussian filter (blur) which transforms each particle (pixels) to a round gradiant with r=image_size (all of them overlapping). This way, you should get a picture where the most white pixels should be the best results. Unfortunately, the demonstration in gimp failed because the extreme blurring made the dots disappearing.
Alternatively, you could incrementelly extend all existing pixels by marking all neighbour pixels in an area (example: r=4), the pixels left would be the same result (those with the biggest distance to any pixel)
I have been reading up quite a bit on Hough circles and how it can be used to detect the center and radius of a circular object. Although I understood how the transform works I am still not able to to understand how to get the center of a circle. The code snippet below is customized purely for coins.png in MATLAB. I tried it with a slightly more complex picture and it fails miserably. I am fixing the radius and then getting the accumulator matrix.
Code:
temp = accum;
temp(find(temp<40))=0; %Thresholding
imshow(temp,[0 max(temp(:))])
temp = imdilate(temp, ones(6,6)); %Dilating
imshow(temp,[0 max(max(temp))]);
temp = imerode(temp,ones(3,3)); %Eroding
imshow(temp,[0 max(max(temp))]);
s = regionprops(im2bw(temp),'centroid'); %Computing centroid
centroids = cat(1, s.Centroid);
I wanted to test the code out on a different picture and found this one on google. The Hough Transform produced a favorable result, but it's more overlapping than the previous case and my method fails.
Can someone suggest what the best method is to compute the centroid of the hough circle?
Original image:
Full Code:
%%Read and find edges using a filter
i = imread('coins4.jpg');
i = rgb2gray(i);
i = imresize(i,0.125);
i_f = edge(i, 'canny',[0.01 0.45]);
imshow(i_f)
%%
[x y] = find(i_f>0); % Finds where the edges are and stores the x and y coordinates
edge_index = size(x);
radius = 30; %Fixed radius value
theta = 0:0.01:2*pi;
accum = zeros(size(i,1), size(i,2)); %Create an accumulator matrix.
r_co = radius*cos(theta);
r_si = radius*sin(theta);
x1 = repmat(x, 1, length(r_co));
y1 = repmat(y, 1, length(r_si));
x_r_co = repmat(r_co, length(x),1);
y_r_si = repmat(r_si, length(y),1);
%% Filling the accumulator
a = x1 - x_r_co;
b = y1 - y_r_si;
for cnt = 1:numel(a)
a_cnt = round(a(cnt));
b_cnt = round(b(cnt));
if(a_cnt>0 && b_cnt>0 && a_cnt<=size(accum,1) && b_cnt<=size(accum,2))
accum(a_cnt,b_cnt) = accum(a_cnt,b_cnt) + 1;
end
end
imshow(accum,[0 max(max(accum))]);
%% Thresholding and get the center of the circle.
close all;
temp = accum;
temp(find(temp<40))=0;
imshow(temp,[0 max(temp(:))])
temp = imdilate(temp, ones(6,6));
imshow(temp,[0 max(max(temp))]);
temp = imerode(temp,ones(3,3));
imshow(temp,[0 max(max(temp))]);
s = regionprops(im2bw(temp),'centroid');
centroids = cat(1, s.Centroid);
imshow(i);
hold on;
plot(centroids(:,1), centroids(:,2),'*b')
If you want to stick with the 2D image you have now, this is a simple algorithm that might work for your first image since all the coins are about the same size and it isn't too cluttered:
find the global maximum of your accumulator array using [~,max_idx] = max(accum(:)); [i,j] = ind2sub(size(accum), max_idx];
Take a small neighbourhood around this pixel (maybe a square with a 10 pixel radius) and calculate its center of mass and get its index (this basically finds the middle of the bright rings you see)
add the center of mass pixel index to a list of circle centers
set all pixels in the small neighbourhood to zero (to prevent double-detection of the same pixel center)
repeat from step 1. until you reach approximately 70% or so of the original global max
If that doesn't work, I'd suggest taking the 3D accumulator approach, which is much better for coins of variable size.
The reason you are getting "donut" shapes for some of the coins rather than circles with intense bright points at the centers is because the radius you are assuming is a little off. If you were to explore the 3rd dimension of the accumulator (the radius dimension), you would find that these "donuts" taper to a point, which you could then detect as a local maximum.
This does require a lot more time and memory but it would lead to the most accurate result and isn't a big adjustment code-wise. You can detect the maxima using a method similar to the one I mentioned above, but in 3D and without the center-of-mass trick in step 2.
So, there is a function in matlab that does exactly what you want, called imfindcircles. You give an image and a range of possible radii, it outputs the computed radii and the centers of the circles. Some other parameters allow to tweak the computation (and in my experience, using them is necessary for good results).
Now, if you want to find the centers and radii yourself (not using matlab) then you want to use a maximum finder on the accumulation matrix. The concept of the Hough transform is that the maxima you find on the accumulation matrix each correspond to a circle (parametrized usually in (radius, x_center, y_center). The tough part here is that you have a gigantic searchspace and many many many many maxima that are not "real" circles but artefacts. I do not know how to reliably differentiate these fake circles of the real ones -- and I expect it's a rather complicated check to do.
Hope this helps!
I have a binary image of a human. In MATLAB, boundary points and the center of the image are also defined, and they are two column matrices. Now I want to draw lines from the center to the boundary points so that I can obtain all points of intersection between these lines and the boundary of the image. How can I do that? Here is the code I have so far:
The code that is written just to get the one intersection point if anyone can help please
clear all
close all
clc
BW = im2bw(imread('C:\fyc-90_1-100.png'));
BW = imfill(BW,'holes');
[Bw m n]=preprocess(BW);
[bord sk pr_sk]=border_skeleton(BW);
boundry=bord;
L = bwlabel(BW);
s = regionprops(L, 'centroid');
centroids = cat(1, s.Centroid);
Step #1 - Generating your line
The first thing you need to do is figure out how to draw your line. To make this simple, let's assume that the centre of the human body is stored as an array of cen = [x1 y1] as you have said. Now, supposing you click anywhere in your image, you get another point linePt = [x2 y2]. Let's assume that both the x and y co-ordinates are the horizontal and vertical components respectively. We can find the slope and intercept of this line, then create points between these two points parameterized by the slope and intercept to generate your line points. One thing I will point out is that if we draw a slope with a vertical line, by definition the slope would be infinity. As such, we need to place in a check to see if we have this situation. If we do, we assume that all of the x points are the same, while y varies. Once you have your slope and intercept, simply create points in between the line. You'll have to choose how many points you want along this line yourself as I have no idea about the resolution of your image, nor how big you want the line to be. We will then store this into a variable called linePoints where the first column consists of x values and the second column consists of y values. In other words:
In other words, do this:
%// Define number of points
numPoints = 1000;
%// Recall the equation of the line: y = mx + b, m = (y2-y1)/(x2-x1)
if abs(cen(1) - linePt(1)) < 0.00001 %// If x points are close
yPts = linspace(cen(2), linePt(2), numPoints); %// y points are the ones that vary
xPts = cen(1)*ones(numPoints, 1); %//Make x points the same to make vertical line
else %// Normal case
slp = (cen(2) - linePt(2)) / cen(1) - linePt(1)); %// Solve for slope (m)
icept = cen(2) - slp*cen(1); %// Solve for intercept (b)
xPts = linspace(cen(1), linePt(1), numPoints); %// Vary the x points
yPts = slp*xPts + icept; %// Solve for the y points
end
linePoints = [xPts(:) yPts(:)]; %// Create point matrix
Step #2 - Finding points of intersection
Supposing you have a 2D array of points [x y] where x denotes the horizontal co-ordinates and y denotes the vertical co-ordinates of your line. We can simply find the distance between all of these points in your boundary with all of your points on the line. Should any of the points be under a certain threshold (like 0.0001 for example), then this indicates an intersection. Note that due to the crux of floating point data, we can't check to see if the distance is 0 due to the step size in between each discrete point in your data.
I'm also going to assume border_skeleton returns points of the same format. This method works without specifying what the centroid is. As such, I don't need to use the centroids in the method I'm proposing. Also, I'm going to assume that your line points are stored in a matrix called linePoints that is of the same type that I just talked about.
In other words, do this:
numBoundaryPoints = size(boundry, 1); %// boundary is misspelled in your code BTW
ptsIntersect = []; %// Store points of intersection here
for idx = 1 : numBoundaryPoints %// For each boundary point...
%//Obtain the i'th boundary point
pt = boundry(:,idx);
%//Get distances - This computes the Euclidean distance
%//between the i'th boundary point and all points along your line
dists = sqrt(sum(bsxfun(#minus, linePoints, pt).^2, 2));
%//Figure out which points intersect and store
ptsIntersect = [ptsIntersect; linePoints(dists < 0.0001, :)];
end
In the end, ptsIntersect will store all of the points along the boundary that intersect with this line. Take note that I have made a lot of assumptions here because you haven't (or seem reluctant to) give any more details than what you've specified in your comments.
Good luck.
I have a closed non-self-intersecting polygon. Its vertices are saved in two vectors X, and Y. Finally the values of X and Y are bound between 0 and 22.
I'd like to construct a matrix of size 22x22 and set the value of each bin equal to true if part of the polygon overlaps with that bin, otherwise false.
My initial thought was to generate a grid of points defined with [a, b] = meshgrid(1:22) and then to use inpolygon to determine which points of the grid were in the polygon.
[a b] = meshgrid(1:22);
inPoly1 = inpolygon(a,b,X,Y);
However this only returns true if if the center of the bin is contained in the polygon, ie it returns the red shape in the image below. However what need is more along the lines of the green shape (although its still an incomplete solution).
To get the green blob I performed four calls to inpolygon. For each comparison I shifted the grid of points either NE, NW, SE, or SW by 1/2. This is equivalent to testing if the corners of a bin are in the polygon.
inPoly2 = inpolygon(a-.5,b-.5,X,Y) | inpolygon(a+.5,b-.5,X,Y) | inpolygon(a-.5,b+5,X,Y) | inpolygon(a+.5,b+.5,X,Y);
While this does provide me with a partial solution it fails in the case when a vertex is contain in a bin but none of the bin corners are.
Is there a more direct way of attacking this problem, with preferably a solution that produces more readable code?
This plot was drawn with:
imagesc(inPoly1 + inPoly2); hold on;
line(a, b, 'w.');
line(X, Y, 'y);
One suggestion is to use the polybool function (not available in 2008b or earlier). It finds the intersection of two polygons and returns resulting vertices (or an empty vector if no vertices exist). To use it here, we iterate (using arrayfun) over all of the squares in your grid check to see whether the output argument to polybool is empty (e.g. no overlap).
N=22;
sqX = repmat([1:N]',1,N);
sqX = sqX(:);
sqY = repmat(1:N,N,1);
sqY = sqY(:);
intersects = arrayfun((#(xs,ys) ...
(~isempty(polybool('intersection',X,Y,[xs-1 xs-1 xs xs],[ys-1 ys ys ys-1])))),...
sqX,sqY);
intersects = reshape(intersects,22,22);
Here is the resulting image:
Code for plotting:
imagesc(.5:1:N-.5,.5:1:N-.5,intersects');
hold on;
plot(X,Y,'w');
for x = 1:N
plot([0 N],[x x],'-k');
plot([x x],[0 N],'-k');
end
hold off;
How about this pseudocode algorithm:
For each pair of points p1=p(i), p2=p(i+1), i = 1..n-1
Find the line passing through p1 and p2
Find every tile this line intersects // See note
Add intersecting tiles to the list of contained tiles
Find the red area using the centers of each tile, and add these to the list of contained tiles
Note: This line will take a tiny bit of effort to implement, but I think there is a fairly straightforward, well-known algorithm for it.
Also, if I was using .NET, I would simply define a rectangle corresponding to each grid tile, and then see which ones intersect the polygon. I don't know if checking intersection is easy in Matlab, however.
I would suggest using poly2mask in the Image Processing Toolbox, it does more or less what you want, I think, and also more or less what youself and Salain has suggested.
Slight improvement
Firstly, to simplify your "partial solution" - what you're doing is just looking at the corners. If instead of considering the 22x22 grid of points, you could consider the 23x23 grid of corners (which will be offset from the smaller grid by (-0.5, -0.5). Once you have that, you can mark the points on the 22x22 grid that have at least one corner in the polygon.
Full solution:
However, what you're really looking for is whether the polygon intersects with the 1x1 box surrounding each pixel. This doesn't necessarily include any of the corners, but it does require that the polygon intersects one of the four sides of the box.
One way you could find the pixels where the polygon intersects with the containing box is with the following algorithm:
For each pair of adjacent points in the polygon, calling them pA and pB:
Calculate rounded Y-values: Round(pA.y) and Round(pB.y)
For each horizontal pixel edge between these two values:
* Solve the simple linear equation to find out at what X-coordinate
the line between pA and pB crosses this edge
* Round the X-coordinate
* Use the rounded X-coordinate to mark the pixels above and below
where it crosses the edge
Do a similar thing for the other axis
So, for example, say we're looking at pA = (1, 1) and pB = (2, 3).
First, we calculated the rounded Y-values: 1 and 3.
Then, we look at the pixel edges between these values: y = 1.5 and y = 2.5 (pixel edges are half-offset from pixels
For each of these, we solve the linear equation to find where pA->pB intersects with our edges. This gives us: x = 1.25, y = 1.5, and x = 1.75, y = 2.5.
For each of these intersections, we take the rounded X-value, and use it to mark the pixels either side of the edge.
x = 1.25 is rounded to 1 (for the edge y = 1.5). We therefore can mark the pixels at (1, 1) and (1, 2) as part of our set.
x = 1.75 is rounded to 2 (for the edge y = 2.5). We therefore can mark the pixels at (2, 2) and (2, 3).
So that's the horizontal edges taken care of. Next, let's look at the vertical ones:
First we calculate the rounded X-values: 1 and 2
Then, we look at the pixel edges. Here, there is only one: x = 1.5.
For this edge, we find the where it meets the line pA->pB. This gives us x = 1.5, y = 2.
For this intersection, we take the rounded Y-value, and use it to mark pixels either side of the edge:
y = 2 is rounded to 2. We therefore can mark the pixels at (1, 2) and (2, 2).
Done!
Well, sort of. This will give you the edges, but it won't fill in the body of the polygon. However, you can just combine these with your previous (red) results to get the complete set.
First I define a low resolution circle for this example
X=11+cos(linspace(0,2*pi,10))*5;
Y=11+sin(linspace(0,2.01*pi,10))*5;
Like your example it fits with in a grid of ~22 units. Then, following your lead, we declare a meshgrid and check if points are in the polygon.
stepSize=0.1;
[a b] = meshgrid(1:stepSize:22);
inPoly1 = inpolygon(a,b,X,Y);
Only difference is that where your original solution took steps of one, this grid can take smaller steps. And finally, to include anything within the "edges" of the squares
inPolyFull=unique( round([a(inPoly1) b(inPoly1)]) ,'rows');
The round simply takes our high resolution grid and rounds the points appropriately to their nearest low resolution equivalents. We then remove all of the duplicates in a vector style or pair-wise fashion by calling unique with the 'rows' qualifier. And that's it
To view the result,
[aOrig bOrig] = meshgrid(1:22);
imagesc(1:stepSize:22,1:stepSize:22,inPoly1); hold on;
plot(X,Y,'y');
plot(aOrig,bOrig,'k.');
plot(inPolyFull(:,1),inPolyFull(:,2),'w.'); hold off;
Changing the stepSize has the expected effect of improving the result at the cost of speed and memory.
If you need the result to be in the same format as the inPoly2 in your example, you can use
inPoly2=zeros(22);
inPoly2(inPolyFull(:,1),inPolyFull(:,2))=1
Hope that helps. I can think of some other ways to go about it, but this seems like the most straightforward.
Well, I guess I am late, though strictly speaking the bounty time was till tomorrow ;). But here goes my attempt. First, a function that marks cells that contain/touch a point. Given a structured grid with spacing lx, ly, and a set of points with coordinates (Xp, Yp), set containing cells:
function cells = mark_cells(lx, ly, Xp, Yp, cells)
% Find cell numbers to which points belong.
% Search by subtracting point coordinates from
% grid coordinates and observing the sign of the result.
% Points lying on edges/grid points are assumed
% to belong to all surrounding cells.
sx=sign(bsxfun(#minus, lx, Xp'));
sy=sign(bsxfun(#minus, ly, Yp'));
cx=diff(sx, 1, 2);
cy=diff(sy, 1, 2);
% for every point, mark the surrounding cells
for i=1:size(cy, 1)
cells(find(cx(i,:)), find(cy(i,:)))=1;
end
end
Now, the rest of the code. For every segment in the polygon (you have to walk through the segments one by one), intersect the segment with the grid lines. Intersection is done carefully, for horizontal and vertical lines separately, using the given grid point coordinates to avoid numerical inaccuracies. For the found intersection points I call mark_cells to mark the surrounding cells to 1:
% example grid
nx=21;
ny=51;
lx = linspace(0, 1, nx);
ly = linspace(0, 1, ny);
dx=1/(nx-1);
dy=1/(ny-1);
cells = zeros(nx-1, ny-1);
% for every line in the polygon...
% Xp and Yp contain start-end points of a single segment
Xp = [0.15 0.61];
Yp = [0.1 0.78];
% line equation
slope = diff(Yp)/diff(Xp);
inter = Yp(1) - (slope*Xp(1));
if isinf(slope)
% SPECIAL CASE: vertical polygon segments
% intersect horizontal grid lines
ymax = 1+floor(max(Yp)/dy);
ymin = 1+ceil(min(Yp)/dy);
x=repmat(Xp(1), 1, ymax-ymin+1);
y=ly(ymin:ymax);
cells = mark_cells(lx, ly, x, y, cells);
else
% SPECIAL CASE: not horizontal polygon segments
if slope ~= 0
% intersect horizontal grid lines
ymax = 1+floor(max(Yp)/dy);
ymin = 1+ceil(min(Yp)/dy);
xmax = (ly(ymax)-inter)/slope;
xmin = (ly(ymin)-inter)/slope;
% interpolate in x...
x=linspace(xmin, xmax, ymax-ymin+1);
% use exact grid point y-coordinates!
y=ly(ymin:ymax);
cells = mark_cells(lx, ly, x, y, cells);
end
% intersect vertical grid lines
xmax = 1+floor(max(Xp)/dx);
xmin = 1+ceil(min(Xp)/dx);
% interpolate in y...
ymax = inter+slope*lx(xmax);
ymin = inter+slope*lx(xmin);
% use exact grid point x-coordinates!
x=lx(xmin:xmax);
y=linspace(ymin, ymax, xmax-xmin+1);
cells = mark_cells(lx, ly, x, y, cells);
end
Output for the example mesh/segment:
Walking through all polygon segments gives you the polygon 'halo'. Finally, the interior of the polygon is obtained using standard inpolygon function. Let me know if you need more details about the code.
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!