This post is continuation of the previous post:
By detecting a single shape i.e., a triangle, some parameters needed are merely sum of its corners – without any restriction like object width. For example a pitfall found on the previous post above when a square shape – having four corners – is given, the result will detect two objects while there is just single object given. The problem is the window where the image attached has four corners too, and then our program ‘think’ that it is also a square. Therefore a parameter is needed to give restriction on particular object we want. Here we can use the width of the object to definitely eliminate any unwanted shape.

For the purpose above, an image with four object shapes will be used to demonstrate role of the parameter. Here is the original image going to use:

Figure 1. Multiple basic shapes

Image bellow is the result of the code that the parameter has been set the way we discuss:

Figure 2. Objects detected which are intended

A parameter that has been set in on the line bellow:

if(contourArea(contours[i]) > 100 && contourArea(contours[i]) <= 90000)
{
   //detect and label object shape detected
}

a contourArea() function inside if() condition calculates the area of contour (closed-contour or object detected) in pixel unit. The parameter passed on the function is just contours that have been calculated. Then the return value is in the form of double value which can be compared directly with integer value. The expression:

contourArea(contours[i]) > 100 && contourArea(contours[i]) <= 90000

means that the area of object detected will be in range of 100 to 90000 pixel. Otherwise outside of this range, the object shape will not be considered as the object intended i.e., we do not give any label or boundary or any sign to these unwanted shapes. Therefore in this if() condition is where objects labels and boundaries are drawn.

The line of code for the object shape detected drawing is bellow:

if(contourArea(contours[i]) > 100 && contourArea(contours[i]) <= 90000)
{
   unsigned sum_of_pn = pn.size(); //sum of corner
   
   switch(sum_of_pn) //check for number of corner
   {
      case TRIANGLE: img_label = "triangle"; break;
      case SQUARE: img_label = "square"; break;
      case PENTA: img_label = "penta"; break; //pentagon
      case HEXA: img_label = "hexa"; //hexagon
   }

   //attach name (mark) to the object shape detected
   mark_object_detected(contours[i],pn,im,sum_of_pn,img_label);
}

For the above code, I intentionally attach the if() condition once more to make a good sense of understanding.

The code line of:

unsigned sum_of_pn = pn.size(); //sum of corner

simply counts contours connected to each other, in this case they are closed-contours, in other words they are corners that we wanted. The siwtch-case conditional is used to selectively choose which label to which shape by passing value of sum_of_pn variable.

switch(sum_of_pn) //check for number of corner
{
   case TRIANGLE: img_label = "triangle"; break;
   case SQUARE: img_label = "square"; break;
   case PENTA: img_label = "penta"; break; //pentagon
   case HEXA: img_label = "hexa"; //hexagon
}

The TRIANGLE, SQUARE, and so on are simply constants containing variable 3, 4 and so on. These constants are declared above of the code, out of the main() function, or global variables.

constexpr unsigned TRIANGLE = 3; //sum of corner
constexpr unsigned SQUARE = 4;
constexpr unsigned PENTA = 5;
constexpr unsigned HEXA = 6;

note that constexpr keyword is valid for C++11 or higher. For you using OpenCV 3.x or C++98 or C++03, use const instead.

Back to the switch-case code. Suppose that sum_of_pn had a value of 3, when passed into switch-case the TRIANGLE (having value of 3) will be selected and img_label which is a string instance will be assigned by “triangle” and store that value (string). Then it ‘break’s or simply the program flow qiuts the switch-case scope to execute the next next code. For the next code:
mark_object_detected(contours[i],pn,im,sum_of_pn,img_label);
simply gives label, boundary and marks the detected corner on the shapes that are detected.

The definition of the function is bellow:

void mark_object_detected(vector<Point> cn,const vector<Point>& point,
   Mat img,unsigned pn_sz,const string& label_img)
{
   Rect r = boundingRect(cn); //points of rectangle
   rectangle(img,r,Scalar(255,2,22)); //draw the rectangle
   for(unsigned i=0; i < pn_sz; ++i)
      circle(img,point[i],8,Scalar(100,100,1),-1);//mark the corner
   label(img,label_img,cn); //give label
   cout<<label_img<<" detected\n"; //print info message
}

the boundingRect() function just has one parameter passed, i.e., input array of point type. This function calculates and returns the minimal up-right bounding rectangle for the specified point set.
The second funciton, rectangle() simply draws a simple, thick, or filled up-right rectangle with its parameters passed that can be specified. The third parameter of this function is for color of the rectangle that are going to draw.

The third function, circle() simply draws a cricle. The second argument of this function, point[i] indicates center of the circle. The third argument is the circle radius or how big the circle that is going to draw. The next argument is for its color. And for the last argument is for thickness of the circle, here the negative value is given meaning that the circle drawn is a filled circle.
for the label() function above has been discussed on the previous article.

Just for to know the deferences to compare, bellow is the result of the previous article code to use the same image:

Figure 3. Many pitfall: objects unwanted detected

Based on the figure 3 above you can see in the green circle that I mark, that there are many unwanted objects detected since it lacks of parameter, i.e., the area of object that are going to be detected.

For the full code can be found on my github:
If there is something you want to ask, please ask in the comment bellow.