Contents

Squares Detector

It loads several images sequentially and tries to find squares in each image.

A program using pyramid scaling, Canny, contours, and contour simpification (it's got it all folks) to find squares in a list of images pic*.png. Returns sequence of squares detected on the image.

Sources:

function squares_detector_demo()

loop over sequence of images

    names = cv.glob(fullfile(mexopencv.root(), 'test', 'pic*.png'));
    for i=1:numel(names)

load image

        img = cv.imread(names{i}, 'Color',true);

find and draw squares (TODO: apply something like groupRectangles)

        squares = findSquares(img);
        if true
            img = cv.polylines(img, squares, 'Closed',true, ...
                'Color',[255 0 255], 'Thickness',2, 'LineType','AA');
        else
            img = cv.drawContours(img, squares, ...
                'Color',[255 0 255], 'Thickness',2, 'LineType','AA');
        end

show result

        [~,fname,ext] = fileparts(names{i});
        figure, imshow(img), title([fname ext])
    end
end

Helper functions

function squares = findSquares(img)
    if true
        % down-scale and upscale the image to filter out the noise
        sz = size(img);
        img = cv.pyrDown(img, 'DstSize',fix(sz([2 1])/2));
        img = cv.pyrUp(img, 'DstSize',sz([2 1]));
    else
        img = cv.GaussianBlur(img, 'KSize',[5 5]);
    end

    % find squares in every color plane of the image
    squares = {};
    for ch=1:size(img,3)
        % try several threshold levels
        N = 11;
        thresh = 50;
        for t=1:N
            if t == 1
                % apply Canny (helps to catch squares with gradient shading).
                % Take the upper threshold from slider and set the lower to 0
                % (which forces edges merging)
                gray = cv.Canny(img(:,:,ch), [0 thresh], 'ApertureSize',5);
                % dilate to remove potential holes between edge segments
                gray = cv.dilate(gray);
            else
                % apply threshold if not first threshold level
                gray = cv.threshold(img(:,:,ch), t*255/N, 'Type','Binary');
            end

            % find and test each contour
            contours = cv.findContours(gray, 'Mode','List', 'Method','Simple');
            for i=1:numel(contours)
                % approximate contour with accuracy proportional to its perimeter
                alen = cv.arcLength(contours{i}, 'Closed',true);
                approx = cv.approxPolyDP(contours{i}, ...
                    'Epsilon',alen*0.02, 'Closed',true);

                % to be a square/rectangle, a contour should have:
                %
                % * 4 vertices after approximation,
                % * relatively large area (to filter out noisy contours),
                % * be convex.
                %
                % Note: we take absolute value of area because it can be
                % positive or negative, according to contour orientation
                if numel(approx) == 4 && ...
                    abs(cv.contourArea(approx)) > 1000 && ...
                    cv.isContourConvex(approx)
                    % find the maximum cosine of angles between joint edges
                    idx0 = 2:4;                % current
                    idx1 = modulu4(idx0 + 1);  % next
                    idx2 = modulu4(idx0 - 1);  % previous
                    maxCosine = max(abs(cellfun(@cosineAngle, ...
                        approx(idx0), approx(idx1), approx(idx2))));

                    % if cosines of all angles are small (~90 degree angles),
                    % then write quandrange vertices to resultant sequence
                    if maxCosine < 0.3  % acosd(0.3) ~ 72.5 degrees
                        squares{end+1} = approx;
                    end
                end
            end
        end
    end
end
function cosine = cosineAngle(pt0, pt1, pt2)
    % find cosine of angle between vectors from pt0->pt1 and from pt0->pt2
    % cos(theta) = a.b / ||a|| * ||b||
    vec1 = (pt1 - pt0);
    vec2 = (pt2 - pt0);
    cosine = dot(vec1, vec2) / (norm(vec1) * norm(vec2));
end
function idx = modulu4(idx)
    idx = mod(idx - 1, 4) + 1;
end