After we have known how to show an image with OpenCV on the previous post – OpenCV hello world; displaying an Image. This part will discuss about how to detect basic shapes, for example: triangle, square, etc., based on its corners quantity, and then showing the result on the screen. And for the result, the image detected is attached by a label on it, and for the angles detected we are going to give points to mark on it.

Related post:
Therefore, this post will discuss the features needed to do what intended above, such as cvtColor(), findContours(), approxPolyDP() and more. Also the discussion is done step-by step from bottom-up. 

So, here is my hierarchical folder:
--build
----main.cpp
----CMakeLists.txt
----tri.png

First of all, we need to make a window for our image to be attached and image location that we store in im variable bellow:
namedWindow("win");
Mat im = imread("../all.png");
then, continued by detecting image contours:
Mat gray;
cvtColor(im,gray,COLOR_RGB2GRAY);
vector> contours;
findContours(gray,contours,RETR_LIST,CHAIN_APPROX_SIMPLE);
for the findContours() function to work, a gray image type must be passed to the function. And for the result is stored in countours container-variable having vector of vector Point type. Finally the last two arguments in the function means:
  • RETR_LIST means retrieving all of the contours without establishing any hierarchical relationships.
  • CHAIN_APPROX_SIMPLE means compressing horizontal, vertical, and diagonal segments and leaves only their end points. For example, an up-right rectangular contour is encoded with 4 points.
Related Post:

Since the contours have been found or detected, the corners of the object can simply be detected by approxPolyDP() functions. This is where polygonal curve approximation happens in which the precision can be specified by its parameters.
approxPolyDP(contours[i],pn,arcLength(contours[i],true)*0.02,true);
On the function above, the first argument (contours[i]) is the input curve having vector container of Point type. In each of its index contains at least three curves (or simply lines) in acontour. The second argument (pn) is approximation curve where its type must have the same type as the input type, that is vector of Point type. The third argument (arcLength()’s return value) is an epsilon value which is a parameter specifying the approximation accuracy: This is the maximum distance between the original curve and its approximation. For this case I multiply the result by a ‘magic value’ 0.02 to make the best approximation. You can try another value to see the result. And finally the last argument (true) makes the contours closed (its first and last vertices are connected).
Note that the arcLength() here calculates a contour perimeter or simply a curve length.

The last step, but not least is just to get the size of pn variable which is the result of approxPolyDP() above.
unsigned pn_size = pn.size();
if(pn_size == 4)
{
   //draw rectangle around the object detected
   Rect r = boundingRect(contours[i]);
   rectangle(im,r,Scalar(255,2,22));
   //mark each of point detected
   for(unsigned i=0;i<pn_size;++i)
      circle(im,pn[i],8,Scalar(100,100,1),-1);  
   //make “triangle” label on the image detected
   label(im,"square",contours[i]);
} 
here is an example of the result code:
Figure 1. a triangle detected with label and other marks

if we want to detect another than triangle, lets say a square, just change the condition according to its sum of corners i.e., pn_size == 4:
if(pn_size == 4) //must be a square
{
//draw rectangle around the object detected
…
//label or anything you want to add
}
But if you wish to detect a square shape with the same parameter with the above triangle (excepting the label name), there is another pitfall that the result would be:
Figure 2. two square detected

The reason is that the code above is still lack of other parameters like minimal and maximal area of the object. Here the window itself has four corners that of course makes it as a square. The problem like this one will be discussed on another article.

Here is the complete code:
#include <opencv4/opencv2/imgproc/imgproc.hpp>
#include <opencv4/opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;

void label(Mat img,const String& lb,vector<Point>& pn)
{
   int font_type = FONT_HERSHEY_SIMPLEX;
   double scale = 0.6;
   int thickness = 1;
   int baseline = 0;
   Size text_sz = getTextSize(lb,font_type,scale,thickness,&baseline);
   Rect r = boundingRect(pn);
   //get center position of the object detected
   Point p{r.x+(r.width-text_sz.width)/2,r.y+(r.height-text_sz.height)/2};
   //make a rectangle to attach the text at
   rectangle(img,p+Point{0,baseline},p+Point{text_sz.width,-text_sz.height},Scalar{230,255,200},-1);
   //put a text
   putText(img,lb,p,font_type,scale,Scalar{0,0,255},thickness);
}

int main()
{
   namedWindow("win");
   Mat im = imread("../topat.png"); 
   
   Mat gray;
   cvtColor(im,gray,COLOR_RGB2GRAY);
   vector<vector<Point>> contours;
   vector<Point> pn;
   findContours(gray,contours,RETR_LIST,CHAIN_APPROX_SIMPLE);
   
   for(unsigned int i=0;i<contours.size();++i)
   {
      cout<<"arc["<<i<<"]: "<<arcLength(contours[i],true)*0.02<<endl;
      approxPolyDP(contours[i],pn,arcLength(contours[i],true)*0.02,true);
      if(true/*contourArea(contours[i]) > 100 && contourArea(contours[i]) <= 90000*/)
      {
         unsigned pn_size = pn.size();
         if(pn_size == 4)
         {
            //draw rectangle around the object detected
            Rect r = boundingRect(contours[i]);
            rectangle(im,r,Scalar(255,2,22));
            //mark each of point detected
            for(unsigned i=0;i<pn_size;++i)
               circle(im,pn[i],8,Scalar(100,100,1),-1);  
            //make “triangle” label on the image detected
            label(im,"square",contours[i]);
      }
   }

   imshow("win",im);
   while(static_cast<char>(waitKey(1)) != 'q');
   return 0;
}
Up to this step we have known how to detect a basic shape based on sum of corners. If there is any part of the code that you are not understand or there is any mistake that I made, just ask and say it right away on the comment bellow.