Fit ellipses demo

This program is demonstration for ellipse fitting. The program finds contours and approximate them by ellipses using one of three methods:

  1. OpenCV's original method which implements Fitzgibbon 1995 method.
  2. The Approximate Mean Square (AMS) method proposed by Taubin 1991.
  3. The Direct least square (Direct) method proposed by Fitzgibbon 1999.

Trackbar specify threshold parameter.

White lines are contours points. Red lines are fitting ellipses.

Sources:

function varargout = fitellipse_demo_gui(im)
    % load source image
    if nargin < 1
        im = fullfile(mexopencv.root(),'test','ellipses.jpg');
        src = cv.imread(im, 'Grayscale',true);
    elseif ischar(im)
        src = cv.imread(im, 'Grayscale',true);
    else
        src = im;
    end
    % we expect a grayscale image
    if size(src,3) == 3, src = cv.cvtColor(src, 'RGB2GRAY'); end

    % create the UI
    h = buildGUI(src);
    if nargout > 0, varargout{1} = h; end
end

function onChange(~,~,h)
    %ONCHANGE  Event handler for UI controls

    % retrieve current values from UI controls
    algIdx = get(h.pop, 'Value');
    algs = get(h.pop, 'String');
    thresh = round(get(h.slid, 'Value'));
    set(h.txt, 'String',sprintf('Threshold: %3d',thresh));

    % threshold image and find contours
    bimg = uint8(cv.blur(h.src) >= thresh) * 255;
    contours = cv.findContours(bimg, 'Mode','List', 'Method','None');

    % filter out contours that are too simple, probably not an ellipse
    % (note: fitEllipse requires at least 5 points)
    contours(cellfun(@numel, contours) < 50) = [];

    % {{[x y], [x y], ..}, ..} -> {[x y; x y; ..], ..}
    contours = cellfun(@(c) cat(1,c{:}), contours, 'UniformOutput',false);

    % draw all contours points
    if true
        cimg = cv.cvtColor(h.src * 0.3, 'GRAY2RGB');
    else
        cimg = zeros([size(bimg) 3], 'uint8');
    end
    cimg = cv.drawContours(cimg, contours, 'Color',[255 255 255]);

    % for each contour
    for i=1:numel(contours)
        % approximate by an ellipse
        rrect = cv.fitEllipse(contours{i}, 'Method',algs{algIdx});
        if max(rrect.size) > min(rrect.size)*30
            % skip if rectangle is too tall/wide
            continue;
        end

        % draw ellipse
        if true
            cimg = cv.ellipse(cimg, rrect, 'Color',[255 0 0], 'LineType','AA');
        else
            cimg = cv.ellipse(cimg, rrect.center, rrect.size*0.5, ...
                'Angle',rrect.angle, 'Color',[255 255 0], 'LineType','AA');
        end

        % draw rotated rectangle of ellipse
        vtx = cv.RotatedRect.points(rrect);
        cimg = cv.line(cimg, vtx(1:4,:), vtx([2:4 1],:), ...
            'Color',[0 255 0], 'LineType','AA');
    end

    % show result
    set(h.img, 'CData',cimg);
    drawnow;
end

function h = buildGUI(img)
    %BUILDGUI  Creates the UI

    % parameters
    thresh = 70;
    max_thresh = 255;
    sz = size(img);
    sz(2) = max(sz(2), 250);  % minimum figure width

    % build the user interface (no resizing to keep it simple)
    h = struct();
    h.src = img;
    h.fig = figure('Name','Ellipse Fit', ...
        'NumberTitle','off', 'Menubar','none', 'Resize','off', ...
        'Position',[200 200 sz(2) sz(1)+29]);
    if ~mexopencv.isOctave()
        %HACK: not implemented in Octave
        movegui(h.fig, 'center');
    end
    h.ax = axes('Parent',h.fig, ...
        'Units','pixels', 'Position',[1 30 sz(2) sz(1)]);
    if ~mexopencv.isOctave()
        h.img = imshow(img, 'Parent',h.ax);
    else
        %HACK: https://savannah.gnu.org/bugs/index.php?45473
        axes(h.ax);
        h.img = imshow(img);
    end
    h.pop = uicontrol('Parent',h.fig, 'Style','popupmenu', ...
       'Position',[5 5 70 20], 'String',{'Linear', 'Direct', 'AMS'});
    h.txt = uicontrol('Parent',h.fig, 'Style','text', 'FontSize',11, ...
        'Position',[75 5 120 20], 'String',sprintf('Threshold: %3d',thresh));
    h.slid = uicontrol('Parent',h.fig, 'Style','slider', 'Value',thresh, ...
        'Min',1, 'Max',max_thresh, 'SliderStep',[1 10]./(max_thresh-1), ...
        'Position',[200 5 sz(2)-200-5 20]);

    % hook event handlers, and trigger default start
    set([h.slid, h.pop], 'Callback',{@onChange,h}, ...
        'Interruptible','off', 'BusyAction','cancel');
    onChange([],[],h);
end