How does MATLAB's `regionprops` function computes perimeter? - matlab

I have found that the function uses chain codes to encode the boundary of the shape, and then computes the perimeter this way using this formula:
perimeter = sum(isEven)*0.980 + sum(~isEven)*1.406 - sum(isCorner)*0.091;
What I do not know, however, is how this chain code is computed in some special cases.
Consider the following example (for which MATLAB yields a perimeter of 10.0150):
1 1 1 1 1
1 1 0 0 1
How does MATLAB defines/computes the perimeter around the one pixel wide line attached to the 2x2 square on the left?
More precisely:
If I denote non-zero border pixels using letters (all 1s are borders in this example):
a d e f g
b c h
A chain code could start at, say, a. If we compute it clockwise, it would then continue at c, e, f... Which means it cannot come back to a, otherwise it would have to go twice over the same letter (even more than twice if there are 1px wide lines attached to other 1px wide lines etc).

There are two steps in obtaining chain codes: tracing the boundary and encoding the coordinates as chain codes. This latter step is trivial, I won't go into details. Tracing the boundary is what I think this question is about.
Typically what is traced are the object pixels that form the boundary (i.e. have at least one background neighbor). It is important that this happen in order, just listing these pixels is not enough. But do note that this description of the boundary is biased: the true object is larger than the polygon formed by joining the centers of the pixels at the object boundary. A perimeter computation needs to take this into account (as discussed in the blog post you linked).
This code is adapted slightly from this blog post. img is a logical array:
% Data for chain code encoding:
directions = [ 1, 0
1,-1
0,-1
-1,-1
-1, 0
-1, 1
0, 1
1, 1];
% Get a start point, any pixel on the boundary is OK:
indx = find(img,1)-1; % 0-based indexing is easier
% Image sizes
sz = [size(img,2),size(img,1)]; % x,y sizes, rather than y,x sizes
% Coordinates for start point
start = [floor(indx/sz(2)),0];
start(2) = indx-(start(1)*sz(2));
% Initialize algorithm
cc = []; % The chain code
coord = start; % Coordinates of the current pixel
dir = 1; % The starting direction
% Loop till full boundary is traced
while 1
newcoord = coord + directions(dir+1,:);
if all(newcoord>=0) && all(newcoord<sz) ...
&& img(newcoord(2)+1,newcoord(1)+1)
cc = [cc,dir];
coord = newcoord;
dir = mod(dir+2,8);
else
dir = mod(dir-1,8);
end
if all(coord==start) && dir==1 % back to starting situation
break;
end
end
As you can see here, the algorithm starts at a random pixel, and picks a direction to go around. Then it follows the boundary by finding the next neighbor in the given direction. The linked blog post has details explaining how this neighbor is found. In short, you look in the current direction, for the first neighboring object pixel that has a background neighbor. Given the current location and direction we came from, it is provable that the neighbor in a specific direction will be a background pixel. Going in clockwise (or anticlockwise, pick one) direction around the current point, starting with that background pixel, the first object pixel will guaranteed be a boundary pixel. We add that to out list and continue.
The algorithm terminates when we reach the start position and direction. The 1-pixel thick section of an object will thus be visited twice, to complete the boundary trace.

Related

Apply operations to a cell array where each cell is a polygon

