Calculate objects circumscribed radius and inscribed radius - Matlab - matlab

I am trying to calculate two objects circumscribed radius and inscribed radius using the code below. I used for the inscribedRadius parameter from this script and for the circumscribed radius I used this function
I do not understand why I get for the box shape that the inscribed radius is bigger than the circumscribed radius. Any idea what is wrong? And how to fix it?
Image
Code:
clc;
clear;
RGB = imcomplement(imread('https://i.stack.imgur.com/8WLAt.jpg'));
I = rgb2gray(RGB);
bw = imbinarize(I);
bw = imfill(bw,'holes');
imshow(bw)
hold on;
[B,L] = bwboundaries(bw,'noholes');
stats = regionprops(L,'Centroid','MajorAxisLength');
for i = 1 : numel(stats)
b = B{i};
c = stats(i).Centroid;
y = b(:,1);
x = b(:,2);
plot( b(:,2),b(:,1),'Color','red','linewidth',2);
text(c(1),c(2),num2str(i),'Color','red');
xMin = min(x);
xMax = max(x);
yMin = min(y);
yMax = max(y);
scalingFactor = 1000 / min([xMax-xMin, yMax-yMin]);
x2s = (x - xMin) * scalingFactor + 1;
y2s = (y - yMin) * scalingFactor + 1;
mask = poly2mask(x2s, y2s, ceil(max(y2s)), ceil(max(x2s)));
edtImage = bwdist(~mask);
inscribedRadius = max(edtImage(:));
[yCenter, xCenter] = find(edtImage == inscribedRadius);
xCenter = (xCenter - 1)/ scalingFactor + xMin;
yCenter = (yCenter - 1)/ scalingFactor + yMin;
inscribedRadius = inscribedRadius / scalingFactor
[circumscribedCenter,circumscribedRadius] = minboundcircle(x,y); % from https://www.mathworks.com/matlabcentral/fileexchange/34767-a-suite-of-minimal-bounding-objects?focused=3820656&tab=function
circumscribedRadius
end
The results are:
Object 1: inscribedRadius = 264, cumscribedRadius = 186.6762
Object 2: inscribedRadius = 130.4079, circumscribedRadius = 132.3831
The values of object 1 (box) are wrong as the inscribedRadius can not be bigger than the cumscribedRadius. They are fine for object 2 (circle)

If you look at the mask image, you'll notice that the square shape is drawn touching the right and bottom edges of the image. There's background only along the left and top of the shape. The distance transform bwdist(~mask) subsequently computes the distance to the background for each pixel within the shape, but since there's background only to the left and top, the pixel at the bottom right of the shape has a distance of 1000, rather than 1. The distance transform is supposed to have a maximum in the middle, at a point equidistant to at least the three nearest shape edge points.
The solution is simple: poly2mask must create an image that is one pixel wider and taller:
mask = poly2mask(x2s, y2s, ceil(max(y2s)) + 1, ceil(max(x2s)) + 1);
^^^ ^^^
With this change, the computed inscribedRadius for the square is 132, as expected.

Related

How to draw a rectangle using 4 corners on an image in Matlab R2018b?

In Matlab, there is rectangle('Position',[x,y,w,h]) to draw a rectangle object, that w and h are a width and a height respectively (This link). While I try to draw a rectangle on an image using 4 corners: min_x, max_x, min_y and max_y that are specified in the following image.
Ex:
min_x = 193; max_x = 220; min_y = 168; max_y = 190;
I saw this link and like that, but they couldn't help me. Is there any way to draw a rectangle with 4 corners?
A = imresize( imread('peppers.png'),0.6);%resizing for better visibility
min_x = 193; max_x = 220; min_y = 168; max_y = 190;
x = min_x;
y = min_y;
w = max_x-min_x;
h = max_y-min_y;
imshow(A)
rectangle('Position',[x,y,w,h],'EdgeColor','r','Linewidth',3);
% Or insert shape to bitmap:
% B = insertShape(A,'rectangle',[x,y,w,h]);
% imshow(B)

How to rotate image around the center of object in matlab?

