-
Notifications
You must be signed in to change notification settings - Fork 18
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.
#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
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.
#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);
}
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;
}
...
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)};
}
...
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.