How do I fit an ellipse to a binary shape while staying withing its boundaries?

27 次查看(过去 30 天)
I have a binary image (included with decsription, titled single_mask.tif) which containts a single blob. I am trying to fit an ellipse to this shape in such a way that it takes up as much of the blob as possible while staying within its boundaries. I included another image showing a hand-drawn ellipse that reflects more or less what I would want to achieve through the algorithm alone.
I also attached some code that I have been working on but have not had any success yet. I am trying to use lsqnonlin to fit the variables making up an ellipse (centroid indeces, angle, and small/large axes) based on a residual function that uses a fitness value which gets very large if any pixels outside of the shape are included in the fit and gets small if more pixels from inside the shape are included in the fit. The algorithm does not work in its current state since it does not even change the variables from their starting values.
Any advice on this problem would be greatly appreciated.
Code:
clear;
close all; clc;
%%
single_mask = logical(tiffreadVolume('single_mask.tif'));
%%
surf_fcn = @(B, image_mask) comp_img2el(B,image_mask);
% Create a residual function
res_fcn = @(B,im_mask,fcn) fcn(B,im_mask);
% Define starting values for variables
Xg = size(single_mask,2)/2;
Yg = size(single_mask,1)/2;
SMA_Lg = 10;
SMA_Sg = 10;
Angleg = 0;
B_guess = [Xg,Yg,SMA_Lg,SMA_Sg,Angleg];
% Fitting
B2 = lsqnonlin(@(B) res_fcn(B,single_mask,surf_fcn),B_guess);
% Extracting fit variables
Xfit = B2(1);
Yfit = B2(2);
SMA_Lfit = B2(3);
SMA_Sfit = B2(4);
Anglefit = B2(5);
% Plotting
figure;
imshow(single_mask,[]);
hold on
h = drawellipse('Center',[Xfit Yfit],'SemiAxes',[SMA_Lfit SMA_Sfit], ...
'RotationAngle',Anglefit);
hold off
% Fitness function
function fitness_val = comp_img2el(B,image_mask)
X = B(1);
Y = B(2);
SMA_L = B(3);
SMA_S = B(4);
Angle = B(5);
figure;
imshow(image_mask,[]);
hold on
% Draw an ellipse based on variables in B
h = drawellipse('Center',[X Y],'SemiAxes',[SMA_L SMA_S], ...
'RotationAngle',Angle);
hold off
ellipse_mask = createMask(h);
close all;
% Identify fitness value by setting area outside of the blob to a high
% number and adding the inverse of the area within the blob and ellipse
image_mask_comp = imcomplement(image_mask);
sum_error = sum(0.1*image_mask_comp(ellipse_mask));
fitness_val = 1/sum(ellipse_mask(:)) + sum_error;
end

回答(1 个)

Milan Bansal
Milan Bansal 2023-9-21
编辑:Milan Bansal 2023-9-21
Hi,
As per my understanding, you want to fit an ellipse inside the boundary of an object in a binary image and are facing issues with it.
This can be achieved by following the steps given below:
1. Read the image and convert it into a binary image.
I = imread('single_mask.tif');
% I = rgb2gray(I); uncomment if the imported image is in RGB format
binaryImage = imbinarize(I);
2. Extract the boundary of the object using "bwboundaries" function in MATLAB.
boundary = bwboundaries(binaryImage);
boundary = boundary{1};
3. Create a mask using "poly2mask" function.
mask = poly2mask(boundary(:, 2), boundary(:, 1), size(binaryImage, 1), size(binaryImage, 2));
4. Extract the properties of the mask such as Orientation, Centroid, Length of Minor and Major axes using "regionprops" function.
props = regionprops(mask, 'Centroid', 'MajorAxisLength', 'MinorAxisLength', 'Orientation');
centroid = props.Centroid;
majorAxis = props.MajorAxisLength / 2;
minorAxis = props.MinorAxisLength / 2;
orient = deg2rad(-props.Orientation);
5. Generate the coordinates of the Ellipse using the properties extracted in the previous step.
angle = linspace(0, 2 * pi, 100);
ellX = centroid(1) + majorAxis * cos(angle) * cos(orient) - minorAxis * sin(angle) * sin(orient);
ellY = centroid(2) + majorAxis * cos(angle) * sin(orient) + minorAxis * sin(angle) * cos(orient);
6. Plot the image and the generated ellipse.
figure;
imshow(binaryImage);
hold on;
plot(ellX, ellY, 'g', 'LineWidth', 1);
hold off;
Refer to the documentation links given below to learn more about the "bwboundaries", "poly2mask" and "regionprops" functions.
Hope it helps!
  1 个评论
DGM
DGM 2023-9-21
编辑:DGM 2023-9-21
There's no obvious reason to use bwboundaries to calculate the object boundary and then re-generate the object from its boundary -- but I'm sure ChatGPT would have included that because the question mentioned "boundary".
On its face, this unnecessary routine seems like it would accomplish nothing. You start with a solid blob, you get its boundary, and then you use the boundary to get the solid blob again. Even if it's useless, that seems harmless enough. In practice, that's not what happens. Due to the ways that bwboundaries() and poly2mask() handle pixel coordinates, you end up shrinking the proximal edges of the blob away from the image origin.
binaryImage = imread('badsfkj.png');
boundary = bwboundaries(binaryImage);
boundary = boundary{1};
mask = poly2mask(boundary(:, 2), boundary(:, 1), size(binaryImage, 1), size(binaryImage, 2));
imshow(imfuse(binaryImage,mask),'border','tight')
So this roundabout bit of filler code isn't so much useless as it is strictly counterproductive.
Disregarding that bit, this answer doesn't actually answer the question that was asked. The question asks for the largest inscribed ellipse. This answer gives the ellipse with the same normalized area moment. These are not the same things. The fit ellipse described by regionprops is not inscribed. That's plainly visible from your example. Here's something a bit more obvious.
I = imread('blobgrow0.png');
binaryImage = imbinarize(I);
boundary = bwboundaries(binaryImage);
boundary = boundary{1};
mask = poly2mask(boundary(:, 2), boundary(:, 1), size(binaryImage, 1), size(binaryImage, 2));
props = regionprops(mask, 'Centroid', 'MajorAxisLength', 'MinorAxisLength', 'Orientation');
centroid = props.Centroid;
majorAxis = props.MajorAxisLength / 2;
minorAxis = props.MinorAxisLength / 2;
orient = deg2rad(-props.Orientation);
angle = linspace(0, 2 * pi, 100);
ellX = centroid(1) + majorAxis * cos(angle) * cos(orient) - minorAxis * sin(angle) * sin(orient);
ellY = centroid(2) + majorAxis * cos(angle) * sin(orient) + minorAxis * sin(angle) * cos(orient);
imshow(binaryImage);
hold on;
plot(ellX, ellY, 'g', 'LineWidth', 1);
hold off;
Before anyone objects that this would have worked fine for convex blobs, no it won't.
These two different goals only approach equivalence when the blob is a large perfect ellipse. There's usually little merit in finding solutions which are strictly overestimates suited only for the most trivial cases.
See also:
... maybe that explains why nobody answered this question.

请先登录,再进行评论。

类别

Help CenterFile Exchange 中查找有关 Data Clustering 的更多信息

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by