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

    function [features, varargout] = extractHOGFeatures(I,varargin)
%extractHOGFeatures Extract HOG features.
%  features = extractHOGFeatures(I) extracts HOG features from a truecolor
%  or grayscale image I and returns the features in a 1-by-N vector. These
%  features encode local shape information from regions within an image and
%  can be used for many tasks including classification, detection, and
%  tracking. 
%
%  The HOG feature length, N, is based on the image size and the parameter
%  values listed below. See the <a href="matlab:helpview(fullfile(docroot,'toolbox','vision','vision.map'),'extractHOGFeatures')" >documentation</a> for more information. 
%
%  [features, validPoints] = extractHOGFeatures(I, points) returns HOG
%  features extracted around point locations within I. The function also
%  returns validPoints, which contains the input point locations whose
%  surrounding [CellSize.*BlockSize] region is fully contained within I.
%  The input points can be specified as an M-by-2 matrix of [x y]
%  coordinates, SURFPoints, cornerPoints, MSERRegions, or BRISKPoints. Any
%  scale information associated with the points is ignored. The class of
%  validPoints is the same as the input points.
%
%  [..., visualization] = extractHOGFeatures(I, ...) optionally returns a
%  HOG feature visualization that can be shown using plot(visualization).
%
%  [...] = extractHOGFeatures(..., Name, Value) specifies additional
%  name-value pairs described below:
%
%  'CellSize'     A 2-element vector that specifies the size of a HOG cell
%                 in pixels. Select larger cell sizes to capture large
%                 scale spatial information at the cost of loosing small
%                 scale detail.
%                 
%                 Default: [8 8]
%
%  'BlockSize'    A 2-element vector that specifies the number of cells in
%                 a block. Large block size values reduce the ability to
%                 minimize local illumination changes.
%
%                 Default: [2 2]
%
%  'BlockOverlap' A 2-element vector that specifies the number of
%                 overlapping cells between adjacent blocks. Select an
%                 overlap of at least half the block size to ensure
%                 adequate contrast normalization. Larger overlap values
%                 can capture more information at the cost of increased
%                 feature vector size. This property has no effect when
%                 extracting HOG features around point locations.
% 
%                 Default: ceil(BlockSize/2)
%                  
%  'NumBins'      A positive scalar that specifies the number of bins in
%                 the orientation histograms. Increase this value to encode
%                 finer orientation details.
%                 
%                 Default: 9
%
%  'UseSignedOrientation' A logical scalar. When true, orientation
%                         values are binned into evenly spaced bins
%                         between -180 and 180 degrees. Otherwise, the
%                         orientation values are binned between 0 and
%                         180 where values of theta less than 0 are
%                         placed into theta + 180 bins. Using signed
%                         orientations can help differentiate light to
%                         dark vs. dark to light transitions within
%                         an image region.
%
%                         Default: false
%
% Class Support
% -------------
% The input image I can be uint8, int16, double, single, or logical, and it
% must be real and non-sparse. POINTS can be SURFPoints, cornerPoints,
% MSERRegions, BRISKPoints, int16, uint16, int32, uint32, single, or
% double.
%
%
% Example 1 - Extract HOG features from an image.
% -----------------------------------------------
%
%    I1 = imread('gantrycrane.png');
%    [hog1, visualization] = extractHOGFeatures(I1,'CellSize',[32 32]);
%    subplot(1,2,1);
%    imshow(I1);
%    subplot(1,2,2);
%    plot(visualization);
%
% Example 2 - Extract HOG features around corner points.
% ------------------------------------------------------
%
%    I2 = imread('gantrycrane.png');
%    corners   = detectFASTFeatures(rgb2gray(I2));
%    strongest = selectStrongest(corners, 3);
%    [hog2, validPoints, ptVis] = extractHOGFeatures(I2, strongest);
%    figure;
%    imshow(I2); hold on;
%    plot(ptVis, 'Color','green');
% 
% See also extractFeatures, extractLBPFeatures, detectHarrisFeatures,
% detectFASTFeatures, detectMinEigenFeatures, detectSURFFeatures,
% detectMSERFeatures, detectBRISKFeatures