I have some problems that require me to manipulate polygons using operations such as translating, dilating, rotating, and shearing. The data I have is actually on state boundaries and geometries from data.gov on the state of Delaware. The function delaware.m returns a cell array (1x3 cell) of polygon matrices describing the shape of the state of Delaware, and this is the shape I need to do operations on. I will post the specific questions so you can get a sense of what I'm being asked of, but I'm still asking for more general guidance than a specific answer to each question.
Translate the state of Delaware so that its center is approximately at the origin.
Dilate the translated state of Delaware so that it fits inside a square of side length one centered at the origin.
Rotate the translated, dilated state of Delaware so that New Castle County is at the bottom and Sussex is at the top.
Dilate the translated, dilated, rotated state of Delaware without changing its area, so that it is about as wide as it is tall.
Shear the translated, dilated, rotated, dilated state of Delaware the northernmost tip is at least 2 units to the right of the southernmost tip.
The thing is, I know how to do all these operations in Matlab with just a single polygon/matrix. I am mostly struggling with how to use this with the cell array.
For example, say I have matrix S.
newS=S+[1;2]; %move S one unit to the right and two units up
R=[sqrt(2)/2 -sqrt(2)/2; sqrt(2)/2 sqrt(2)/2];
newS=R*S %rotate the polygon by 45 degrees
D = [alpha 0; 0 beta];
%alpha is the dilation scaling the x direction and beta in the y direction
%left multiply S by this dilation matrix to dilate along the cardinal axes
Sh=[1 y; 0 1] %y controls the amount of shearing
%left multiply by S to shear a shape along the x-axis relative to the y-axis
So for example, when I try to do an operation for moving the shape up/down/left/right as I described above for the cell array, I get the error message
Undefined operator '+' for input arguments of type 'cell'.
I also tried:
DEBoundary1 = cellfun(#sum, DEBoundary, [75.562;-39.6]);
%this is how much I wanted to move the polygons
But got:
>> Lab_code
Error using cellfun
All of the input arguments must be of the same size and shape.
Previous inputs had size 1 in dimension 1. Input #3 has size 2
I suppose in general, is there an easy way to take these operations I already know and apply them to a cell array consisting of polygon matrices? Or do I have to go about it a different way?
I believe this is what you're trying to do with your + example:
DEBoundary = {[0 1 -1 0; 1 -1 -1 1], [0 -1 1 0; 1 1 1 1]};
offset = [3;-2];
DEBoundary1 = cellfun(#(c) c + offset, DEBoundary, 'UniformOutput', false)
What this does is:
cellfun(#(c) % c is each element in the cell
c + offset % add the offset to each element
, DEB % The cell array to operate on
'UniformOutput', 0) % Specifies that the output is a cell and not a scalar
Try it online!
If you thing cellfun is confusing, then you may do this manually:
DEBoundary1 = cell(size(DEBoundary))
for i = 1:numel(DEBoundary)
DEBoundary1{i} = DEBoundary{i} + offset;
end
This should work with multiplication and other operations as well, as long as the dimensions match (but that's a mathematical question, not MATLAB specific).

Find coordinates from a contour in Matlab

I suppose this is not something difficult but i wonder if there is any function or any optimal way.
Consider that after an image process i have a matrix-image with 0 everywhere and 1 at the contour.
Now i want to find the x y along that contour line
The important is that eg [ x(2) y(2) ] should be the next pixel to [x(1) y(1)]
I have used this:
[c h]=contour(image,1)
x=c(1,:)
y=c(2,:)
But the result is not very good and it gives some noise points which is very bad (and for some reason it appears mirrored)
If you have the image processing toolbox, I highly suggest using bwperim which returns a logical image where true is a perimeter or contour point and false otherwise.... not to mention that it's faster.
Try doing:
bw = bwperim(image == 1); % image == 1 to ensure binary
[y, x] = find(bw);
The first line of code finds an image that only contains contour points, and we can use find after that on the result to find the row and column locations. Here y represents the row and x represents the column locations.
If you desire that the contour is ordered, then use the bwtraceboundary function instead. However, this will require that you specify an initial contour point prior to running the function.
An easy way to do this would be to find any non-zero point along the contour of your object. You could use bwperim first and sample any point from here. Choosing just any point may not give you an actual contour point.
Therefore:
bw = bwperim(image == 1); % image == 1 to ensure binary
[y, x] = find(bw, 1); % Find the first contour point
ctr = bwtraceboundary(image == 1, [y, x], 'SE');
'SE' is the direction of where to look first given the initial contour point. Here I've chosen south east. This will produce a N x 2 matrix where the first column contains the rows and second column contains the columns of the ordered contour points starting at the initial position provided by y and x.
I have posted my complete solution to help other people:
Problem:
I have a grayscale image and i want to find the coordinates X Y in order along the contour .
Solution:
Set a threshold for black and white and make the image binary (optional)
`image=image>0.5 %This is optional but some may found it usefull`
Find the Start Point:
[yStart xStart]=find(image,1);
This will scan the image column by column from left to right and up to down and will return the first non zero pixel. So this will return the 'left-est up' pixel of the image. Remember, X is the column and Y is the row. Zero is at the top-left corner!
Find the contour:
contour=bwtraceboundary(image, [yStart, xStart],'NE');
or
contour = bwtraceboundary(image,[yStart xStart],'NE',8,Inf,'clockwise');
NE is the initial direction (NorthEast)
x=contour(:,2)
y=contour(:,1)
If the point [yStart xStart] is not on the contour of an image this will not work !
If you plot (x,y) that will be mirrored. This is because the zero at the coordinate system is at the top left corner of the image and not at the bottom left. To do it properly you can do this:
y=-y
y=y+abs(min(y))+1 % +1 is to avoid y=0

Fitting largest circle in free area in image with distributed particle

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)

Find the index of pixels in an image statisfies one condition

I have an image include 4 values {3,-3,1,-1} as figure
Let call the index of pixel that its values equals 1 or -1 pixel in contour. These pixels will create a contour that surrounds the yellow color (-3). Now, I want to find all index pixels in the contour and plus padding position inward and outward contour. As the red color, padding is set 1, hence, the index of these pixels include pixel in the contour {1,-1} and padding index as the red color. In that task, I want to find all pixel indices. How to implement that idea in matlab code. This is my code to find the index in the contour
%% Let define the image I
idx=find(I==1|I==-1);
padding=1;
%%Continue
Update: My expected result as the above figure in white region. Hence, the indices are such as 13,14,15,..21,24,...
UPDATE
Firstly, thank Andrew and Rayryeng for your answer. I would like to extedn my problem. As the above description, the contour is created by {1,-1}. Now, I want to ignore 1 and -1, so the image I only has {3,-3}. And I defined the contour is pixel in the edge of {3,-3} such as figure. Keep the same idea of padding and pixel index. How to find the indices of pixels in contour and near contour (call narrow band of contour)(expected result is white color)
Not too difficult you are on the right track. If you have the image processing toolbox, I recommend taking a look at morphological operators. Specifically you want to use imdilate my code has all the details you need.
%rather than using find, we create a binary mask. Its not the indicies of
%the matching elements as find gives. its is 1/true if the value matches the
%criteria, and 0/false otherwise.
mask = (im=1 | im=-1);
%create a 3x3 rectangle structuring element. We use a 3x3 because we want
%to expand the image by one pixel. basically the structring element (Strel)
%is our kernal, if you know image processing this is the same thing.
%a = [0 0 0 0;
% 0 1 1 1;
% 0 1 1 1;
% 0 1 1 1];
%our kernal is center at 2,2 (for this example) which are these elements
% 0 0 0 of a think a(1:3,1:3) now what the dialate operation
% 0 1 1 says is, if the majority of these pixels are ones... they
% 0 1 1 should probabaly all be ones so all those 0s will become ones
%the size of the kernal 3x3 ensures we are only growing our image one
%pixel, hope that makes sense
se = strel('square',3);
%now we dilate, or 'expand' our mask with our structuring element
expanded_mask = imdilate(mask,se);
%if you still want the indicies you can use find on our expanded mask
idx = find(expanded_mask==1);
EDIT: without morphological operations/image processing toolbox
This method uses lots of for loops, so it isn't the fastest, and doens't do error checking, but it will work. My dilate function says if the majority of the pixels are ones make them all ones.
function expanded_mask=DilateBinaryImage(bin_im, kernal_size)
[max_row,max_col] = size(bin_im);
%since we are opening the mask (only adding 1s), we can start off with the
%same values of the mask, and simply add extra 1's as needed
expanded_mask = bin_im;
%we don't want to go off the edge of our image with this kernal
%so we offset it a bit
kern_padding = floor(kernal_size/2);
%this ignores the edges
for (curr_row=kern_padding+1:1:max_row - kern_padding)
for (curr_col=kern_padding+1:1:max_col - kern_padding)
%we do 2 sums, one for rows, one for columns
num_ones = sum(sum(bin_im(curr_row-kern_padding:curr_row+kern_padding,curr_col-kern_padding:curr_col+kern_padding)));
%if the majority of vlaues are 1, we use floor to help with corner
%cases
if (num_ones >= floor((kernal_size*kernal_size)/2))
%make all the values one
expanded_mask(curr_row-kern_padding:curr_row+kern_padding,curr_col-kern_padding:curr_col+kern_padding) = 1;
end
end
end
end
and then called it like this
kernal_size= 3;
mask = (I==1 | I==-1);
expanded_mask = DilateBinaryImage(mask, kernal_size);
idx = find(expanded_mask==1);
my dilate function doesn't work at the edges of the binary image. it just copies them exactly.
Lets say your image is N-by-M pixles. In MATLAB arrays are stored in column order (see http://www.mathworks.com/help/matlab/math/matrix-indexing.html for more information). You can use I in column format as follows. First you contour pixels are given by
idx=find(I(:)==1|I(:)==-1);
Now, if you wish to pad downward and upward it is quite simple:
idx_up=idx - padding;
idx_up = idx_up(idx_up>0);
idx_down=idx + padding;
idx_down = idx_down(idx_down<=N*M);
Note that idx_up and idx_down will also contain contour pixels.
Similarly you can pad to the left\right:
idx_left=idx - padding*N;
idx_left = idx_left(idx_left>0);
idx_right=idx + padding*N;
idx_right = idx_right(idx_right<=N*M);
And combine the overall pixels:
PaddedContour = false(N,M);
PaddedContour(unique([idx;idx_up;idx_down;idx_left;idx_right])) = true;

What is an simple way to compute the overlap between an image and a polygon?

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.