gusucode.com > vision工具箱matlab源码程序 > vision/+vision/PointTracker.m
classdef PointTracker < matlab.System %PointTracker Track points in video using Kanade-Lucas-Tomasi (KLT) algorithm % % H = vision.PointTracker returns a System object, H, that % tracks a set of points using the Kanade-Lucas-Tomasi feature tracking % algorithm. To initialize the tracking process, you must use % the initialize method to specify the initial locations of the points % and the initial video frame. Then, use the step method to track the % points in subsequent video frames. You can also reset the points % at any time by using the setPoints method. % % H = vision.PointTracker(Name, Value, ...) % configures the tracker object properties, specified as one or more % name-value pair arguments. Unspecified properties have default values. % % initialize method syntax: % ------------------------- % % initialize(H, POINTS, I) initializes points to track and sets the % initial video frame. The initial locations POINTS, must be an M-by-2 % array of [x y] coordinates. The initial video frame I, must be a 2-D % grayscale or RGB image, and must be the same size and data type as the % video frames passed to the step method. % % step method syntax: % ------------------- % % [POINTS, POINT_VALIDITY] = step(H, I) tracks the points in the input % frame, I. The output POINTS contain an M-by-2 array of [x y] % coordinates that correspond to the new locations of the points in the % input frame, I. The output, POINT_VALIDITY provides an M-by-1 logical % array, indicating whether or not each point has been reliably tracked. % The input frame, I must be the same size and data type as the video % frames passed to the initialize method. % % [POINTS, POINT_VALIDITY, SCORES] = step(H, I) additionally returns the % confidence score for each point. The M-by-1 output array, SCORE, % contains values between 0 and 1. These values correspond to the degree % of similarity between the neighborhood around the previous location and % new location of each point. These values are computed as a function of % the sum of squared differences between the previous and new % neighborhoods. The greatest tracking confidence corresponds to a % perfect match score of 1. % % setPoints method syntax: % ------------------------ % % setPoints(H, POINTS) sets the points for tracking. The method sets the % M-by-2 POINTS array of [x y] coordinates with the points to track. This % method can be used if the points need to be re-detected because too % many of them have been lost during tracking. % % setPoints(H, POINTS, POINT_VALIDITY) additionally lets you mark points % as either valid or invalid. The input logical vector POINT_VALIDITY of % length M, contains the true or false value corresponding to the % validity of the point to be tracked. The length M corresponds to the % number of points. A false value indicates an invalid point that should % not be tracked. For example, you can use this method with the % estimateGeometricTransform function to determine the transformation % between the point locations in the previous and current frames, and % then mark the outliers as invalid. % % 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. % % PointTracker methods: % --------------------- % % step - See above description for use of this method % initialize - See above description for use of this method % setPoints - 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) % % PointTracker properties: % ------------------------ % % BlockSize - The size of the integration window around each point. % NumPyramidLevels - The number of pyramid levels. % MaxIterations - The maximum number of search iterations. % MaxBidirectionalError - Maximum threshold on the forward-backward error. % % % Example: Tracking a face % ------------------------ % % % Create System objects for reading and displaying video, and for % % drawing a bounding box of the object. % videoFileReader = vision.VideoFileReader('visionface.avi'); % videoPlayer = vision.VideoPlayer('Position', [100, 100, 680, 520]); % % % Read the first video frame which contains the object and then show % % the object region % objectFrame = step(videoFileReader); % read the first video frame % % objectRegion = [264, 122, 93, 93]; % define the 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)) % % objectImage = insertShape(objectFrame, 'Rectangle', objectRegion, ... % 'Color', 'red'); % figure; imshow(objectImage); title('Red box shows object region'); % % % Detect interest points in the object region % points = detectMinEigenFeatures(rgb2gray(objectFrame), 'ROI', objectRegion); % % % Display the detected points % pointImage = insertMarker(objectFrame, points.Location, '+', ... % 'Color', 'white'); % figure, imshow(pointImage), title('Detected interest points'); % % % Create a tracker object. % tracker = vision.PointTracker('MaxBidirectionalError', 1); % % % Initialize the tracker % initialize(tracker, points.Location, objectFrame); % % % Track and display the points in each video frame % while ~isDone(videoFileReader) % frame = step(videoFileReader); % Read next image frame % [points, validity] = step(tracker, frame); % Track the points % out = insertMarker(frame, points(validity, :), '+'); % Display points % step(videoPlayer, out); % Show results % end % % release(videoPlayer); % release(videoFileReader); % % See also vision.MarkerInserter, detectHarrisFeatures, % detectMinEigenFeatures, detectFASTFeatures, imcrop, imrect, % vision.HistogramBasedTracker. % Copyright 2011-2016 The MathWorks, Inc. % % References: % ----------- % % Bruce D. Lucas and Takeo Kanade. An Iterative Image Registration % Technique with an Application to Stereo Vision. % International Joint Conference on Artificial Intelligence, 1981. % % Carlo Tomasi and Takeo Kanade. Detection and Tracking of Point Features. % Carnegie Mellon University Technical Report CMU-CS-91-132, 1991. % % Jianbo Shi and Carlo Tomasi. Good Features to Track. % IEEE Conference on Computer Vision and Pattern Recognition, 1994. % % Zdenek Kalal, Krystian Mikolajczyk and Jiri Matas. Forward-Backward % Error: Automatic Detection of Tracking Failures. % International Conference on Pattern Recognition, 2010 %#codegen %#ok<*EMCA> properties(Nontunable) % MaxBidirectionalError Forward-backward error threshold % Specify a numeric scalar for the maximum bidirectional error. If % the value is less than inf, the object tracks each point from the % previous to the current frame, and then tracks the same points back % to the previous frame. The object calculates the bidirectional % error, which is the distance in pixels from the original location % of the points to the final location after the backward tracking. % The corresponding points are considered invalid when the error is % greater than the value set for this property. Recommended values % are between 0 and 3 pixels. % % Using the bidirectional error is an effective way to eliminate % points that could not be reliably tracked. However, the % bidirectional error requires additional computation. When you set % the MaxBidirectionalError property to inf, the object will not % compute the bidirectional error. % % Default: inf MaxBidirectionalError = inf; % BlockSize Size of integration window % Specify a two-element vector, [height, width] to represent the % neighborhood around each point being tracked. The height and width % must be odd integers. This neighborhood defines the area for the % spatial gradient matrix computation. The minimum value for % BlockSize is [5 5]. Increasing the size of the neighborhood, % increases the computation time. % % Default: [31 31] BlockSize = [31, 31]; % NumPyramidLevels Number of pyramid levels % Specify an integer scalar number of pyramid levels. The point % tracker implementation of the KLT algorithm uses image pyramids. % The object generates an image pyramid, where each level is reduced % in resolution by a factor of two compared to the previous level. % Selecting a pyramid level greater than one, enables the algorithm % to track the points at multiple levels of resolution, starting at % the lowest level. Increasing the number of pyramid levels allows % the algorithm to handle larger displacements of points between % frames, but also increases computation. Recommended values are % between 1 and 4. % % Default: 3 NumPyramidLevels = 3; % MaxIterations Maximum number of search iterations % Specify a positive integer scalar for the maximum number of search % iterations for each point. The KLT algorithm performs an iterative % search for the new location of each point until convergence. % Typically, the algorithm converges within 10 iterations. This % property sets the limit on the number of search iterations. % Recommended values are between 10 and 50. % % Default: 30 MaxIterations = 30; end properties (Transient, Access=private) % C++ wrapper for OpenCV KLT pTracker; end properties (Hidden, Access=private) %The size of the video frame. Needed for input validation. FrameSize = [0 0]; %Number of points NumPoints = 0; % Is the image RGB or grayscale? Needed for input validation. IsRGB = false; % The class of the video frame. Needed for clone FrameClassID = 0; end properties (Nontunable, Hidden, Access=private) % Has the object been initialized? IsInitialized = false; % The class of the POINTS array. Needed for clone. PointClassID = 0; end methods %---------------------------------------------------------------------- % Constructor function obj = PointTracker(varargin) setProperties(obj, nargin, varargin{:}); if (isSimMode()) obj.pTracker = vision.internal.PointTracker; else obj.pTracker = ... vision.internal.buildable.pointTrackerBuildable.pointTracker_construct(); end end % Public properties validation %---------------------------------------------------------------------- function set.MaxBidirectionalError(obj, val) validateattributes(val, {'numeric'}, ... {'scalar', 'real', 'nonnegative', 'nonnan', 'nonsparse'}, ... 'PointTracker', 'MaxBidirectionalError'); obj.MaxBidirectionalError = val; end %---------------------------------------------------------------------- function set.BlockSize(obj, val) validateattributes(val, {'numeric'}, ... {'positive', 'nonempty', 'nonsparse', 'numel', 2, 'integer', ... 'vector', 'odd', '>=', 5}, 'PointTracker', 'BlockSize'); obj.BlockSize = val(:)'; end %---------------------------------------------------------------------- function set.NumPyramidLevels(obj, val) validateattributes(val, {'numeric'}, ... {'scalar', 'nonnegative', 'integer', 'nonsparse'}, ... 'PointTracker', 'NumPyramidLevels'); obj.NumPyramidLevels = val; end %---------------------------------------------------------------------- function set.MaxIterations(obj, val) validateattributes(val, {'numeric'}, ... {'scalar', 'positive', 'integer', 'nonsparse'}, ... 'PointTracker', 'MaxIterations') obj.MaxIterations = val; end end methods(Access=protected) %---------------------------------------------------------------------- function [points, pointValidity, scores] = stepImpl(obj, I) % step() can only be called after initialize() coder.internal.errorIf(~obj.IsInitialized, ... 'vision:PointTracker:trackerUninitialized'); coder.assertDefined(obj.IsInitialized); coder.assertDefined(obj.PointClassID); % the class of the frame cannot be changed unless the object % is released and re-initialized coder.internal.errorIf(getClassID(I) ~= obj.FrameClassID, ... 'vision:PointTracker:expectedSameImageClassAsInitialized'); Iu8 = im2uint8(I); % see if the image needs to be converted to grayscale if size(I, 3) > 1 coder.internal.errorIf(~obj.IsRGB, ... 'vision:PointTracker:expectedGrayscale'); Iu8_gray = rgb2gray(Iu8); else Iu8_gray = Iu8; coder.internal.errorIf(obj.IsRGB, ... 'vision:PointTracker:expectedRGB'); end % the size of the frame cannot be changed unless the object % is released and re-initialized coder.internal.errorIf(any(size(Iu8_gray) ~= obj.FrameSize), ... 'vision:PointTracker:expectedSameImageSizeAsInitialized'); if (isSimMode()) [pointsTmp, pointValidity, scores] = obj.pTracker.step(Iu8_gray); else [pointsTmp, pointValidity, scores] = ... vision.internal.buildable.pointTrackerBuildable.pointTracker_step(... obj.pTracker, Iu8_gray', obj.NumPoints); end % conversion from 0-based to 1-based is done in % vision.internal.PointTracker % mark the points that fell outside the image as invalid badPoints = pointsOutsideImage(obj, pointsTmp); pointValidity(badPoints) = false; scores = normalizeScores(obj, scores, pointValidity); if isSimMode() % data type locked in codegen; so no cast required points = cast(pointsTmp, getClassFromID(obj.PointClassID)); else points = castFromID(pointsTmp,coder.internal.const(obj.PointClassID)); 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) % public properties s.MaxBidirectionalError = obj.MaxBidirectionalError; s.BlockSize = obj.BlockSize; s.NumPyramidLevels = obj.NumPyramidLevels; s.MaxIterations = obj.MaxIterations; % private properties s.IsInitialized = obj.IsInitialized; s.IsRGB = obj.IsRGB; s.PointClassID = obj.PointClassID; s.FrameClassID = obj.FrameClassID; % internal state if obj.IsInitialized if isSimMode() s.Frame = getPreviousFrame(obj.pTracker); [points, validity] = getPointsAndValidity(obj.pTracker); else s_Frame = vision.internal.buildable.pointTrackerBuildable.pointTracker_getPreviousFrame(obj.pTracker, obj.FrameSize); s.Frame = s_Frame'; [points, validity] = vision.internal.buildable.pointTrackerBuildable.pointTracker_getPointsAndValidity(obj.pTracker, obj.NumPoints); end s.Points = points; s.Validity = validity; end end %---------------------------------------------------------------------- function loadObjectImpl(obj,s,~) % public properties obj.MaxBidirectionalError = s.MaxBidirectionalError; obj.BlockSize = s.BlockSize; obj.NumPyramidLevels = s.NumPyramidLevels; obj.MaxIterations = s.MaxIterations; % private properties obj.IsInitialized = s.IsInitialized; obj.IsRGB = s.IsRGB; if isfield(s, 'PointClass') % Backwards compatibility - saved before R2016a s = rmfield(s, 'PointClass'); end if isfield(s, 'PointClassID') obj.PointClassID = s.PointClassID; end if isfield(s, 'FrameClass') % Backwards compatibility - saved before R2016a s = rmfield(s, 'FrameClass'); end if isfield(s, 'FrameClassID') obj.FrameClassID = s.FrameClassID; end % internal state if s.IsInitialized obj.FrameSize = size(s.Frame); obj.NumPoints = size(s.Points,1); points = cast(s.Points, 'single'); if isSimMode() obj.pTracker.initialize(getKLTParams(obj), s.Frame, points); else vision.internal.buildable.pointTrackerBuildable.pointTracker_initialize( ... obj.pTracker, getKLTParams(obj), s.Frame', points); end obj.IsInitialized = true; setPoints(obj, points, s.Validity); end end %---------------------------------------------------------------------- function releaseImpl(obj) if isSimMode() % after release the object must be re-initialized obj.IsInitialized = false; else vision.internal.buildable.pointTrackerBuildable.pointTracker_deleteObj( ... obj.pTracker); end end end methods function initialize(obj, points, I) % initialize Sets the points to track and the initial video frame % initialize(H, POINTS, I) sets the points to track in an image I. % POINTS must be an N-by-2 array of [x y] coordinates, and I must % be a grayscale or RGB image representing the first video frame. coder.internal.errorIf(nargin<3, ... 'vision:PointTracker:invalidInputsToInitialize'); if isSimMode() coder.internal.errorIf(isLocked(obj), ... 'vision:PointTracker:initializeWhenLocked'); %else % in codegen, the isLocked method cannot be called after calling % the release method. end obj.validatePoints(points); obj.validateImage(I); % Calling setup is nessary in codegen to prevent the possibility of % the step() method taking an image that has a different size or % datatype from the one passed into the initialize() method. if ~isSimMode() setup(obj, I); end obj.PointClassID = coder.internal.const(getClassID(points)); % conversion from 1-based to 0-based is done in % vision.internal.PointTracker points = single(points); obj.FrameClassID = getClassID(I); Iu8 = im2uint8(I); if size(Iu8, 3) > 1 obj.IsRGB = true; Iu8_gray = rgb2gray(Iu8); else Iu8_gray = Iu8; end obj.FrameSize = size(Iu8_gray); obj.NumPoints = size(points,1); if isSimMode() obj.pTracker.initialize(getKLTParams(obj), Iu8_gray, points); else vision.internal.buildable.pointTrackerBuildable.pointTracker_initialize( ... obj.pTracker, getKLTParams(obj), Iu8_gray', points); end obj.IsInitialized = true; end %---------------------------------------------------------------------- function setPoints(obj, points, pointValididty) % setPoints Sets the points to track. % setPoints(H, POINTS, PointValididty) sets the points to be % tracked. POINTS must be an N-by-2 array of [x y] coordinates. % PointValididty must be an N-element logical vector, in which the % value of true indicates that the corresponding point is currently % visible and should be tracked. This method can be used to % re-initialize the points when too many points are lost during % tracking. It can also be used to modify the locations of % previously tracked points, or to mark certain points as invalid. coder.internal.errorIf(~obj.IsInitialized, ... 'vision:PointTracker:trackerUninitialized'); coder.assertDefined(obj.IsInitialized); coder.assertDefined(obj.PointClassID); obj.validatePoints(points); if isSimMode() coder.internal.errorIf(isLocked(obj) && ... (obj.PointClassID ~= getClassID(points)),... 'vision:PointTracker:pointsClassChangedWhenLocked'); %else % in codegen, the isLocked method cannot be called after calling % the release method. end % conversion from 1-based to 0-based is done in % vision.internal.PointTracker points = single(points); if nargin > 2 % check pointValidity validateattributes(pointValididty, {'logical', 'numeric'}, ... {'vector', 'nonsparse', 'nonnegative', 'integer'}, ... 'PointTracker', 'PointValididty'); % make sure the number of elements in pointValidity is the same % as the number of points coder.internal.errorIf(size(points, 1) ~= length(pointValididty),... 'vision:PointTracker:pointsPointValiditySizeMismatch'); pointValidity = logical(pointValididty); else pointValidity = true(size(points, 1), 1); end if isSimMode() obj.pTracker.setPoints(points, pointValidity); else obj.NumPoints = size(points, 1); vision.internal.buildable.pointTrackerBuildable.pointTracker_setPoints( ... obj.pTracker, points, pointValidity); end end end methods(Access=protected) %---------------------------------------------------------------------- % Normalizes the error values returned by OpenCV to be a score % with the value between 0 and 1, where 1 means perfect match. function scores = normalizeScores(obj, scores, validity) maxError = sqrt(prod(obj.BlockSize) * 255^2); scores = 1 - scores ./ maxError; scores(~validity) = 0; end %---------------------------------------------------------------------- % returns a struct containing parameters for the initialization % of pTracker function kltParams = getKLTParams(obj) kltParams = struct(... 'BlockSize', double(obj.BlockSize),... 'NumPyramidLevels', getNumPyramidLevels(obj), ... 'MaxIterations', double(obj.MaxIterations), ... 'Epsilon', 0.01, ... 'MaxBidirectionalError', double(obj.MaxBidirectionalError)); if isinf(kltParams.MaxBidirectionalError) kltParams.MaxBidirectionalError = -1; end end %---------------------------------------------------------------------- % clips numPyramidLevels function level = getNumPyramidLevels(obj) % Set NumPyramidLevels so that the top level of the pyramid % has the size of at least 3x3, which is the minimum permissible % size for the KLT algorithm. topOfPyramid = floor(log2(min(obj.FrameSize)) - 2); level = max(0, min(topOfPyramid, double(obj.NumPyramidLevels))); end %---------------------------------------------------------------------- % returns logical index of points that fall outside the video frame function inds = pointsOutsideImage(obj, points) x = points(:, 1); y = points(:, 2); inds = (x < 1) | (y < 1) | ... (x > obj.FrameSize(2)) | (y > obj.FrameSize(1)); end end methods (Static, Access=protected) %---------------------------------------------------------------------- function [] = validateImage(I) validateattributes(I, {'uint8', 'uint16', 'int16', 'single', 'double'}, ... {'real', 'nonempty', 'nonsparse'}, 'PointTracker', 'I'); coder.internal.errorIf(~(ismatrix(I) || ... (ndims(I) == 3 && size(I, 3) == 3)), ... 'vision:dims:imageNot2DorRGB'); end %---------------------------------------------------------------------- function validatePoints(points) validateattributes(points, {'single', 'double'}, ... {'real', 'nonsparse', 'positive', 'finite', '2d', 'ncols', 2,... 'nonempty'}, 'PointTracker', 'POINTS'); end end end %========================================================================== function flag = isSimMode() flag = isempty(coder.target); end %========================================================================== function id = getClassID(img) id = 5; if isa(img,'double') id = 0; elseif isa(img,'single') id = 1; elseif isa(img,'uint8') id = 2; elseif isa(img,'uint16') id = 3; elseif isa(img,'int16') id = 4; end id = coder.internal.const(id); end %========================================================================== function pointsOut = castFromID(pointsIn, pointClassID) switch (pointClassID) case coder.internal.const(0) pointsOut = double(pointsIn); case coder.internal.const(1) pointsOut = single(pointsIn); case coder.internal.const(2) pointsOut = uint8(pointsIn); case coder.internal.const(3) pointsOut = uint16(pointsIn); case coder.internal.const(4) pointsOut = int16(pointsIn); otherwise pointsOut = double(pointsIn); end end %========================================================================== function class = getClassFromID(id) class = 'unknown'; switch id case 0 class = 'double'; case 1 class = 'single'; case 2 class = 'uint8'; case 3 class = 'uint16'; case 4 class = 'int16'; end end