% Copyright 2012 The MathWorks, Inc.
%
% References
% ----------
% N. Dalal and B. Triggs, "Histograms of Oriented Gradients for Human
% Detection", Proc. IEEE Conf. Computer Vision and Pattern Recognition,
% vol. 1, pp. 886-893, 2005. 
%

%#codegen
%#ok<*EMCA>

notCodegen = isempty(coder.target);

[points, isPoints, params, maxargs] = parseInputs(I,varargin{:});

% check number of outputs 
if notCodegen
    nargoutchk(0,maxargs);
else    
    checkNumOutputsForCodegen(nargout, maxargs);
end

if isPoints

    [features, validPoints] = extractHOGFromPoints(I, points, params);
    
    if nargout >= 2
        varargout{1} = validPoints;
    end
    
    if notCodegen
        if nargout == 3
            params.Points = validPoints;
            varargout{2}  = vision.internal.hog.Visualization(features, params);
        end
    end
else   
   
    features = extractHOGFromImage(I, params);  
   
    if notCodegen
        if nargout == 2
            varargout{1} = vision.internal.hog.Visualization(features, params);
        end
    end
end
 
% -------------------------------------------------------------------------
% Extract HOG features from whole image 
% -------------------------------------------------------------------------
function features = extractHOGFromImage(I, params)
[gMag, gDir] = hogGradient(I);

[gaussian, spatial] = computeWeights(params);

features = extractHOG(gMag, gDir, gaussian, spatial, params);

% -------------------------------------------------------------------------
% Extract HOG features from point locations 
% -------------------------------------------------------------------------
function [features, validPoints] = extractHOGFromPoints(I, points, params)

featureClass = coder.internal.const('single');
uintClass    = coder.internal.const('uint32');

blockSizeInPixels = params.CellSize.*params.BlockSize;

% compute weights
[gaussian, spatial] = computeWeights(params);

if ~isnumeric(points)
    xy = points.Location;
else
    xy = points;
end

featureSize = vision.internal.hog.getFeatureSize(params);

halfSize = (single(blockSizeInPixels) - mod(single(blockSizeInPixels),2))./2;

roi = [1 1 blockSizeInPixels]; % [r c height width]

numPoints       = cast(size(xy,1), uintClass);
validPointIdx   = zeros(1, numPoints , uintClass);
validPointCount = zeros(1, uintClass);

features = zeros(numPoints, featureSize, featureClass);
for i = 1:numPoints
    
    % ROI centered at point location
    roi(1:2) = cast(round(xy(i,[2 1])), featureClass) - halfSize;
    
    % only process if ROI is fully contained within the image
    if all(roi(1:2) >= 1) && ...
            roi(1)+roi(3)-1 <= params.ImageSize(1) && ...
            roi(2)+roi(4)-1 <= params.ImageSize(2)
        
        validPointCount = validPointCount + 1;
        
        [gMag, gDir] = hogGradient(I, roi);
               
        hog = extractHOG(gMag, gDir, gaussian, spatial, params);
        
        features(validPointCount,:) = hog(:);
        validPointIdx(validPointCount) = i; % store valid indices
    end
    
end

features = features(1:validPointCount,:);

validPoints = extractValidPoints(points, validPointIdx(1:validPointCount));

% -------------------------------------------------------------------------
% Extract HOG features given gradient magnitudes and directions
% -------------------------------------------------------------------------
function hog = extractHOG(gMag, gDir, gaussianWeights, weights, params)
    
if isempty(coder.target)         
    hog = visionExtractHOGFeatures(gMag, gDir, gaussianWeights, params, weights);                  
