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

    % 

%#codegen
%#ok<*EMCLS>
%#ok<*EMCA>        
classdef CameraParametersImpl < vision.internal.EnforceScalarHandle
    properties (GetAccess=public, SetAccess=protected)
        % RadialDistortion A 2-element vector [k1 k2] or a 3-element
        %   vector [k1 k2 k3]. If a 2-element vector is supplied, k3 is
        %   assumed to be 0. The radial distortion is caused by the fact
        %   that light rays are bent more the farther away they are from
        %   the optical center. Distorted location of a point is computed
        %   as follows:
        %     x_distorted = x(1 + k1 * r^2 + k2 * r^4 + k3 * r^6)
        %     y_distorted = y(1 + k1 * r^2 + k2 * r^4 + k3 * r^6)
        %   where [x,y] is a non-distorted image point in normalized image
        %   coordinates in world units with the origin at the optical center,
        %   and r^2 = x^2 + y^2. Typically two coefficients are sufficient,
        %   and k3 is only needed for severe distortion.
        %
        %   Default: [0 0 0]
        RadialDistortion;
        
        % TangentialDistortion A 2-element vector [p1 p2]. Tangential
        %   distortion is caused by the lens not being exactly parallel to
        %   to the image plane. Distorted location of a point is computed
        %   as follows:
        %     x_distorted = x + [2 * p1 * x * y + p2 * (r^2 + 2 * x^2)]
        %     y_distorted = y + [p1 * (r^2 + 2*y^2) + 2 * p2 * x * y]
        %   where [x,y] is a non-distorted image point in normalized image
        %   coordinates in world units with the origin at the optical center,
        %   and r^2 = x^2 + y^2.
        %
        %   Default: [0 0]'
        TangentialDistortion;
        
        % WorldPoints An M-by-2 array of [x,y] world coordinates of
        %   keypoints on the calibration pattern, where M is the number of
        %   keypoints in the pattern.  WorldPoints must be non-empty for
        %   showExtrinsics to work.
        %
        %   Default: []
        WorldPoints;
        
        % WorldUnits A string describing the units, in which the
        %   WorldPoints are specified.
        %
        %   Default: 'mm'
        WorldUnits;
        
        % EstimateSkew A logical scalar that specifies whether image axes
        %   skew was estimated. When set to false, the image axes are
        %   assumed to be exactly perpendicular.
        %
        %   Default: false
        EstimateSkew;
        
        % NumRadialDistortionCoefficients 2 or 3. Specifies the number
        %   of radial distortion coefficients that were estimated.
        %
        %   Default: 2
        NumRadialDistortionCoefficients;
        
        % EstimateTangentialDistortion A logical scalar that specifies
        %   whether tangential distortion was estimated. When set to false,
        %   tangential distortion is assumed to be negligible.
        %
        %   Default: false
        EstimateTangentialDistortion;
        
        % TranslationVectors An M-by-3 matrix containing M translation vectors.
        %   Each vector describes the translation of the camera's image plane
        %   relative to the corresponding calibration pattern in world units.
        %
        %   The transformation relating a world point [X Y Z] and the
        %   corresponding image point [x y] is given by the following equation:
        %              s * [x y 1] = [X Y Z 1] * [R; t] * K
        %   where
        %     - R is the 3-D rotation matrix
        %     - t is the translation vector
        %     - K is the IntrinsicMatrix
        %     - s is a scalar
        %   Note that this equation does not account for lens distortion.
        %   Thus it assumes that the distortion has been removed using
        %   undistortImage function.
        %
        %   Default: []
        TranslationVectors;
        
        % ReprojectionErrors An M-by-2-by-P array of [x,y] pairs representing
        %   the translation in x and y between the reprojected pattern
        %   keypoints and the detected pattern keypoints. These values
        %   indicate the accuracy of the estimated camera parameters. P is
        %   the number of pattern images used to estimate camera
        %   parameters, and M is the number of keypoints in each image.
        %
        %   Default: []
        ReprojectionErrors;
    end
        
    properties (GetAccess=public, SetAccess=protected)
        % RotationVectors An M-by-3 matrix containing M rotation vectors
        %   Each vector describes the 3-D rotation of the camera's image
        %   plane relative to the corresponding calibration pattern. The
        %   vector specifies the 3-D axis about which the camera is rotated,
        %   and its magnitude is the rotation angle in radians. The
        %   corresponding 3-D rotation matrices are given by the
        %   RotationMatrices property.
        %
        %   Default: []
        RotationVectors;
    end
    
    properties(Dependent)
        % NumPatterns The number of calibration patterns which were used to
        %   estimate the camera extrinsics. This is also the number of
        %   translation and rotation vectors.
        NumPatterns;
        
        % IntrinsicMatrix A 3-by-3 projection matrix of the form
        %   [fx 0 0; s fy 0; cx cy 1], where [cx, cy] are the coordinates
        %   of the optical center (the principal point) in pixels and s is
        %   the skew parameter which is 0 if the x and y axes are exactly
        %   perpendicular. fx = F * sx and fy = F * sy, where F is the
        %   focal length in world units, typically millimeters, and [sx, sy]
        %   are the number of pixels per world unit in the x and y direction
        %   respectively. Thus, fx and fy are in pixels.
        %
        %   Default: eye(3)
        IntrinsicMatrix;
        
        % FocalLength A 2-element vector [fx, fy].  fx = F * sx and
        %   fy = F * sy, where F is the focal length in world units,
        %   typically millimeters, and [sx, sy] are the number of pixels
        %   per world unit in the x and y direction respectively. Thus,
        %   fx and fy are in pixels.
        FocalLength;
        
        % PrincipalPoint A 2-element vector [cx, cy], containing the
        %   coordinates of the optical center of the camera in pixels.
        PrincipalPoint;
        
        % Skew A scalar, containing the camera axes skew, which is 0 if the
        % x and the y axes are exactly perpendicular.
        Skew;
        
        % MeanReprojectionError Average Euclidean distance between
        %   reprojected points and detected points.
        MeanReprojectionError;
        
        % ReprojectedPoints An M-by-2-by-NumPatterns array of [x,y] coordinates of
        %   world points re-projected onto calibration images. M is the
        %   number of points per image. NumPatterns is the number of
        %   patterns.
        ReprojectedPoints;
        
        % RotationMatrices A 3-by-3-by-P array containing P rotation matrices.
        %   Each 3-by-3 matrix represents the 3-D rotation of the camera's
        %   image plane relative to the corresponding calibration pattern.
        %
        %   The transformation relating a world point [X Y Z] and the
        %   corresponding image point [x y] is given by the following equation:
        %              s * [x y 1] = [X Y Z 1] * [R; t] * K
        %   where
        %     - R is the 3-D rotation matrix
        %     - t is the translation vector
        %     - K is the IntrinsicMatrix
        %     - s is a scalar
        %   Note that this equation does not account for lens distortion.
        %   Thus it assumes that the distortion has been removed using
        %   undistortImage function.
        RotationMatrices;
    end
    
    properties (Access=protected, Hidden)
        UndistortMap;
        IntrinsicMatrixInternal;
        Version = ver('vision');
    end
    
    
    methods
        %----------------------------------------------------------------------
        function this = CameraParametersImpl(varargin)
            if isempty(coder.target)                
                parser = inputParser;
                parser.addParameter('IntrinsicMatrix', eye(3),...
                    @vision.internal.calibration.CameraParametersImpl.checkIntrinsicMatrix);
                parser.addParameter('RadialDistortion', [0 0 0], ...
                    @vision.internal.calibration.CameraParametersImpl.checkRadialDistortion);
                parser.addParameter('TangentialDistortion', [0 0],...
                    @vision.internal.calibration.CameraParametersImpl.checkTangentialDistortion);
                parser.addParameter('RotationVectors', zeros(0, 3), ...
                    @vision.internal.calibration.CameraParametersImpl.checkRotationVectors);
                parser.addParameter('TranslationVectors', zeros(0, 3), ...
                    @vision.internal.calibration.CameraParametersImpl.checkTranslationVectors);
                parser.addParameter('WorldPoints', zeros(0, 2), ...
                    @vision.internal.calibration.CameraParametersImpl.checkWorldPoints);
                parser.addParameter('WorldUnits', 'mm',...
                    @vision.internal.calibration.CameraParametersImpl.checkWorldUnits);
                parser.addParameter('EstimateSkew', false, ...
                    @vision.internal.calibration.CameraParametersImpl.checkEstimateSkew);
                parser.addParameter('NumRadialDistortionCoefficients', 2, ...
                    @vision.internal.calibration.CameraParametersImpl.checkNumRadialCoeffs);
                parser.addParameter('EstimateTangentialDistortion', false, ...
                    @vision.internal.calibration.CameraParametersImpl.checkEstimateTangentialDistortion);
                parser.addParameter('ReprojectionErrors', zeros(0, 2), ...
                    @vision.internal.calibration.CameraParametersImpl.checkReprojectionErrors);
                parser.addParameter('Version', ver('vision'));
                
                parser.parse(varargin{:});
                paramStruct = parser.Results;
                
                this.IntrinsicMatrixInternal = paramStruct.IntrinsicMatrix';
                this.RadialDistortion = paramStruct.RadialDistortion(:)';
                this.TangentialDistortion = paramStruct.TangentialDistortion(:)';
                this.WorldPoints = paramStruct.WorldPoints;
                this.WorldUnits = paramStruct.WorldUnits;
                this.EstimateSkew = paramStruct.EstimateSkew;
                this.NumRadialDistortionCoefficients = ...
                    paramStruct.NumRadialDistortionCoefficients;
                this.EstimateTangentialDistortion = ...
                    paramStruct.EstimateTangentialDistortion;
                
                this.RotationVectors = paramStruct.RotationVectors;
                this.TranslationVectors = paramStruct.TranslationVectors;
                if isempty(paramStruct.ReprojectionErrors)
                    this.ReprojectionErrors = zeros(0, 2);
                else
                    this.ReprojectionErrors = paramStruct.ReprojectionErrors;
                end
            else
                parseInputsCodegen(this, varargin{:});
            end
            
            coder.internal.errorIf((isempty(this.RotationVectors) && ...
                    ~isempty(this.TranslationVectors)) || ...
                    (isempty(this.TranslationVectors) && ...
                    ~isempty(this.RotationVectors)),... 
                'vision:calibrate:rotationAndTranslationVectorsMustBeSetTogether');
            
            coder.internal.errorIf(...
                any(size(this.RotationVectors) ~= size(this.TranslationVectors)),...
                'vision:calibrate:rotationAndTranslationVectorsNotSameSize');
            
            coder.internal.errorIf(~isempty(this.ReprojectionErrors) &&...
                size(this.ReprojectionErrors, 3) ~= size(this.TranslationVectors, 1),...
                'vision:calibrate:reprojectionErrorsSizeMismatch');
            
            % Initialize the undistort map property. It must be done in the
            % constructor in order to successfully cache the undistortion
            % map inside a CameraParameters object.
            this.UndistortMap = vision.internal.calibration.ImageTransformer;
        end
        
        %------------------------------------------------------------------
        function paramStruct = toStruct(this)
            % toStruct Convert a cameraParameters object into a struct.
            %   paramStruct = toStruct(cameraParams) returns a struct
            %   containing the camera parameters, which can be used to
            %   create an identical cameraParameters object.
            %
            %   This method is useful for C code generation. You can call
            %   toStruct, and then pass the resulting structure into the generated
            %   code, which re-creates the cameraParameters object.
            paramStruct = saveobj(this);
        end                
    end
    
    methods(Access=private)
        %------------------------------------------------------------------
        function parseInputsCodegen(this, varargin)
            params = struct( ...
                'IntrinsicMatrix',  uint32(0), ...
                'RadialDistortion', uint32(0),...
                'TangentialDistortion', uint32(0), ...
                'RotationVectors', uint32(0),...
                'TranslationVectors', uint32(0),...
                'WorldPoints', uint32(0), ...
                'WorldUnits', uint32(0),...
                'EstimateSkew', uint32(0), ...
                'NumRadialDistortionCoefficients', uint32(0),...
                'EstimateTangentialDistortion', uint32(0),...
                'ReprojectionErrors', uint32(0), ...
                'Version', uint32(0));
            
            popt = struct( ...
                'CaseSensitivity', false, ...
                'StructExpand',    true, ...
                'PartialMatching', true);
            
            optarg = eml_parse_parameter_inputs(params, popt, varargin{:});
            
            intrinsicMatrix = eml_get_parameter_value(...
                optarg.IntrinsicMatrix, eye(3), varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkIntrinsicMatrix(...
                intrinsicMatrix);            
            this.IntrinsicMatrixInternal = intrinsicMatrix';
            
            this.RadialDistortion = eml_get_parameter_value(...
                optarg.RadialDistortion, [0 0 0], varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkRadialDistortion(...
                this.RadialDistortion);
            
            this.TangentialDistortion = eml_get_parameter_value(...
                optarg.TangentialDistortion, [0 0], varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkTangentialDistortion(...
                this.TangentialDistortion);
            
            this.RotationVectors = eml_get_parameter_value(...
                optarg.RotationVectors, zeros(0, 3), varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkRotationVectors(...
                this.RotationVectors);
            
            this.TranslationVectors = eml_get_parameter_value(...
                optarg.TranslationVectors, zeros(0, 3), varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkTranslationVectors(...
                this.TranslationVectors);
            
            this.WorldPoints = eml_get_parameter_value(...
                optarg.WorldPoints, zeros(0, 2), varargin{:});
             vision.internal.calibration.CameraParametersImpl.checkWorldPoints(...
                 this.WorldPoints);
             
            this.WorldUnits = eml_get_parameter_value(...
                optarg.WorldUnits, 'mm', varargin{:});
             vision.internal.calibration.CameraParametersImpl.checkWorldUnits(...
                 this.WorldUnits);
            
            this.EstimateSkew = eml_get_parameter_value(...
                optarg.EstimateSkew, false, varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkEstimateSkew(...
                 this.EstimateSkew);
             
            this.NumRadialDistortionCoefficients = eml_get_parameter_value(...
                optarg.NumRadialDistortionCoefficients, 2, varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkNumRadialCoeffs(...
                 this.NumRadialDistortionCoefficients);
             
            this.EstimateTangentialDistortion = eml_get_parameter_value(...
                optarg.EstimateTangentialDistortion, false, varargin{:});
            vision.internal.calibration.CameraParametersImpl.checkEstimateTangentialDistortion(...
                this.EstimateTangentialDistortion);
            
            reprojErrors = eml_get_parameter_value(...
                optarg.ReprojectionErrors, zeros(0, 2), varargin{:});
            if isempty(reprojErrors)
                this.ReprojectionErrors = zeros(0, 2);
            else
                this.ReprojectionErrors = reprojErrors;
            end
            
            vision.internal.calibration.CameraParametersImpl.checkReprojectionErrors(...
                this.ReprojectionErrors);
            
            eml_get_parameter_value(...
                optarg.Version, '', varargin{:});
        end
    end
    
    
        
    methods
        %------------------------------------------------------------------
        function numPatterns = get.NumPatterns(this)
            numPatterns = size(this.RotationVectors, 1);
        end
        
        %------------------------------------------------------------------
        function intrinsicMatrix = get.IntrinsicMatrix(this)
            intrinsicMatrix = this.IntrinsicMatrixInternal';
        end
        
        %------------------------------------------------------------------
        function meanError = get.MeanReprojectionError(this)
            meanError = computeMeanError(this);
        end
        
        %------------------------------------------------------------------
        function reprojectedPoints = get.ReprojectedPoints(this)
            points = this.WorldPoints;
            reprojectedPoints = zeros([size(points), this.NumPatterns]);
            
            for i = 1:this.NumPatterns
                reprojectedPoints(:, :, i) = ...
                    reprojectWorldPointsOntoPattern(this, i);
            end
            
            % apply distortion
            reprojectedPoints = distortPoints(this, reprojectedPoints);
        end
        
        %------------------------------------------------------------------
        function rotationMatrices = get.RotationMatrices(this)
            rotationMatrices = zeros(3, 3, this.NumPatterns);
            for i = 1:this.NumPatterns
                v = this.RotationVectors(i, :);
                R = vision.internal.calibration.rodriguesVectorToMatrix(v);
                rotationMatrices(:, :, i) = R';
            end
        end
        
        %------------------------------------------------------------------
        function focalLength = get.FocalLength(this)
            focalLength = zeros(1, 2, 'like', this.IntrinsicMatrix);
            focalLength(1) = this.IntrinsicMatrix(1, 1);
            focalLength(2) = this.IntrinsicMatrix(2, 2);
        end
        
        %------------------------------------------------------------------
        function principalPoint = get.PrincipalPoint(this)
            principalPoint = zeros(1, 2, 'like', this.IntrinsicMatrix);
            principalPoint(1) = this.IntrinsicMatrix(3, 1);
            principalPoint(2) = this.IntrinsicMatrix(3, 2);
        end
        
        %------------------------------------------------------------------
        function skew = get.Skew(this)
            skew = this.IntrinsicMatrix(2, 1);
        end
        
        function worldPoints = pointsToWorld(this, rotationMatrix, ...
                translationVector, imagePoints)
            %pointsToWorld Determine world coordinates of image points. 
            %  worldPoints = pointsToWorld(cameraParams, rotationMatrix,
            %  translationVector, imagePoints) maps undistorted image 
            %  points onto points on the X-Y plane in the world coordinates. 
            %
            %  Inputs:
            %  -------
            %  cameraParams      - cameraParameters object. 
            %
            %  rotationMatrix    - 3-by-3 matrix representing rotation 
            %                      of the camera in world coordinates.
            %
            %  translationVector - 3-element vector representing 
            %                      translation of the camera in world 
            %                      coordinates.  
            %
            %  imagePoints       - M-by-2 matrix containing [x, y] 
            %                      coordinates of undistorted image points.
            %                      M is the number of points.
            %
            %  Output:
            %  -------
            %  worldPoints       - M-by-2 matrix containing corresponding 
            %                      [X,Y] world coordinates. Z coordinate 
            %                      for every world point is 0.
            %
            %  Notes
            %  -----
            %  The function does not account for lens distortion. 
            %  imagePoints must either be detected in the undistorted 
            %  image, or they must be undistorted using the undistortPoints
            %  function. If imagePoints are detected in undistorted image,
            %  their coordinates must be translated to the coordinate 
            %  system of the original image.
            %
            %  Class Support
            %  -------------
            %  rotationMatrix, translationVector, and imagePoints must be
            %  real and nonsparse numeric arrays. worldPoints is of class
            %  double if imagePoints are double. Otherwise worldPoints is
            %  of class single.
            %
            %  Example - Measuring Planar Objects with a Calibrated Camera
            %  -----------------------------------------------------------
            %  % This example shows how to measure the diameter of coins in 
            %  % world units using a single calibrated camera.
            %  % <a href="matlab:web(fullfile(matlabroot,'toolbox','vision','visiondemos','html','MeasuringPlanarObjectsExample.html'))">View example</a>
            %
            %
            %  See also worldToImage, estimateCameraParameters, cameraCalibrator,
            %      extrinsics, undistortImage, cameraParameters,
            %      reconstructScene, triangulate, triangulateMultiview
            
            points = vision.internal.inputValidation.checkAndConvertPoints(...
                imagePoints, 'cameraParameters', 'imagePoints');

            [R, t, K, pts, outputClass] = parseProjectionInputs(this, ...
                rotationMatrix, translationVector, points);
            
            tform = projective2d([R(1, :); R(2, :); t] * K);
            worldPoints = cast(transformPointsInverse(tform, pts), outputClass);            
        end
                
        function imagePoints = worldToImage(this, rotationMatrix, ...
                translationVector, worldPoints, varargin)
            % worldToImage Project world points into the image
            %   imagePoints = worldToImage(cameraParams, rotationMatrix, 
            %   translationVector, worldPoints) projects 3-D world points 
            %   into the image given camera parameters.
            %
            %   Inputs:
            %   -------
            %   cameraParams      - cameraParameters object
            %
            %   rotationMatrix    - 3-by-3 matrix representing rotation from 
            %                       world coordinates into camera coordinates.
            %
            %   translationVector - 3-element vector representing translation 
            %                       from world coordinates into camera coordinates. 
            %                       It must be in the same units as worldPoints.
            %
            %   worldPoints       - M-by-3 matrix containing [X,Y,Z] coordinates 
            %                       of the world points. The coordinates must 
            %                       be in the same units as translationVector.
            %
            %   Output:
            %   -------
            %   imagePoints       - M-by-2 matrix containing [x,y] coordinates 
            %                       of image points in pixels.
            %
            %   imagePoints = worldToImage(..., 'ApplyDistortion', Value) 
            %   optionally applies lens distortion to imagePoints. Set Value 
            %   to true to apply distortion. By default Value is false.
            %
            %   Class Support
            %   -------------
            %   rotationMatrix, translationVector, and worldPoints can be of 
            %   class double or single. imagePoints are the same class as worldPoints.
            %
            %   Example
            %   -------
            %   % Create a set of calibration images.
            %   images = imageDatastore(fullfile(toolboxdir('vision'), 'visiondata', ...
            %         'calibration', 'slr'));
            %
            %   % Detect the checkerboard corners in the images.
            %   [imagePoints, boardSize] = detectCheckerboardPoints(images.Files);
            %
            %   % Generate the world coordinates of the checkerboard corners in the
            %   % pattern-centric coordinate system, with the upper-left corner at (0,0).
            %   squareSize = 29; % in millimeters
            %   worldPoints = generateCheckerboardPoints(boardSize, squareSize);
            %
            %   % Calibrate the camera.
            %   cameraParams = estimateCameraParameters(imagePoints, worldPoints);
            %
            %   % Load image at new location.
            %   imOrig = imread(fullfile(matlabroot, 'toolbox', 'vision', 'visiondata', ...
            %           'calibration', 'slr', 'image9.jpg'));
            %   figure
            %   imshow(imOrig, 'InitialMagnification', 30);
            %
            %   % Undistort image.
            %   imUndistorted = undistortImage(imOrig, cameraParams);
            %
            %   % Find reference object in new image.
            %   [imagePoints, boardSize] = detectCheckerboardPoints(imUndistorted);
            %
            %   % Compute new extrinsics.
            %   [R, t] = extrinsics(imagePoints, worldPoints, cameraParams);
            %
            %   % Add a z-coordinate to the world points
            %   worldPoints = [worldPoints, zeros(size(worldPoints, 1), 1)];
            %
            %   % Project world points back into original image
            %   projectedPoints = worldToImage(cameraParams, R, t, worldPoints);
            %   hold on
            %   plot(projectedPoints(:,1), projectedPoints(:,2), 'g*-');
            %   legend('Projected points');
            %
            %   See also pointsToWorld, cameraParameters, cameraCalibrator,
            %       estimateCameraParameters, cameraPose, estimateWorldCameraPose, 
            %       extrinsics
            
            [R, t, K, pts, applyDistortion, outputClass] = ...
                parseWorldToImageInputs(this,...
                rotationMatrix, translationVector, worldPoints, varargin{:});
                            
            cameraMatrix = [R; t] * K;
            projectedPoints = [pts ones(size(pts, 1), 1)] * ...
                cameraMatrix;
            imagePointsTmp = bsxfun(@rdivide, projectedPoints(:, 1:2), ...
                projectedPoints(:, 3));
            
            if applyDistortion
                imagePointsTmp = distortPoints(this, imagePointsTmp);
            end
            
            imagePoints = cast(imagePointsTmp, outputClass);
        end
    end
    
    methods(Access=private)
        %------------------------------------------------------------------
        function [R, t, K, pts, applyDistortion, outputClass] = parseWorldToImageInputs(this, ...
                rotationMatrix, translationVector, worldPoints, varargin)
            validateattributes(worldPoints, {'double', 'single'}, ...
                {'real', 'nonsparse', 'nonempty', '2d', 'ncols', 3}, ...
                'cameraParameters', 'worldPoints');
            [R, t, K, pts, outputClass] = parseProjectionInputs(this, rotationMatrix, ...
                translationVector, worldPoints);
            
            if isempty(coder.target) %matlab
                parser = inputParser;
                parser.FunctionName = 'worldToImage';
                parser.addParameter('ApplyDistortion', false, @checkApplyDistortion);
                parser.parse(varargin{:});
                applyDistortion = parser.Results.ApplyDistortion;
            else % codegen
                params = struct('ApplyDistortion', uint32(0));
                popt = struct( ...
                    'CaseSensitivity', false, ...
                    'StructExpand',    true, ...
                    'PartialMatching', true);
                optarg = eml_parse_parameter_inputs(params, popt,...
                    varargin{:});
                applyDistortion = eml_get_parameter_value(optarg.ApplyDistortion, ...
                    false, varargin{:});
                checkApplyDistortion(applyDistortion);
            end
        end

        %--------------------------------------------------------------
        function [R, t, K, pts, outputClass] = parseProjectionInputs(this,...
                rotationMatrix, translationVector, points)
            
            vision.internal.inputValidation.validateRotationMatrix(...
                rotationMatrix, 'cameraParameters', 'rotationMatrix');
            vision.internal.inputValidation.validateTranslationVector(...
                translationVector, 'cameraParameters', 'translationVector');
            
            % if any of the inputs is double, internal math is done in
            % doubles.  Otherwise, internal math is  done in singles.
            if isa(rotationMatrix, 'double') || isa(translationVector, 'double')...
                || isa(points, 'double')
                R = double(rotationMatrix);
                tTemp = double(translationVector);                
                pts = double(points);
                K = double(this.IntrinsicMatrix);
            else
                R = single(rotationMatrix);
                tTemp = single(translationVector);
                pts = single(points);
                K = single(this.IntrinsicMatrix);
            end
            
            % Force t to be a row vector.
            t = tTemp(:)';
            
            % if imagePoints is double, then the output worldPoints is
            % double. Otherwise worldPoints is single.
            if isa(points, 'double')
                outputClass = 'double';
            else
                outputClass = 'single';
            end
        end
    end
    
    methods(Hidden, Access=public)
        %------------------------------------------------------------------
        % This method is different from the MeanReprojectionError property,
        % because it also computes the mean reprojection error per image.
        %------------------------------------------------------------------
        function [meanError, meanErrorsPerImage] = computeMeanError(this)
            errors = hypot(this.ReprojectionErrors(:, 1, :), ...
                this.ReprojectionErrors(:, 2, :));
            meanErrorsPerImage = squeeze(mean(errors, 1));
            meanError = mean(meanErrorsPerImage);
        end
        
        %------------------------------------------------------------------
        function [Jout, newOrigin] = undistortImageImpl(this, I, interp, ...
                outputView, fillValues)
            % undistortImageImpl implements the core lens undistortion
            % algorithm for the undistortImage.m function.  See help for
            % undistortImage for further details.
            if needToUpdate(this.UndistortMap, I, outputView)
                [xBounds, yBounds] = computeUndistortBounds(this, ...
                    [size(I, 1), size(I, 2)], outputView);

                this.UndistortMap.update(I, this.IntrinsicMatrix, ...
                    this.RadialDistortion, this.TangentialDistortion, ...
                    outputView, xBounds, yBounds);
            end
            
            [J, newOrigin] = transformImage(this.UndistortMap, I, interp, fillValues); 
            
            if strcmp(outputView, 'same')
                Jout = coder.nullcopy(zeros(size(I), 'like', I));
                Jout(:,:,:) = J(1:size(I, 1), 1:size(I, 2), 1:size(I,3));
            else
                Jout = J;
            end
        end        
    end
            
    %----------------------------------------------------------------------
    % constructor parameter validation
    %----------------------------------------------------------------------
    methods(Static)
        %------------------------------------------------------------------        
        function tf = checkIntrinsicMatrix(IntrinsicMatrix)
            validateattributes(IntrinsicMatrix, {'double', 'single'}, ...
                {'2d', 'ncols', 3, 'nrows', 3, 'nonsparse', 'real'}, ...
                'cameraParameters', 'IntrinsicMatrix');
            tf = true;
        end
        
        %------------------------------------------------------------------        
        function tf = checkRadialDistortion(radialDistortion)
            validTypes = {'double', 'single'};
            validateattributes(radialDistortion, validTypes,...
                {'vector', 'real', 'nonsparse'},...
                'cameraParameters', 'RadialDistortion');
            
            if numel(radialDistortion) ~= 2 
                validateattributes(radialDistortion, validTypes,...
                    {'numel', 3}, 'cameraParameters', 'RadialDistortion');
            end
            
            tf = true;
        end
        %------------------------------------------------------------------        
        function tf = checkTangentialDistortion(tangentialDistortion)
            validateattributes(tangentialDistortion, {'double', 'single'},...
                {'vector', 'real', 'nonsparse', 'numel', 2},...
                'cameraParameters', 'TangentialDistortion');
            tf = true;
        end
       
        %------------------------------------------------------------------                
        function tf = checkRotationVectors(rotationVectors)
            tf = true;
            if isempty(rotationVectors)
                return;
            end
            validateattributes(rotationVectors, {'double', 'single'},...
                {'2d', 'real', 'nonsparse', 'ncols', 3},...
                'cameraParameters', 'RotationVectors');
        end
        
        %------------------------------------------------------------------                
        function tf = checkTranslationVectors(translationVectors)
            tf = true;
            if isempty(translationVectors)
                return;
            end
            validateattributes(translationVectors, {'double', 'single'},...
                {'2d', 'real', 'nonsparse', 'ncols', 3},...
                'cameraParameters', 'TranslationVectors');
        end        
        
        %------------------------------------------------------------------                        
        function tf = checkWorldPoints(worldPoints)
            tf = true;
            if isempty(worldPoints)
                return;        
            end    
            validateattributes(worldPoints, {'double', 'single'},...
                {'2d', 'real', 'nonsparse', 'ncols', 2},...
                'cameraParameters', 'WorldPoints');
        end
        
        %------------------------------------------------------------------                        
        function tf = checkWorldUnits(worldUnits)
            tf = true;
            validateattributes(worldUnits, {'char'}, ...
                {'vector'}, 'cameraParameters', 'WorldUnits');
        end
        
        %------------------------------------------------------------------                        
        function tf = checkEstimateSkew(esitmateSkew)
            tf = true;
            validateattributes(esitmateSkew, {'logical'}, {'scalar'}, ...
                'cameraParameters', 'EstimateSkew');
        end
        
        %------------------------------------------------------------------                        
        function tf = checkNumRadialCoeffs(numRadialCoeffs)
            tf = true;
            validateattributes(numRadialCoeffs, {'numeric'}, ...
                {'scalar', 'integer', '>=', 2, '<=', 3}, ...
                 'cameraParameters', 'NumRadialDistortionCoefficients');
        end
        
        %------------------------------------------------------------------                        
        function tf = checkEstimateTangentialDistortion(estimateTangential)
            tf = true;
            validateattributes(estimateTangential, {'logical'}, {'scalar'},...
                'cameraParameters', 'EstimateTangentialDistortion');            
        end
        
        %------------------------------------------------------------------                        
        function tf = checkReprojectionErrors(reprojErrors)
            tf = true;
            if ~isempty(reprojErrors)
                validateattributes(reprojErrors, {'double', 'single'},...
                    {'3d', 'real', 'nonsparse', 'ncols', 2},...
                    'cameraParameters', 'ReprojectionErrors');
            end
        end
    end
    
    methods(Access=private)
        %------------------------------------------------------------------
        % Reproject world points using one set of extrinsics without
        % applying distortion
        %------------------------------------------------------------------
        function reprojectedPoints = ...
                reprojectWorldPointsOntoPattern(this, patternIdx)
            
            points = this.WorldPoints;
            R = vision.internal.calibration.rodriguesVectorToMatrix(...
                this.RotationVectors(patternIdx, :));
            t = this.TranslationVectors(patternIdx, :)';
            tform = projective2d((this.IntrinsicMatrixInternal * ...
                [R(:, 1), R(:, 2), t])');
            reprojectedPoints = transformPointsForward(tform, points);
        end
    end
    
    methods (Hidden=true)
        %------------------------------------------------------------------
        % Apply radial and tangential distortion to a set of points
        %------------------------------------------------------------------
        function distortedPoints = distortPoints(this, points, ~)
            if isempty(coder.target)
                % in matlab use the builtin c++ function
                distortedPoints = visionDistortPoints(points, ...
                    this.IntrinsicMatrixInternal, ...
                    this.RadialDistortion, this.TangentialDistortion);
            else            
                % in codegen use matlab function
                distortedPoints = vision.internal.calibration.distortPoints(...
                    points, this.IntrinsicMatrix, this.RadialDistortion, ...
                    this.TangentialDistortion);
            end
        end    
    end
        
    methods (Hidden=true, Access=public)
        %------------------------------------------------------------------
        % Returns a CameraParameters object with updated reprojection
        % errors.  Used in stereoParameters.
        %------------------------------------------------------------------
        function computeReprojectionErrors(this, imagePoints)
            this.ReprojectionErrors = this.ReprojectedPoints - imagePoints;
        end
        
        %------------------------------------------------------------------
        % Returns a CameraParameters object with new extrinsics.
        %------------------------------------------------------------------
        function setExtrinsics(this, rvecs, tvecs)
            this.RotationVectors = rvecs;
            this.TranslationVectors = tvecs;
        end
    end
        
    %----------------------------------------------------------------------
    % saveobj and loadobj are implemented to ensure compatibility across
    % releases even if architecture of vision.CameraParameters class changes
    methods (Hidden)
       
        function that = saveobj(this)
            that.RadialDistortion = this.RadialDistortion;     
            that.TangentialDistortion = this.TangentialDistortion;
            that.WorldPoints = this.WorldPoints;
            that.WorldUnits = this.WorldUnits;  
            that.EstimateSkew = this.EstimateSkew;
            that.NumRadialDistortionCoefficients = this.NumRadialDistortionCoefficients;
            that.EstimateTangentialDistortion = this.EstimateTangentialDistortion;
            that.RotationVectors = this.RotationVectors;
            that.TranslationVectors = this.TranslationVectors;
            that.ReprojectionErrors = this.ReprojectionErrors;            
            that.IntrinsicMatrix = this.IntrinsicMatrix;
            that.Version = this.Version;
        end
        
    end
    
    
    %--------------------------------------------------------------------------
    
    methods (Static, Hidden)
        
        function this = loadobj(that)
            if isempty(that.ReprojectionErrors)
                reprojErrors = zeros(0, 2, 0);
            else
                reprojErrors = that.ReprojectionErrors;
            end
            
            this = cameraParameters(...
                'IntrinsicMatrix', that.IntrinsicMatrix,...
                'RadialDistortion', that.RadialDistortion,...
                'TangentialDistortion', that.TangentialDistortion,...
                'WorldPoints', that.WorldPoints,...
                'WorldUnits',  that.WorldUnits,...
                'EstimateSkew', that.EstimateSkew,...
                'NumRadialDistortionCoefficients', that.NumRadialDistortionCoefficients,...
                'EstimateTangentialDistortion', that.EstimateTangentialDistortion,...
                'RotationVectors', that.RotationVectors,...
                'TranslationVectors', that.TranslationVectors,...
                'ReprojectionErrors', reprojErrors);
        end
        
    end
    
        methods(Hidden)
        %------------------------------------------------------------------
        function [xBounds, yBounds] = ...
                computeUndistortBounds(this, imageSize, outputView)          
            if strcmp(outputView, 'same')
                xBounds = [1, imageSize(2)];
                yBounds = [1, imageSize(1)];
            else
                [undistortedMask, xBoundsBig, yBoundsBig] = ...
                    createUndistortedMask(this, imageSize, outputView);
                
                if strcmp(outputView, 'full')
                    [xBounds, yBounds] = getFullBounds(undistortedMask, ...
                        xBoundsBig, yBoundsBig);
                else % valid
                    [xBounds, yBounds] = getValidBounds(this, undistortedMask, ...
                        xBoundsBig, yBoundsBig, imageSize);
                end
            end
        end
    end
    
    methods(Access=private)
        %--------------------------------------------------------------------------
        function [undistortedMask, xBoundsBig, yBoundsBig] = ...
                createUndistortedMask(this, imageSize, outputView)
            
            xBounds = [1, imageSize(2)];
            yBounds = [1, imageSize(1)];
            
            width = xBounds(2) - xBounds(1);
            height = yBounds(2) - yBounds(1);            
            
            xBoundsBig = zeros(1, 2, 'like', xBounds);
            yBoundsBig = zeros(1, 2, 'like', yBounds);            
            
            wpad = width;
            hpad = height;
            
            xBoundsBig(1) = xBounds(1) - wpad;
            xBoundsBig(2) = xBounds(2) + wpad;
            yBoundsBig(1) = yBounds(1) - hpad;
            yBoundsBig(2) = yBounds(2) + hpad;
            
            mask = ones(imageSize, 'uint8');
            fillValuesMask = cast(0, 'uint8');
            
            myMap = vision.internal.calibration.ImageTransformer;
            myMap.update(mask, this.IntrinsicMatrix, ...
                this.RadialDistortion, this.TangentialDistortion, ...
                outputView, xBoundsBig, yBoundsBig);
            
            undistortedMask = myMap.transformImage(mask, 'nearest', fillValuesMask);
        end
        
        %------------------------------------------------------------------
        % Compute the circumscribed rectangle of the undistorted image
        % outerBounds is a 4-by-2 matrix of [x,y] coordinates of the corner
        % points: [ul; ur; lr; ll]
        %------------------------------------------------------------------
        function outerBounds = computeOuterBounds(this, imageSize)
            
            boundaryPoints = undistortImageBoundary(this, imageSize);
            
            % find the inner rectangle
            outerBounds = getOuterRectangle(boundaryPoints);
        end
        
        %------------------------------------------------------------------
        function [boundaryPointsUndistorted, numXSamples, numYSamples] = ...
                undistortImageBoundary(this, imageSize)
            
            % sample points along the image boundaries
            [boundaryPoints, numXSamples, numYSamples] = ...
                sampleBoundaryPoints(imageSize);
            
            % undistort the boundary points
            boundaryPointsUndistorted = this.undistortPointsImpl(boundaryPoints);
        end
        
        %--------------------------------------------------------------------------
        function [xBounds, yBounds] = getValidBounds(this, undistortedMask, ...
                xBoundsBig, yBoundsBig, imageSize)
            
            % Get the boundary
            boundaryPixel = getInitialBoundaryPixel(undistortedMask);
            boundaryPixelsUndistorted = bwtraceboundary(undistortedMask, ...
                boundaryPixel, 'W');
            
            % Convert from R-C to x-y
            boundaryPixelsUndistorted = boundaryPixelsUndistorted(:, [2,1]);
            
            % Convert to the coordinate system of the original image
            boundaryPixelsUndistorted(:, 1) = boundaryPixelsUndistorted(:, 1) + xBoundsBig(1);
            boundaryPixelsUndistorted(:, 2) = boundaryPixelsUndistorted(:, 2) + yBoundsBig(1);
            
            % Apply distortion to turn the boundary back into a rectangle
            boundaryPixelsDistorted = distortPoints(this, boundaryPixelsUndistorted);
            
            % Find the pixels that came from the top, bottom, left, and right edges of
            % the original image.
            tolerance = 7;
            topIdx = abs(boundaryPixelsDistorted(:, 2) - 1) < tolerance;
            botIdx = abs(boundaryPixelsDistorted(:, 2) - imageSize(1)) < tolerance;
            leftIdx = abs(boundaryPixelsDistorted(:, 1) - 1) < tolerance;
            rightIdx = abs(boundaryPixelsDistorted(:, 1) - imageSize(2)) < tolerance;
                        
            % Find the inscribed rectangle.
            topPixels = boundaryPixelsUndistorted(topIdx, 2);
            botPixels = boundaryPixelsUndistorted(botIdx, 2);
            leftPixels = boundaryPixelsUndistorted(leftIdx, 1);
            rightPixels = boundaryPixelsUndistorted(rightIdx, 1);

            % Check if we can compute the valid bounds at all
            coder.internal.errorIf(isempty(topPixels) || isempty(botPixels) || ...
                isempty(leftPixels) || isempty(rightPixels), ...
                'vision:calibrate:cannotComputeValidBounds');
            
            top = max(topPixels);
            bot = min(botPixels);
            left = max(leftPixels);
            right = min(rightPixels);
            
            % Check if the valid bounds cross
            if isempty(coder.target) && (left > right || top > bot)
                warning(message('vision:calibrate:badValidUndistortBounds'));
            end
            
            xBounds = sort([ceil(left), floor(right)]);
            yBounds = sort([ceil(top), floor(bot)]);
        end
    end
end


%--------------------------------------------------------------------------
% Get the bounding box of the central connected component of the
% undistortedMask
%--------------------------------------------------------------------------
function [xBounds, yBounds] = getFullBounds(undistortedMask, xBoundsBig,...
    yBoundsBig)
% We have to consider the possibility that there may be more than one
% connected component in the undistorted image.  Our distortion model can
% result in an additional ring of valid pixels around the "correct" valid
% region.

props = regionprops(logical(undistortedMask), 'Centroid', 'BoundingBox');

% find centroid closest to the center
center = round(size(undistortedMask) ./ 2);
dists = zeros(1, numel(props));
for i = 1:numel(props)
    dists(i) = (props(i).Centroid(1) - center(2)).^2 + ...
        (props(i).Centroid(2) - center(1)).^2;
end
[~, idx] = min(dists);
bbox = props(idx).BoundingBox;


xBounds = zeros(1, 2, 'like', xBoundsBig);
yBounds = zeros(1, 2, 'like', yBoundsBig);

xBounds(1) = ceil(xBoundsBig(1) + bbox(1)-1);
xBounds(2) = floor(xBounds(1) + bbox(3));

yBounds(1) = ceil(yBoundsBig(1) + bbox(2)-1);
yBounds(2) = floor(yBounds(1) + bbox(4));
end

%--------------------------------------------------------------------------
% Shoot a ray from the center of the image straight down to find the
% initial boundary pixel
%--------------------------------------------------------------------------
function boundaryPixel = getInitialBoundaryPixel(undistortedMask)

sRow = -1;
sCol = -1;
cx = floor(size(undistortedMask, 2) / 2);
for i = floor(size(undistortedMask, 1)/2):size(undistortedMask, 1)
    if undistortedMask(i, cx) == 0
        sRow = i-1;
        sCol = cx;
        break;
    end
end
boundaryPixel = [sRow, sCol];
end

%--------------------------------------------------------------------------
function tf = checkApplyDistortion(val)                
vision.internal.inputValidation.validateLogical(val, 'ApplyDistortion');
tf = true;
end