top of page
Writer's pictureSunil Kumar Yadav

Shape Detection using OpenCV/C++

OpenCV library is very popular and widely used in computer vision. OpenCV is very easy to use but one should be familiar with different aspects of image processing and how to effectively deal with them before moving to real-world challenges. In this article, we will go through the process of shape detection using OpenCV with C++. In this exercise, I've used Microsoft Visual Studio 2019 and OpenCV 4.5.


Image 1: Geometric Shapes (Credit Pixels)

The process of object/shape detection involves below key steps.

  • Preprocess input image

  • Edge detection and contour drawing

  • Classifying objects based on edges

Let's have a closer look at each of the steps and try to implement them in C++ source code. For this exercise, we will use the below image as an input image which contains different kinds of shapes and a few black contours as noise.


Image 2: Input image with different shaped object

Preprocessing Input Image

We need to preprocess the input image before we apply contour identifier and other operations to find no. of edges. Below image 3 shows different stages involved in preprocessing of the input image.

Image 3: Preprocessing Input Image Tasks

Below is a snippet of code for the steps mentioned in Image 3.

 string path = "Resources/color_shapes.jpg";
 Mat img = imread(path);

 // Declaring few Mat object for further operations
 Matimg_gray, img_blur, img_canny, img_dilate;

 // Convert img color space. Output image is second arg
 cvtColor(img,img_gray,COLOR_BGR2GRAY);   
 
 // Blurring image using gaussian fliter. Size(3,3) is SE kernal
 GaussianBlur(img_gray, img_blur, Size(3, 3), 3, 0);   
             
 // Edge detection using canny algo
 Canny(img_blur, img_canny, 25, 75); 

 // Running dilation on canny output to improve edge thickness
 Mat se1 = getStructuringElement(MORPH_RECT, Size(3, 3));
 dilate(img_canny, img_dilate, se1);  

Below is the output of applying the canny edge detector and dilation on the grey scaled input image.

Image 4: Output of Preprocessor stage

Edge detection and contour drawing

Once we've preprocessed the grayscale image to obtain an image with edges, we need to apply the below steps as mentioned in image 5.


Image 5: Identifying object and drawing contour

Below is a snippet of code for the steps mentioned in Image 5.

// dial_img is preprocessed image from earlier step
// in_img is RGB input image
void get_shape_contours(Mat& dial_img, Mat& in_img)
{
// vector of points to store contour points
vector<vector<Point>> contours;    
vector<Vec4i> hierarchy;

findContours(dial_img, contours, hierarchy, RETR_EXTERNAL, 
            CHAIN_APPROX_SIMPLE); 


vector<vector<Point>> contourPoly(contours.size());

// bouning box/rect around shape
vector<Rect> bound_rect(contours.size());   

// loop through all contours detected
for (int i = 0; i < contours.size();++i)  
{
  int c_area = contourArea(contours[i]);   // area of each object contour
  
  if (c_area > 1000) // Area based threshold for emoving noise 
  {
    float peri = arcLength(contours[i], true);
    
    // Approximate poly curve with stated accurracy
    approxPolyDP(contours[i], contourPoly[i], 0.02 * peri, true);   

    // Finds no. of points in countours and draw lines(purple)
    drawContours(in_img, contourPoly, i, Scalar(255, 0, 255), 2);   
 
    bound_rect[i] = boundingRect(contourPoly[i]);
    
    // Draw bounding rectangle around detected objects of input img
    rectangle(in_img, 
            bound_rect[i].tl(),     //bounding rectanle top left point
            bound_rect[i].br(),     //bounding rectanle bottom right point
            Scalar(0, 255, 0), 3);  // left/ bottom right points

   }
}    // end of for loop

}

Classifying objects based on edges

In the earlier stage using function get_shape_contours(), we've looped through the different detected objects and removed the noise-based of object's contour size. In order to identify shape type, we need to simply extend the earlier function get_shape_contours() by calculating no. of edges/corners the detected object has. If a detected object has 3 edges then it means its shape is a triangle. Similarly, for other shape types, we can write if-else or switch statements. Below is the code snippet to classify shape types based on no. of edges and annotate the input image with shape type.

// annotating image with shape type
int obj_corners = (int)contourPoly[i].size();
string obj_name;

if (obj_corners == 3)
    obj_name = "Triangle";
else if (obj_corners == 4)   // rectangle or square
{
    float aspect_ratio = (float)bound_rect[i].width / 
                         (float) bound_rect[i].height;
    
    // tolerance for l/w ratio 
    if(aspect_ratio>0.95 && aspect_ratio<1.05)    
        obj_name = "Square";
    else
        obj_name = "Rectangle";
}
else if (obj_corners > 6)
    obj_name = "Circle";

// Adding shape type on input image
// y axis offset to ensure text is above detected shape
Point text_point { bound_rect[i].x, bound_rect[i].y - 5 };      
putText(in_img, obj_name,            // shape type
                text_point ,         // x,y co-ordinate 
                FONT_HERSHEY_PLAIN,  // Font name
                1,                   // Font scale
                Scalar(0, 0, 255),   // Font color in BGR (Red)
                1);                  // Thickness

Below is the zip folder containing the source code along with the image for further reference. Once we combine different parts of the above-mentioned steps and execute the application it gives the below output (Image 6).

int main()
{
    string path = "Resources/color_shapes.jpg";
  
    Mat img = imread(path);

    Mat img_gray, img_blur, img_canny, img_dilate, img_erode;

    //--------------------------------------------------------------------
    // convert img color space. Output image is second arg
    cvtColor(img,img_gray,COLOR_BGR2GRAY); 
            
    // blurring image using gaussian fliter
    GaussianBlur(img_gray, img_blur, Size(3, 3), 3, 0);    
                                                         
    Canny(img_blur, img_canny, 25, 75); // edge detection using canny algo

    // Running dilation on canny output to improve edge thickness
    Mat se1 = getStructuringElement(MORPH_RECT, Size(3, 3));
    dilate(img_canny, img_dilate, se1);   
    //--------------------------------------------------------------------
    // draw contour and print shape details on input image
    get_shape_contours(img_dilate, img);  

    imshow("BGR image", img);
    waitKey(0);

    return 0;
}

Image 6: Shape Detection Program Output




1,124 views0 comments

Recent Posts

See All

Comments


bottom of page