else
    featureClass = 'single';
    
    if params.UseSignedOrientation
        % make gDir range from [0 360]
        histRange = single(360);
    else
        % convert to unsigned orientation, range [0 180]
        histRange = single(180);
    end
    
    % range of gDir is [-180 180], convert range to [0 180] or [0 360]
    negDir = gDir < 0;
    gDir(negDir) = histRange + gDir(negDir);
    
    % orientation bin locations for all cells
    binWidth = histRange/cast(params.NumBins, featureClass);
    [x1, b1] = computeLowerHistBin(gDir, binWidth);
    wDir = 1 - (gDir - x1)./binWidth;
    
    blockSizeInPixels = params.CellSize.*params.BlockSize;
    blockStepInPixels = params.CellSize.*(params.BlockSize - params.BlockOverlap);
    
    r = 1:blockSizeInPixels(1);
    c = 1:blockSizeInPixels(2);
    
    nCells  = params.BlockSize;
    nBlocks = vision.internal.hog.getNumBlocksPerWindow(params);
    
    numCellsPerBlock = nCells(1)*nCells(2);
    hog = coder.nullcopy(...
        zeros([params.NumBins*numCellsPerBlock, nBlocks],...
        featureClass));   
    % scan across all blocks
    for j = 1:nBlocks(2)
        
        for i = 1:nBlocks(1)
            
            wz1 = wDir(r,c);
            
            w = trilinearWeights(wz1, weights);
                        
            % apply gaussian weights
            m = gMag(r,c) .* gaussianWeights;
            
            % interpolate magnitudes for binning
            mx1y1z1 = m .* w.x1_y1_z1;
            mx1y1z2 = m .* w.x1_y1_z2;
            mx1y2z1 = m .* w.x1_y2_z1;
            mx1y2z2 = m .* w.x1_y2_z2;
            mx2y1z1 = m .* w.x2_y1_z1;
            mx2y1z2 = m .* w.x2_y1_z2;
            mx2y2z1 = m .* w.x2_y2_z1;
            mx2y2z2 = m .* w.x2_y2_z2;
                                    
            orientationBins = b1(r,c);
            
            % initialize block histogram to zero
            h = zeros(params.NumBins+2, nCells(1)+2, nCells(2)+2, featureClass);
            
            % accumulate interpolated magnitudes into block histogram
            for x = 1:blockSizeInPixels(2)
                cx = weights.cellX(x);
                for y = 1:blockSizeInPixels(1)
                    z  = orientationBins(y,x);
                    cy = weights.cellY(y);
                    
                    h(z,   cy,   cx  ) = h(z,   cy,   cx  ) + mx1y1z1(y,x);
                    h(z+1, cy,   cx  ) = h(z+1, cy,   cx  ) + mx1y1z2(y,x);
                    h(z,   cy+1, cx  ) = h(z,   cy+1, cx  ) + mx1y2z1(y,x);
                    h(z+1, cy+1, cx  ) = h(z+1, cy+1, cx  ) + mx1y2z2(y,x);
                    h(z,   cy,   cx+1) = h(z,   cy,   cx+1) + mx2y1z1(y,x);
                    h(z+1, cy,   cx+1) = h(z+1, cy,   cx+1) + mx2y1z2(y,x);
                    h(z,   cy+1, cx+1) = h(z,   cy+1, cx+1) + mx2y2z1(y,x);
                    h(z+1, cy+1, cx+1) = h(z+1, cy+1, cx+1) + mx2y2z2(y,x);
                end
            end
            
            % wrap orientation bins
            h(2,:,:)     = h(2,:,:)     + h(end,:,:);
            h(end-1,:,:) = h(end-1,:,:) + h(1,:,:);
            
            % only keep valid portion of the block histogram
            h = h(2:end-1,2:end-1,2:end-1);
            
            % normalize and add block to feature vector            
            hog(:,i,j) = normalizeL2Hys(h(:));
            
            r = r + blockStepInPixels(1);
        end
        r = 1:blockSizeInPixels(1);
        c = c + blockStepInPixels(2);
    end
    
    hog = reshape(hog, 1, []);
end

% -------------------------------------------------------------------------
% Normalize vector using L2-Hys
% -------------------------------------------------------------------------
function x = normalizeL2Hys(x)
classToUse = class(x);
x = x./(norm(x,2) + eps(classToUse)); % L2 norm
x(x > 0.2) = 0.2;                     % Clip to 0.2
x = x./(norm(x,2) + eps(classToUse)); % repeat L2 norm

