gusucode.com > vision工具箱matlab源码程序 > vision/estimateGeometricTransform.m

    function [tform, inlierPoints1, inlierPoints2, status] ...
    = estimateGeometricTransform(matchedPoints1, matchedPoints2, ...
    transformType, varargin)
%estimateGeometricTransform Estimate geometric transformation from matching point pairs.
%   tform = estimateGeometricTransform(matchedPoints1,matchedPoints2,
%   transformType) returns a 2-D geometric transform which maps the inliers
%   in matchedPoints1 to the inliers in matchedPoints2. matchedPoints1 and 
%   matchedPoints2 can be M-by-2 matrices of [x,y] coordinates or objects of
%   any of the <a href="matlab:helpview(fullfile(docroot,'toolbox','vision','vision.map'),'pointfeaturetypes')">point feature types</a>. transformType can be 'similarity', 'affine', 
%   or 'projective'. Outliers in matchedPoints1 and matchedPoints2 are 
%   excluded by using the M-estimator SAmple Consensus (MSAC) algorithm. 
%   The MSAC algorithm is a variant of the Random Sample Consensus (RANSAC)
%   algorithm. The returned tform is an affine2d object if transformType is
%   set to 'similarity' or 'affine', and is a projective2d object otherwise.
%
%   [tform,inlierPoints1,inlierPoints2] = estimateGeometricTransform(...)
%   additionally returns the corresponding inlier points in inlierPoints1
%   and inlierPoints2.
%
%   [tform,inlierPoints1,inlierPoints2,status] =
%   estimateGeometricTransform(...) additionally returns a status code with
%   the following possible values:
% 
%     0: No error. 
%     1: matchedPoints1 and matchedPoints2 do not contain enough points.
%     2: Not enough inliers have been found.
%
%   When the STATUS output is not given, the function will throw an error
%   if matchedPoints1 and matchedPoints2 do not contain enough points or
%   if not enough inliers have been found.
%
%   [...] = estimateGeometricTransform(matchedPoints1,matchedPoints2, 
%   transformType,Name,Value) specifies additional
%   name-value pair arguments described below:
%
%   'MaxNumTrials'        A positive integer scalar specifying the maximum
%                         number of random trials for finding the inliers.
%                         Increasing this value will improve the robustness
%                         of the output at the expense of additional
%                         computation.
% 
%                         Default value: 1000
%  
%   'Confidence'          A numeric scalar, C, 0 < C < 100, specifying the
%                         desired confidence (in percentage) for finding
%                         the maximum number of inliers. Increasing this
%                         value will improve the robustness of the output
%                         at the expense of additional computation.
%
%                         Default value: 99
% 
%   'MaxDistance'         A positive numeric scalar specifying the maximum
%                         distance in pixels that a point can differ from
%                         the projection location of its associated point.
% 
%                         Default value: 1.5
% 
%   Class Support
%   -------------
%   matchedPoints1 and matchedPoints2 can be double, single, or any of the
%   <a href="matlab:helpview(fullfile(docroot,'toolbox','vision','vision.map'),'pointfeaturetypes')">point feature types</a>.
%
%   % EXAMPLE: Recover a transformed image using SURF feature points
%   % --------------------------------------------------------------
%   Iin  = imread('cameraman.tif'); imshow(Iin); title('Base image');
%   Iout = imresize(Iin, 0.7); Iout = imrotate(Iout, 31);
%   figure; imshow(Iout); title('Transformed image');
%  
%   % Detect and extract features from both images
%   ptsIn  = detectSURFFeatures(Iin);
%   ptsOut = detectSURFFeatures(Iout);
%   [featuresIn,   validPtsIn] = extractFeatures(Iin,  ptsIn);
%   [featuresOut, validPtsOut] = extractFeatures(Iout, ptsOut);
%  
%   % Match feature vectors
%   indexPairs = matchFeatures(featuresIn, featuresOut);
%   matchedPtsIn  = validPtsIn(indexPairs(:,1));
%   matchedPtsOut = validPtsOut(indexPairs(:,2));
%   figure; showMatchedFeatures(Iin,Iout,matchedPtsIn,matchedPtsOut);
%   title('Matched SURF points, including outliers');
%  
%   % Exclude the outliers and compute the transformation matrix
%   [tform,inlierPtsOut,inlierPtsIn] = estimateGeometricTransform(...
%        matchedPtsOut,matchedPtsIn,'similarity');
%   figure; showMatchedFeatures(Iin,Iout,inlierPtsIn,inlierPtsOut);
%   title('Matched inlier points');
%  
%   % Recover the original image Iin from Iout
%   outputView = imref2d(size(Iin));
%   Ir = imwarp(Iout, tform, 'OutputView', outputView);
%   figure; imshow(Ir); title('Recovered image');
%
% See also fitgeotrans, cornerPoints, SURFPoints, MSERRegions, BRISKPoints,
%          detectMinEigenFeatures, detectFASTFeatures, detectSURFFeatures,
%          detectMSERFeatures, detectBRISKFeatures, extractFeatures,
%          matchFeatures, imwarp