imrotate function in matlab rotates the image around the center. How to rotate image by custom coordinates?
For example center of blob in binary mask. If using imcrop and place blob in the center, how to reshape that to same as original image?
Code
% create binary mask
clc;
clear;
mask= zeros(400,600,'logical');
positein = [280,480];
L = 40;
x = positein (1);
y = positein (2);
mask(x,y-L:y+L) = 1;
for i =1:8
mask(x+i,y-L:y+L) = 1;
mask(x-i,y-L:y+L) = 1;
end
Angle = 45;
mask_after_rotation = imrotate(mask,-Angle,'crop');
figure,
subplot(1,2,1),imshow(mask),title('before rotation');
subplot(1,2,2),imshow(mask_after_rotation),title('after rotate 45');
This is generally performed by constructing an affine transform which translates the point about which we want to rotate to the origin, then performs a rotation, then translates back.
For example, to rotate by -45 degrees like in your example we could do the following
% create binary mask
mask = zeros(400, 600,'logical');
positein = [480, 200];
W = 40; H = 8;
x = positein(1); y = positein(2);
mask(y-H:y+H, x-W:x+W) = 1;
angle = -45;
% translate by -positein, rotate by angle, then translate back by pt
T = [1 0 0; 0 1 0; -positein 1];
R = [cosd(angle) -sind(angle) 0; sind(angle) cosd(angle) 0; 0 0 1];
Tinv = [1 0 0; 0 1 0; positein 1];
tform = affine2d(T*R*Tinv);
mask_rotated = imwarp(mask, tform, 'OutputView', imref2d(size(mask)));
figure(1); clf(1);
subplot(1,2,1),imshow(mask),title('before rotation');
subplot(1,2,2),imshow(mask_rotated),title('after rotate 45');
Alternatively you can set the reference object so that the desired coordinate is at the origin
% Set origin to positein, then rotate
xlimits = 0.5 + [0, size(mask,2)] - positein(1);
ylimits = 0.5 + [0, size(mask,1)] - positein(2);
ref = imref2d(size(mask), xlimits, ylimits);
R = [cosd(angle) -sind(angle) 0; sind(angle) cosd(angle) 0; 0 0 1];
tform = affine2d(R);
mask_rotated = imwarp(mask, ref, tform, 'OutputView', ref);
You could do a series of transformation to achieve the rotation around an arbitrary point, which are: 1) Move the arbitrary point to the center of the image, 2) Rotate the image by a predefined angle, & 3) Translate it back to the original position. For example, if you want to rotate the mask around the center of the blob in your case, then you could carry out the following steps.
trns_mask = imtranslate(mask, [-180, -80]); % Move to the origin
trns_mask_rotated = imrotate(trns_mask,-Angle,'crop'); % Rotate
mask_after_rotation = imtranslate(trns_mask_rotated, [180, 80]); % Move back
There are options for the function imtranslate() which you could play with to make sure you are not losing any image information during the transformation.

How to straighten a tilted square shape in an image?

How can I straighten a tilted square shape in an image?
I do not know the angle with which it is tilted and the code must calculate it and then rotate it automatically.
For example, I have the following image:
which should be rotated to give the following output image:
One way:
I = imread('img.jpg');
I = rgb2gray(I);
Ibw = I<threshold; %find the good threshold
se = strel('square',sizesquare); %find the good size for the strel function.
Ibw = imdilate(Ibw,se); %fill the hole
imshow(Ibw);
stat = regionprops(Ibw,'Extrema'); %extrema detection of the image.
point = stat.Extrema;
hold on
for i = 2:2:length(stat.Extrema)
x = point(i,1);
y = point(i,2);
plot(x,y,'o');
text(x,y,num2str(i),'color','w')
end
%construct the triangle that will help us to determine the shift angle.
P2 = [point(8,1),point(2,2)];
P1 = [point(8,1),point(8,2)];
P0 = [point(2,1),point(2,2)];
ang = atan2(abs(det([P2-P0;P1-P0])),dot(P2-P0,P1-P0))*180/pi
close all
imshow(imrotate(I,-ang))
STEP 1
STEP 2
STEP 3
A simple way using only the top and bottom corners. Note that this approach relies on the upper and lower most corners:
i = imread('sq.jpg');
i_bw = im2bw(i)==0;
% Modify the strel as required
se = strel('square', 10);
i_ed = imopen(i_bw, se);
limits = sum(i_ed, 2);
top_y = find(limits>0, 1);
bottom_y = find(limits>0, 1, 'last');
top_x = round(mean(find(i_ed(top_y, :)>0)));
bottom_x = round(mean(find(i_ed(bottom_y, :)>0)));
slope = -1 * (top_y - bottom_y)/(top_x - bottom_x);
rot_angle = 2 * pi * atan(slope);
i2 = imrotate(i, -rot_angle);
imshow(i2)
BEFORE
AFTER

