gusucode.com > vision工具箱matlab源码程序 > vision/+vision/+internal/+calibration/+checkerboard/Checkerboard.m

    %#codegen

classdef Checkerboard < handle
    properties(GetAccess=public, SetAccess=public)
        isValid = false;
        Energy = single(inf);        
        BoardCoords;
        BoardIdx;
    end
    
    properties(Access=private)        
        Points;      
        IsDirectionBad = false(1, 4);
        LastExpandDirection = 1; %'up'
        PreviousEnergy = single(inf);
    end
    
    methods        
        function this = Checkerboard()
            this.BoardIdx = 0;
            this.BoardIdx = zeros(3, 3);      
            this.BoardCoords = 0;
            this.BoardCoords = zeros(3, 3, 2);  
        end
        
        function initialize(this, seedIdx, points, v1, v2)
            % Constructor. Creates a 4x4 checkerboard with the seed point
            % in the center. points is an Mx2 matrix of x,y coordinates of
            % all possible checkerboard corners. seedIdx is the index of
            % the seed point. The coordinates of the seed point are
            % points(seedIdx, :). e1 and e2 are 2-element vectors
            % specifying the edge orientations at the seed point.
            
            this.BoardIdx = 0;
            this.BoardIdx = zeros(3, 3);                                
            this.IsDirectionBad = false(1, 4);
            this.BoardCoords = 0;
            this.BoardCoords = zeros(3, 3, 2);            
            this.Points = points;
            center = this.Points(seedIdx, :);            
            this.BoardIdx(2, 2) = seedIdx;
            this.BoardCoords(2, 2, :) = center;
            this.LastExpandDirection = 1; %'up';
            this.PreviousEnergy = single(inf);
            this.isValid = false;
            
            % compute distances from all the points to the center
            pointVectors = bsxfun(@minus, this.Points, center);
            euclideanDists = hypot(pointVectors(:, 1), pointVectors(:, 2));
            
            % find vertical and horizontal neighbors
            [this.BoardIdx(2, 3)] = findNeighbor(this, pointVectors, euclideanDists, v1);
            [this.BoardIdx(2, 1)] = findNeighbor(this, pointVectors, euclideanDists, -v1);            
            [this.BoardIdx(3, 2)] = findNeighbor(this, pointVectors, euclideanDists, v2);            
            [this.BoardIdx(1, 2)] = findNeighbor(this, pointVectors, euclideanDists, -v2);
            
            if any(this.BoardIdx(:) < 0)
                this.isValid = false;
                return;
            end
            
            r = this.Points(this.BoardIdx(2, 3), :);
            this.BoardCoords(2, 3, :) = r;
            l = this.Points(this.BoardIdx(2, 1), :);
            this.BoardCoords(2, 1, :) = l;
            d = this.Points(this.BoardIdx(3, 2), :);
            this.BoardCoords(3, 2, :) = d;
            u = this.Points(this.BoardIdx(1,2), :);
            this.BoardCoords(1, 2, :) = u;
                       
            % find diagonal neighbors
            up    = u - center;
            down  = d - center;
            right = r - center;
            left  = l - center;
            
            [this.BoardIdx(1, 1)] = findNeighbor(this, pointVectors, euclideanDists, up + left);
            [this.BoardIdx(3, 1)] = findNeighbor(this, pointVectors, euclideanDists, down + left);
            [this.BoardIdx(3, 3)] = findNeighbor(this, pointVectors, euclideanDists, down + right);
            [this.BoardIdx(1, 3)] = findNeighbor(this, pointVectors, euclideanDists, up + right);
            this.isValid = all(this.BoardIdx(:) > 0);
            if ~this.isValid
                return;
            end
            
            this.BoardCoords(1, 1, :) = this.Points(this.BoardIdx(1, 1), :);
            this.BoardCoords(3, 1, :) = this.Points(this.BoardIdx(3, 1), :);
            this.BoardCoords(3, 3, :) = this.Points(this.BoardIdx(3, 3), :);
            this.BoardCoords(1, 3, :) = this.Points(this.BoardIdx(1, 3), :);
            
            this.Energy = computeInitialEnergy(this);
            % a perfect initial board should have the energy of -9.
            maxEnergy = -7;
            this.isValid = this.Energy < maxEnergy;                        
        end
    end

    methods(Access=private)
        %------------------------------------------------------------------
        function e = computeInitialEnergy(this)
            if any(this.BoardIdx(:) < 0)
                e = single(inf);
                return;
            end
            
            e = single(0);
            
            % compute energy over rows
            row1 = this.getPoints(1, 1:3);
            row2 = this.getPoints(2, 1:3);
            row3 = this.getPoints(3, 1:3);
            
            num = row1 + row3 - 2 * row2;
            denom = row1 - row3;
            e = max(e, max(hypot(num(:, 1), num(:, 2)) ./ hypot(denom(:, 1), denom(:, 2))));
            
            % compute energy over columns
            col1 = this.getPoints(1:3, 1);
            col2 = this.getPoints(1:3, 2);
            col3 = this.getPoints(1:3, 3);
            
            num = col1 + col3 - 2 * col2;
            denom = col1 - col3;
            e = max(e, max(hypot(num(:, 1), num(:, 2)) ./ hypot(denom(:, 1), denom(:, 2))));
            
            boardSize = single(numel(this.BoardIdx));
            e = boardSize * e - boardSize;
        end
    end
    
    methods
        %------------------------------------------------------------------
        function this = expandBoardFully(this)
            % expands the board as far as possible
            if ~this.isValid
                return;
            end
            
            hasExpanded = true;
            while hasExpanded
                hasExpanded = this.expandBoardOnce();
            end
        end
        
        %------------------------------------------------------------------
        function plot(this)
            % plot the detected checkerboard points
            idx = this.BoardIdx';
            idx = idx(idx > 0);
            points = this.Points(idx, :);
            plot(points(:, 1), points(:, 2), 'r*-'); hold on;
            text(this.BoardCoords(1, 1, 1), this.BoardCoords(1, 1, 2), '(0,0)', 'Color', [1 0 0]);
        end
        
        %------------------------------------------------------------------
        function [points, boardSize] = toPoints(this)
            % returns the points as an Mx2 matrix of x,y coordinates, and
            % the size of the board
            
            if any(this.BoardIdx(:) == 0)
                points = [];
                boardSize = [0 0];
                return;
            end
            
            numPoints = size(this.BoardCoords, 1) * size(this.BoardCoords, 2);
            points = zeros(numPoints, 2);
            x = this.BoardCoords(:, :, 1)';
            points(:, 1) = x(:);
            y = this.BoardCoords(:, :, 2)';
            points(:, 2) = y(:);
            boardSize = [size(this.BoardCoords, 2)+1, size(this.BoardCoords, 1)+1];
        end
    end
    
    methods(Access=private)        
        %------------------------------------------------------------------
        function neighborIdx = findNeighbor(this, pointVectors, euclideanDists, v)
            % find the nearest neighbor point in the direction of vector v
            
            % compute normalized dot products
            angleCosines = pointVectors * v' ./ (euclideanDists * hypot(v(1), v(2)));
            
            % dists is a linear combination of euclidean distances and
            % "directional distances"
            dists = euclideanDists + 1.5 * euclideanDists .* (1 - angleCosines);
            
            % eliminate points already in the board
            dists(this.BoardIdx(this.BoardIdx > 0)) = inf; 
            
            % eliminate points "behind" the center
            dists(angleCosines < 0) = inf;
            
            % find the nearest neighbor
            [dirDist, neighborIdx] = min(dists);
            if isinf(dirDist)
                neighborIdx = -1;
            end
        end
                        
        %------------------------------------------------------------------
        function p = getPoints(this, i, j)
            p = single(this.Points(this.BoardIdx(i, j), :));
        end
        
        %------------------------------------------------------------------
        function success = expandBoardOnce(this)
            %directions = {'up', 'down', 'left', 'right'};      
            %directions = [1 2 3 4];
            this.PreviousEnergy = this.Energy;            
            for i = 1:4
                if ~this.IsDirectionBad(i)
                    this.LastExpandDirection = i;
                    expandBoardDirectionally(this, i);
                    if this.Energy < this.PreviousEnergy
                        success = true;
                        return;
                    else
                        this.undoLastExpansion();
                        this.IsDirectionBad(i) = true;
                    end
                end
            end                
            success = false;
        end
        
        %------------------------------------------------------------------
        function undoLastExpansion(this)
            this.Energy = this.PreviousEnergy;            
            switch this.LastExpandDirection
                case 1 %'up'
                    this.BoardIdx = this.BoardIdx(2:end, :);
                    this.BoardCoords = this.BoardCoords(2:end, :, :);

                case 2 %'down'
                    this.BoardIdx = this.BoardIdx(1:end-1, :);
                    this.BoardCoords = this.BoardCoords(1:end-1, :, :);
                    
                case 3 %'left'
                    this.BoardIdx = this.BoardIdx(:, 2:end);
                    this.BoardCoords = this.BoardCoords(:, 2:end, :);
                    
                case 4 %'right'
                    this.BoardIdx = this.BoardIdx(:, 1:end-1);
                    this.BoardCoords = this.BoardCoords(:, 1:end-1, :);
            end
        end        
                
        %------------------------------------------------------------------
        function expandBoardDirectionally(this, direction)
            oldEnergy = (this.Energy + numel(this.BoardIdx)) / numel(this.BoardIdx);
            switch direction
                case 1 %'up'
                    idx = 1:3;
                    predictedPoints = predictPointsVertical(this, idx);
                    newIndices = findClosestIndices(this, predictedPoints);                    
                    [this.BoardIdx, this.BoardCoords] = expandBoardUp(this, newIndices);
                    newEnergy = computeNewEnergyVertical(this, idx, oldEnergy);
                    
                case 2 %'down'
                    numRows = size(this.BoardCoords, 1);
                    idx = numRows:-1:numRows-2;
                    predictedPoints = predictPointsVertical(this, idx);
                    newIndices = findClosestIndices(this, predictedPoints);
                    [this.BoardIdx, this.BoardCoords] = expandBoardDown(this, newIndices); 
                    idx = idx + 1;
                    newEnergy = computeNewEnergyVertical(this, idx, oldEnergy);
                                     
                case 3 %'left'
                    idx = 1:3;
                    predictedPoints = predictPointsHorizontal(this, idx);
                    newIndices = findClosestIndices(this, predictedPoints);
                    [this.BoardIdx, this.BoardCoords] = expandBoardLeft(this, newIndices);     
                    newEnergy = computeNewEnergyHorizontal(this, idx, oldEnergy);
                                     
                case 4 %'right'
                    numCols = size(this.BoardCoords, 2);
                    idx = numCols:-1:numCols-2;
                    predictedPoints = predictPointsHorizontal(this, idx);
                    newIndices = findClosestIndices(this, predictedPoints);                    
                    [this.BoardIdx, this.BoardCoords] = expandBoardRight(this, newIndices);  
                    idx = idx + 1;
                    newEnergy = computeNewEnergyHorizontal(this, idx, oldEnergy);  
                otherwise
                    newEnergy = single(inf);
                    
            end
            
            this.Energy = newEnergy;
        end
        
        %------------------------------------------------------------------
        function newPoints = predictPointsVertical(this, idx)
            p1 = squeeze(this.BoardCoords(idx(2), :, :));
            p2 = squeeze(this.BoardCoords(idx(1), :, :));
            newPoints = p2 + p2 - p1;
        end
        
        %------------------------------------------------------------------
        function newPoints = predictPointsHorizontal(this, idx)
            p1 = squeeze(this.BoardCoords(:, idx(2), :));
            p2 = squeeze(this.BoardCoords(:, idx(1), :));
            newPoints = p2 + p2 - p1;
        end
        
        %------------------------------------------------------------------
        function indices = findClosestIndices(this, predictedPoints)
            % returns indices of points closest to the predicted points
            
            indices = zeros(1, size(predictedPoints, 1));
            for i = 1:size(predictedPoints, 1)
                p = predictedPoints(i, :);
                diffs = bsxfun(@minus, this.Points, p);
                dists = hypot(diffs(:, 1), diffs(:, 2));
                dists(indices(indices > 0)) = inf;
                [~, indices(i)] = min(dists);
            end
        end
        
        %------------------------------------------------------------------
        function [newBoard, newBoardCoords] = expandBoardUp(this, indices)
            newBoard = zeros(size(this.BoardIdx, 1)+1, size(this.BoardIdx, 2));
            newBoard(1, :) = indices;
            newBoard(2:end, :) = this.BoardIdx;
            
            newBoardCoords = zeros(size(this.BoardCoords, 1)+1, ...
                size(this.BoardCoords, 2), size(this.BoardCoords, 3));
            newBoardCoords(1, :, :) = this.Points(indices, :);
            newBoardCoords(2:end, :, :) = this.BoardCoords;
        end

        %------------------------------------------------------------------
        function [newBoard, newBoardCoords] = expandBoardDown(this, indices)
            newBoard = zeros(size(this.BoardIdx, 1)+1, size(this.BoardIdx, 2));
            newBoard(end, :) = indices;
            newBoard(1:end-1, :) = this.BoardIdx;
            
            newBoardCoords = zeros(size(this.BoardCoords, 1)+1, ...
                size(this.BoardCoords, 2), size(this.BoardCoords, 3));
            newBoardCoords(end, :, :) = this.Points(indices, :);
            newBoardCoords(1:end-1, :, :) = this.BoardCoords;
        end

        %------------------------------------------------------------------
        function [newBoard, newBoardCoords] = expandBoardLeft(this, indices)
            newBoard = zeros(size(this.BoardIdx, 1), 1 + size(this.BoardIdx, 2));
            newBoard(:, 1) = indices;
            newBoard(:, 2:end) = this.BoardIdx;
            
            newBoardCoords = zeros(size(this.BoardCoords, 1), ...
                size(this.BoardCoords, 2) + 1, size(this.BoardCoords, 3));
            newBoardCoords(:, 1, :) = this.Points(indices, :);
            newBoardCoords(:, 2:end, :) = this.BoardCoords;            
        end
        
        %------------------------------------------------------------------
        function [newBoard, newBoardCoords] = expandBoardRight(this, indices)
            newBoard = zeros(size(this.BoardIdx, 1), 1 + size(this.BoardIdx, 2));
            newBoard(:, end) = indices;
            newBoard(:, 1:end-1) = this.BoardIdx;
            
            newBoardCoords = zeros(size(this.BoardCoords, 1), ...
                size(this.BoardCoords, 2) + 1, size(this.BoardCoords, 3));
            newBoardCoords(:, end, :) = this.Points(indices, :);
            newBoardCoords(:, 1:end-1, :) = this.BoardCoords;                        
        end        
        
        %------------------------------------------------------------------
        function newEnergy = computeNewEnergyVertical(this, idx, oldEnergy)
            num = squeeze(this.BoardCoords(idx(1),:,:) + this.BoardCoords(idx(3),:,:) ...
                - 2*this.BoardCoords(idx(2),:,:));
            denom = squeeze(this.BoardCoords(idx(1),:,:) - this.BoardCoords(idx(3),:,:));
            newEnergy = max(oldEnergy, ...
                max(hypot(num(:, 1), num(:,2)) ./ hypot(denom(:, 1), denom(:, 2))));
            
            for i = 1:size(this.BoardCoords, 2)-2
                num = this.BoardCoords(idx(1), i, :) + this.BoardCoords(idx(1), i+2, :)...
                    - 2*this.BoardCoords(idx(1), i+1, :);
                denom = this.BoardCoords(idx(1), i, :) - this.BoardCoords(idx(1),i+2,:);
                newEnergy = max(newEnergy, norm(num(:)) ./ norm(denom(:)));
            end
            newEnergy = newEnergy * numel(this.BoardIdx) - numel(this.BoardIdx);
        end
        
        %------------------------------------------------------------------
        function newEnergy = computeNewEnergyHorizontal(this, idx, oldEnergy)
            num = squeeze(this.BoardCoords(:,idx(1),:) + this.BoardCoords(:,idx(3),:) ...
                - 2*this.BoardCoords(:,idx(2),:));
            denom = squeeze(this.BoardCoords(:,idx(1),:) - this.BoardCoords(:,idx(3),:));
            newEnergy = max(oldEnergy, ...
                max(hypot(num(:, 1), num(:,2)) ./ hypot(denom(:, 1), denom(:, 2))));
            
            for i = 1:size(this.BoardCoords, 1)-2
                num = this.BoardCoords(i, idx(1), :) + this.BoardCoords(i+2, idx(1), :)...
                    - 2*this.BoardCoords(i+1, idx(1), :);
                denom = this.BoardCoords(i, idx(1), :) - this.BoardCoords(i+2,idx(1),:);
                newEnergy = max(newEnergy, norm(num(:)) ./ norm(denom(:)));
            end
            newEnergy = newEnergy * numel(this.BoardIdx) - numel(this.BoardIdx);
        end        
    end
end