How can I draw a circle on an image in MATLAB? - image

I have an image in MATLAB:
im = rgb2gray(imread('some_image.jpg');
% normalize the image to be between 0 and 1
im = im/max(max(im));
And I've done some processing that resulted in a number of points that I want to highlight:
points = some_processing(im);
Where points is a matrix the same size as im with ones in the interesting points.
Now I want to draw a circle on the image in all the places where points is 1.
Is there any function in MATLAB that does this? The best I can come up with is:
[x_p, y_p] = find (points);
[x, y] = meshgrid(1:size(im,1), 1:size(im,2))
r = 5;
circles = zeros(size(im));
for k = 1:length(x_p)
circles = circles + (floor((x - x_p(k)).^2 + (y - y_p(k)).^2) == r);
end
% normalize circles
circles = circles/max(max(circles));
output = im + circles;
imshow(output)
This seems more than somewhat inelegant. Is there a way to draw circles similar to the line function?

You could use the normal PLOT command with a circular marker point:
[x_p,y_p] = find(points);
imshow(im); %# Display your image
hold on; %# Add subsequent plots to the image
plot(y_p,x_p,'o'); %# NOTE: x_p and y_p are switched (see note below)!
hold off; %# Any subsequent plotting will overwrite the image!
You can also adjust these other properties of the plot marker: MarkerEdgeColor, MarkerFaceColor, MarkerSize.
If you then want to save the new image with the markers plotted on it, you can look at this answer I gave to a question about maintaining image dimensions when saving images from figures.
NOTE: When plotting image data with IMSHOW (or IMAGE, etc.), the normal interpretation of rows and columns essentially becomes flipped. Normally the first dimension of data (i.e. rows) is thought of as the data that would lie on the x-axis, and is probably why you use x_p as the first set of values returned by the FIND function. However, IMSHOW displays the first dimension of the image data along the y-axis, so the first value returned by FIND ends up being the y-coordinate value in this case.

This file by Zhenhai Wang from Matlab Central's File Exchange does the trick.
%----------------------------------------------------------------
% H=CIRCLE(CENTER,RADIUS,NOP,STYLE)
% This routine draws a circle with center defined as
% a vector CENTER, radius as a scaler RADIS. NOP is
% the number of points on the circle. As to STYLE,
% use it the same way as you use the rountine PLOT.
% Since the handle of the object is returned, you
% use routine SET to get the best result.
%
% Usage Examples,
%
% circle([1,3],3,1000,':');
% circle([2,4],2,1000,'--');
%
% Zhenhai Wang <zhenhai#ieee.org>
% Version 1.00
% December, 2002
%----------------------------------------------------------------

Funny! There are 6 answers here, none give the obvious solution: the rectangle function.
From the documentation:
Draw a circle by setting the Curvature property to [1 1]. Draw the circle so that it fills the rectangular area between the points (2,4) and (4,6). The Position property defines the smallest rectangle that contains the circle.
pos = [2 4 2 2];
rectangle('Position',pos,'Curvature',[1 1])
axis equal
So in your case:
imshow(im)
hold on
[y, x] = find(points);
for ii=1:length(x)
pos = [x(ii),y(ii)];
pos = [pos-0.5,1,1];
rectangle('position',pos,'curvature',[1 1])
end
As opposed to the accepted answer, these circles will scale with the image, you can zoom in an they will always mark the whole pixel.

Hmm I had to re-switch them in this call:
k = convhull(x,y);
figure;
imshow(image); %# Display your image
hold on; %# Add subsequent plots to the image
plot(x,y,'o'); %# NOTE: x_p and y_p are switched (see note below)!
hold off; %# Any subsequent plotting will overwrite the image!
In reply to the comments:
x and y are created using the following code:
temp_hull = stats_single_object(k).ConvexHull;
for k2 = 1:length(temp_hull)
i = i+1;
[x(i,1)] = temp_hull(k2,1);
[y(i,1)] = temp_hull(k2,2);
end;
it might be that the ConvexHull is the other way around and therefore the plot is different. Or that I made a mistake and it should be
[x(i,1)] = temp_hull(k2,2);
[y(i,1)] = temp_hull(k2,1);
However the documentation is not clear about which colum = x OR y:
Quote: "Each row of the matrix contains the x- and y-coordinates of one vertex of the polygon. "
I read this as x is the first column and y is the second colum.

In newer versions of MATLAB (I have 2013b) the Computer Vision System Toolbox contains the vision.ShapeInserter System object which can be used to draw shapes on images. Here is an example of drawing yellow circles from the documentation:
yellow = uint8([255 255 0]); %// [R G B]; class of yellow must match class of I
shapeInserter = vision.ShapeInserter('Shape','Circles','BorderColor','Custom','CustomBorderColor',yellow);
I = imread('cameraman.tif');
circles = int32([30 30 20; 80 80 25]); %// [x1 y1 radius1;x2 y2 radius2]
RGB = repmat(I,[1,1,3]); %// convert I to an RGB image
J = step(shapeInserter, RGB, circles);
imshow(J);

With MATLAB and Image Processing Toolbox R2012a or newer, you can use the viscircles function to easily overlay circles over an image. Here is an example:
% Plot 5 circles at random locations
X = rand(5,1);
Y = rand(5,1);
% Keep the radius 0.1 for all of them
R = 0.1*ones(5,1);
% Make them blue
viscircles([X,Y],R,'EdgeColor','b');
Also, check out the imfindcircles function which implements the Hough circular transform. The online documentation for both functions (links above) have examples that show how to find circles in an image and how to display the detected circles over the image.
For example:
% Read the image into the workspace and display it.
A = imread('coins.png');
imshow(A)
% Find all the circles with radius r such that 15 ≤ r ≤ 30.
[centers, radii, metric] = imfindcircles(A,[15 30]);
% Retain the five strongest circles according to the metric values.
centersStrong5 = centers(1:5,:);
radiiStrong5 = radii(1:5);
metricStrong5 = metric(1:5);
% Draw the five strongest circle perimeters.
viscircles(centersStrong5, radiiStrong5,'EdgeColor','b');

Here's the method I think you need:
[x_p, y_p] = find (points);
% convert the subscripts to indicies, but transposed into a row vector
a = sub2ind(size(im), x_p, y_p)';
% assign all the values in the image that correspond to the points to a value of zero
im([a]) = 0;
% show the new image
imshow(im)

Related

How to get actual pixel indices from MATLAB function ginput?

I am using the MATLAB function ginput to label my image data for further process. Here is my code:
file_name = "test.jpg";
% Read the image
img = imread(file_name);
% Get the image dimension
imgInfo = imfinfo(file_name);
width = imgInfo.Width;
height = imgInfo.Height;
% Using ginput function to label the image
figure(1);
imshow(img);
hold on;
[x, y] = ginput(4); % Manually label 4 points
scatter(x, y, 100, 'ro', 'filled'); % Plot the marked points on img
hold off;
My Problem:
I found that the output x and yare not integers, so they are not representing the pixel indices.
Sometimes, these two conditions max(x) > width and max(y) > height are satisfied. It seems to suggest that the 4 points I marked using ginput are outside the image (but actually it is not).
I am aware of this issue is related to Image Coordinate System setting, but I am still not sure how to convert x and y obtained from ginput function to the actual pixel indices?
Thanks.
The code below shows a 2x2 image, enlarges the axes so we can see it, then turns on the axes ticks and labels. What this does is allow you to see the coordinate system used to render images in an axes object.
imshow([255,0;0,255])
set(gca,'position',[0.2,0.2,0.6,0.6])
axis on
The coordinates returned by ginput match these coordinates.
In short, what you need to do is simply round the coordinates returned by ginput to get indices into the image:
[x, y] = ginput(4); % Manually label 4 points
x = round(x);
y = round(y);

Merging 2 images by showing one next to the other separated by a diagonal line

I have 2 images ("before" and "after"). I would like to show a final image where the left half is taken from the before image and the right half is taken from the after image.
The images should be separated by a white diagonal line of predefined width (2 or 3 pixels), where the diagonal is specified either by a certain angle or by 2 start and end coordinates. The diagonal should overwrite a part of the final image such that the size is the same as the sources'.
Example:
I know it can be done by looping over all pixels to recombine and create the final image, but is there an efficient way, or better yet, a built-in function that can do this?
Unfortunately I don't believe there is a built-in solution to your problem, but I've developed some code to help you do this but it will unfortunately require the image processing toolbox to play nicely with the code. As mentioned in your comments, you have this already so we should be fine.
The logic behind this is relatively simple. We will assume that your before and after pictures are the same size and also share the same number of channels. The first part is to declare a blank image and we draw a straight line down the middle of a certain thickness. The intricacy behind this is to declare an image that is slightly bigger than the original size of the image. The reason why is because I'm going to draw a line down the middle, then rotate this blank image by a certain angle to achieve the first part of what you desire. I'll be using imrotate to rotate an image by any angle you desire. The first instinct is to declare an image that's the same size as either the originals, draw a line down the middle and rotate it. However, if you do this you'll end up with the line being disconnected and not draw from the top to the bottom of the image. That makes sense because the line being drawn on an angle covers more pixels than if you were to draw this vertically.
Using Pythagorean's theorem, we know that the longest line that can ever be drawn on your image is the diagonal. Therefore we declare an image that is sqrt(rows*rows + cols*cols) in both the rows and columns where rows and cols are the rows and columns of the original image. After, we'll take the ceiling to make sure we've covered as much as possible and we add a bit of extra room to accommodate for the width of the line. We draw a line on this image, rotate it then we'll crop the image after so that it's the same size as the input images. This ensures that the line drawn at whatever angle you wish is fully drawn from top to bottom.
That logic is the hardest part. Once you do that, you declare two logical masks where you use imfill to fill the left side of the mask as one mask and we'll invert the mask to find the other mask. You will also need to use the line image that we created earlier with imrotate to index into the masks and set the values to false so that we ignore these pixels that are on the line.
Finally, you take each mask, index into your image and copy over each portion of the image you desire. You finally use the line image to index into the output and set the values to white.
Without further ado, here's the code:
% Load some example data
load mandrill;
% im is the image before
% im2 is the image after
% Before image is a colour image
im = im2uint8(ind2rgb(X, map));
% After image is a grayscale image
im2 = rgb2gray(im);
im2 = cat(3, im2, im2, im2);
% Declare line image
rows = size(im, 1); cols = size(im, 2);
width = 5;
m = ceil(sqrt(rows*rows + cols*cols + width*width));
ln = false([m m]);
mhalf = floor(m / 2); % Find halfway point width wise and draw the line
ln(:,mhalf - floor(width/2) : mhalf + floor(width/2)) = true;
% Rotate the line image
ang = 20; % 20 degrees
lnrotate = imrotate(ln, ang, 'crop');
% Crop the image so that it's the same dimensions as the originals
mrowstart = mhalf - floor(rows/2);
mcolstart = mhalf - floor(cols/2);
lnfinal = lnrotate(mrowstart : mrowstart + rows - 1, mcolstart : mcolstart + cols - 1);
% Make the masks
mask1 = imfill(lnfinal, [1 1]);
mask2 = ~mask1;
mask1(lnfinal) = false;
mask2(lnfinal) = false;
% Make sure the masks have as many channels as the original
mask1 = repmat(mask1, [1 1 size(im,3)]);
mask2 = repmat(mask2, [1 1 size(im,3)]);
% Do the same for the line
lnfinal = repmat(lnfinal, [1 1 size(im, 3)]);
% Specify output image
out = zeros(size(im), class(im));
out(mask1) = im(mask1);
out(mask2) = im2(mask2);
out(lnfinal) = 255;
% Show the image
figure;
imshow(out);
We get:
If you want the line to go in the other direction, simply make the angle ang negative. In the example script above, I've made the angle 20 degrees counter-clockwise (i.e. positive). To reproduce the example you gave, specify -20 degrees instead. I now get this image:
Here's a solution using polygons:
function q44310306
% Load some image:
I = imread('peppers.png');
B = rgb2gray(I);
lt = I; rt = B;
% Specify the boundaries of the white line:
width = 2; % [px]
offset = 13; % [px]
sz = size(I);
wlb = [floor(sz(2)/2)-offset+[0,width]; ceil(sz(2)/2)+offset-[width,0]];
% [top-left, top-right; bottom-left, bottom-right]
% Configure two polygons:
leftPoly = struct('x',[1 wlb(1,2) wlb(2,2) 1], 'y',[1 1 sz(1) sz(1)]);
rightPoly = struct('x',[sz(2) wlb(1,1) wlb(2,1) sz(2)],'y',[1 1 sz(1) sz(1)]);
% Define a helper grid:
[XX,YY] = meshgrid(1:sz(2),1:sz(1));
rt(inpolygon(XX,YY,leftPoly.x,leftPoly.y)) = intmin('uint8');
lt(repmat(inpolygon(XX,YY,rightPoly.x,rightPoly.y),1,1,3)) = intmin('uint8');
rt(inpolygon(XX,YY,leftPoly.x,leftPoly.y) & ...
inpolygon(XX,YY,rightPoly.x,rightPoly.y)) = intmax('uint8');
final = bsxfun(#plus,lt,rt);
% Plot:
figure(); imshow(final);
The result:
One solution:
im1 = imread('peppers.png');
im2 = repmat(rgb2gray(im1),1,1,3);
imgsplitter(im1,im2,80) %imgsplitter(image1,image2,angle [0-100])
function imgsplitter(im1,im2,p)
s1 = size(im1,1); s2 = size(im1,2);
pix = floor(p*size(im1,2)/100);
val = abs(pix -(s2-pix));
dia = imresize(tril(ones(s1)),[s1 val]);
len = min(abs([0-pix,s2-pix]));
if p>50
ind = [ones(s1,len) fliplr(~dia) zeros(s1,len)];
else
ind = [ones(s1,len) dia zeros(s1,len)];
end
ind = uint8(ind);
imshow(ind.*im1+uint8(~ind).*im2)
hold on
plot([pix,s2-pix],[0,s1],'w','LineWidth',1)
end
OUTPUT:

Image Repetition from Binary to Cartesian

I'd like to take in an RGB image, find the points in the image that are white, and get the cartesian coordinates of those points in the image. I've gotten most of the way there, but when I try to plot the cartesian coordinates, I get a vertically tiled image (i.e. 5 overlapped copies of what I should see). Anyone know what could be causing this?
,
Code: (JPG comes in as 2448 x x3264 x 3 uint8)
I = imread('IMG_0245.JPG');
imshow(I); % display unaltered image
% Convert image to grayscale
I = rgb2gray(I);
% Convert image to binary (black/white)
I = im2bw(I, 0.9);
% Generate cartesian coordinates of image
imageSize = size(I);
[x, y] = meshgrid( 1:imageSize(1), 1:imageSize(2) );
PerspectiveImage = [x(:), y(:), I(:)];
% Get indices of white points only
whiteIndices = find(PerspectiveImage(:,3));
figure; plot( PerspectiveImage(whiteIndices, 1), PerspectiveImage(whiteIndices, 2),'.');
% Flip vertically to correct indexing vs. plotting issue
axis ij
Very simple. You're declaring your meshgrid wrong. It should be:
[x, y] = meshgrid( 1:imageSize(2), 1:imageSize(1) );
The first parameter denotes the horizontal extents of the 2D grid, and so you want to make this vary for as many columns as you have. Similarly, the second parameter denotes the vertical extents of the 2D grid, and so you want to make this for as many rows as you have.
I had to pre-process some of your image to get some good results because your original image had a large white border surrounding the image. I had to remove this border by removing all pure white pixels. I also read in the image directly from StackOverflow:
I = imread('http://s7.postimg.org/ovb53w4ff/Track_example.jpg');
mask = all(I == 255, 3);
I = bsxfun(#times, I, uint8(~mask));
This was the image I get after doing my pre-processing:
Once I do this and change your meshgrid call, I get this:

MATLAB How to convert axis coordinates to pixel coordinates?

What is the preferred way of converting from axis coordinates (e.g. those taken in by plot or those output in point1 and point2 of houghlines) to pixel coordinates in an image?
I see the function axes2pix in the Mathworks documentation, but it is unclear how it works. Specifically, what is the third argument? The examples just pass in 30, but it is unclear where this value comes from. The explanations depend on a knowledge of several other functions, which I don't know.
The related question: Axis coordinates to pixel coordinates? suggests using poly2mask, which would work for a polygon, but how do I do the same thing for a single point, or a list of points?
That question also links to Scripts to Convert Image to and from Graph Coordinates, but that code threw an exception:
Error using /
Matrix dimensions must agree.
Consider the following code. It shows how to convert from axes coordinates to image pixel coordinates.
This is especially useful if you plot the image using custom XData/YData locations other than the default 1:width and 1:height. I am shifting by 100 and 200 pixels in the x/y directions in the example below.
function imageExample()
%# RGB image
img = imread('peppers.png');
sz = size(img);
%# show image
hFig = figure();
hAx = axes();
image([1 sz(2)]+100, [1 sz(1)]+200, img) %# shifted XData/YData
%# hook-up mouse button-down event
set(hFig, 'WindowButtonDownFcn',#mouseDown)
function mouseDown(o,e)
%# get current point
p = get(hAx,'CurrentPoint');
p = p(1,1:2);
%# convert axes coordinates to image pixel coordinates
%# I am also rounding to integers
x = round( axes2pix(sz(2), [1 sz(2)], p(1)) );
y = round( axes2pix(sz(1), [1 sz(1)], p(2)) );
%# show (x,y) pixel in title
title( sprintf('image pixel = (%d,%d)',x,y) )
end
end
(note how the axis limits do not start at (1,1), thus the need for axes2pix)
There may be a built-in way that I haven't heard of, but this shouldn't be hard to do from scratch...
set(axes_handle,'units','pixels');
pos = get(axes_handle,'position');
xlim = get(axes_handle,'xlim');
ylim = get(axes_handle,'ylim');
Using these values, you can convert from axes coordinates to pixels easily.
x_in_pixels = pos(1) + pos(3) * (x_in_axes-xlim(1))/(xlim(2)-xlim(1));
%# etc...
The above uses pos(1) as the x-offset of the axes within the figure. If you don't care about this, don't use it. Likewise, if you want it in screen coordinates, add the x-offset of the position obtained by get(figure_handle,'position')

Matlab: how to avoid ellipses overlapping in image?

I've been using a function file [ret]=drawellipse(x,y,a,b,angle,steps,color,img). Calling the function through a script file to draw random ellipses in image. But once i set the random center point(x,y), and random a, b, there is high possibility that the ellipses intersection would occur. How can i prevent the intersection? (I'm supposed to draw the ellipses that are all separate from each other)
Well, over here i have a function file which is to check whether the ellipses got overlap or not,overlap = overlap_ellipses(x0,y0,a0,b0,angle0,x1,y1,a1,b1,angle1). If the two ellipses are overlap, then the 'overlap=1', otherwise 'overlap=0'.
Based on all these, i tested in the command window:
x=rand(4,1)*400; % x and y are the random coodinates for the center of ellipses
y=rand(4,1)*400;
a=[50 69 30 60]; % major axis for a and b, i intend to use random also in the future
b=[20 40 10 40]; % minor axis
angle=[30 90 45 0]; % angle of ellipse
steps=10000;
color=[255 0 0]; % inputs for another function file to draw the ellipse
img=zeros(500,500,3);
The following i want to dispaly the ellipses if overlap==0, and 'if overlap==1', decrease the a and b, till there is no intersection. Lastly, to imshow the img.
for i=1:length(x)
img=drawellipse(x(i),y(i),a(i),b(i),angle(i),steps,color,img);
end
For me now, i have difficulty in coding the middle part. How can i use the if statement to get the value of overlap and how to make the index corresponding to the ellipse i need to draw.
i tested a bit like
for k=1:(length(x)-1)
overlap = overlap_ellipses(x(1),y(1),a(1),b(1),angle(1),x(1+k),y(1+k),a(1+k),b(1+k),angle(1+k))
end
it returns
overlap=0
overlap=0
overlap=1
it is not [0 0 1]. I can't figure it out, thus stuck in the process.
The final image shoule look like the picture in this voronoi diagram of ellipses.
(There is no intersection between any two ellipses)
Assuming you are drawing the ellipses into a raster graphics image, you could calculate the pixels you would have to draw for an ellipse, check whether these pixels in the image are still of the background color, and draw the ellipse only if the answer is yes, otherwise reject it (because something else, i.e. another ellipse, is in the way) and try other x,y,a and b.
Alternatively, you could split your image into rectangles (not neccessarily of equal size) and place one ellipse in each of those, picking x,y,a,b such that no ellipse exceeds its rectangle - then the ellipses cannot overlap either, but it depends on how much "randomness" your ellipse placing should have whether this suffices.
The mathematically rigorous way would be to store x,y,a,b of each drawn ellipse and for each new ellipse, do pairwise checks with each of those whether they have common points by solving a system of two quadratic equations. However, this might be a bit complicated, especially once the angle is not 0.
Edit in response to the added code: Instead of fixing all x's and y's before the loop, you can determine them inside the loop. Since you know how many ellipses you want, but not how many you have to sample, you need a while loop. The test loop you give may come in handy, but you need to compare all previous ellipses to the one created in the loop iteration, not the first one.
i=1;
while (i<=4) %# or length(a), or, more elegantly, some pre-defined max
x(i) = rand*400; y(i) = rand*400; %# or take x and y as givren and decrease a and b
%# now, check overlap for given center
overlap = false;
for k=1:(i-1)
overlap = overlap || overlap_ellipses(x(i),y(i),a(i),b(i),angle(i),x(k),y(k),a(k),b(k),angle(k))
end
if (~overlap)
img = drawellipse(x(i),y(i),a(i),b(i),angle(i),steps,color,img);
i = i+1; %# determine next ellipse
end %# else x(i) and y(i) will be overwritten in next while loop iteration
end
Of course, if a and b are fixed, it may happen that no ellipse fits the image dimensions if the already present ones are unfortunately placed, resulting in an infinite loop.
Regarding your plan of leaving the center fixed and decreasing the ellipse's size until it fits: where does your overlap_ellipses method come from? Maybe itcan be adapted to return a factor by which one ellipse needs to be shrinked to fit next to the other (and 1 if it fits already)?
The solution proposed by #arne.b (the first one) is a good way to rasterize non-overlapping ellipses.
Let me illustrate that idea with an example. I will be extending my previous answer:
%# color image
I = imread('pears.png');
sz = size(I);
%# parameters of ellipses
num = 7;
h = zeros(1,num);
clr = lines(num); %# color of each ellipse
x = rand(num,1) .* sz(2); %# center x-coords
y = rand(num,1) .* sz(1); %# center y-coords
a = rand(num,1) .* 200; %# major axis length
b = rand(num,1) .* 200; %# minor axis length
angle = rand(num,1) .* 360; %# angle of rotation
%# label image, used to hold rasterized ellipses
BW = zeros(sz(1),sz(2));
%# randomly place ellipses one-at-a-time, skip if overlaps previous ones
figure, imshow(I)
axis on, hold on
for i=1:num
%# ellipse we would like to draw directly on image matrix
[ex,ey] = calculateEllipse(x(i),y(i), a(i),b(i), angle(i), 100);
%# lets plot the ellipse (overlayed)
h(i) = plot(ex,ey, 'LineWidth',2, 'Color',clr(i,:));
%# create mask for image pixels inside the ellipse polygon
mask = poly2mask(ex,ey,sz(1),sz(2));
%# get the perimter of this mask
mask = bwperim(mask,8);
%# skip if there is an existing overlapping ellipse
if any( BW(mask)~=0 ), continue, end
%# use the mask to place the ellipse in the label image
BW(mask) = i;
end
hold off
legend(h, cellstr(num2str((1:num)','Line%d')), 'Location','BestOutside') %'
%# set pixels corresponding to ellipses using specified colors
clr = im2uint8(clr);
II = I;
for i=1:num
BW_ind = bsxfun(#plus, find(BW==i), prod(sz(1:2)).*(0:2));
II(BW_ind) = repmat(clr(i,:), [size(BW_ind,1) 1]);
end
figure, imshow(II, 'InitialMagnification',100, 'Border','tight')
Note how the overlap test is performed in the order the ellipses are added, thus after Line1 (blue) and Line2 (green) are drawn, Line3 (red) will be skipped because it overlaps one of the previous ones, and so on for the rest...
One option is to keep track of all the ellipses already drawn, and to make sure the next set of [x,y,a,b] does not produce a new ellipse which intersects with the existing ones. You can either invoke random numbers until you come up with a set that fulfills the condition, or once you have a set which violates the condition, decrease the values of a and/or b until no intersection occurs.

Resources