Color detection is the process of detecting any color. Single color detection or tracking is easy in OpenCV, key function is cvInRangeS() provided by OpenCV, we can easily tracking any color by using this function.

void cvInRangeS(const CvArr* src, CvScalar lower, CvScalar upper, CvArr* dst);

Why?

In the robot competition, the environment is a little complicated, our robots need to track multiple colors at the same time. For our humanoid robots, all the computation of computer vision runs on the Raspberry Pi 1, an USB camera is connected to the Pi.

For example, if we want to track 3 colors at the same time, we can detect three colors one by one, repeat 3 times cvInRangeS() and other processes. The single color tracking program is 8.5 FPS in raspberry pi 1, now FPS decreases to 2.5, and it runs very slowly in Pi 1, it’s not satisfactory for our real-time humanoid robot vision system.

How?

To have a better tracking performance, I rewrote the threshold function, and do not use the cvInRangeS() function, so that it can threshold more than one color at the same time. After the threshold, I use cvFindContours() function to find contours, but we do not know the contour is from which color blob, so the program will go back to the original image to find out what color it is at the same contour, then we can get the different color blob position.

Demo

A very important point is that we need to convert images from RGB to HSV (Hue, Saturation, Value) format using OpenCV, it’s convenient and reliable to check what color it is by comparing the Hue value only in the same lighting environment, and Hue difference is more robust in different lighting environment, which similar with the scene of robots competition.

HSV color space

Code

Here is the code (also can get it from my Github), I have added key comments. This multiple color tracking program speed is 7.5 FPS in raspberry pi 1, which is very close to the single color tracking speed.

#include <stdio.h>
#include <time.h>

//OpenCV 2.4 version
#include "cv.h" 
#include "highgui.h"
 
#define N 3 //find the N largest contour for each color
 
IplImage* Threshold(IplImage* imgThreshed, IplImage* imgHSV, unsigned char min[], unsigned char max[])
{
    unsigned char *data_ts = (unsigned char *)imgThreshed->imageData, *data_hsv = (unsigned char *)imgHSV->imageData;  
    int step_ts = imgThreshed->widthStep/sizeof(unsigned char), step_hsv = imgHSV->widthStep/sizeof(unsigned char) ;  
    int chanels_hsv = imgHSV->nChannels;
    int a, b;  
    unsigned char H, S, V;
     
    for(a=0; a < imgHSV->height; a++)
        for(b=0; b < imgHSV->width; b++)
        {
            H = data_hsv[a*step_hsv+b*chanels_hsv + 0];
            S = data_hsv[a*step_hsv+b*chanels_hsv + 1];
            V = data_hsv[a*step_hsv+b*chanels_hsv + 2];

            //detect three colors at the same time. 
            if( (H >= min[0] && H <= max[0] && S >= 170 && S<=255 && V>=75 && V<=255)
            ||  (H >= min[1] && H <= max[1] && S >= 170 && S<=255 && V>=75 && V<=255)
            ||  (H >= min[2] && H <= max[2] && S >= 170 && S<=255 && V>=75 && V<=255)  )
            {
                data_ts[a*step_ts+b]=255; //mark the pixel as white if it belong to the any of three colors
                 
            }else{  
         
                data_ts[a*step_ts+b]=0;
 
                }               
         }      
     
  return imgThreshed;   
}