% References:
% [1] R. Hartley, A. Zisserman, "Multiple View Geometry in Computer
%     Vision," Cambridge University Press, 2003.
% [2] P. H. S. Torr and A. Zisserman, "MLESAC: A New Robust Estimator
%     with Application to Estimating Image Geometry," Computer Vision
%     and Image Understanding, 2000.

% Copyright  The MathWorks, Inc.

%#codegen
%#ok<*EMCA>

% List of status codes
statusCode = struct(...
    'NoError',           int32(0),...
    'NotEnoughPts',      int32(1),...
    'NotEnoughInliers',  int32(2));

% Parse and check inputs
[points1, points2, ransacParams, sampleSize, status, classToUse] = parseInputs(...
    statusCode, matchedPoints1, matchedPoints2, transformType, varargin{:});

% return identity matrix in case of failure
failedMatrix = eye([3,3], classToUse);

% Compute the geometric transformation
if status == statusCode.NoError
    ransacFuncs = getRansacFunctions(sampleSize);
    points = cast(cat(3, points1, points2), classToUse);
    
    [isFound, tmatrix, inliers] = vision.internal.ransac.msac(...
        points, ransacParams, ransacFuncs);
    
    if ~isFound
        status = statusCode.NotEnoughInliers;        
    end   
    
    % Do an extra check to verify the tform matrix. Check if matrix is 
    % singular or contains infs or nans.
    if isequal(det(tmatrix),0) || any(~isfinite(tmatrix(:)))
        status = statusCode.NotEnoughInliers;
        tmatrix = failedMatrix;
    end
else
    inliers = false(size(matchedPoints1,1)); % Need to assign a dummy value to satisfy def-before-use analysis.
    tmatrix = failedMatrix;
end

% Extract inlier points
if status == statusCode.NoError
    inlierPoints1 = matchedPoints1(inliers, :);
    inlierPoints2 = matchedPoints2(inliers, :);
else
    inlierPoints1 = matchedPoints1([]);
    inlierPoints2 = matchedPoints2([]);
    tmatrix = failedMatrix;
end

% Report runtime error if the status output is not requested
reportError = (nargout ~= 4);
if reportError
    checkRuntimeStatus(statusCode, status, sampleSize);
end

isSimilarityOrAffine = sampleSize < 4;

if isSimilarityOrAffine
    % Use the 3x2 affine2d syntax to have last column automatically
    % added to tform matrix. This prevents precision issues from
    % propagating downstream.
    tform = affine2d(tmatrix(:,1:2));
else % projective
    tform = projective2d(tmatrix(1:3,1:3));
end

%==========================================================================
% Check runtime status and report error if there is one
%==========================================================================
function checkRuntimeStatus(statusCode, status, sampleSize)
coder.internal.errorIf(status==statusCode.NotEnoughPts, ...
    'vision:points:notEnoughMatchedPts', 'matchedPoints1', ...
    'matchedPoints2', sampleSize);

coder.internal.errorIf(status==statusCode.NotEnoughInliers, ...
    'vision:points:notEnoughInlierMatches', 'matchedPoints1', ...
    'matchedPoints2');

%==========================================================================
% Parse and check inputs
%==========================================================================
function [points1, points2, ransacParams, sampleSize, status, classToUse] = ...
    parseInputs(statusCode, matchedPoints1, matchedPoints2, ...
    transform_type, varargin)

