How to draw a filled circle on a video frame using matlab - matlab

I have an "inverted-pendulum" video which I try to find the mid point of moving part. I am using Computer Vision Toolbox
I change the mid point's color using detected coordinates. Assume that X is the frame's row number for the detected mid point and the Y is the col number.
while ~isDone(hVideoFileReader)
frame = step(hVideoFileReader);
...
frame(X-3:X+3, Y-3:Y+3, 1) = 1; % # R=1 make the defined region red
frame(X-3:X+3, Y-3:Y+3, 2) = 0; % # G=0
frame(X-3:X+3, Y-3:Y+3, 3) = 0; % # B=0
step(hVideoPlayer, frame);
end
Then I easily have a red square. But I want to add a red filled circle on the detected point, instead of a square. How can I do that?

You can use the insertShape function. Example:
img = imread('peppers.png');
img = insertShape(img, 'FilledCircle', [150 280 35], ...
'LineWidth',5, 'Color','blue');
imshow(img)
The position parameter is specified as [x y radius]
EDIT:
Here is an alternative where we manually draw the circular shape (with transparency):
% some RGB image
img = imread('peppers.png');
[imgH,imgW,~] = size(img);
% circle parameters
r = 35; % radius
c = [150 280]; % center
t = linspace(0, 2*pi, 50); % approximate circle with 50 points
% create a circular mask
BW = poly2mask(r*cos(t)+c(1), r*sin(t)+c(2), imgH, imgW);
% overlay filled circular shape by using the mask
% to fill the image with the desired color (for all three channels R,G,B)
clr = [0 0 255]; % blue color
a = 0.5; % blending factor
z = false(size(BW));
mask = cat(3,BW,z,z); img(mask) = a*clr(1) + (1-a)*img(mask);
mask = cat(3,z,BW,z); img(mask) = a*clr(2) + (1-a)*img(mask);
mask = cat(3,z,z,BW); img(mask) = a*clr(3) + (1-a)*img(mask);
% show result
imshow(img)
I'm using the poly2mask function from Image Processing Toolbox to create the circle mask (idea from this post). If you don't have access to this function, here is an alternative:
[X,Y] = ndgrid((1:imgH)-c(2), (1:imgW)-c(1));
BW = (X.^2 + Y.^2) < r^2;
That way you get a solution using core MATLAB functions only (no toolboxes!)

If you have an older version of MATLAB with the Computer Vision System Toolbox installed, you can use vision.ShapeInserter system object.

Thanks #Dima, I have created a shapeInserter object.
greenColor = uint8([0 255 0]);
hFilledCircle = vision.ShapeInserter('Shape','Circles',...
'BorderColor','Custom',...
'CustomBorderColor', greenColor ,...
'Fill', true, ...
'FillColor', 'Custom',...
'CustomFillColor', greenColor );
...
fc = int32([Y X 7;]);
frame = step(hFilledCircle, frame, fc);
I then applied it to detected point.

Related

Area between circular curves

