Saturday, 14 May 2011

Detection of shapes like quads,rectangle,triangle and other complex shapes using OpenCV

To detect the shapes we would be utilising the property contour. It can be thought of more like the number of corners of a quadilateral in an image. Based on the number of contour and the angle correlation we can even detect complex shapes.

The function of most importance here is cvFindContours(). Its format is:
Parameters

image (IntPtr)
The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
storage (IntPtr)
Container of the retrieved contours
firstContour ( IntPtr %)
Output parameter, will contain the pointer to the first outer contour
headerSize (Int32)
Size of the sequence header, >=sizeof(CvChain) if method=CV_CHAIN_CODE, and >=sizeof(CvContour) otherwise
mode (RETR_TYPE)
Retrieval mode
method (CHAIN_APPROX_METHOD)
Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
offset (Point)
Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context

Now lets check it on an image. Let the input image after thresholding be
 STEP1: Convert image to gray scale image

cvCvtColor(img, grayimg, CV_BGR2GRAY);

This converts the BGR image to Gray scale image and store as grayimg.

STEP2: Find contours

cvFindContours(IntPtr image,IntPtr storage,IntPtr% firstContour,int headerSize,RETR_TYPE mode,CHAIN_APPROX_METHOD method,Point offset

It finds the contours, their coordinates and stores it .Then on analyzing it and the angles,we can infer the shapes.

STEP3: Toggle through the contours and find the number of points in a contour

We then toggle through the contours to find the number of points in a contour is 4, it can be infered as quadilateral, and if the angles are nearly 90 degree then we can label it as rectangle. If the number of points in a contour is 3, then it is a triangle and so on we can detect other complex shapes also.

//detecting quad with 4 sides

 while(contours)
    {
        result = cvApproxPoly(contours, sizeof(CvContour), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0);


        if(result->total==4 && fabs(cvContourArea(result, CV_WHOLE_SEQ)) > 20)
        {
            CvPoint *pt[4];


            for(int i=0;i<4;i++)
                pt[i] = (CvPoint*)cvGetSeqElem(result, i);


            cvLine(ret, *pt[0], *pt[1], cvScalar(255));
            cvLine(ret, *pt[1], *pt[2], cvScalar(255));
            cvLine(ret, *pt[2], *pt[3], cvScalar(255));
            cvLine(ret, *pt[3], *pt[0], cvScalar(255));


            cout<<pt[0]->x<<" "<<pt[0]->y<<" "<<angle(pt[0],pt[2],pt[1])<<endl;
            cout<<pt[1]->x<<" "<<pt[1]->y<<" "<<angle(pt[1],pt[3],pt[2])<<endl;
            cout<<pt[2]->x<<" "<<pt[2]->y<<" "<<angle(pt[2],pt[0],pt[3])<<endl;
            cout<<pt[3]->x<<" "<<pt[3]->y<<" "<<angle(pt[3],pt[1],pt[0])<<endl;
            cout<<endl;
n++;
xt[n]=(int)((pt[0]->x + pt[1]->x + pt[2]->x + pt[3]->x)/4);
yt[n]=(int)((pt[0]->y + pt[1]->y + pt[2]->y + pt[3]->y)/4);
}

contours = contours->h_next;
        }


STEP4: Use text to write the shape of a quad at its centroid

In the previous tutorial u have been through text adding.
Use it to add the text regaurding shape at the centre of figure.

//initiate font and font size
cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 0, 1, CV_AA);
 

//write text in red color
cvPutText(contourDrawn, "Rectangle", cvPoint(x,y), &font, cvScalar(255, 0, 0, 0));


Now u r ready for the final prog. :)

Do tell me if u make something new out of it.  ^_^






//code to detect rectangle and rectangle.U can modify it for other shapes



#ifdef _CH_
#pragma package <opencv>
#endif

#define CV_NO_BACKWARD_COMPATIBILITY
#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <iostream>
using namespace std;


 CvFont font;
 int xt[20],yt[20],n=-1,m=-1,xtr[20],ytr[20];


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);
}


IplImage* DetectAndDrawQuads(IplImage* img)
{
    CvSeq* contours;
    CvSeq* result;


    CvMemStorage *storage = cvCreateMemStorage(0);

    IplImage* ret = cvCreateImage(cvGetSize(img), 8, 3);
    IplImage* temp = cvCreateImage(cvGetSize(img), 8, 1);

    cvCvtColor(img, temp, CV_BGR2GRAY);

    cvFindContours(temp, storage, &contours, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));

    while(contours)
    {
        result = cvApproxPoly(contours, sizeof(CvContour), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0);


        if(result->total==4 && fabs(cvContourArea(result, CV_WHOLE_SEQ)) > 20)
        {
            CvPoint *pt[4];
            for(int i=0;i<4;i++)
                pt[i] = (CvPoint*)cvGetSeqElem(result, i);


//to eliminate the outer recatngle,replcae 1 & 398 by
// the respective min and max value of x
if(pt[0]->x==1 &&  pt[1]->x==1 && pt[2]->x==398 &&  pt[3]->x==398)
{}

else
{
            cvLine(ret, *pt[0], *pt[1], cvScalar(255));
            cvLine(ret, *pt[1], *pt[2], cvScalar(255));
            cvLine(ret, *pt[2], *pt[3], cvScalar(255));
            cvLine(ret, *pt[3], *pt[0], cvScalar(255));

            cout<<pt[0]->x<<" "<<pt[0]->y<<" "<<angle(pt[0],pt[2],pt[1])<<endl;
            cout<<pt[1]->x<<" "<<pt[1]->y<<" "<<angle(pt[1],pt[3],pt[2])<<endl;
            cout<<pt[2]->x<<" "<<pt[2]->y<<" "<<angle(pt[2],pt[0],pt[3])<<endl;
            cout<<pt[3]->x<<" "<<pt[3]->y<<" "<<angle(pt[3],pt[1],pt[0])<<endl;
            cout<<endl;

n++;
xt[n]=(int)((pt[0]->x + pt[1]->x + pt[2]->x + pt[3]->x)/4);
yt[n]=(int)((pt[0]->y + pt[1]->y + pt[2]->y + pt[3]->y)/4);
}
        }
    else if(result->total==3 && fabs(cvContourArea(result, CV_WHOLE_SEQ)) > 20)
        {
            CvPoint *pt1[3];

            for(int i=0;i<3;i++)
                pt1[i] = (CvPoint*)cvGetSeqElem(result, i);
            cvLine(ret, *pt1[0], *pt1[1], cvScalar(255));
            cvLine(ret, *pt1[1], *pt1[2], cvScalar(255));
            cvLine(ret, *pt1[2], *pt1[0], cvScalar(255));
  
            cout<<pt1[0]->x<<" "<<pt1[0]->y<<" "<<angle(pt1[0],pt1[2],pt1[1])<<endl;
           ;
            cout<<endl;
        
 m++;
xtr[m]=(int)((pt1[0]->x + pt1[1]->x + pt1[2]->x)/3);
ytr[m]=(int)((pt1[0]->y + pt1[1]->y + pt1[2]->y)/3);
        }
        contours = contours->h_next;
    }
    cvReleaseImage(&temp);
    cvReleaseMemStorage(&storage);
    return ret;
}