isSimulationMode = isempty(coder.target);
if isSimulationMode
    % Instantiate an input parser
    parser = inputParser;
    parser.FunctionName = 'estimateGeometricTransform';
    
    % Specify the optional parameters
    parser.addParameter('MaxNumTrials', 1000);
    parser.addParameter('Confidence',   99);
    parser.addParameter('MaxDistance',  1.5);
    
    % Parse and check optional parameters
    parser.parse(varargin{:});
    r = parser.Results;
    
    maxNumTrials = r.MaxNumTrials;
    confidence   = r.Confidence;
    maxDistance  = r.MaxDistance;
    
else
    % Instantiate an input parser
    parms = struct( ...
        'MaxNumTrials',       uint32(0), ...
        'Confidence',         uint32(0), ...
        'MaxDistance',        uint32(0));
    
    popt = struct( ...
        'CaseSensitivity', false, ...
        'StructExpand',    true, ...
        'PartialMatching', false);
    
    % Specify the optional parameters
    optarg       = eml_parse_parameter_inputs(parms, popt,...
        varargin{:});
    maxNumTrials = eml_get_parameter_value(optarg.MaxNumTrials,...
        1000, varargin{:});
    confidence   = eml_get_parameter_value(optarg.Confidence,...
        99, varargin{:});
    maxDistance  = eml_get_parameter_value(optarg.MaxDistance,...
        1.5, varargin{:});
end

% Check required parameters
sampleSize = checkTransformType(transform_type);

[points1, points2] = vision.internal.inputValidation.checkAndConvertMatchedPoints(...
    matchedPoints1, matchedPoints2, ...
    mfilename, 'MATCHEDPOINTS1', 'MATCHEDPOINTS2');

status  = checkPointsSize(statusCode, sampleSize, points1);

% Check optional parameters
checkMaxNumTrials(maxNumTrials);
checkConfidence(confidence);
checkMaxDistance(maxDistance);

classToUse = getClassToUse(points1, points2);

ransacParams.maxNumTrials = int32(maxNumTrials);
ransacParams.confidence   = cast(confidence,  classToUse);
ransacParams.maxDistance  = cast(maxDistance, classToUse);
ransacParams.recomputeModelFromInliers = true;

% Must return sampleSize separately, because it stops being a constant
% as soon as it is placed into the struct.
sampleSize = cast(sampleSize,  classToUse);
ransacParams.sampleSize   =  sampleSize;


%==========================================================================
function status = checkPointsSize(statusCode, sampleSize, points1)
if size(points1,1) < sampleSize
    status = statusCode.NotEnoughPts;
else
    status = statusCode.NoError;
end

%==========================================================================
function r = checkMaxNumTrials(value)
validateattributes(value, {'numeric'}, ...
    {'scalar', 'nonsparse', 'real', 'integer', 'positive', 'finite'},...
    'estimateGeometricTransform', 'MaxNumTrials');
r = 1;

%========================================================================== 
function r = checkConfidence(value)
validateattributes(value, {'numeric'}, ...
    {'scalar', 'nonsparse', 'real', 'positive', 'finite', '<', 100},...
    'estimateGeometricTransform', 'Confidence');
r = 1;

%==========================================================================
function r = checkMaxDistance(value)
validateattributes(value, {'numeric'}, ...
    {'scalar', 'nonsparse', 'real', 'positive', 'finite'},...
    'estimateGeometricTransform', 'MaxDistance');
r = 1;

%==========================================================================
function sampleSize = checkTransformType(value)
list = {'similarity', 'affine', 'projective'};
validatestring(value, list, 'estimateGeometricTransform', ...
    'TransformType');

switch(lower(value(1)))
    case 's'
        sampleSize = 2;
    case 'a'
        sampleSize = 3;
    otherwise
        sampleSize = 4;
end

%==========================================================================
function c = getClassToUse(points1, points2)
if isa(points1, 'double') || isa(points2, 'double')
    c = 'double';
else
    c = 'single';
end

%==========================================================================
function ransacFuncs = getRansacFunctions(sampleSize)
ransacFuncs.evalFunc = @evaluateTForm;
ransacFuncs.checkFunc = @checkTForm;
switch(sampleSize)
    case 2
        ransacFuncs.fitFunc = @computeSimilarity;
    case 3
        ransacFuncs.fitFunc = @computeAffine;
    otherwise % 4
        ransacFuncs.fitFunc = @computeProjective;