% -------------------------------------------------------------------------
% Compute the interpolation weights for the spatial histogram over cells
% -------------------------------------------------------------------------
function weights = spatialHistWeights(params)
% 2D interpolation weights are computed for 4 points surrounding (x,y)
%
% (x1,y1) o---------o (x2,y1)
%         |         |
%         |  (x,y)  |
%         |         |
% (x1,y2) o---------o (x2,y2)
%
% (x,y) are the pixel centers within a HOG Block
%
% (x1,y1); (x2,y1); (x1,y2); (x2,y2) are cell centers within a block

width  = single(params.BlockSize(2)*params.CellSize(2));
height = single(params.BlockSize(1)*params.CellSize(1));

x = 0.5:1:width;
y = 0.5:1:height;

[x1, cellX1] = computeLowerHistBin(x, params.CellSize(2));
[y1, cellY1] = computeLowerHistBin(y, params.CellSize(1));

wx1 = 1 - (x - x1)./single(params.CellSize(2));
wy1 = 1 - (y - y1)./single(params.CellSize(1));

weights.x1y1 = wy1' * wx1;
weights.x2y1 = wy1' * (1-wx1);
weights.x1y2 = (1-wy1)' * wx1;
weights.x2y2 = (1-wy1)' * (1-wx1);

% also store the cell indices
weights.cellX = cellX1;
weights.cellY = cellY1;

% -------------------------------------------------------------------------
% Compute tri-linear weights
% -------------------------------------------------------------------------
function weights = trilinearWeights(wz1, spatialWeights)

% define struct fields before usage
weights.x1_y1_z1 = coder.nullcopy(wz1);
weights.x1_y1_z2 = coder.nullcopy(wz1);
weights.x2_y1_z1 = coder.nullcopy(wz1);
weights.x2_y1_z2 = coder.nullcopy(wz1);
weights.x1_y2_z1 = coder.nullcopy(wz1);
weights.x1_y2_z2 = coder.nullcopy(wz1);
weights.x2_y2_z1 = coder.nullcopy(wz1);
weights.x2_y2_z2 = coder.nullcopy(wz1);

weights.x1_y1_z1 = wz1 .* spatialWeights.x1y1;
weights.x1_y1_z2 = spatialWeights.x1y1 - weights.x1_y1_z1;
weights.x2_y1_z1 = wz1 .* spatialWeights.x2y1;
weights.x2_y1_z2 = spatialWeights.x2y1 - weights.x2_y1_z1;
weights.x1_y2_z1 = wz1 .* spatialWeights.x1y2;
weights.x1_y2_z2 = spatialWeights.x1y2 - weights.x1_y2_z1;
weights.x2_y2_z1 = wz1 .* spatialWeights.x2y2;
weights.x2_y2_z2 = spatialWeights.x2y2 - weights.x2_y2_z1;

% -------------------------------------------------------------------------
% Compute the closest bin center x1 that is less than or equal to x
% -------------------------------------------------------------------------
function [x1, b1] = computeLowerHistBin(x, binWidth)
% Bin index
width    = single(binWidth);
invWidth = 1./width;
bin      = floor(x.*invWidth - 0.5);

% Bin center x1
x1 = width * (bin + 0.5);

% add 2 to get to 1-based indexing
b1 = int32(bin + 2);

% -------------------------------------------------------------------------
% Compute Gaussian and spatial weights
% -------------------------------------------------------------------------
function [gaussian, spatial] = computeWeights(params)
blockSizeInPixels = params.CellSize.*params.BlockSize;
gaussian = gaussianWeights(blockSizeInPixels);
spatial  = spatialHistWeights(params);

% -------------------------------------------------------------------------
% Gradient computation using central difference filter [-1 0 1]. Gradients
% at the image borders are computed using forward difference. Gradient
% directions are between -180 and 180 degrees measured counterclockwise
% from the positive X axis.
% -------------------------------------------------------------------------
function [gMag, gDir] = hogGradient(img,roi)

if nargin == 1
    roi = [];    
    imsize = size(img);
else
    imsize = roi(3:4);
end

img = single(img);

