User Tools

Site Tools


opencv_tutorials_t7

Tutorial 7

On this tutorial you will learn how to apply threshold to track an object by its center of mass using image moments.

I recommend you to type the code on your own to get familiarized with the program language. If you have trouble , the original code is attached bellow ( Running on Visual Studio 2015 + OpenCV 3.1 ) * Check the installation guide to make sure that you linked all the OpenCV modules to your Visual Studio.

Track Object by image moments


Tracking an object using image moments

#include <sstream>
#include <string>
#include <iostream>
#include <opencv/highgui.h>
#include <opencv/cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>


#define _CRT_SECURE_NO_WARNINGS

using namespace cv;
using namespace std;

//initial min and max HSV filter values.
//these will be changed using trackbars
int iLowH = 0;
int iHighH = 179;

int iLowS = 0;
int iHighS = 255;

int iLowV = 0;
int iHighV = 255;

//default capture width and height
const int FRAME_WIDTH = 640;
const int FRAME_HEIGHT = 480;
//max number of objects to be detected in frame
const int MAX_NUM_OBJECTS = 50;
//minimum and maximum object area
const int min_area = 20 * 20;
const int max_area = FRAME_HEIGHT*FRAME_WIDTH /2; 


String intToString(int number) {


	stringstream ss;
	ss << number;
	return ss.str();
}

//Function to create a window with the Trackbars to apply the Threshold.
void createTrackbars() {
	
	//Open the window to display the Trackbars
	namedWindow("Trackbars", CV_WINDOW_AUTOSIZE);

	//Hue values (0 - 179)
	cvCreateTrackbar("LowH", "Trackbars", &iLowH, 179);
	cvCreateTrackbar("HighH", "Trackbars", &iHighH, 179);

	//Saturation Values (0-255)
	cvCreateTrackbar("LowS", "Trackbars", &iLowS, 255);
	cvCreateTrackbar("HighS", "Trackbars", &iHighS, 255);

	//Value (0-255)
	cvCreateTrackbar("LowV", "Trackbars", &iLowV, 255);
	cvCreateTrackbar("HighV", "Trackbars", &iHighV, 255);

}



//Function to apply the erode and dilate features.
void DilateAndErode(Mat &image) {

	//Defining the erode and dilate properties
	//the erode element chosen here is a 3x3 piexels rectangle.
	//Change the Size argument to optimize your threshold. 
	//dilate with 8x8 size element to make the threshold object more visible

	Mat erodeElement = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat dilateElement = getStructuringElement(MORPH_RECT, Size(8, 8));

	//Apply erode and dilate
	erode(image, image, erodeElement);
	dilate(image, image, dilateElement);

}

//Function to track an object using image moments.
void TrackObject(Mat threshold, Mat &image) {

	//We will not use the threshold image directly we will use a copy
	//if you use the original threshold , when you turn the tracking ON , the window
	//displaying the threshold image will have an interference.
	Mat temp;
	threshold.copyTo(temp);

        //x and y values for the location of the object
	int x = 0, y = 0;

	//Initializing two vectors to be used on the findContours function
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;

	//findContours function using the output of the threshold operation
	findContours(temp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);

	//Variables to store the largest area on the threshold image
	int largest_area = 0;  
	
	//Tracking success.
	bool objectFound = false;

	//use moments method to find our filtered object
	if (contours.size() > 0) 
	{
		int numObjects = contours.size();
		//if number of objects greater than MAX_NUM_OBJECTS we have a noisy filter
		if (numObjects<MAX_NUM_OBJECTS) 
                     {
			for (int i = 0; i < contours.size(); i++) 
			{

				//Image moments: m00 is the area , m10/m00 is the column of the center of mass
				//m01/m00 is the row of the center of mass.
				Moments moment;

				moment = moments((Mat)contours[i]);
				double area = moment.m00;

				//Find the largest contour to be draw on the image
				//Also filter using a minimum and maximum area value to avoid false positives.
				//When all the conditions are true we probably have a track able object.
				
				if (area>min_area && area<max_area && area>largest_area)
				{
					x = moment.m10 / area;  //center of mass column
					y = moment.m01 / area;  //center of mass row
					objectFound = true;
					largest_area = area;
				}
											
				else objectFound = false;


			}
			//if we found an object lets track it
			if (objectFound == true) 
			{
			putText(image, "Object Found", Point(0, 50), 2, 1, Scalar(0, 255, 0), 2);
			circle(image, Point(x, y), 3, Scalar(0, 255, 0), -1, 8, 0);
			putText(image, "(" + intToString(x) + "," + intToString(y) + ")" , Point(x, y + 30), 1, 1, Scalar(0, 255, 0), 2);
			}
			else putText(image, "Searching for object", Point(0, 50), 1, 2, Scalar(0, 0, 255), 2);

		}
		
	}
}


