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

    classdef HistogramBasedTracker < matlab.System
%HistogramBasedTracker Track object in video based on histogram
% 
%   H = vision.HistogramBasedTracker returns a System object, H, that
%   tracks an object by using the Continuously Adaptive Mean Shift
%   (CAMShift) algorithm. It uses the histogram of pixel values to identify
%   the tracked object. To initialize the tracking process, you must use
%   the initializeObject method to specify an exemplar image of the object.
%   Then, use the step method to track the object in consecutive video
%   frames.
% 
%   H = vision.HistogramBasedTracker('PropertyName', PropertyValue, ...)
%   returns a tracker System object, H, with each specified property set to
%   the specified value.
% 
%   initializeObject method syntax:
% 
%   initializeObject(H, I, R) sets the object to track by extracting it
%   from the [x y width height] region R, in an M-by-N image I. I can be
%   any 2-D feature map that distinguishes the object from the background.
%   For example, I can be a hue channel of the HSV color space. R also
%   represents the initial search window for the next call to the step
%   method. Typically, I is the first frame of a video in which the object
%   appears. For best results, the object must occupy the majority of R.
% 
%   initializeObject(H, I, R, N) additionally, lets you specify N, the
%   number of histogram bins. By default, N is set to 16. Increasing N
%   enhances the ability of the tracker to discriminate the object.
%   However, it also narrows the range of changes to the object's visual
%   characteristics that the tracker can accommodate, making it more
%   susceptible to losing track.
%
%   step method syntax:
% 
%   BBOX = step(H, I) returns the [x y width height] bounding box, BBOX, of
%   the tracked object. Before calling the step method, use the
%   initializeObject method to identify the object to track, and to set the
%   initial search window.
% 
%   [BBOX, ORIENTATION] = step(H, I) additionally returns the angle between
%   the x-axis and the major axis of the ellipse, which has the same second
%   order moments as the object. The returned angle ranges from -pi/2 to
%   pi/2.
% 
%   [BBOX, ORIENTATION, SCORE] = step(H, I) additionally returns the
%   confidence score indicating whether the returned bounding box, BBOX,
%   contains the tracked object. SCORE is between 0 and 1, with the
%   greatest confidence equal to 1.
% 
%   initializeSearchWindow method syntax:
% 
%   initializeSearchWindow(H, R) sets the initial search  window, R,
%   specified as [x y width height]. The next call to the step method will
%   use R as the initial window to search for the object. This method is
%   useful when you lose track of the object. You can use it to
%   re-initialize object's initial location and size.
% 
%   System objects may be called directly like a function instead of using
%   the step method. For example, y = step(obj, x) and y = obj(x) are
%   equivalent.
%
%   HistogramBasedTracker methods:
% 
%   step                   - See above description for use of this method
%   initializeObject       - See above description for use of this method
%   initializeSearchWindow - See above description for use of this method
%   release                - Allow property value and input characteristics changes
%   clone                  - Create a tracker object with the same property values
%   isLocked               - Locked status (logical)
% 
%   HistogramBasedTracker properties:
% 
%   ObjectHistogram - Normalized pixel value histogram
% 
%   Notes:
% 
%   - The HistogramBasedTracker is most suitable for tracking a single
%     object. 
%   - You can improve the computational speed of the HistogramBasedTracker
%     by setting the class of the image, I, to uint8.
% 
%   Example: Tracking a face
% 
%   % Create System objects for reading and displaying video, and for
%   % drawing bounding box of the object.
%   videoFileReader = vision.VideoFileReader('vipcolorsegmentation.avi');
%   videoPlayer = vision.VideoPlayer();
% 
%   % Read the first video frame which contains the object and then show
%   % the object region
%   objectFrame = step(videoFileReader);  % read the first video frame
%   objectHSV = rgb2hsv(objectFrame); % convert to HSV color space
%   objectRegion = [40, 45, 25, 25];  % define the object region
%   objectImage = insertShape(objectFrame, 'Rectangle', objectRegion, ...
%                            'Color', [1 0 0]);
%   figure; imshow(objectImage); title('Red box shows object region');
%   % You can also use the following commands to select the object region
%   % using a mouse. The object must occupy majority of the region.
%   % figure; imshow(objectFrame); objectRegion=round(getPosition(imrect))
% 
%   % Set the object based on the hue channel of the first video frame
%   tracker = vision.HistogramBasedTracker;
%   initializeObject(tracker, objectHSV(:,:,1) , objectRegion);
% 
%   % Track and display the object in each video frame
%   while ~isDone(videoFileReader)
%     frame = step(videoFileReader);          % Read next image frame
%     hsv = rgb2hsv(frame);                   % Convert to HSV color space
%     bbox = step(tracker, hsv(:,:,1));       % Track object in hue channel
%                                             % where it's distinct from
%                                             % the background
%     out = insertShape(frame, 'Rectangle', ...
%                   bbox, 'Color', 'red');  % Draw a box around the object
%     step(videoPlayer, out);                 % Show results
%   end
% 
%   release(videoPlayer);
%   release(videoFileReader);