if ndims(img)==3
    rgbMag = zeros([imsize(1:2) 3], 'like', img);
    rgbDir = zeros([imsize(1:2) 3], 'like', img);
    
    for i = 1:3
        [rgbMag(:,:,i), rgbDir(:,:,i)] = computeGradient(img(:,:,i),roi);
    end
    
    % find max color gradient for each pixel
    [gMag, maxChannelIdx] = max(rgbMag,[],3);
    
    % extract gradient directions from locations with maximum magnitude
    sz = size(rgbMag);
    [rIdx, cIdx] = ndgrid(1:sz(1), 1:sz(2));
    ind  = sub2ind(sz, rIdx(:), cIdx(:), maxChannelIdx(:));
    gDir = reshape(rgbDir(ind), sz(1:2));
else
    [gMag,gDir] = computeGradient(img,roi);
end

% -------------------------------------------------------------------------
% Gradient computation for ROI within an image.
% -------------------------------------------------------------------------
function [gx, gy] = computeGradientROI(img, roi)
img    = single(img);
imsize = size(img);

% roi is [r c height width]
rIdx = roi(1):roi(1)+roi(3)-1;
cIdx = roi(2):roi(2)+roi(4)-1;

imgX = coder.nullcopy(zeros([roi(3)   roi(4)+2], 'like', img)); %#ok<NASGU>
imgY = coder.nullcopy(zeros([roi(3)+2 roi(4)  ], 'like', img)); %#ok<NASGU>

% replicate border pixels if ROI is on the image border. 
if rIdx(1) == 1 || cIdx(1)==1  || rIdx(end) == imsize(1) ...
        || cIdx(end) == imsize(2)
    
    if rIdx(1) == 1
        padTop = img(rIdx(1), cIdx);
    else
        padTop = img(rIdx(1)-1, cIdx);
    end
    
    if rIdx(end) == imsize(1)
        padBottom = img(rIdx(end), cIdx);
    else
        padBottom = img(rIdx(end)+1, cIdx);
    end
    
    if cIdx(1) == 1
        padLeft = img(rIdx, cIdx(1));
    else
        padLeft = img(rIdx, cIdx(1)-1);
    end
    
    if cIdx(end) == imsize(2)
        padRight = img(rIdx, cIdx(end));
    else
        padRight = img(rIdx, cIdx(end)+1);
    end
    
    imgX = [padLeft img(rIdx,cIdx) padRight];
    imgY = [padTop; img(rIdx,cIdx);padBottom];
else  
    imgX = img(rIdx,[cIdx(1)-1 cIdx cIdx(end)+1]);
    imgY = img([rIdx(1)-1 rIdx rIdx(end)+1],cIdx);
end

gx = conv2(imgX, [1 0 -1], 'valid');
gy = conv2(imgY, [1;0;-1], 'valid');

% -------------------------------------------------------------------------
function [gMag,gDir] = computeGradient(img,roi)

if isempty(roi)
    gx = zeros(size(img), 'like', img);
    gy = zeros(size(img), 'like', img);
    
    gx(:,2:end-1) = conv2(img, [1 0 -1], 'valid');
    gy(2:end-1,:) = conv2(img, [1;0;-1], 'valid');
    
    % forward difference on borders
    gx(:,1)   = img(:,2)   - img(:,1);
    gx(:,end) = img(:,end) - img(:,end-1);
    
    gy(1,:)   = img(2,:)   - img(1,:);
    gy(end,:) = img(end,:) - img(end-1,:);
else
    [gx, gy] = computeGradientROI(img, roi);
end

% return magnitude and direction
gMag = hypot(gx,gy);
gDir = atan2d(-gy,gx);

% -------------------------------------------------------------------------
% Compute spatial weights for HOG blocks.
% -------------------------------------------------------------------------
function h = gaussianWeights(blockSize)

sigma = 0.5 * cast(blockSize(1), 'double');

h = fspecial('gaussian', double(blockSize), sigma);

h = cast(h, 'single');

% -------------------------------------------------------------------------
% Extract valid points 
% -------------------------------------------------------------------------
function validPoints = extractValidPoints(points, idx)
if isnumeric(points)
    validPoints = points(idx,:);
else    
    if isempty(coder.target)
        validPoints = points(idx);
    else
        validPoints = getIndexedObj(points, idx);
    end
end