int main(int argc, char* argv[])
{
	//some boolean variables for different functionality within this
	//program
	bool useTrack = false;
	bool useFeatures = false;
	char key = 0;

	//Matrix to store each frame of the webcam feed
	Mat coloredimage;
	//matrix storage for HSV image
	Mat HSV;
	//matrix storage for binary threshold image
	Mat threshold;
	
	//create slider bars for HSV filtering
	createTrackbars();
	//video capture object to acquire webcam feed
	VideoCapture capture;
	//open capture object at location zero (default location for webcam)
	capture.open(0);
	//set height and width of capture frame
	capture.set(CV_CAP_PROP_FRAME_WIDTH, FRAME_WIDTH);
	capture.set(CV_CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT);
	

	//Until the User press q the loop will run
	//Get the image from the webcam -> convert to HSV -> Threshold the image
	//using the HSV max and min set on the Trackbar window.
	while (key != 'q')
	   {
		//Get the image from the webcam
		capture >> coloredimage;
		//Convert the frame from BGR (RGB) to HSV
		cvtColor(coloredimage, HSV, COLOR_BGR2HSV);
		
		//filter the HSV image using the minimum and maximum values set on the 
		//Trackbars window using the inRange function.	
		inRange(HSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), threshold);
		

		

		//If 'm' is pressed it will turn on/off the morphological transformations
		//dilate and erode
		if (key == 'm')
		{
			useFeatures = !useFeatures;
		}

		if (useFeatures)
		{
			DilateAndErode(threshold);
		}
		

		//If 't' is pressed it will turn on/off the tracking algorithm
		//First we need to treshold the image by hand , so it will start as false
		//After you get a good threshold image you can turn the tracking ON by pressing 't'.
		if (key == 't')
		{
			useTrack = !useTrack;
		}

		if (useTrack)
		{
			TrackObject(threshold, coloredimage);
		}

		//show frames 
		imshow("Threshold Image", threshold);
		imshow("Live Web Cam", coloredimage);
		imshow("HSV Image", HSV);

		key = waitKey(25);

	}



	return 0;
}


Understanding the Code

//initial min and max HSV filter values.
//these will be changed using trackbars
int iLowH = 0;
int iHighH = 179;
 
int iLowS = 0;
int iHighS = 255;
 
int iLowV = 0;
int iHighV = 255;
 
//default capture width and height
const int FRAME_WIDTH = 640;
const int FRAME_HEIGHT = 480;
//max number of objects to be detected in frame
const int MAX_NUM_OBJECTS = 50;
//minimum and maximum object area
const int min_area = 20 * 20;
const int max_area = FRAME_HEIGHT*FRAME_WIDTH /2; 

The code start with the same idea of the tracking using the object's contour. We need to define the variables to be used on the Window with the slider bars to do the threshold . Also we set the parameter that will set the web camera video properties . Also , we will be more accurate on the object filtering , by setting a maximum number of object that can be found in the image , a minimum area (avoid noise) and a maximum area (avoid false positives). Everything set here are global variables , so you can use inside every function and inside the main program.


String intToString(int number) {
 
 
	stringstream ss;
	ss << number;
	return ss.str();
}

Defining a simple function that will convert an integer number to a string. It will be useful when printing the center value on the screen (we want to track the object by showing its center and center position live on the image). It will take the integer from the center mass column and row positions and will return a stream with these values.


//Function to create a window with the Trackbars to apply the Threshold.
void createTrackbars() {
	
	//Open the window to display the Trackbars
	namedWindow("Trackbars", CV_WINDOW_AUTOSIZE);
 
	//Hue values (0 - 179)
	cvCreateTrackbar("LowH", "Trackbars", &iLowH, 179);
	cvCreateTrackbar("HighH", "Trackbars", &iHighH, 179);
 
	//Saturation Values (0-255)
	cvCreateTrackbar("LowS", "Trackbars", &iLowS, 255);
	cvCreateTrackbar("HighS", "Trackbars", &iHighS, 255);
 
	//Value (0-255)
	cvCreateTrackbar("LowV", "Trackbars", &iLowV, 255);
	cvCreateTrackbar("HighV", "Trackbars", &iHighV, 255);
 
}

Exactly the same function used on Tutorials 5 and 6. You can read the step by step here. Basically it will start a window and create the slider bars so we can set the minimum and maximum HSV to do the threshold.


//Function to apply the erode and dilate features.
void DilateAndErode(Mat &image) {
 
	//Defining the erode and dilate properties
	//the erode element chosen here is a 3x3 piexels rectangle.
	//Change the Size argument to optimize your threshold. 
	//dilate with 8x8 size element to make the threshold object more visible
 
	Mat erodeElement = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat dilateElement = getStructuringElement(MORPH_RECT, Size(8, 8));
 
	//Apply erode and dilate
	erode(image, image, erodeElement);
	dilate(image, image, dilateElement);
 
}