I'm trying to calculate the surface between two circular curves (yellow surface in this picture as simplification) but I'm somehow stuck since I don't have datapoints at the same angular values of the two curves. Any ideas?
Thanks for your help!
Picture:
I assume you have the x,y coordinates which you used to the plot. I obtained them here using imfreehand. I used inpolygon to generate a binary mask for each curve and then apply xor on them to get a mask of the desired area:
% x,y were obtained using imfreehand on 100x100 image and getPosition()
x = [21;22;22;22;22;22;22;23;23;23;23;23;23;24;25;25;26;26;27;28;29;30;30;31;32;32;33;34;35;36;37;38;39;40;41;42;43;44;45;46;47;48;49;50;51;52;53;54;55;56;57;58;59;60;61;62;63;64;65;66;67;68;69;70;71;72;73;74;75;76;77;78;79;79;80;80;81;81;81;82;82;82;82;83;83;83;84;84;85;85;86;86;86;86;86;86;85;84;84;83;82;81;80;79;78;77;76;75;74;73;72;71;70;69;68;67;66;65;64;63;62;61;60;59;58;57;56;55;54;53;52;51;50;49;48;47;46;45;44;43;42;41;40;39;38;37;36;35;34;33;32;31;30;29;28;27;26;25;25;24;24;23;22;21;21;21;21;21;21;21;21;21;21;21;21;21];
y = [44;43;42;41;40;39;38;37;36;35;34;33;32;31;30;29;28;27;26;25;24;23;22;21;20;19;18;18;17;17;17;17;17;16;16;16;16;16;16;15;15;14;14;14;14;14;14;15;15;15;16;16;17;17;17;17;18;18;18;19;20;20;21;22;23;23;24;25;26;27;28;29;30;31;32;33;34;35;36;37;38;39;40;41;42;43;44;45;46;47;48;49;50;51;52;53;54;55;56;56;57;57;58;59;59;60;61;61;61;61;61;60;60;60;59;58;57;56;56;55;55;54;54;54;54;54;54;54;54;54;55;55;55;55;56;57;58;59;60;61;61;62;63;63;64;64;65;65;66;66;66;66;66;66;65;64;63;62;61;60;59;58;57;56;55;54;53;52;51;50;49;48;47;46;45;44];
% generate arbitrary xy
x1 = (x - 50)./10; y1 = (y - 50)./10;
x2 = (x - 50)./10; y2 = (y - 40)./10;
% generate binary masks using poly2mask
pixelSize = 0.01; % resolution
xx = min([x1(:);x2(:)]):pixelSize:max([x1(:);x2(:)]);
yy = min([y1(:);y2(:)]):pixelSize:max([y1(:);y2(:)]);
[xg,yg] = meshgrid(xx,yy);
mask1 = inpolygon(xg,yg,x1,y1);
mask2 = inpolygon(xg,yg,x2,y2);
% add both masks (now their common area pixels equal 2)
combinedMask = mask1 + mask2;
% XOR on both of them
xorMask = xor(mask1,mask2);
% compute mask area in units (rather than pixels)
Area = bwarea(xorMask)*pixelSize^2;
% plot
subplot(131);
plot(x1,y1,x2,y2,'LineWidth',2);
title('Curves');
axis square
set(gca,'YDir','reverse');
subplot(132);
imshow(combinedMask,[]);
title('Combined Mask');
subplot(133);
imshow(xorMask,[]);
title(['XNOR Mask, Area = ' num2str(Area)]);
function area = area_between_curves(initial,corrected)
interval = 0.1;
x = -80:interval:80;
y = -80:interval:80;
[X,Y] = meshgrid(x,y);
in_initial = inpolygon(X,Y,initial(:,1),initial(:,2));
in_corrected = inpolygon(X,Y,corrected(:,1),corrected(:,2));
in_area = xor(in_initial,in_corrected);
area = interval^2*nnz(in_area);
% visualization
figure
hold on
plot(X(in_area),Y(in_area),'r.')
plot(X(~in_area),Y(~in_area),'b.')
end
If I use the lines of the question, this is the result:
area = 1.989710000000001e+03

Draw line and Cut off Circuler area