% -------------------------------------------------------------------------
% Input parameter parsing and validation
% -------------------------------------------------------------------------
function [points, isPoints, params, maxargs] = parseInputs(I, varargin)

notCodegen = isempty(coder.target);

sz = size(I);
validateImage(I);

if mod(nargin-1,2) == 1
    isPoints = true;
    points = varargin{1};
    checkPoints(points);
else
    isPoints = false;
    points = ones(0,2);
end

if notCodegen
    p = getInputParser();    
    parse(p, varargin{:});
    userInput = p.Results;          
    validate(userInput);    
    autoOverlap =  ~isempty(regexp([p.UsingDefaults{:} ''],...
        'BlockOverlap','once'));
else
    if isPoints
        [userInput, autoOverlap] = codegenParseInputs(varargin{2:end});
    else
        [userInput, autoOverlap] = codegenParseInputs(varargin{:});    
    end   
    validate(userInput);      
end

params = setParams(userInput,sz);
if autoOverlap
    params.BlockOverlap = getAutoBlockOverlap(params.BlockSize); 
end
crossValidateParams(params);

if isPoints
    maxargs = 3;
    params.WindowSize = params.BlockSize .* params.CellSize;
else
    maxargs = 2;
    params.WindowSize = params.ImageSize;
end

% -------------------------------------------------------------------------
% Input image validation
% -------------------------------------------------------------------------
function validateImage(I)
% validate image
validateattributes(I, {'double','single','int16','uint8','logical'},...
    {'nonempty','real', 'nonsparse','size', [NaN NaN NaN]},...
    'extractHOGFeatures');

sz = size(I);
coder.internal.errorIf(ndims(I)==3 && sz(3) ~= 3,...
                       'vision:dims:imageNot2DorRGB');

coder.internal.errorIf(any(sz(1:2) < 3),...
                       'vision:extractHOGFeatures:imageDimsLT3x3');

% -------------------------------------------------------------------------
% Input parameter parsing for codegen
% -------------------------------------------------------------------------
function [results, usingDefaultBlockOverlap] = codegenParseInputs(varargin)
pvPairs = struct( ...
    'CellSize',     uint32(0), ...
    'BlockSize',    uint32(0), ...
    'BlockOverlap', uint32(0),...
    'NumBins',      uint32(0),...
    'UseSignedOrientation', uint32(0));

popt = struct( ...
    'CaseSensitivity', false, ...
    'StructExpand'   , true, ...
    'PartialMatching', true);

defaults = getParamDefaults();

optarg = eml_parse_parameter_inputs(pvPairs, popt, varargin{:});

usingDefaultBlockOverlap = ~optarg.BlockOverlap;

results.CellSize  = eml_get_parameter_value(optarg.CellSize, ...
    defaults.CellSize, varargin{:});

results.BlockSize = eml_get_parameter_value(optarg.BlockSize, ...
    defaults.BlockSize, varargin{:});

results.BlockOverlap = eml_get_parameter_value(optarg.BlockOverlap, ...
    defaults.BlockOverlap, varargin{:});

results.NumBins = eml_get_parameter_value(optarg.NumBins, ...
    defaults.NumBins, varargin{:});

results.UseSignedOrientation  = eml_get_parameter_value(...
    optarg.UseSignedOrientation, ...
    defaults.UseSignedOrientation, varargin{:});

% -------------------------------------------------------------------------
% Set block overlap based on block size
% -------------------------------------------------------------------------
function autoBlockSize = getAutoBlockOverlap(blockSize)
szGTOne = blockSize > 1;
autoBlockSize = zeros(size(blockSize), 'like', blockSize);
autoBlockSize(szGTOne) = cast(ceil(double(blockSize(szGTOne))./2), 'like', ...
    blockSize);

% -------------------------------------------------------------------------
% Default parameter values
% -------------------------------------------------------------------------
function defaults = getParamDefaults()
intClass = 'int32';
defaults = struct('CellSize'    , cast([8 8],intClass),...
                  'BlockSize'   , cast([2 2],intClass), ...
                  'BlockOverlap', cast([1 1],intClass), ...
                  'NumBins'     , cast( 9   ,intClass), ...
                  'UseSignedOrientation', false,...
                  'ImageSize' , cast([1 1],intClass),...
                  'WindowSize', cast([1 1],intClass));
              
