Skip to content

Implement your tracker

Andrés Solís Montero edited this page Mar 23, 2016 · 2 revisions

Step 2: Implement Your Tracker

To create a new tracker you need to inherit the Tracker interface defined in tracker.h and implement its 4 virtual methods. For more details about the Tracker Interface follow this link

void virtual getTrackedArea(vector<Point2f> &pts) = 0;
void virtual initialize(const cv::Mat &image,
                          const cv::Rect &rect) = 0;
void virtual processFrame(const cv::Mat &image) = 0;
string virtual getDescription() { return "default";};

To accomplish this, we will declare our NCCTracker inside the ncc.h file as follows.

ncc.h file contents:
#ifndef __VivaTracker__ncc__
#define __VivaTracker__ncc__

//Necessary headers for implementing the new Tracker
#include "tracker.h"

//Create your new Tracker class and public inherits from Tracker 
//defined in the "tracker.h" header file
class NCCTracker :	public Tracker
{
public:
    
    //Inherited methods from the Tracker Interface
    virtual void initialize(const cv::Mat &img, const cv::Rect &rect);
    virtual void processFrame(const cv::Mat &img);
    virtual void getTrackedArea(vector<Point2f> &pts);
    virtual string getDescription();
    
private:
    // Private members of my NCCTracker implementation
    cv::Point2f p_position;
    cv::Size p_size;
    float p_window;
    cv::Mat p_template;
};


#endif

Implementing NCCTracker::initialize

We start by implementing the tracker initialization. The initialization takes an image and a rectangular region of it which defines the area of interest (i.e., region with the object to track). This will start building the our model for the NCCTracker.

The area is defined as a rectangular selection starting at the top left coordinates defined with rect.x and rect.y and rect.width and rect.height. This area could have been defined by the user or loaded from the groundtruth.txt file

Note: As a general rule, your code should work for any type of image (i.e., color, gray, etc). The img argument will be defined by the sequence used for the tracker. Many datasets could have grayscale and color sequences. In any other case you should check the type of the image and make the correct conversion for your algorithm to work.

Your initialization code should restart any data structure to the tracker's initial state. An user could restart the tracker multiple times in the same sequence or select a different target.

Contents of ncc.cpp file:
#include "ncc.h"

void NCCTracker::initialize(const cv::Mat &img, const cv::Rect &rect)
{
    //Hold the maximum dimension of the selected area. 
    p_window = MAX(rect.width, rect.height);

    int left = MAX(rect.x, 0);
    int top  = MAX(rect.y, 0);

    int right  = MIN(rect.x + rect.width, img.cols - 1);
    int bottom = MIN(rect.y + rect.height, img.rows - 1);
    
    cv::Rect roi(left, top, right - left, bottom - top);
    //Select the region of interest and copy our template
    img(roi).copyTo(p_template);

    //Update the current centre of the tracked object to the 
    //the centre of the selected area. 
    p_position.x = (float)rect.x + (float)rect.width / 2.f;
    p_position.y = (float)rect.y + (float)rect.height / 2.f;

    //Update the size of the object using the selected area
    p_size = cv::Size2f(rect.width, rect.height);

}

Implementing NCCTracker::processFrame

After the initialization, only the next frames of the sequence are available to the tracker. They will deliver to the tracker following the sequence's order.

...
void NCCTracker::processFrame(const cv::Mat &img)
{
    //Selecting an area of the image based in the previous
    //detected target location and in their max dimension 
    //among the two axis. The areas are carefully selected without exceeding 
   // the images boundary
    float left  = MAX(round(p_position.x - p_window), 0);
    float top   = MAX(round(p_position.y - p_window), 0);

    float right  = MIN(round(p_position.x + p_window), img.cols - 1);
    float bottom = MIN(round(p_position.y + p_window), img.rows - 1);

    cv::Rect roi((int) left, (int) top, (int) (right - left), (int) (bottom - top));

    if (roi.width < p_template.cols || roi.height < p_template.rows) {
        cv::Rect result;

        result.x = p_position.x - p_size.width / 2.f;
        result.y = p_position.y - p_size.height / 2.f;
        result.width = p_size.width;
        result.height = p_size.height;
    }

    cv::Mat matches;
    cv::Mat cut = img(roi);
    //Mat the selected region to the current tracker model template
    cv::matchTemplate(cut, p_template, matches, CV_TM_CCOEFF_NORMED);
    
    //Find the location of maximum response, aka the new target location
    cv::Point matchLoc;
    cv::minMaxLoc(matches, NULL, NULL, NULL, &matchLoc, cv::Mat());
   // Update targets position 
    p_position.x = left + matchLoc.x + (float)p_size.width / 2.f;
    p_position.y = top + matchLoc.y + (float)p_size.height / 2.f;

}
...

Implementing NCCTracker::getTrackedArea

The getTrackedArea method is executed to obtain the current tracker location; defined as a list of points in clock-wise orientation. When called the argument pts will be empty and should be filled with the polygonal information. A quadrilateral should be the minimum polygon to define a tracked area.

...
void NCCTracker::getTrackedArea(vector<Point2f> &pts)
{
    //Insert the four corners of the tracked area starting by
    //the top-left corner , and followed by the top-right, bottom-right and bottom-left corners. 
    float w2 = p_size.width/2.f;
    float h2 = p_size.height/2.f;
    pts = {Point2f(p_position.x - w2, p_position.y - h2),
        Point2f(p_position.x + w2, p_position.y - h2),
        Point2f(p_position.x + w2, p_position.y + h2),
        Point2f(p_position.x - w2, p_position.y + h2)};

}
...

Implementing NCCTracker::getDescription

The final virtual method is the method getDescription. This method in particular doesn't not interfere with the tracking pipeline. It's added only for completion and description of the algorithm.

The result of this method will be displayed while executing the [command line argument] -?.

As a general rule, the output of this method could follow the following format.

AUTHOR, INITIALS: FULL NAME, [PUBLICATION] YEAR.

...
string NCCTracker::getDescription()
{
    return "AUTHOR, ALGORITHM_INITIALS: FULL NAME, [PUBLICATION] YEAR";
    //return "Andrés Solís Montero, NCC: Normalized Cross Correlation. 2016";
}
...

The tracker code is completed and we should continue with the final step to integrate our new algorithm.