How to create a polygon out of the outer cells of a contiguous patch of cells - matlab

I have below a contiguous patch of cells plotted in Matlab.
The outer cells of the red patch have to be determined and then a polygon joining the centers of these cells will give me a polygon. How do i compute the outer cells of the contiguous patch?
I have an array of integers whose elements denote the cell in the red patch, for example,
a=[1;64;23;456;345];
Each element , say 64 corresponds to a cell in the image, and it is the cell belonging to the red patch.
The motivation to solve the problem is to deal with a polygon with minimal number of edges rather than so many cells. it slows down computation. Convex hull is not good enough. I don't want the resulting polygon to overlap with the brown area at all.
What i am suggesting is the case on the left in image below but it seems ugly. So a better way would be as in right to just skip the cells only sharing a single point with the outer brown area. I would like my outer cells to then be only those that share more than just a single point with the outer brown area.
But we want to avoid large number of edges in the resultant polygon!

I first processed the sample image from your question to create a logical mask (you already have an example of how to do that here).
Once you have this mask, there is a really easy way to generate the polygon you want using the bwtraceboundary function from the Image Processing Toolbox. This will give you a set of pixel indices in order around the perimeter of your masked region:
[r, c] = find(mask, 1);
coords = bwtraceboundary(mask, [r c], 'N');
And we can visualize it like so:
imagesc(mask);
colormap([0.9 0.9 0.9; 0.6 0.6 0.6]);
axis equal
set(gca, 'XLim', [0.5 0.5+size(mask, 2)], 'YLim', [0.5 0.5+size(mask, 1)]);
hold on;
plot(coords(:, 2), coords(:, 1), 'r', 'LineWidth', 2);
plot(coords(1, 2), coords(1, 1), 'go', 'LineWidth', 2);
The coordinates for the red line are ordered starting from the green circle and moving clockwise around the perimeter pixels of the masked region.
If you would prefer to generate a boundary outline that follows along the perimeter pixel edges of the region instead of the perimeter pixel centers, you can use the solution from my answer to a related question. This will yield the following:

Although the answer by #rahnema1 is really cool, I think the OP is asking more how to extract the set of edges according to the described rules.
Here is my approach identifying all the 10 patterns of 2x2 pixels that contain edges. Assuming the matrix A has the image with 1s and 0s (A = zeros(ny, nx); A(a) = 1):
% we identify patterns with edges over 2x2 patches, describing with
% the first 4 binary values what pixels are set, and with the next 2
% the edge with 2 indices over the 2x2 patch
patterns = [
0,1,0,1, 3,4 % vertical edge at rhe right
1,0,1,0, 1,2 % vertical edge at the left
0,0,1,1, 2,4 % horizontal edge at the bottom
1,1,0,0, 1,3 % horizontal edge at the top
1,0,0,1, 1,4 % diagonal edge
0,1,1,0, 2,3 % diagonal edge
1,0,1,1, 1,4 % diagonal edge, extra pixel set
1,1,0,1, 1,4 % diagonal edge, extra pixel set
1,1,1,0, 2,3 % diagonal edge, extra pixel set
0,1,1,1, 2,3 % diagonal edge, extra pixel set
];
% 2x2 patches (matrix form)
P00 = A(1:end-1,1:end-1);
P10 = A(2:end,1:end-1);
P01 = A(1:end-1,2:end);
P11 = A(2:end,2:end);
% edge unique identifier using powers of 2
id = #(p00,p01,p10,p11) 1*p00 + 2*p10 + 4*p01 + 8*p11;
P = id(P00,P01,P10,P11); % vectorized pattern identification
% edges
e0 = []; % from (i,j)
e1 = []; % to (i,j)
for i = 1:size(patterns, 1) % small loop over the 10 patterns
p = patterns(i, :);
E = (P == id(p(1),p(2),p(3),p(4))); % pattern search, vectorized
[c,r] = ind2sub(size(E), find(E));
[c0,r0] = ind2sub([2,2], p(5));
[c1,r1] = ind2sub([2,2], p(6));
e0 = [e0; c+c0, r+r0];
e1 = [e1; c+c1, r+r1];
end
And here the result applying it to your image (I used GIMP for capture, resize and filter, so maybe the image is not exactly the same):
X = [e0(:,2) e1(:,2)];
Y = size(A,1) - [e0(:,1) e1(:,1)];
plot(X', Y', '.-')
I am assuming that obtaining an ordered sequence of edges describing the polygon (or polygons) is not the main problem here once you have the aforementioned set.

