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.
0 Comments
Post a Comment