Width and height of a rotated polyshape object - Matlab - matlab

Based on this question I am trying to calculate the width and height of an object. I did so by converting the examined object to a polyshape and rotated. How can I extract the width and height of the rotated polyshape object? Is there a way to do it using regionprop and will it be more efficient?
Code:
clc;
clear;
close all;
Image = rgb2gray(imread('pillsetc.png'));
BW = imbinarize(Image);
BW = imfill(BW,'holes');
BW = bwareaopen(BW, 100);
[B,L] = bwboundaries(BW,'noholes');
imshow(Image);
hold on;
k=3;
stat = regionprops(BW,'Centroid','Orientation','MajorAxisLength');
b = B{k};
yBoundary = b(:,2);
xBoundary = b(:,1);
centroidObject = stat(k).Centroid;
xCentre = centroidObject(:,2);
yCentre = centroidObject(:,1);
plot(yCentre, xCentre, 'r*')
orientationDegree = stat(k).Orientation
hlen = stat(k).MajorAxisLength/2;
cosOrient = cosd(stat(k).Orientation);
sinOrient = sind(stat(k).Orientation);
xcoords = xCentre + hlen * [cosOrient -cosOrient];
ycoords = yCentre + hlen * [-sinOrient sinOrient];
plot(yBoundary, xBoundary, 'r', 'linewidth', 3);
pgon = polyshape(yBoundary, xBoundary);
polyRot = rotate(pgon,(90+orientationDegree),centroidObject);
plot(polyRot);
[xlim,ylim] = boundingbox(polyRot);
Height = xlim(2) - xlim(1);
Width = ylim(2) - ylim(1);

I would use the angle returned by the minimum Feret diameter calculation to rotate the polygon. Usually, the box at this rotation is the box with minimal area (exceptions seem to be very rare). The 'Orientation' feature is computed based on the best fit ellipse, and would not necessarily yield a small box.
Instead of rotating the full object polygon, you can also rotate only the convex hull, which typically contains fewer points and thus would be more efficient. The Feret computation already uses the convex hull, so there is no additional cost to requesting it from regionprops.
This is code that does what I describe:
Image = rgb2gray(imread('pillsetc.png'));
BW = imbinarize(Image);
BW = imfill(BW,'holes');
BW = bwareaopen(BW, 100);
stat = regionprops(BW,'ConvexHull','MinFeretProperties');
% Compute Feret diameter perpendicular to the minimum diameter
for ii=1:numel(stat)
phi = stat(ii).MinFeretAngle; % in degrees
p = stat(ii).ConvexHull * [cosd(phi),-sind(phi); sind(phi),cosd(phi)];
minRect = max(p) - min(p); % this is the size (width and height) of the minimal bounding box
stat(ii).MinPerpFeretDiameter = minRect(2); % add height to the measurement structure
end
Note that the first value of minRect in the code above is the width of the object (smallest side of minimum bounding box), and equivalent to stat(ii).MinFeretDiameter. The two values are not identical because they are computed differently, but they are pretty close. The second value of minRect, which is saved as “MinPerpFeretDiameter”, is the height (or rather the longest side of the minimum bounding box).

Related

Fit an inside circle of an object - Matlab

I wish to fit an inside circle of an outline object, how can I do it? in the example shown I tried to calculate the r by dividing the MajorAxisLength but it does not work.
Code:
clc;
clear;
RGB = imread('pillsetc.png');
I = rgb2gray(RGB);
bw = imbinarize(I);
imshow(bw)
bw = bwareaopen(bw,30);
bw = imfill(bw,'holes');
imshow(bw)
[B,L] = bwboundaries(bw,'noholes');
stats = regionprops(L,'Centroid','MajorAxisLength');
hold on
k=3;
boundary = B{k};
r = stats(k).MajorAxisLength/2;
centroid = stats(k).Centroid;
plot(centroid(1),centroid(2),'+');
theta = linspace(0,2*pi);
x = r*cos(theta) + centroid(1);;
y = r*sin(theta) + centroid(2);;
plot(x,y)
axis equal
Ok, this is an approximated solution, but given the limited parameters you can get from regionprops, this is probably good enough.
My derivations are follows:
the goal to calculate the radius of the inscribed circle of a rectangle is to estimate the length of the short-edge.
If we assume the elliptic fitting to a rectangle yields approximately the same area as the rectangle, and the short/long edge ratio is the same as the minor/major axis ratios, then, we can obtain the following equation:
x=short edge of the rectangle;
y=long edge of the rectangle;
b=minor axis of the fitted ellipse;
a=major axis of the fitted ellipse;
then we have
x/y=b/a
x*y=a*b*pi
from that, we can solve the value of x is sqrt(pi)*b. That makes the radius of the inscribed circle sqrt(pi)/2*b.
changing your below two lines of code
stats = regionprops(L,'Centroid','MinorAxisLength');
...
r = stats(k).MinorAxisLength*(sqrt(pi)/4);
...
I was able to get something pretty close to the inscribed circle. Give it a try.

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;

Creat a circular ROI and account for partial pixel values

I have a image of low resolution, 128x128 pixels. I need to obtain the mean value of a circle ROI, in order to do that I use the easy method:
%% Draw circle ROI
t = 0:pi/500:2*pi;
xi = ((R0/pixelSize)*cos(t)+63.5+x0+((Rsphere)/pixelSize)*cos(theta))*4;
yi = ((R0/pixelSize)*sin(t)+63.5+y0+((Rsphere)/pixelSize)*sin(theta))*4;
%% Calculate roi statistics
line(xi,yi,'LineWidth',1,'Color',color);
ROImask = poly2mask(xi,yi, size(im,1),size(im,2));
ptROI = find(ROImask);
ROImean = mean(im(ptROI));
The problem here is that using this method I don't account for the partial value of a pixel in the ROI, as can be seen in the image.
Is there any direct way to obtain the mean of the ROI weighting the value of the pixels?
Thanks
If you really want to do this exactly, you'll need to do some calculus (integral of a circle on a square domain for each pixel). But, this probably overkill for your application. My suggestion is to calculate your circle on a fine grid, then resize it to match the image:
upFactor = 3;
% load built-in example image
x = imread('rice.png');
% convert to double
x = im2double(x);
% define the ROI
center = [68.5, 180]; % [row, column]
radius = 1; % pixels
% do the distance calculation
% (getting the coordinate systems to match is the hardest part, try making
% small examples to see how it works)
iVector = (0:size(x,1)*upFactor-1)/upFactor + .5 + 1/upFactor/2;
jVector = (0:size(x,2)*upFactor-1)/upFactor + .5 + 1/upFactor/2;
[I, J] = ndgrid( iVector - center(1), jVector - center(2));
sqDist = I.^2 + J.^2;
insideBig = double(sqDist <= radius^2); % need this to not be logical type
% this resizes back to the original image size my taking the mean of each
% upFactor by upFactor block
inside = reshape( mean( im2col( insideBig, [upFactor upFactor], 'distinct')), size(x));
% check that we have the values we expect
uniqueVals = unique(inside(:))
% show examples
figure
imagesc(x)
figure
imagesc(inside)
result = sum(sum( x .* inside )) / sum(inside(:))

How to draw a filled circle on a video frame using 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.