% -------------------------------------------------------------------------
function params = setParams(userInput,sz)
params.CellSize     = reshape(int32(userInput.CellSize), 1, 2);
params.BlockSize    = reshape(int32(userInput.BlockSize), 1 , 2);
params.BlockOverlap = reshape(int32(userInput.BlockOverlap), 1, 2);
params.NumBins      = int32(userInput.NumBins);
params.UseSignedOrientation = logical(userInput.UseSignedOrientation);
params.ImageSize  = int32(sz(1:2));
params.WindowSize = int32([1 1]);

% -------------------------------------------------------------------------
% Input parameter validation 
% -------------------------------------------------------------------------
function validate(params)

checkSize(params.CellSize,  'CellSize');

checkSize(params.BlockSize, 'BlockSize');

checkOverlap(params.BlockOverlap);

checkNumBins(params.NumBins);

checkUsedSigned(params.UseSignedOrientation);

% -------------------------------------------------------------------------
% Cross validation of input values
% -------------------------------------------------------------------------
function crossValidateParams(params)
% Cross validate parameters
                   
coder.internal.errorIf(any(params.BlockOverlap(:) >= params.BlockSize(:)), ...
    'vision:extractHOGFeatures:blockOverlapGEBlockSize');

% -------------------------------------------------------------------------
function parser = getInputParser()
persistent p;
if isempty(p)
    
    defaults = getParamDefaults();
    p = inputParser();
    
    addOptional(p, 'Points', []);    
    addParameter(p, 'CellSize',     defaults.CellSize);
    addParameter(p, 'BlockSize',    defaults.BlockSize);
    addParameter(p, 'BlockOverlap', defaults.BlockOverlap);
    addParameter(p, 'NumBins',      defaults.NumBins);
    addParameter(p, 'UseSignedOrientation', defaults.UseSignedOrientation);    
    
    parser = p;
else
    parser = p;
end

% -------------------------------------------------------------------------
function checkPoints(pts)

if vision.internal.inputValidation.isValidPointObj(pts);    
    vision.internal.inputValidation.checkPoints(pts, mfilename, 'POINTS');   
else
    validateattributes(pts, ...
        {'int16', 'uint16', 'int32', 'uint32', 'single', 'double'}, ...
        {'2d', 'nonsparse', 'real', 'size', [NaN 2]},...
        mfilename, 'POINTS');
end

% -------------------------------------------------------------------------
function checkSize(sz,name)

vision.internal.errorIfNotFixedSize(sz, name);
validateattributes(sz, {'numeric'}, ...
                   {'real','finite','positive','nonsparse','numel',2,'integer'},...
                   'extractHOGFeatures',name); 

% -------------------------------------------------------------------------
function checkOverlap(sz)

vision.internal.errorIfNotFixedSize(sz, 'BlockOverlap');
validateattributes(sz, {'numeric'}, ...
                   {'real','finite','nonnegative','nonsparse','numel',2,'integer'},...
                   'extractHOGFeatures','BlockOverlap');

% -------------------------------------------------------------------------
function checkNumBins(x)

vision.internal.errorIfNotFixedSize(x, 'NumBins');
validateattributes(x, {'numeric'}, ...
                   {'real','positive','scalar','finite','nonsparse','integer'},...
                   'extractHOGFeatures','NumBins');

% -------------------------------------------------------------------------
function checkUsedSigned(isSigned)

vision.internal.errorIfNotFixedSize(isSigned, 'UseSignedOrientation');
validateattributes(isSigned, {'logical','numeric'},...
    {'nonnan', 'scalar', 'real','nonsparse'},...
    'extractHOGFeatures','UseSignedOrientation');

% -------------------------------------------------------------------------
function checkNumOutputsForCodegen(numOut, maxargs)

if ~isempty(coder.target)
    % Do not allow HOG visualization if generating code
    coder.internal.errorIf(numOut > maxargs-1,...
        'vision:extractHOGFeatures:hogVisualizationNotSupported');    
end