I have got the below Image after running the below code.
file='grayscale.png';
I=imread(file);
bw = im2bw(I);
bw = bwareaopen(bw,870);
imwrite(bw,'noiseReduced.png')
subplot(2,3,1),imshow(bw);
[~, threshold] = edge(bw, 'sobel');
fudgeFactor = .5;
im = edge(bw,'sobel', threshold * fudgeFactor);
subplot(2,3,2), imshow(im), title('binary gradient mask');
se = strel('disk',5);
closedim = imclose(im,se);
subplot(2,3,3), imshow(closedim), title('Connected Cirlces');
cc = bwconncomp(closedim);
S = regionprops(cc,'Centroid'); //returns the centers S(2) for innercircle
numPixels = cellfun(#numel,cc.PixelIdxList);
[biggest,idx] = min(numPixels);
im(cc.PixelIdxList{idx}) = 0;
subplot(2,3,4), imshow(im), title('Inner Cirlces Only');
c = S(2);
My target is now to draw a red cirle around the circular object(see image) and cut the circle region(area) from the original image 'I' and save the cropped area as image or perform other tasks. How can I do it?
Alternatively, you can optimize/fit the circle with least r that contains all the points:
bw = imread('http://i.stack.imgur.com/il0Va.png');
[yy xx]=find(bw);
Now, let p be a three vector parameterizing a circle: p(1), p(2) are the x-y coordinates of the center and p(3) its radii. Then we want to minimize r (i.e., p(3)):
obj = #(p) p(3);
Subject to all points inside the circle
con = #(p) deal((xx-p(1)).^2+(yy-p(2)).^2-p(3).^2, []);
Optimizing with fmincon:
[p, fval] = fmincon(obj, [mean(xx), mean(yy), size(bw,1)/4], [],[],[],[],[],[],con);
Yields
p =
471.6397 484.4164 373.2125
Drawing the result
imshow(bw,'border','tight');
colormap gray;hold on;
t=linspace(-pi,pi,1000);
plot(p(3)*cos(t)+p(1),p(3)*sin(t)+p(2),'r', 'LineWidth',1);
You can generate a binary mask of the same size as bw with true in the circle and false outside
msk = bsxfun(#plus, ((1:size(bw,2))-p(1)).^2, ((1:size(bw,1)).'-p(2)).^2 ) <= p(3).^2;
The mask looks like:
The convexhull of the white pixels will give you a fairly good approximation of the circle. You can find the center as the centroid of the area of the hull and the radius as the average distance from the center to the hull vertices.

How to transform different shapes to circles in Matlab

I have this image:
that has different shapes, and I want to transform each shape in a circle. And each circle must have different radius, depending on the size of the shape. How can I do that? With Morphology Operations or there are any function on Matlab that does that?
I used the function Regionprops to detect every individual shape, then I can do operations on each region separately.
I would use bwlabel to first label all of the components. Then I would use regionprops to find the bounding box of each component. You can then use the rectangle with a Curvature value of [1 1] to plot an ellipse at each bounding box.
%// Load the image and convert to 0's and 1's
img = imread('http://i.stack.imgur.com/9wRYK.png');
img = double(img(:,:,1) > 0);
%// Label the image
L = bwlabel(img);
%// Compute region properties
P = regionprops(L);
imshow(img)
for k = 1:numel(P)
%// Get the bounding box
bb = P(k).BoundingBox;
%// Plot an ellipse around each object
hold on
rectangle('Position', bb, ...
'Curvature', [1 1], ...
'EdgeColor', 'r', ...
'LineWidth', 2);
end
If you actually want circles, you will need to decide how exactly you define a circle from a rectangle. For this, I just used the maximum of the width and height for the diameter.
t = linspace(0, 2*pi, 100);
cost = cos(t);
sint = sin(t);
for k = 1:numel(P)
bb = P(k).BoundingBox;
%// Compute the radius and center of the circle
center = [bb(1)+0.5*bb(3), bb(2)+0.5*bb(4)];
radius = max(bb(3:4)) / 2;
%// Plot each circle
plot(center(1) + radius * cost, ...
center(2) + radius * sint, ...
'Color', 'r');
end
Now if you actually want to modify the image data itself rather than simply displaying it, you can use a meshgrid of all of the pixel centers to test whether a given pixel is within a circle or not.
%// Create a new image the size of the old one
newImage = zeros(size(img));
%// Determine the x/y coordinates for each pixel
[xx,yy] = meshgrid(1:size(newImage, 2), 1:size(newImage, 1));
xy = [xx(:), yy(:)];
for k = 1:numel(P)
bb = P(k).BoundingBox;
%// Compute the circle that fits each bounding box
center = [bb(1)+0.5*bb(3), bb(2)+0.5*bb(4)];
radius = max(bb(3:4)) / 2;
%// Now check if each pixel is within this circle
incircle = sum(bsxfun(#minus, xy, center).^2, 2) <= radius^2;
%// Change the values of newImage
newImage(incircle) = k;
end
%// Create a binary mask of all points that were within any circle
mask = newImage > 0;

How can i render lineseries/contour/etc objects to array of pixel data?

I have an array of pixel data frames for use with VideoWriter. I want to overlay lineseries/contour objects into each frame. I don't want to make the movie by iteratively drawing each frame to a figure and capturing it with getframe, because that gives poor resolution and is slow. I tried using getframe on a plot of just the contour, but that returns images scaled to the wrong size with weird margins, especially when using 'axis equal,' which I need.
Updated to accommodate feedback from OP
Getting the contour data as pixel data is not trivial (if possible at all) since using getframe doesn't yield predictable results
What we can do is to extract the contour data and then overlay it on the pixel data frames, forcing them to be to the same scale and then do a getframe on the resultant merged image. This will at least ensure that they two data sets area aligned.
The following code shows the principle though you'd need to modify it for your own needs:
%% Generate some random contours to use
x = linspace(-2*pi,2*pi);
y = linspace(0,4*pi);
[X,Y] = meshgrid(x,y);
Z = sin(X)+cos(Y);
[~,h] = contour(X,Y,Z);
This yields the following contours
Now we get the handles of the children of this image. These will all be 'patch' type objects
patches = get(h,'Children');
Also get the axis limits for the contours
lims = axis;
Next, create a new figure and render the pixel frame data into it
In this example I'm just loading an image but you get the idea.
%% Render frame data
figure
i = imread( some_image_file_png );
This image is actually 194 x 259 x 3. I can display it and rescale the X and Y axes using
%% Set image axes
image(flipdim(i,1),'XData',[lims(1) lims(2)],'YData',[lims(4) lims(3)]);
Note the use of flipdim() to vertically flip the image since the image Y-axis runs in the opposite sense to the contour Y axis. This gives me:
Now I can plot the contours (patches) form the contour plot over the top of the image in the same coordinate space
%% Plot patches
for p =1:length(patches)
xd = get( patches(p), 'XData' );
yd = get( patches(p), 'YData' );
% This causes all contours to be rendered in white. You may
% want to play with this a little
cd = zeros(size(xd));
patch( xd, yd, cd, 'EdgeColor', 'w');
end
This yields
You can now use getframe to extract the frame. If it's important to have coloured contours, you will need to extract colour data from the original contour map and use it to apply an appropriate colouring in the overlaid image.
As a short cut, it's also possible to compile all patch data into a single MxN matrix and render with a single call to patch but I wrote it this way to demonstrate the process.
Well, here's a Bresenham-esque solution based on the ContourMatrix. Not ideal cuz doesn't handle line width, antialiasing, or any more than a single color. But it's pretty efficient (not quite Bham efficient).
function renderContour
clc
close all
x = randn(100,70);
[c,h] = contour(x,[0 0],'LineColor','r');
axis equal
if ~isnumeric(h.LineColor)
error('not handled')
end
cs = nan(size(c,2),4);
k = 0;
ci = 1;
for i = 1:size(c,2)
if k <= 0
k = c(2,i);
else
if k > 1
cs(ci,:) = reshape(c(:,i+[0 1]),[1 4]);
ci = ci + 1;
end
k = k - 1;
end
end
pix = renderLines(cs(1:ci-1,:),[1 1;fliplr(size(x))],10,h.LineColor);
figure
image(pix)
axis equal
end
function out = renderLines(cs,rect,res,color)
% cs = [x1(:) y1(:) x2(:) y2(:)]
% rect = [x(1) y(1);x(2) y(2)]
% doesnt handle line width, antialiasing, etc
% could do those with imdilate, imfilter, etc.
test = false;
if test
if false
cs = [0 0 5 5; 0 5 2.5 2.5];
rect = [0 0; 10 10];
else
cs = 100 * randn(1000,4);
rect = 200 * randn(2);
end
res = 10;
color = [1 .5 0];
end
out = nan(abs(res * round(diff(fliplr(rect)))));
cs = cs - repmat(min(rect),[size(cs,1) 2]);
d = [cs(:,1) - cs(:,3) cs(:,2) - cs(:,4)];
lens = sqrt(sum(d.^2,2));
for i = 1:size(cs,1)
n = ceil(sqrt(2) * res * lens(i));
if false % equivalent but probably less efficient
pts = linspace(0,1,n);
pts = round(res * (repmat(cs(i,1:2),[length(pts) 1]) - pts' * d(i,:)));
else
pts = round(res * [linspace(cs(i,1),cs(i,3),n);linspace(cs(i,2),cs(i,4),n)]');
end
pts = pts(all(pts > 0 & pts <= repmat(fliplr(size(out)),[size(pts,1) 1]),2),:);
out(sub2ind(size(out),pts(:,2),pts(:,1))) = 1;
end
out = repmat(flipud(out),[1 1 3]) .* repmat(permute(color,[3 1 2]),size(out));
if test
image(out)
axis equal
end
end

Matlab crop a polygon from an image

I have an image of a product on a solid background that I would like to crop as close as possible to the product.
I brighten it and find the edges with the following code:
limits = stretchlim(original, 0.01);
img1 = imadjust(original, limits, []);
img = rgb2gray(img1);
BW = edge(img,'canny',0.2);
[B,L,N,A] = bwboundaries(BW);
figure; imshow(BW); hold on;
for k=1:length(B),
if(~sum(A(k,:)))
boundary = B{k};
plot(boundary(:,2),boundary(:,1),'r','LineWidth',2);hold on;
end
end
Which give me the following image:
The following code gives me rectangles on every blob/line detected:
blobMeasurements = regionprops(logical(BW), 'BoundingBox');
numberOfBlobs = size(blobMeasurements, 1);
rectCollection = [];
for k = 1 : numberOfBlobs % Loop through all blobs.
rects = blobMeasurements(k).BoundingBox; % Get list ofpixels in current blob.
x1 = rects(1);
y1 = rects(2);
x2 = x1 + rects(3);
y2 = y1 + rects(4);
x = [x1 x2 x2 x1 x1];
y = [y1 y1 y2 y2 y1];
rectCollection(k,:,:,:) = [x1; y1; x2; y2];
end
I'm able to then draw a bounding rectangle and crop with all these points collected with the following code:
% get min max
xmin=min(rectCollection(:,1))-1;
ymin=min(rectCollection(:,2))-1;
xmax=max(rectCollection(:,3))+1;
ymax=max(rectCollection(:,4))+1;
% define outer rect:
outer_rect=[xmin ymin xmax-xmin ymax-ymin];
crop = imcrop(original,outer_rect);
Which gives me the following result:
My question is how can I get a polygon as close as possible to the product and crop it with the polygon or, alternatively, just crop as close as possible to the product and its cap?
If you don't want to get a bounding box but a polygon, I think you need to generate a mask - a matrix with the same size of your image, value 1 if the pixel in on your object, 0 if not.
I heard about an algorithm (sorry I can't find the name, I'll edit this post if I find it) which works with a lasso :
step 0 : your lasso is your bounding box.
step i : segment the lasso and for each part, you retract it if the color (or any other) gradient in the image is less than a fixed value.
step n (last) : you cannot retract any part of the lasso, it is finished. Inside your lasso : your object. Outside : the background.
As I remember there is a lot of work with this method : the definition of the lasso, the retractation step, the solidity of your lasso (to avoid too much deformation of the lasso).
Beside the lasso method you can search about the watershed transform, it can also work with your problem.
Finally, if you generate the pictures, take shots with a plained background (green, pink, blue, etc) and use a simple chromakey.
Using active contours also looks like a good approach but getting a nice mask is troublesome.
original = imread('1.jpg');
level = graythresh(original);
img = rgb2gray(original);
mask = im2bw(img,level+0.1);
mask = imfill(~mask,'holes');
bw = activecontour(img,mask);
rows = numel(original(:,1,1));
columns = numel(original(1,:,1));
for i = 1:rows
for j = 1:columns
if ( bw(i,j,1) == 0 )
original(i,j,:) = 255;
end
end
end
imshow(original);