end

%==========================================================================
% Algorithms for computing the transformation matrix.
%==========================================================================
function T = computeSimilarity(points)
classToUse = class(points);

[points1, points2, normMatrix1, normMatrix2] = ...
    normalizePoints(points, classToUse);

numPts = size(points1, 1);
constraints = zeros(2*numPts, 5, classToUse);
constraints(1:2:2*numPts, :) = [-points1(:, 2), points1(:, 1), ...
    zeros(numPts, 1), -ones(numPts,1), points2(:,2)];
constraints(2:2:2*numPts, :) = [points1, ones(numPts,1), ...
    zeros(numPts, 1), -points2(:,1)];
[~, ~, V] = svd(constraints, 0);
h = V(:, end);
T = coder.nullcopy(eye(3, classToUse));
T(:, 1:2) = [h(1:3), [-h(2); h(1); h(4)]] / h(5);
T(:, 3)   = [0; 0; 1];

T = denormalizeTForm(T, normMatrix1, normMatrix2);

%==========================================================================
function T = computeAffine(points)
classToUse = class(points);

[points1, points2, normMatrix1, normMatrix2] = ...
    normalizePoints(points, classToUse);

numPts = size(points1, 1);
constraints = zeros(2*numPts, 7, classToUse);
constraints(1:2:2*numPts, :) = [zeros(numPts, 3), -points1, ...
    -ones(numPts,1), points2(:,2)];
constraints(2:2:2*numPts, :) = [points1, ones(numPts,1), ...
    zeros(numPts, 3), -points2(:,1)];
[~, ~, V] = svd(constraints, 0);
h = V(:, end);
T = coder.nullcopy(eye(3, classToUse));
T(:, 1:2) = reshape(h(1:6), [3,2]) / h(7);
T(:, 3)   = [0; 0; 1];
T = denormalizeTForm(T, normMatrix1, normMatrix2);

%==========================================================================
function T = computeProjective(points)
classToUse = class(points);

[points1, points2, normMatrix1, normMatrix2] = ...
    normalizePoints(points, classToUse);

numPts = size(points1, 1);
p1x = points1(:, 1);
p1y = points1(:, 2);
p2x = points2(:, 1);
p2y = points2(:, 2);
constraints = zeros(2*numPts, 9, classToUse);
constraints(1:2:2*numPts, :) = [zeros(numPts,3), -points1, ...
    -ones(numPts,1), p1x.*p2y, p1y.*p2y, p2y];
constraints(2:2:2*numPts, :) = [points1, ones(numPts,1), ...
    zeros(numPts,3), -p1x.*p2x, -p1y.*p2x, -p2x];
[~, ~, V] = svd(constraints, 0);
h = V(:, end);
T = reshape(h, [3,3]) / h(9);
T = denormalizeTForm(T, normMatrix1, normMatrix2);

%==========================================================================
function [samples1, samples2, normMatrix1, normMatrix2] = ...
    normalizePoints(points, classToUse)
points1 = cast(points(:,:,1), classToUse);
points2 = cast(points(:,:,2), classToUse);

[samples1, normMatrix1] = ...
    vision.internal.normalizePoints(points1', 2, classToUse);
[samples2, normMatrix2] = ...
    vision.internal.normalizePoints(points2', 2, classToUse);

samples1 = samples1';
samples2 = samples2';

%==========================================================================
function tform = denormalizeTForm(tform, normMatrix1, normMatrix2)
tform = normMatrix1' * (tform / normMatrix2');
tform = tform ./ tform(end);

%==========================================================================
function dis = evaluateTForm(tform, points)
points1 = points(:, :, 1);
points2 = points(:, :, 2);

numPoints = size(points1, 1);
pt1h = [points1, ones(numPoints, 1, 'like', points)];
pt1h = pt1h * tform;
w = pt1h(:, 3);
pt = pt1h(:,1:2) ./ [w, w];
delta = pt - points2;
dis   = hypot(delta(:,1),delta(:,2));
dis(abs(pt1h(:,3)) < eps(class(points))) = inf;

%==========================================================================
function tf = checkTForm(tform)
tf = all(isfinite(tform(:)));