Motion,Tracking Feature and Contour Detection


This tutorial originated from the bham.ac.uk website. It is now taken offline.

Motion Detection

IplImage* greyImage     = cvCreateImage( imgSize, IPL_DEPTH_8U, 1);
IplImage* colourImage   = cvCreateImage( imgSize, IPL_DEPTH_8U,3);
IplImage* movingAverage = cvCreateImage( imgSize, IPL_DEPTH_32F, 3);
IplImage* difference    = cvCreateImage( imgSize, IPL_DEPTH_8U, 3);
IplImage* temp          = cvCreateImage( imgSize, IPL_DEPTH_8U, 3);
// Create a window
cvNamedWindow( "Image", 1 ); // creation of a visualisation window
cvNamedWindow( "BG", 1 ); // creation of a visualisation window
cvNamedWindow( "Source", 1 ); // creation of a visualisation wind

int key=-1;
int flag=0;
while(key != 'q')
  {
    // Take a picture
    phil.grabImage();

    // Copy from the camera buffer to the OpenCV image buffer
    cvSetImageData(greyImage,  phil.getGreyPointer(),imgSize.width*1);
    cvSetImageData(colourImage,phil.getColourPointer(),imgSize.width*3);

    if (flag==0)
    {
        cvConvertScale(colourImage,movingAverage,1.0,0.0);
        flag=1;
    }
    else
    {
        cvRunningAvg( colourImage, movingAverage, 0.015 ,NULL);
    }

    cvConvertScale(movingAverage,temp,1.0,0.0);
    cvShowImage("BG",temp);
    cvAbsDiff(colourImage,temp,difference);
    cvThreshold(difference,difference,50,255,CV_THRESH_BINARY);
    cvCvtColor( difference,greyImage, CV_BGR2GRAY );

    // Display the image
    cvShowImage("Source",colourImage);
    cvShowImage("Image",greyImage);

    // Capture a key press, but more importantly allow the
    // window to refresh
    key = cvWaitKey(1);  
  }    

Tracking Feature Detection

// Allocate space for image
IplImage* image = cvCreateImage( imgSize, IPL_DEPTH_8U, 1);
IplImage* output = cvCreateImage( imgSize, IPL_DEPTH_8U, 3);
IplImage * eigImage  = cvCreateImage( imgSize, IPL_DEPTH_32F, 1);
IplImage * tempImage = cvCreateImage( imgSize, IPL_DEPTH_32F, 1);

int cornerCount=20;
CvPoint2D32f corners[cornerCount];

// Create a window
cvNamedWindow( "Image", 1 ); // Source image
cvNamedWindow( "Output", 1 ); // working window
int key=-1;
while(key != 'q')
  {
    // Take a picture
    phil.grabImage();

    // Copy from the camera buffer to the OpenCV image buffer
    cvSetImageData(image,phil.getGreyPointer(),imgSize.width);
    cvSetImageData(output,phil.getColourPointer(),imgSize.width*3);
    cvSmooth(image,image,CV_GAUSSIAN,7,7);
    cvGoodFeaturesToTrack(image,eigImage,tempImage,corners,&cornerCount,0.001,50);
    for (int i=0;i<cornerCount;i++)
    {
        cvCircle(output,cvPoint(corners[i].x,corners[i].y),10,CV_RGB(255,0,0),3);
    }
    cvShowImage("Output",output);
    // Capture a key press, but more importantly allow the
    // window to refresh
    key = cvWaitKey(1);
  }  

Square Finding

#include <cv.h>
#include <highgui.h>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <PhilipsCamera.h>

using namespace std;

int thresh = 50;
IplImage* img = 0;
IplImage* img0 = 0;
CvMemStorage* storage = 0;
CvPoint pt[4];

// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2 
double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 )
{
  double dx1 = pt1->x - pt0->x;
  double dy1 = pt1->y - pt0->y;
  double dx2 = pt2->x - pt0->x;
  double dy2 = pt2->y - pt0->y;
  return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
CvSeq* findSquares4( IplImage* img, CvMemStorage* storage )
{
  CvSeq* contours;
  int i, c, l, N = 11;
  CvSize sz = cvSize( img->width & -2, img->height & -2 );
  IplImage* timg = cvCloneImage( img ); // make a copy of input image
  IplImage* gray = cvCreateImage( sz, 8, 1 ); 
  IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 );
  IplImage* tgray;
  CvSeq* result;
  double s, t;
  // create empty sequence that will contain points -
  // 4 points per square (the square's vertices)
  CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage );

  // select the maximum ROI in the image
  // with the width and height divisible by 2
  cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height ));

  // down-scale and upscale the image to filter out the noise
  cvPyrDown( timg, pyr, 7 );
  cvPyrUp( pyr, timg, 7 );
  tgray = cvCreateImage( sz, 8, 1 );

  // find squares in every color plane of the image
  for( c = 0; c < 3; c++ )
    {
      // extract the c-th color plane
      cvSetImageCOI( timg, c+1 );
      cvCopy( timg, tgray, 0 );

      // try several threshold levels
      for( l = 0; l < N; l++ )
        {
      // hack: use Canny instead of zero threshold level.
      // Canny helps to catch squares with gradient shading   
      if( l == 0 )
            {
          // apply Canny. Take the upper threshold from slider
          // and set the lower to 0 (which forces edges merging) 
          cvCanny( tgray, gray, 0, thresh, 5 );
          // dilate canny output to remove potential
          // holes between edge segments 
          cvDilate( gray, gray, 0, 1 );
            }
      else
            {
          // apply threshold if l!=0:
          //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
          cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY );
            }

      // find contours and store them all as a list
      cvFindContours( gray, storage, &contours, sizeof(CvContour),
              CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE );

      // test each contour
      while( contours )
            {
          // approximate contour with accuracy proportional
          // to the contour perimeter
          result = cvApproxPoly( contours, sizeof(CvContour), storage,
                     CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
          // square contours should have 4 vertices after approximation
          // relatively large area (to filter out noisy contours)
          // and be convex.
          // Note: absolute value of an area is used because
          // area may be positive or negative - in accordance with the
          // contour orientation
          if( result->total == 4 &&
          fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 &&
          cvCheckContourConvexity(result) )
                {
          s = 0;

          for( i = 0; i < 5; i++ )
                    {
              // find minimum angle between joint
              // edges (maximum of cosine)
              if( i >= 2 )
                        {
              t = fabs(angle(
                     (CvPoint*)cvGetSeqElem( result, i, 0 ),
                     (CvPoint*)cvGetSeqElem( result, i-2, 0 ),
                     (CvPoint*)cvGetSeqElem( result, i-1, 0 )));
              s = s > t ? s : t;
                        }
                    }

          // if cosines of all angles are small
          // (all angles are ~90 degree) then write quandrange
          // vertices to resultant sequence 
          if( s < 0.1 )
            for( i = 0; i < 4; i++ )
              cvSeqPush( squares,
                 (CvPoint*)cvGetSeqElem( result, i, 0 ));
                }

          // take the next contour
          contours = contours->h_next;
            }
        }
    }

  // release all the temporary images
  cvReleaseImage( &gray );
  cvReleaseImage( &pyr );
  cvReleaseImage( &tgray );
  cvReleaseImage( &timg );

  return squares;
}


// the function draws all the squares in the image
void drawSquares( IplImage* img, CvSeq* squares )
{
  CvSeqReader reader;
  IplImage* cpy = cvCloneImage( img );
  int i;

  // initialize reader of the sequence
  cvStartReadSeq( squares, &reader, 0 );

  // read 4 sequence elements at a time (all vertices of a square)
  for( i = 0; i < squares->total; i += 4 )
    {
      CvPoint* rect = pt;
      int count = 4;

      // read 4 vertices
      memcpy( pt, reader.ptr, squares->elem_size );
      CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
      memcpy( pt + 1, reader.ptr, squares->elem_size );
      CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
      memcpy( pt + 2, reader.ptr, squares->elem_size );
      CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
      memcpy( pt + 3, reader.ptr, squares->elem_size );
      CV_NEXT_SEQ_ELEM( squares->elem_size, reader );

      // draw the square as a closed polyline 
      cvPolyLine( cpy, &rect, &count, 1, 1, CV_RGB(255,0,0), 3, 8 );
    }

  // show the resultant image
  cvShowImage("image",cpy);
  cvReleaseImage( &cpy );
}


void on_trackbar( int a )
{
  if( img )
    drawSquares( img, findSquares4( img, storage ) );
}



int main(int argc, char** argv)
{
  PhilipsCamera phil;             // Declare the camera object 
  phil.openDevice("/dev/video1"); // ...and open the device
  CvSize imgSize;                 // A CvSize structure to hold the dimensions
  imgSize.width = 640; 
  imgSize.height = 480;

  // Set the camera to the right resolution
  phil.setResolution(imgSize.width,imgSize.height);
  img = cvCreateImage( imgSize, IPL_DEPTH_8U, 3);
  int i;
  // create memory storage that will contain all the dynamic data
  storage = cvCreateMemStorage(0);
  // create window with name "image"
  cvNamedWindow( "image", 1 );
  // create trackbar (slider) with parent "image" and set callback
  // (the slider regulates upper threshold, passed to Canny edge detector) 
  cvCreateTrackbar( "thresh1", "image", &thresh, 1000, on_trackbar );
  int key;
  while(key!='q')
    {
      // Take a picture
      phil.grabImage();

      // Copy from the camera buffer to the OpenCV image buffer
      cvSetImageData(img,phil.getColourPointer(),imgSize.width*3);

      // force the image processing
      on_trackbar(0);
      // wait for key.
      // Also the function cvWaitKey takes care of event processing
      key=cvWaitKey(1);
      // release image
      //cvReleaseImage( &img );
      // clear memory storage - reset free space position
      cvClearMemStorage( storage );
    }

  cvDestroyWindow("image");

  return 0;
}

Cam Shift Tracker

Source

The Continuously Adaptive Mean SHIFT (CAMSHIFT) algorithm [4], is based on the mean shift algorithm [5], a robust non-parametric iterative technique for ¯nding the mode of probability distributions. Given a color image and a color histogram, the image produced from the original color image by using the histogram as a look-up table is called back-projection image. If the histogram is a model density distribution, then the back projection image is a probability distribution of the model in the color image. CAMSHIFT detects the mode in the probability distribution image by applying mean shift while dynamically adjusting the parameters of the target distribution. In a single image, the process is iterated until convergence (or until an upper bound on the number of iterations is reached). A detection algorithm can be applied to successive frames of a video sequence to track a single target. The search area can be restricted around the last known position of the target, resulting in possibly large computational savings. This type of scheme introduces a feed-back loop, in which the result of the detection is used as input to the next detection process. The version of CAMSHIFT applying these concepts to tracking of a single target in a video stream is called Coupled CAMSHIFT.