How to square the corners of a "rectangle" in a bw image with matlab

I have images of rectangles or deformed rectangles with rounded corners, like this:
or this:
is there a way to make the corners squared with matlab?
And then how can i get the coordinates of those new corners?
Thank you
Explanation
This problem is similar to the following question. My answer will be somehow similar to my answer there, with the relevant modifications.
we want to find the parallelogram corners which fits the most to the given shape.
The solution can be found by optimization, as follows:
find an initial guess for the 4 corners of the shape. This can be done by finding the boundary points with the highest curvature, and use kmean clustering to cluster them into 4 groups.
create a parallelogram given these 4 corners, by drawing a line between each pair of corresponding corners.
find the corners which optimize the Jaccard coefficient of the boundary image and the generated parallelogram map.
The optimization will done locally on each corner, in order to spare time.
Results
Initial corner guess (corners are marked in blue)
final results:
Code
main script
%reads image and binarize it
I = rgb2gray(imread('eA4ci.jpg')) > 50;
%finds boundry of largerst connected component
boundries = bwboundaries(I,8);
numPixels = cellfun(#length,boundries);
[~,idx] = max(numPixels);
B = boundries{idx};
%finds best 4 corners
[ corners ] = optimizeCorners(B);
%generate line mask given these corners, fills the result
linesMask = drawLines(size(I),corners,corners([2:4,1],:));
rectMask = imfill(linesMask,'holes');
%remove biggest CC from image, adds linesMask instead
CC = bwconncomp(I,8);
numPixels = cellfun(#numel,CC.PixelIdxList);
[~,idx] = max(numPixels);
res = I;
res(CC.PixelIdxList{idx}) = 0;
res = res | rectMask;
optimize corners function:
function [ corners] = optimizeCorners(xy)
%finds the corners which fits the most for this set of points
Y = xy(:,1);
X = xy(:,2);
%initial corners guess
corners = getInitialCornersGuess(xy);
boundriesIm = zeros(max(Y)+20,max(X)+20);
boundriesIm(sub2ind(size(boundriesIm),xy(:,1),xy(:,2))) = 1;
%R represents the search radius
R = 7;
%continue optimizing as long as there is no change in the final result
unchangedIterations = 0;
while unchangedIterations<4
for ii=1:4
%optimize corner ii
currentCorner = corners(ii,:);
bestCorner = currentCorner;
bestRes = calcEnergy(boundriesIm,corners);
cornersToEvaluate = corners;
for yy=currentCorner(1)-R:currentCorner(1)+R
for xx=currentCorner(2)-R:currentCorner(2)+R
cornersToEvaluate(ii,:) = [yy,xx];
res = calcEnergy(boundriesIm,cornersToEvaluate);
if res > bestRes
bestRes = res;
bestCorner = [yy,xx];
end
end
end
if isequal(bestCorner,currentCorner)
unchangedIterations = unchangedIterations + 1;
else
unchangedIterations = 0;
corners(ii,:) = bestCorner;
end
end
end
end
function res = calcEnergy(boundriesIm,corners)
%calculates the score of the corners list, given the boundries image.
%the result is acutally the jaccard index of the boundries map and the
%lines map
linesMask = drawLines(size(boundriesIm),corners,corners([2:4,1],:));
res = sum(sum(linesMask&boundriesIm)) / sum(sum(linesMask|boundriesIm));
end
get initial corners function:
function corners = getInitialCornersGuess(boundryPnts)
%calculates an initial guess for the 4 corners
%finds corners by performing kmeans on largest curvature pixels
[curvatureArr] = calcCurvature(boundryPnts, 5);
highCurv = boundryPnts(curvatureArr>0.3,:);
[~,C] = kmeans([highCurv(:,1),highCurv(:,2)],4);
%sorts the corners from top to bottom - preprocessing stage
C = int16(C);
corners = zeros(size(C));
%top left corners
topLeftInd = find(sum(C,2)==min(sum(C,2)));
corners(1,:) = C(topLeftInd,:);
%bottom right corners
bottomRightInd = find(sum(C,2)==max(sum(C,2)));
corners(3,:) = C(bottomRightInd,:);
%top right and bottom left corners
C([topLeftInd,bottomRightInd],:) = [];
topRightInd = find(C(:,2)==max(C(:,2)));
corners(4,:) = C(topRightInd,:);
bottomLeftInd = find(C(:,2)==min(C(:,2)));
corners(2,:) = C(bottomLeftInd,:);
end
function [curvatureArr] = calcCurvature(xy, halfWinSize)
%calculate the curvature of a list of points (xy) given a window size
%curvature calculation
curvatureArr = zeros(size(xy,1),1);
for t=1:halfWinSize
y = xy(t:halfWinSize:end,1);
x = xy(t:halfWinSize:end,2);
dx = gradient(x);
ddx = gradient(dx);
dy = gradient(y);
ddy = gradient(dy);
num = abs(dx .* ddy - ddx .* dy) + 0.000001;
denom = dx .* dx + dy .* dy + 0.000001;
denom = sqrt(denom);
denom = denom .* denom .* denom;
curvature = num ./ denom;
%normalizing
if(max(curvature) > 0)
curvature = curvature / max(curvature);
end
curvatureArr(t:halfWinSize:end) = curvature;
end
end
draw lines function:
function mask = drawLines(imgSize, P1, P2)
%generates a mask with lines, determine by P1 and P2 points
mask = zeros(imgSize);
P1 = double(P1);
P2 = double(P2);
for ii=1:size(P1,1)
x1 = P1(ii,2); y1 = P1(ii,1);
x2 = P2(ii,2); y2 = P2(ii,1);
% Distance (in pixels) between the two endpoints
nPoints = ceil(sqrt((x2 - x1).^2 + (y2 - y1).^2));
% Determine x and y locations along the line
xvalues = round(linspace(x1, x2, nPoints));
yvalues = round(linspace(y1, y2, nPoints));
% Replace the relevant values within the mask
mask(sub2ind(size(mask), yvalues, xvalues)) = 1;
end

Creating a circular mask for my graph

I'm plotting a square image, but since my camera views out of a circular construction, I want the image to look circular as well. So to do this, I just wanted to create a mask for the image (basically create a matrix, and multiply my data by the mask, so if I want to retain my image I am multiplying by one, and if I want that part of the image to go to black, I multiply by 0).
I'm not sure the best way to make a matrix that will represent a circular opening though. I just want every element within the circle to be a "1" and every element outside the circle to be a "0" so I can color my image accordingly. I was thinking of doing a for loop, but I was hoping there was a faster way to do it. So...all I need is:
A matrix that is 1280x720
I need a circle that has a diameter of 720, centered in the middle of the 1280x720 matrix (what I mean by this is all elements corresponding to being within the circle have a "1" and all other elements have a "0"
My attempt
mask = zeros(1280,720)
for i = 1:1280
for j = 1:720
if i + j > 640 && i + j < 1360
mask(i,j) = 1;
end
end
end
Well the above obviously doesn't work, I need to look at it a little better to form a better equation for determing when to add a 1 =P but ideally I would like to not use a for loop
Thanks, let me know if anything is unclear!
#kol 's answer looks correct. You can do this with vectorized code using the meshgrid function.
width = 1280;
height = 720;
radius = 360;
centerW = width/2;
centerH = height/2;
[W,H] = meshgrid(1:width,1:height);
mask = ((W-centerW).^2 + (H-centerH).^2) < radius^2;
Here is a possible solution:
width = 160;
height = 120;
mask = zeros(width, height);
center_x = width / 2;
center_y = height / 2;
radius = min(width, height) / 2;
radius2 = radius ^ 2;
for i = 1 : width
for j = 1 : height
dx = i - center_x;
dy = j - center_y;
dx2 = dx ^ 2;
dy2 = dy ^ 2;
mask(i, j) = dx2 + dy2 <= radius2;
end;
end;
picture = randn(width, height); % test image :)
masked_image = picture .* mask;
imagesc(masked_image);