%   Copyright 2011-2016 The MathWorks, Inc.
% 
%   References:
% 
%   G.R. Bradski "Computer Vision Face Tracking for Use in a Perceptual
%   User Interface," Intel, 1998.
%
%   See also insertShape, imrect, rgb2hsv.

%#codegen
%#ok<*EMCLS>
%#ok<*EMCA>

  properties
    %ObjectHistogram Normalized pixel value histogram
    %   Set this property to an N-element vector which is the normalized
    %   histogram of the object's pixel values. Histogram values must be
    %   normalized to between 0 and 1. You can use the initializeObject
    %   method to set this property. This property is tunable.
    %
    %   Default: []
    ObjectHistogram = single([]);
  end
  
  properties (Hidden, Access=private)
    %InitialWindow Initial window for searching the object
    %   Specifies the object's initial location and size prior to refining
    %   them through an iterative search. Format of InitialWindow is [x y
    %   width height], where x and y specify the location of the upper-left
    %   corner of the bounding box.
    % 
    %   This property is initialized by the initializeObject or
    %   initializeSearchWindow method. The algorithm updates this property
    %   every time it processes an image.
    InitialWindow = [];
  end
  
  properties (Nontunable, Access=private)
    %ExpansionRatio Percentage that the object's size may increase between frames
    %   Specifies the percentage that the object's size may increase between
    %   frames. The percentage is defined as the ratio of the change of size
    %   relative to its original size. For example, value of 5 means that
    %   the object may increase its size by 0.05 * max(width, height) in
    %   each dimension in the next video frame.
    %
    %   A small value is preferred if the object has consistent size, while
    %   a large value can be used if the object changes size rapidly. If
    %   this parameter is too small, the returned bounding box will not be
    %   able to cover the whole object when the object expands quickly; on
    %   the other hand, if this parameter is too large, the returned
    %   bounding box may include too much non-object area.
    % 
    %   The actual change of the object's size is affected by, but can be
    %   greater than this property.
    ExpansionRatio = 5;
    %MaximumIterations Maximum number of iterations
    %   Specifies the maximum number of iterations for searching the object
    %   in the image. The actual number of iterations may be less than this
    %   property, if the change of the object's center location is less
    %   than the 'MaximumStepSize' property.
    MaximumIterations = 20;
    %MaximumStepSize Minimum change of the object's center location
    %   Specifies the minimum change of the object's center location for
    %   searching the object in an image. The System object may stop
    %   searching for more accurate location of the object even when the
    %   change of center location is greater than this property, if the
    %   number of iteration is equal to the 'MaximumIterations' property.
    MaximumStepSize = 0.5;
  end

  methods
    function obj = HistogramBasedTracker(varargin)
      setProperties(obj, nargin, varargin{:});
    end
  end
  
  methods(Access=protected)
    function [bbox, orientation, score] = stepImpl(obj, I)
      coder.internal.errorIf(isempty(obj.ObjectHistogram), ...
        'vision:histogramBasedTracker:objectNotSet');
      
      % Determine initial window for search the object
      [h, w] = size(I);
      initialWindow = obj.clipROI([w,h], obj.InitialWindow);
      
      if all(initialWindow(3:4) > 0)
        % Compute location and size of the object
        P = obj.backProject(I, obj.ObjectHistogram);
        window = meanShift(obj, P, initialWindow);
        [bbox, orientation] = computeBoundingBox(obj, P, window);

        % Compute confidence score
        bboxArea = bbox(3) * bbox(4);
        if bboxArea > 0
          score = double(sum(sum(obj.cropImage(P, bbox), 1), 2) / bboxArea);
        else
          score = 0;
        end
      else  % The initial window is empty
        bbox = [initialWindow(1:2), 0, 0];
        obj.InitialWindow = bbox;
        orientation = 0;
        score = 0;
      end
    end
    
    %----------------------------------------------------------------------
    function validateInputsImpl(obj, I)
      obj.validateImage(I);
    end

    %----------------------------------------------------------------------
    function num = getNumOutputsImpl(~)
        num = 3;
    end
    
    %----------------------------------------------------------------------
    function flag = isInputComplexityLockedImpl(~,~)
        flag = true;
    end
    
    %----------------------------------------------------------------------
    function flag = isOutputComplexityLockedImpl(~,~)
        flag = true;
    end
    
    %----------------------------------------------------------------------
    function s = saveObjectImpl(obj)
        s.ObjectHistogram = obj.ObjectHistogram;
        s.InitialWindow = obj.InitialWindow;
    end
    
    %----------------------------------------------------------------------
    function loadObjectImpl(obj,s, ~)
        if ~isempty(s.ObjectHistogram)
          obj.ObjectHistogram = s.ObjectHistogram;
        end
        
        if ~isempty(s.InitialWindow)
          obj.InitialWindow = s.InitialWindow;
        end
    end
  end
  
  methods
    function initializeObject(obj, I, roi, varargin)
      % initializeObject Sets the object to track
      %   initializeObject(H, I, R) sets the object to track by extracting
      %   it from the [x y width height] region R, in an M-by-N image I. I
      %   can be any 2-D feature map that distinguishes the object from the
      %   background. For example, I can be a hue channel of the HSV color
      %   space. R also represents the initial search window for the next
      %   call to the step method. Typically, I is the first frame of a
      %   video in which the object appears. For best results, the object
      %   must occupy the majority of R.
      % 
      %   initializeObject(H, I, R, N) additionally, lets you specify the
      %   number of histogram bins N. By default, N is set to 16. N must be
      %   chosen according to the image data. A larger N does not always
      %   produce better result.
      coder.internal.errorIf(nargin<3 || nargin>4, ...
        'vision:histogramBasedTracker:invalidInputsToSetObject');
    
      obj.validateImage(I);
      
      validateattributes(roi, {'numeric'}, ...
        {'real', 'nonsparse', 'integer', 'positive', 'finite', ...
        'size', [1, 4]}, 'HistogramBasedTracker', 'R');
      
      [h, w] = size(I);
      roi = obj.clipROI([w, h], double(roi));
      coder.internal.errorIf(any(roi(3:4) <= 0), ...
        'vision:histogramBasedTracker:invalidObjectRegion');

      if nargin < 4
        N = 16;
      else
        N = varargin{1};
        validateattributes(N, {'numeric'}, ...
          {'real', 'scalar', 'finite', 'positive', 'integer'},...
          'HistogramBasedTracker', 'N');
      end

      Isub = obj.cropImage(I, roi);
      objectHistogram = obj.hist2D(Isub, N);
      obj.ObjectHistogram = objectHistogram / max(objectHistogram);
      initializeSearchWindow(obj, roi);
    end

    %----------------------------------------------------------------------
    function initializeSearchWindow(obj, value)
      % initializeSearchWindow Sets the initial search window
      %   initializeSearchWindow(H, R) sets the initial search  window, R,
      %   specified as [x y width height]. The next call to the step method
      %   will use R as the initial window to search for the object. This
      %   method is useful when you lose track of the object. You can use
      %   it to re-initialize object's initial location and size.
      obj.InitialWindow = value;
    end
  end
  
  methods
    function set.ObjectHistogram(obj, value)
      validateattributes(value, {'numeric'}, ...
        {'vector', 'real', 'nonsparse', 'nonnegative', '<=', 1}, ...
        'HistogramBasedTracker', 'ObjectHistogram');
      
      obj.ObjectHistogram = single(value);
    end
    
    %----------------------------------------------------------------------
    function set.InitialWindow(obj, value) %#ok<*MCSGA>
      validateattributes(value, {'numeric'}, ...
        {'real', 'nonsparse', 'integer', 'finite', 'nonnegative', ...
        'size', [1, 4]}, 'HistogramBasedTracker', 'InitialWindow');

      obj.InitialWindow = double(value);
    end
  end

  methods (Static, Access=protected)
    function out = cropImage(in, roi)
      out = in(roi(2):roi(2)+roi(4)-1, roi(1):roi(1)+roi(3)-1, :);
    end
    
    %----------------------------------------------------------------------
    function out = clipROI(imageSize, in)
      upperLeft = in(1:2);
      bottomRight = in(1:2) + in(3:4) - 1;
      
      if all(upperLeft <= imageSize) && all(bottomRight >= [1,1])
        upperLeft = max(upperLeft, [1, 1]);
        bottomRight = min(bottomRight, imageSize);
        out = [upperLeft, bottomRight-upperLeft+1];
      else % The object is completely outside the image
        if all(upperLeft <= imageSize)
          out = [1, 1, 0, 0];
        else
          out = [imageSize, 0, 0];
        end
      end
    end
    
    %----------------------------------------------------------------------
    function h = hist2D(I, N)
      if isfloat(I)
        binWidth = 1 / N;
        edge = 0: binWidth: 1;
        edge(1) = -inf;
        edge(end) = inf;
      else
        edge = 0 : 255/N : 256;
      end
      h = single(histc(I(:), edge, 1));
      h(end-1) = h(end-1) + h(end);
      h = h(1:end-1);
    end
    
    %----------------------------------------------------------------------
    function P = backProject(I, h)
      N = length(h);
      switch class(I)
        case 'uint8'
          scale = N / 256;
          bin = floor(double(I) * scale) + 1;
        case {'double', 'single'}
          bin = floor(I * N) + 1;
          bin = max(bin, 1);
          bin = min(bin, N);
        otherwise
          classToUse = class(I);
          scale = N ...
            / (double(intmax(classToUse)) - double(intmin(classToUse)) + 1);
          bin = floor((double(I) - double(intmin(classToUse))) * scale) + 1;
      end
      if size(bin, 1) == 1
        ht = h'; % Transpose the histogram in order to keep the shape
                 % of image after matrix indexing.
        P = ht(bin);
      else
        P = h(bin);
      end
    end
    
    %----------------------------------------------------------------------
    function m = moment2D(P, mode)
      switch(mode)
        case 'm'
          m = sum(sum(P, 1), 2);
        case 'mx'
          ms = sum(P, 1);
          m = sum(ms .* (1: length(ms)));
        case 'my'
          ms = sum(P, 2);
          m = sum(ms .* (1: length(ms))');
        case 'mxy'
          m = sum(sum(P .* ((1:size(P,1))' * (1:size(P,2))), 1), 2);
        case 'mxx'
          ms = sum(P, 1);
          m = sum(ms .* ((1: length(ms)).^2));
        case 'myy'
          ms = sum(P, 2);
          m = sum(ms .* ((1: length(ms)).^2)');
      end
      m = double(m);
    end
    
    %----------------------------------------------------------------------
    function validateImage(I)
      validateattributes(I, ...
        {'uint8', 'single', 'double'}, ...
        {'real', 'nonempty', 'nonsparse', '2d'},...
        'HistogramBasedTracker', 'I');
    end
  end
  
  methods(Access=protected)
    function bbox = meanShift(obj, P, roi)
      idx = 0;
      dis = obj.MaximumStepSize;
      [h, w] = size(P);
      r = double(roi);
      while idx < obj.MaximumIterations && dis >= obj.MaximumStepSize
        Psub = obj.cropImage(P, r);
        m = obj.moment2D(Psub, 'm');
        my = obj.moment2D(Psub, 'my');
        mx = obj.moment2D(Psub, 'mx');
        if m == 0
          % All pixels in roi are zeros
          dis = 0;
          r(3:4) = [0,0];
        else
          % Shift bounding box to mass center
          halfSize = (r(3:4) - 1) / 2;
          oldCent = r(1:2) + halfSize;
          newCent = r(1:2) - 1 + [mx, my] / m;
          dis = norm(newCent - oldCent);
          r(1:2) = round(newCent - halfSize);
          r = obj.clipROI([w, h], r);
        end
        idx = idx + 1;
      end
      bbox = r;
    end
    
    %----------------------------------------------------------------------
    function [cent, orientation, majorAxis, minorAxis]...
        = fitEllipse(obj, P, roi)
      
      % Compute the moments of the probability map in the extended ROI.
      Psub = obj.cropImage(P, roi);
      m = obj.moment2D(Psub, 'm') + eps;
      my = obj.moment2D(Psub, 'my');
      mx = obj.moment2D(Psub, 'mx');
      mxy = obj.moment2D(Psub, 'mxy');
      myy = obj.moment2D(Psub, 'myy');
      mxx = obj.moment2D(Psub, 'mxx');
      uyy = myy / m - (my / m)^2;
      uxx = mxx / m - (mx / m)^2;
      % The angle of the major axis is measured in the counter-clockwise
      % direction, so uxy is computed in the following method.
      uxy = -mxy / m + my * mx / m^2;

      % Calculate major axis length, minor axis length, and eccentricity.
      common = sqrt((uxx - uyy)^2 + 4*uxy^2);
      majorAxis = 2 * sqrt(2) * sqrt(uxx + uyy + common);
      minorAxis = 2 * sqrt(2) * sqrt(uxx + uyy - common);

      % Calculate orientation.
      if (uyy > uxx)
          num = uyy - uxx + sqrt((uyy - uxx)^2 + 4*uxy^2);
          den = 2*uxy;
      else
          num = 2*uxy;
          den = uxx - uyy + sqrt((uxx - uyy)^2 + 4*uxy^2);
      end
      if (num == 0) && (den == 0)
          orientation = 0;
      else
          orientation = atan(num/den);
      end
      cent = [mx, my] / m + roi(1:2) - 1;
    end
    
    %----------------------------------------------------------------------
    function [bbox, orientation] = computeBoundingBox(obj, P, roi)
      expansionRatio = obj.ExpansionRatio / 200;
      [h, w] = size(P);
      imageSize = [w, h];
      expandedSize = ceil(max(roi(3:4)) * expansionRatio);
      expandedROI = zeros(1, 4);
      expandedROI(1:2) = max(roi(1:2) - expandedSize * [1, 1], [1, 1]);
      expandedROI(3:4) = min(roi(3:4) + 2 * expandedSize * [1, 1], ...
                         imageSize - expandedROI(1:2) + 1);

      % Estimate size of the bounding box of the object based on the
      % orientation and axis of the ellipse.
      [cent, orientation, majorAxis, minorAxis]...
        = fitEllipse(obj, P, expandedROI);
      sn = abs(sin(orientation));
      cs = abs(cos(orientation));
      newWidth  = max(majorAxis * cs, minorAxis * sn);
      newHeight = max(majorAxis * sn, minorAxis * cs);
      
      % Compute the new bounding box
      halfSize = [newWidth, newHeight] / 2;
      bbox = round([cent - halfSize, newWidth, newHeight]);
      bbox = obj.clipROI([w, h], bbox);
      initializeSearchWindow(obj, bbox);
    end
  end
end