//Find out the contours belong to which color, and return the N largest contours for each color. 
IplImage* Blob(IplImage* frame,IplImage* imgHSV, IplImage* imgThreshed, unsigned char min[], unsigned char max[])
{   
    int i = 0, j = 0, m = 0, k, l;
    int total, avg; 
    double area, tmp_area;
    int cnt1 = 0, cnt2 = 0, cnt3 = 0, cnt4 = 0; 
    double maxArea1[N] = {0}, maxArea2[N] = {0}, maxArea3[N] = {0},maxArea4[N] = {0};
     
    CvSeq *contour, *tmp_cont;
    CvSeq *contours1[N], *contours2[N], *contours3[N], *contours4[N];
    CvPoint *pt;
    CvScalar data;
     
    unsigned char *data_hsv = (unsigned char *)imgHSV->imageData;  
    int step_hsv = imgHSV->widthStep/sizeof(unsigned char) ;  
    int chanels_hsv = imgHSV->nChannels; 
    unsigned char H;
     
    CvMemStorage *storage = cvCreateMemStorage(0); 
     
    cvFindContours(imgThreshed, storage, &contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));

    //iterate all contours
    for(; contour; contour = contour->h_next)
    {
        area=fabs(cvContourArea(contour,CV_WHOLE_SEQ,1 ));
         
        if(area<100 || area>50000 )
        {
            cvSeqRemove(contour,0);
            continue;
        }   
         
        total = 0;      
        
        //iterate all pixels in the contour to sum total Hue value
        for(l = 0;l < contour->total; ++l)   
        {
            pt = (CvPoint *)cvGetSeqElem(contour, l);
            H = data_hsv[step_hsv*pt->y+chanels_hsv*pt->x+0];
            total = H + total;
        }

        //get the average Hue value of all pixels in this contour 
        avg = total / (contour->total);          

        //check the contour color by comparing average Hue value 
        if((avg >= min[0])&&(avg <= max[0]))  //red color
        {
            cnt1++;
            // Find the N largets contours of each color 
            for(i = N-1; i >= 0; --i)
            {
              if(area > maxArea1[i])
              {
                maxArea1[i] = area;
                contours1[i] = contour;
                for(m = (i-1); m >= 0; --m)
                {
                   if(maxArea1[m] < maxArea1[m+1])
                   {
                     tmp_area = maxArea1[m+1];
                     tmp_cont = contours1[m+1];
                     maxArea1[m+1] = maxArea1[m];
                     contours1[m+1] = contours1[m];
                     maxArea1[m] = tmp_area;
                     contours1[m] = tmp_cont;
                    }
                }
              break;
              }
            }      
             
        }
        else if((avg >= min[1])&&(avg <= max[1]))      // yellow color
        {
            cnt2++;
             
            for(i = N-1; i >= 0; --i)
            {
              if(area > maxArea2[i])
              {
                maxArea2[i] = area;
                contours2[i] = contour;
                for(m = (i-1); m >= 0; --m)
                {
                   if(maxArea2[m] < maxArea2[m+1])
                   {
                     tmp_area = maxArea2[m+1];
                     tmp_cont = contours2[m+1];
                     maxArea2[m+1] = maxArea2[m];
                     contours2[m+1] = contours2[m];
                     maxArea2[m] = tmp_area;
                     contours2[m] = tmp_cont;
                    }
                }
              break;
              }
            }        
        }
        else if((avg >= min[2])&&(avg <= max[2]))     //Blue Color
        {
            cnt3++;
             
            for(i = N-1; i >= 0; --i)
            {
              if(area > maxArea3[i])
              {
                maxArea3[i] = area;
                contours3[i] = contour;
                for(m = (i-1); m >= 0; --m)
                {
                   if(maxArea3[m] < maxArea3[m+1])
                   {
                     tmp_area = maxArea3[m+1];
                     tmp_cont = contours3[m+1];
                     maxArea3[m+1] = maxArea3[m];
                     contours3[m+1] = contours3[m];
                     maxArea3[m] = tmp_area;
                     contours3[m] = tmp_cont;
                    }
                }
              break;
              }
            }    
        }
    }
     
    if(cnt1 != 0)
    {
        CvRect rect = ((CvContour*)contours1[0])->rect;
        cvRectangle(frame, cvPoint(rect.x, rect.y), cvPoint(rect.x + rect.width, rect.y + rect.height),CV_RGB(0, 0, 0), 2, 8, 0);
        //get the largest contour location of color 1
        printf("Red: (%d , %d) (%d , %d)\n", rect.x,rect.y,rect.x + rect.width,rect.y + rect.height);
    }   
     
    if(cnt2 != 0)
    {
        CvRect rect = ((CvContour*)contours2[0])->rect;
        cvRectangle(frame, cvPoint(rect.x, rect.y), cvPoint(rect.x + rect.width, rect.y + rect.height),CV_RGB(0, 0, 255), 2, 8, 0);
        printf("Yellow: (%d , %d) (%d , %d)\n", rect.x,rect.y,rect.x + rect.width,rect.y + rect.height);
    }   
     
    if(cnt3 != 0)
    {
        CvRect rect = ((CvContour*)contours3[0])->rect;
        cvRectangle(frame, cvPoint(rect.x, rect.y), cvPoint(rect.x + rect.width, rect.y + rect.height),CV_RGB(255, 0, 255), 2, 8, 0);
        printf("Blue: (%d , %d) (%d , %d)\n", rect.x,rect.y,rect.x + rect.width,rect.y + rect.height);
    }   
     
    cvReleaseMemStorage(&storage);  
    return frame;
}
 
int main( )
{
    time_t start,end;
    IplImage *frame, *imgHSV, *imgThreshed;

    // Hue value range of three color. color1 is 160 to 179, color2 is 22 to 38...
    unsigned char min[] = {160,22,85};
    unsigned char max[] = {179,38,115};
     
    imgHSV = cvCreateImage(cvSize(320,240), IPL_DEPTH_8U, 3); 
    imgThreshed = cvCreateImage(cvSize(320,240), IPL_DEPTH_8U, 1);
     
    cvNamedWindow("Original", 1);
    cvMoveWindow("Original", 100, 100);      
    cvNamedWindow("Result", 1);
    cvMoveWindow("Result", 100, 380);
         
    CvCapture *capture = cvCaptureFromCAM(0);        
    cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, 320);
    cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, 240);
     
    if(!capture){
          printf("Capture failure\n");
          return -1;
    }
     
    time(&start);
    int counter = 0;
     
    while(1){
         
        frame = cvQueryFrame(capture);  
        if(!frame) break; 
     
        cvCvtColor(frame, imgHSV, CV_BGR2HSV);   
 
        imgThreshed = Threshold(imgThreshed, imgHSV, min, max); 
         
        cvShowImage("Result", imgThreshed);    
                         
        frame = Blob(frame, imgHSV, imgThreshed, min, max);
         
        time(&end);
        ++counter;
        double sec=difftime(end, start);
        double fps=counter/sec;
        printf("FPS = %.2f\n\n",fps);
         
        cvShowImage("Original", frame);
     
        if ( (cvWaitKey(10) & 255) == 27 ) break;
    } 
     
    cvReleaseImage(&imgHSV);
    cvReleaseImage(&imgThreshed);   
           
    cvReleaseCapture(&capture);
    cvDestroyAllWindows();
         
    return 0;
}