Again Exactly the same function used on Tutorials 5 and 6. You can read the step by step here. When this function is called it will apply the morphological transformations dilate and erode to optimize the object silhouette on the threshold image.


//Function to track an object using image moments.
void TrackObject(Mat threshold, Mat &image) {
 
	//We will not use the threshold image directly we will use a copy
	//if you use the original threshold , when you turn the tracking ON , the window
	//displaying the threshold image will have an interference.
	Mat temp;
	threshold.copyTo(temp);
        
        //x and y values for the location of the object
	int x = 0, y = 0;

        //Tracking success.
	bool objectFound = false;
 
        //Variables to store the largest area on the threshold image
	int largest_area = 0;  

	//Initializing two vectors to be used on the findContours function
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
 
	//findContours function using the output of the threshold operation
	findContours(temp, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
 

To make the program more readable lets separate the algorithm that will track the object from the main program - making it as a function and only be used when it's called on the main program ( we will create a on/off toggle similar to the morphological transformations use from the previous tutorials ). The arguments will be the column position of the center of mass (x) , the row position of the center o mass (y) , the image after the threshold made by the “inRange” function and the original image.

To avoid errors lets use a copy of the threshold image on the tracking algorithm , this way you will not get any interference on the window that is showing the threshold image when you toggle on/off the tracking algorithm. We can easily copy the threshold image into other variable using “.copyTo()“. Lets define a Boolean to say if we the tracking algorithm is running or not. Also a variable that will store the largest contour area will be set to help when filtering the contours found by the “findContours” function.

Also lets define the vectors that will be used by the “findContours” function , same as Tutorials 6 and 4. A vector to store the contour points and a vector to the hierarchy -the hierarchy vector contain as many element as the number of contours found (if we know its size , we know how many contours were found) , and contains information about the image topology . We find all the contours in the threshold image (that was copied to the temp variable ) using the “findContours” function .


//use moments method to find our filtered object
	if (contours.size() > 0) 
	{
		int numObjects = contours.size();
		//if number of objects greater than MAX_NUM_OBJECTS we have a noisy filter
		if (numObjects<MAX_NUM_OBJECTS) 
                     {
 

Now we start the loop that will search for the largest area , and will calculate the center of mass of this area using the image moments. Image moments is analogy to the moments in physics. The 'weight' of each pixels is denoted by its intensity , so if the pixel has 'mass' it will have a center of mass (read more about image moments Here).

The loop will run every time that we find a contour , i.e size >0 (the contours vector is outputted by the “FindContours” function ). Also , the number of objects founds will be equal to the size of the contours vector. To avoid noise , lets run the algorithm only when the number of object detected is lass than a maximum value that we set on the beginning (this way we avoid the use of incorrect threshold ( very noisy binary image)).


for (int i = 0; i < contours.size(); i++) 
			{
 
				//Image moments: m00 is the area , m10/m00 is the column of the center of mass
				//m01/m00 is the row of the center of mass.
				Moments moment;
 
				moment = moments((Mat)contours[i]);
				double area = moment.m00; 
             
                                //Find the largest contour to be draw on the image
				//Also filter using a minimum and maximum area value to avoid false positives.
				//When all the conditions are true we probably have a track able object.
				
				if (area>min_area && area<max_area && area>largest_area)
				{
					x = moment.m10 / area;  //center of mass column
					y = moment.m01 / area;  //center of mass row
					objectFound = true;
					largest_area = area;
				}

                                else objectFound = false;
              

To find the image moments we can use a OpenCV function for it , but first we need to set a variable that will store those moments. The variable type is “Moments” and lets call the variable moment. We can get the moments from each contour as the loop goes on ('i' increases) using the assign “moment = moments((Mat)contours[i])“ - which will store the moments of the i-contour on the moment variable.

You can check Here what each moment represent. The m00 element is the area , the m10/m00 is the column of the center of mass and m01/m00 is the row of the center of mass. We can access those values by putting ”.mxx” after the variable that has the moments stored and assign this value to any variable that we want. Lets create a area variable to store the area “double area = moment.m00” .

We will enter in the conditional statement that will define if we found an object or no based on the area value. If the area value is greater than a minimum and less than a maximum that we set on the beginning of the program and if the area is greater than the previous largest area. If all is true then we still have track of the object and the new column and row position of the center of mass is calculated, also the Boolean that flag if the object is found will be TRUE. If the conditional statement is not satisfied the Boolean will be FALSE .


//if we found an object lets track it
			if (objectFound == true) 
			{
			putText(image, "Object Found", Point(0, 50), 2, 1, Scalar(0, 255, 0), 2);
			circle(image, Point(x, y), 3, Scalar(0, 255, 0), -1, 8, 0);
			putText(image, "(" + intToString(x) + "," + intToString(y) + ")" , Point(x, y + 30), 1, 1, Scalar(0, 255, 0), 2);
			}
			else putText(image, "Searching for object", Point(0, 50), 1, 2, Scalar(0, 0, 255), 2);
 
		}
		
	}
}

Now if we found an object we want to track its position. So the conditional statement if the objectFound variable is true will be used to print on the screen some useful informations. Lets draw a small circle on the center of mass of the contour and print the value of the center of mass column and row.

The first “putText” function will display on the top of the screen that an object was found. The “circle” function will draw a circle centered in (x,y)- which has stored the value of the center of mass column and row - with radius 3. The second “putText” will display the (x,y) value on the screen , a little bit offset from the center to make the visualization of the center more clear.

If the object found flag is not true , it will display on the top of the screen that the searching is on progress. And this is the end of our tracking algorithm.


int main(int argc, char* argv[])
{
	//some boolean variables for different functionality within this
	//program
	bool useTrack = false;
	bool useFeatures = false;
	char key = 0;
 
	//Matrix to store each frame of the webcam feed
	Mat coloredimage;
	//matrix storage for HSV image
	Mat HSV;
	//matrix storage for binary threshold image
	Mat threshold;
	
	//create slider bars for HSV filtering
	createTrackbars();
	//video capture object to acquire webcam feed
	VideoCapture capture;
	//open capture object at location zero (default location for webcam)
	capture.open(0);
	//set height and width of capture frame
	capture.set(CV_CAP_PROP_FRAME_WIDTH, FRAME_WIDTH);
	capture.set(CV_CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT);

We already have all the algorithm separated into functions , our main program will only be to make sure everything is called in the right time and to create the on/off toggles to show the morphological transformations and the tracking algorithm.

We start defining a set of variables that we will use. Mat variables to store the web camera video , the HSV version of this video and a threshold version after the “inRange” function do the threshold. Also we define the x and y positions of the center of mass ( calculated on the track function ).

Also lets initialize the video capture from the web camera video , and set the video properties using “capture.open(0)“ and “capture.set()“.


        //Until the User press q the loop will run
	//Get the image from the webcam -> convert to HSV -> Threshold the image
	//using the HSV max and min set on the Trackbar window.
	while (key != 'q')
	   {
		//Get the image from the webcam
		capture >> coloredimage;
		//Convert the frame from BGR (RGB) to HSV
		cvtColor(coloredimage, HSV, COLOR_BGR2HSV);
		
		//filter the HSV image using the minimum and maximum values set on the 
		//Trackbars window using the inRange function.	
		inRange(HSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), threshold);

The loop will run until the 'q' key is pressed . First thing is to capture a frame from the web cam video and store it on the 'coloredimage' - which is converted after using the “cvtColor” to HSV to be used on the threshold process. The threshold process is done by hand , slinding the bars in the Trackbars window . The maximum and minimum values will be used by the “inRange” function to do the threshold ( same process from Tutorial 6 and 5 ). It will output a binary image stored on the last argument - 'threshold'.


                //If 'm' is pressed it will turn on/off the morphological transformations
		//dilate and erode
		if (key == 'm')
		{
			useFeatures = !useFeatures;
		}
 
		if (useFeatures)
		{
			DilateAndErode(threshold);
		}
		
 
		//If 't' is pressed it will turn on/off the tracking algorithm
		//First we need to treshold the image by hand , so it will start as false
		//After you get a good threshold image you can turn the tracking ON by pressing 't'.
		if (key == 't')
		{
			useTrack = !useTrack;
		}
 
		if (useTrack)
		{
			TrackObject(threshold, coloredimage);
		}

Here we create the on/off toggles to use the morphological transformations (by pressing 'm') and to run the tracking algorithm (by pressing 't') . Each time 'm' or 't' is pressed the Booleans useTrack and useFeatures will change values from TRUE to FALSE and vice-versa. Every time these values are TRUE , it will enter inside the conditional loop that will call the respective functions , “DilateAndErode()“ and “TrackObject()“.


                //show frames 
		imshow("Threshold Image", threshold);
		imshow("Live Web Cam", coloredimage);
		imshow("HSV Image", HSV);
 
		key = waitKey(25);
 
	}
 
 
 
	return 0;
}
 

Finally we open and display three different windows to show the Live web cam video , the threshold version and the HSV version. The key value is updated using “key = waitKey(25)“. And our program is done !

opencv_tutorials_t7.txt · Last modified: 2016/06/07 15:57 by joaomatos