int main()
{
    int i;
   IplImage* img = cvLoadImage("C:/Users/amit/Desktop/iitb/1.png");

   IplImage* contourDrawn = 0;
   cvNamedWindow("original");

   cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 0, 1, CV_AA);
    contourDrawn = DetectAndDrawQuads(img);

    if(n!=-1)
    {
    for(i=0;i<=n;i++)
    {
    cvPutText(contourDrawn, "Rectangle", cvPoint(xt[i],yt[i]), &font, cvScalar(255, 0, 0, 0));
    }
    }

    if(m!=-1)
    {
    for(i=0;i<=m;i++)
    {
    cvPutText(contourDrawn, "triangle", cvPoint(xtr[i],ytr[i]), &font, cvScalar(255, 0, 0, 0));
    }
    }

   cvShowImage("original", img);
    cvNamedWindow("contours");

    cvShowImage("contours", contourDrawn);
    cvWaitKey(0);
    return 0;
}
 
 

5 comments:

  1. i'm sorry sir,

    i want to detect rectangle, but the source is from webcam,so i wnat to ask you, could your code edited for detected rectangle from video from webcam?

    ReplyDelete
  2. Hiii
    Yes it can be edited to detecting from webcam.
    All that needs to be done is replace the image input to continuous capture from cam..

    Replace the line
    IplImage* img = cvLoadImage("C:/Users/amit/Desktop/iitb/1.png");

    with
    frame = cvQueryFrame( capture );
    and put it in while loop..For better understanding, check:
    http://nayakamitarup.blogspot.in/2011/05/take-video-input-from-webcam-and-store.html

    ReplyDelete
    Replies
    1. where do i put while?
      i try it, and i got error = 'expected unqualified-id before 'while''

      sorry, beacuse i'm new in C and opencv..:)

      Delete
  3. could you give explanation about this lines:

    while(contours)
    {
    result = cvApproxPoly(contours, sizeof(CvContour), storage, CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0);

    if(result->total==4 && fabs(cvContourArea(result, CV_WHOLE_SEQ)) > 20)
    {
    CvPoint *pt[4];
    for(int i=0;i<4;i++)
    pt[i] = (CvPoint*)cvGetSeqElem(result, i);

    //to eliminate the outer recatngle,replcae 1 & 398 by
    // the respective min and max value of x
    if(pt[0]->x==1 && pt[1]->x==1 && pt[2]->x==398 && pt[3]->x==398)
    {}

    and this lines:

    n++;
    xt[n]=(int)((pt[0]->x + pt[1]->x + pt[2]->x + pt[3]->x)/4);
    yt[n]=(int)((pt[0]->y + pt[1]->y + pt[2]->y + pt[3]->y)/4);
    }
    }
    else if(result->total==3 && fabs(cvContourArea(result, CV_WHOLE_SEQ)) > 20)
    {
    CvPoint *pt1[3];

    for(int i=0;i<3;i++)
    pt1[i] = (CvPoint*)cvGetSeqElem(result, i);
    cvLine(ret, *pt1[0], *pt1[1], cvScalar(255));
    cvLine(ret, *pt1[1], *pt1[2], cvScalar(255));
    cvLine(ret, *pt1[2], *pt1[0], cvScalar(255));

    cout<x<<" "<y<<" "<x + pt1[1]->x + pt1[2]->x)/3);
    ytr[m]=(int)((pt1[0]->y + pt1[1]->y + pt1[2]->y)/3);
    }
    contours = contours->h_next;

    ReplyDelete
  4. I want to use your approach for detection of rectangles and other quadrilaterals in javacv....
    I am having problem with implementation of this...

    CvPoint *pt[4];

    for(int i=0;i<4;i++)
    pt[i] = (CvPoint*)cvGetSeqElem(result, i);

    cvLine(ret, *pt[0], *pt[1], cvScalar(255));
    cvLine(ret, *pt[1], *pt[2], cvScalar(255));
    cvLine(ret, *pt[2], *pt[3], cvScalar(255));
    cvLine(ret, *pt[3], *pt[0], cvScalar(255));

    please help me with this...

    ReplyDelete