Using Image Processing toolbox You can apply dilation on the image and than apply and operator between result of dilation and the original image.
A = imread('bnhfm.png');
B = A & imdilate(~A, true(3));
imshow(B);
imwrite(B, 'result.png');

Related

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)

Calculate the pixel distance to three defined pixel in matlab

I want to classify pixels of one tiff image according to pixel's RGB colour. The input is an image and three predefined colours for water(r0,g0,b0), forest(r1,g1,b1) and building(r2,g2,c2). The classification is based on the distance between image pixel and these three colors. If a pixel is closet to the water, the pixel is water and changed it the water RGB. The distance is calculated as (one sample) sqrt((x-r0)^2+(y-g0)^2+(z0-b0)^2)
The sample implementation is:
a=imread(..);
[row col dim] = size(a); % dim =3
for i=1:row
for j=1:col
dis1=sqrtCal(a(i,j,:)-water)
dis2=sqrtCal(a(i,j,:)-forest)
dis3=sqrtCal(a(i,j,:)-build)
a(i,j,:) = water;
if dis2< dis1
dis1 = dis2
a(i,j,:) = forest
end
dis3=sqrt(a(i,j,:)-build)
if dis3<dis1
a(i,j,:) = build
end
end
end
This implementation should work. The problem is that the two for loops is not a good choice.
So is there any good alternatives in the Matlab? The
D = pdist(X,distance)
Seems not appliable for my case.
I think this does what you want. The number of reference colors is arbitrary (3 in your example). Each reference color is defined as a row of the matrix ref_colors. Let MxN denote the number of pixels in the original image. Thus the original image is an MxNx3 array.
result_index is an MxN array which for each original pixel contains the index of the closest reference color.
result is a MxNx3 array in which each pixel has been assigned the RBG values of the closest reference color.
im = rand(10,12,3); %// example image
ref_colors = [ .1 .1 .8; ... %// water
.3 .9 .2; ... %// forest
.6 .6 .6 ]; %// build: example reference colors
dist = sum(bsxfun(#minus, im, permute(ref_colors, [3 4 2 1])).^2, 3);
%// 4D array containing the distance from each pixel to each reference color.
%// Dimensions are: 1st: pixel row, 2nd: pixel col, 3rd: not used,
%// 4th: index of reference color
[~, result_index] = min(dist, [], 4); %// compute arg min along 4th dimension
result = reshape(ref_colors(result_index,:), size(im,1), size(im,2), 3);
This one is another bsxfun based solution, but stays in 3D and could be more efficient -
%// Concatenate water, forest, build as a 2D array for easy processing
wfb = cat(2,water(:),forest(:),build(:))
%// Calculate square root of squared distances & indices corresponding to min vals
[~,idx] = min(sum(bsxfun(#minus,reshape(a,[],3),permute(wfb,[3 1 2])).^2,2),[],3)
%// Get the output with the minimum from the set of water, forest & build
a_out = reshape(wfb(:,idx).',size(a))
If you have the statistics toolbox installed, you can use this version based on knnsearch, that scales well for a large number of colors.
result_index = knnsearch(ref_colors, reshape(im,[],3));
result = reshape(ref_colors(result_index,:), size(im));

Annular ring pattern with alternate bright and dark rings: using matlab

I am attempting to make a pattern consisting of annular rings with radii proportional to the square root of the natural numbers. Also I want the inner most circle to be white followed by a black circle followed by a white and so on.
c = [0 0; 0 0];
r = [5.2494 9.0922];
viscircles(c, r)
r1 = [7.4328 10.4988];
viscircles(c, r1)
I have generated the above code to form the annular ring structure but I want to fill in the color as well. What should I do?
You could go the mathematical route and plot the function ceil(sin(pi*(X.^2 + Y.^2))):
zoomlevel = 50;
for n = 1:zoomlevel
[X,Y] = ndgrid(linspace(-n,n,500));
I = ceil(sin(pi*(X.^2 + Y.^2)));
imshow(mat2gray(I));
drawnow;
pause(0.03);
end
Of course this will only be a raster graphic instead of a vector one, so don't zoom in too much. ;-) (Although the aliasing artefacts will look quite cool if you zoom out. Plot at your own risk.)
My Matlab version doesn't have viscircles, so here's an approach which plots each individual circle with alternating colors. It uses the rectangle function, which lets you define the curvature of the corners so that the rectangle/square becomes an ellipse/circle. Bigger circles should be drawn first, so that they don't completely cover smaller circles.
colors = [.9 .9 .9; 0 0 0]; %// light gray and black
N = 16; %// maximum number
hold on
for n = N:-1:1; %// bigger circles first
s = sqrt(n);
rectangle('curvature', [1 1], 'position', [-s/2 -s/2 s s], ...
'edgecolor', 'none', 'facecolor', colors(mod(n-1,2)+1,:));
end
axis square
You can also create a "surface" with value 1 for all your r radiuses and 0 for the r1. Then either plot as a surface seen form top, or directly use pcolor.
r = [0 5.2494 7.4328 9.0922 10.4988] ; %// define all your radiuses
bw = mod( 1:numel(r) , 2 ) ; %// create an alternance of 0 and 1 (same size as "r")
ntt = 50 ; %// define how many angular division for the plot
theta = linspace(0,2*pi,ntt) ; %// create all the angular divisions
[rr,tt]=meshgrid(r,theta) ; %// generate a grid
z = repmat( bw , ntt , 1 ) ; %// replicate our [0 1 0 ...] vector to match the grid
[xx,yy,zz] = pol2cart(tt,rr,z) ; %// convert everything to cartesian coordinates
pcolor(xx,yy,zz) %// plot everything
colormap(gray(2)) %// make sure we use only 2 colors (black and white)
shading flat ; axis equal %// refine the view (axis ratio and "spokes" not visible)
You can send as many radiuses as you like in the original r.
This will render:
This method look a bit longer at first than other solution, but you could remove many intermediate steps by consolidating some lines, and if you are to reuse the graphics later on, it may present 2 benefits:
if you get the handle of the graphic object (hp=pcolor(xx,yy,zz)), you only have one graphic object to handle.
if you need to change the color, you do not need to cycle through each circle, just change the colormap to the 2 colors you want (for example if you want "red" and "green", just call the colormap colormap([1 0 0;0 1 0]) and you're done.
viscircles returns an hggroup object. One of the properties of such an object is its Children, which is an array of handles to the graphics objects it creates. For instance you could write
h1 = viscircles(c, r)
c1 = h1.Children
The children here should just be the handles to the circular patches defined by viscircles. Now, to set the color of the ith circular patch, you can set the FaceColor property of the handle c1(i).

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.

Find the corners of a polygon represented by a region mask

BW = poly2mask(x, y, m, n) computes a
binary region of interest (ROI) mask,
BW, from an ROI polygon, represented
by the vectors x and y. The size of BW
is m-by-n.
poly2mask sets pixels in BW
that are inside the polygon (X,Y) to 1
and sets pixels outside the polygon to
0.
Problem:
Given such a binary mask BW of a convex quadrilateral, what would be the most efficient way to determine the four corners?
E.g.,
Best Solution so far:
Use edge to find the bounding lines, the Hough transform to find the 4 lines in the edge image and then find the intersection points of those 4 lines or use a corner detector on the edge image. Seems complicated, and I can't help feeling there's a simpler solution out there.
Btw, convhull doesn't always return 4 points (maybe someone can suggest qhull options to prevent that) : it returns a few points along the edges as well.
EDIT:
Amro's answer seems quite elegant and efficient. But there could be multiple "corners" at each real corner since the peaks aren't unique. I could cluster them based on θ and average the "corners" around a real corner but the main problem is the use of order(1:10).
Is 10 enough to account for all the corners or will this exclude a "corner" at a real corner?
This is somewhat similar to what #AndyL suggested. However I'm using the boundary signature in polar coordinates instead of the tangent.
Note that I start by extracting the edges, getting the boundary, then converting it to signature. Finally we find the points on the boundary that are furthest from the centroid, those points constitute the corners found. (Alternatively we can also detect peaks in the signature for corners).
The following is a complete implementation:
I = imread('oxyjj.png');
if ndims(I)==3
I = rgb2gray(I);
end
subplot(221), imshow(I), title('org')
%%# Process Image
%# edge detection
BW = edge(I, 'sobel');
subplot(222), imshow(BW), title('edge')
%# dilation-erosion
se = strel('disk', 2);
BW = imdilate(BW,se);
BW = imerode(BW,se);
subplot(223), imshow(BW), title('dilation-erosion')
%# fill holes
BW = imfill(BW, 'holes');
subplot(224), imshow(BW), title('fill')
%# get boundary
B = bwboundaries(BW, 8, 'noholes');
B = B{1};
%%# boudary signature
%# convert boundary from cartesian to ploar coordinates
objB = bsxfun(#minus, B, mean(B));
[theta, rho] = cart2pol(objB(:,2), objB(:,1));
%# find corners
%#corners = find( diff(diff(rho)>0) < 0 ); %# find peaks
[~,order] = sort(rho, 'descend');
corners = order(1:10);
%# plot boundary signature + corners
figure, plot(theta, rho, '.'), hold on
plot(theta(corners), rho(corners), 'ro'), hold off
xlim([-pi pi]), title('Boundary Signature'), xlabel('\theta'), ylabel('\rho')
%# plot image + corners
figure, imshow(BW), hold on
plot(B(corners,2), B(corners,1), 's', 'MarkerSize',10, 'MarkerFaceColor','r')
hold off, title('Corners')
EDIT:
In response to Jacob's comment, I should explain that I first tried to find the peaks in the signature using first/second derivatives, but ended up taking the furthest N-points. 10 was just an ad-hoc value, and would be difficult to generalize (I tried taking 4 same as number of corners, but it didn't cover all of them). I think the idea of clustering them to remove duplicates is worth looking into.
As far as I see it, the problem with the 1st approach was that if you plot rho without taking θ into account, you will get a different shape (not the same peaks), since the speed by which we trace the boundary is different and depends on the curvature. If we could figure out how to normalize that effect, we can get more accurate results using derivatives.
If you have the Image Processing Toolbox, there is a function called cornermetric which can implement a Harris corner detector or Shi and Tomasi's minimum eigenvalue method. This function has been present since version 6.2 of the Image Processing Toolbox (MATLAB version R2008b).
Using this function, I came up with a slightly different approach from the other answers. The solution below is based on the idea that a circular area centered at each "true" corner point will overlap the polygon by a smaller amount than a circular area centered over an erroneous corner point that is actually on the edge. This solution can also handle cases where multiple points are detected at the same corner...
The first step is to load the data:
rawImage = imread('oxyjj.png');
rawImage = rgb2gray(rawImage(7:473, 9:688, :)); % Remove the gray border
subplot(2, 2, 1);
imshow(rawImage);
title('Raw image');
Next, compute the corner metric using cornermetric. Note that I am masking the corner metric by the original polygon, so that we are looking for corner points that are inside the polygon (i.e. trying to find the corner pixels of the polygon). imregionalmax is then used to find the local maxima. Since you can have clusters of greater than 1 pixel with the same corner metric, I then add noise to the maxima and recompute so that I only get 1 pixel in each maximal region. Each maximal region is then labeled using bwlabel:
cornerImage = cornermetric(rawImage).*(rawImage > 0);
maxImage = imregionalmax(cornerImage);
noise = rand(nnz(maxImage), 1);
cornerImage(maxImage) = cornerImage(maxImage)+noise;
maxImage = imregionalmax(cornerImage);
labeledImage = bwlabel(maxImage);
The labeled regions are then dilated (using imdilate) with a disk-shaped structuring element (created using strel):
diskSize = 5;
dilatedImage = imdilate(labeledImage, strel('disk', diskSize));
subplot(2, 2, 2);
imshow(dilatedImage);
title('Dilated corner points');
Now that the labeled corner regions have been dilated, they will partially overlap the original polygon. Regions on an edge of the polygon will have about 50% overlap, while regions that are on a corner will have about 25% overlap. The function regionprops can be used to find the areas of overlap for each labeled region, and the 4 regions that have the least amount of overlap can thus be considered as the true corners:
maskImage = dilatedImage.*(rawImage > 0); % Overlap with the polygon
stats = regionprops(maskImage, 'Area'); % Compute the areas
[sortedValues, index] = sort([stats.Area]); % Sort in ascending order
cornerLabels = index(1:4); % The 4 smallest region labels
maskImage = ismember(maskImage, cornerLabels); % Mask of the 4 smallest regions
subplot(2, 2, 3);
imshow(maskImage);
title('Regions of minimal overlap');
And we can now get the pixel coordinates of the corners using find and ismember:
[r, c] = find(ismember(labeledImage, cornerLabels));
subplot(2, 2, 4);
imshow(rawImage);
hold on;
plot(c, r, 'r+', 'MarkerSize', 16, 'LineWidth', 2);
title('Corner points');
And here's a test with a diamond shaped region:
I like to solve this problem by working with a boundary, because it reduces this from a 2D problem to a 1D problem.
Use bwtraceboundary() from the image processing toolkit to extract a list of points on the boundary. Then convert the boundary into a series of tangent vectors (there are a number of ways to do this, one way would be to subrtact the
ith point along the boundary from the i+deltath point.) Once you have a list of vectors, take the dot product of adjacent vectors. The four points with the smallest dot products are your corners!
If you want your algorithm to work on polygons with an abritrary number of vertices, then simply search for dot products that are a certain number of standard deviations below the median dot product.
I decided to use a Harris corner detector (here's a more formal description) to obtain the corners. This can be implemented as follows:
%% Constants
Window = 3;
Sigma = 2;
K = 0.05;
nCorners = 4;
%% Derivative masks
dx = [-1 0 1; -1 0 1; -1 0 1];
dy = dx'; %SO code color fix '
%% Find the image gradient
% Mask is the binary image of the quadrilateral
Ix = conv2(double(Mask),dx,'same');
Iy = conv2(double(Mask),dy,'same');
%% Use a gaussian windowing function and compute the rest
Gaussian = fspecial('gaussian',Window,Sigma);
Ix2 = conv2(Ix.^2, Gaussian, 'same');
Iy2 = conv2(Iy.^2, Gaussian, 'same');
Ixy = conv2(Ix.*Iy, Gaussian, 'same');
%% Find the corners
CornerStrength = (Ix2.*Iy2 - Ixy.^2) - K*(Ix2 + Iy2).^2;
[val ind] = sort(CornerStrength(:),'descend');
[Ci Cj] = ind2sub(size(CornerStrength),ind(1:nCorners));
%% Display
imshow(Mask,[]);
hold on;
plot(Cj,Ci,'r*');
Here, the problem with multiple corners thanks to Gaussian windowing function which smooths the intensity change. Below, is a zoomed version of a corner with the hot colormap.
Here's an example using Ruby and HornetsEye. Basically the program creates a histogram of the quantised Sobel gradient orientation to find dominant orientations. If four dominant orientations are found, lines are fitted and the intersections between neighbouring lines are assumed to be the corners of the projected rectangle.
#!/usr/bin/env ruby
require 'hornetseye'
include Hornetseye
Q = 36
img = MultiArray.load_ubyte 'http://imgur.com/oxyjj.png'
dx, dy = 8, 6
box = [ dx ... 688, dy ... 473 ]
crop = img[ *box ]
crop.show
s0, s1 = crop.sobel( 0 ), crop.sobel( 1 )
mag = Math.sqrt s0 ** 2 + s1 ** 2
mag.normalise.show
arg = Math.atan2 s1, s0
msk = mag >= 500
arg_q = ( ( arg.mask( msk ) / Math::PI + 1 ) * Q / 2 ).to_int % Q
hist = arg_q.hist_weighted Q, mag.mask( msk )
segments = ( hist >= hist.max / 4 ).components
lines = arg_q.map segments
lines.unmask( msk ).normalise.show
if segments.max == 4
pos = MultiArray.scomplex *crop.shape
pos.real = MultiArray.int( *crop.shape ).indgen! % crop.shape[0]
pos.imag = MultiArray.int( *crop.shape ).indgen! / crop.shape[0]
weights = lines.hist( 5 ).major 1.0
centre = lines.hist_weighted( 5, pos.mask( msk ) ) / weights
vector = pos.mask( msk ) - lines.map( centre )
orientation = lines.hist_weighted( 5, vector ** 2 ) ** 0.5
corner = Sequence[ *( 0 ... 4 ).collect do |i|
i1, i2 = i + 1, ( i + 1 ) % 4 + 1
l1, a1, l2, a2 = centre[i1], orientation[i1], centre[i2], orientation[i2]
( l1 * a1.conj * a2 - l2 * a1 * a2.conj -
l1.conj * a1 * a2 + l2.conj * a1 * a2 ) /
( a1.conj * a2 - a1 * a2.conj )
end ]
result = MultiArray.ubytergb( *img.shape ).fill! 128
result[ *box ] = crop
corner.to_a.each do |c|
result[ c.real.to_i + dx - 1 .. c.real.to_i + dx + 1,
c.imag.to_i + dy - 1 .. c.imag.to_i + dy + 1 ] = RGB 255, 0, 0
end
result.show
end