4.2. Contour Features

In this article, we will learn

  • To find the different features of contours, like area, perimeter, centroid, bounding box etc

  • You will see plenty of functions related to contours.

4.2.1. 1. Moments

Image moments help you to calculate some features like center of mass of the object, area of the object etc. Check out the wikipedia page on Image Moments

The function cv2.moments() gives a dictionary of all moment values calculated. See below:

>>> %matplotlib inline
>>> import matplotlib.pyplot as plt
>>>
>>> import cv2
>>> import numpy as np
>>>
>>> # img = cv2.imread('/cvdata/star.jpg',0)
>>> img = cv2.imread('/cvdata/star.png', 0)
>>> plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f812624b910>
_images/sec02-contour-features_3_1.png
>>> ret,thresh = cv2.threshold(img,127,255,0)
>>> ret
127.0
>>> plt.imshow(thresh)
<matplotlib.image.AxesImage at 0x7f81265c8f90>
_images/sec02-contour-features_6_1.png
>>> contours,hierarchy = cv2.findContours(thresh, 1, 2)
>>> # contours.shape
>>> hierarchy.shape
(1, 22, 4)
>>> # hierarchy.shape.shape
>>> cnt = contours[0]
>>> M = cv2.moments(cnt)
>>> print (M)
{'m00': 4.0, 'm10': 706.0, 'm01': 1352.0, 'm20': 124610.66666666666, 'm11': 238628.0, 'm02': 456977.0, 'm30': 21994371.0, 'm21': 42118405.333333336, 'm12': 80656440.5, 'm03': 154458902.0, 'mu20': 1.6666666666569654, 'mu11': 0.0, 'mu02': 1.0, 'mu30': 3.725290298461914e-09, 'mu21': 5.762558430433273e-09, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.10416666666606034, 'nu11': 0.0, 'nu02': 0.0625, 'nu30': 1.1641532182693481e-10, 'nu21': 1.800799509510398e-10, 'nu12': 0.0, 'nu03': 0.0}

From this moments, you can extract useful data like area, centroid etc. Centroid is given by the relations, \(C_x = \frac{M_{10}}{M_{00}}\) and \(C_y = \frac{M_{01}}{M_{00}}\). This can be done as follows:

>>> cx = int(M['m10']/M['m00'])
>>> cy = int(M['m01']/M['m00'])

4.2.2. 2. Contour Area

Contour area is given by the function cv2.contourArea() or from moments, M[‘m00’].

>>> for x in contours:
>>>     area = cv2.contourArea(x)
>>>     print(area)
4.0
4.0
2.0
2.0
8.5
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
76185.5

4.2.3. 3. Contour Perimeter

It is also called arc length. It can be found out using cv2.arcLength() function. Second argument specify whether shape is a closed contour (if passed True), or just a curve.

>>> for cnt in contours:
>>>     perimeter = cv2.arcLength(cnt,True)
>>>     print(perimeter)
7.656854152679443
7.656854152679443
5.656854152679443
5.656854152679443
11.071067690849304
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
5.656854152679443
1680.687520623207

4.2.4. 4. Contour Approximation

It approximates a contour shape to another shape with less number of vertices depending upon the precision we specify. It is an implementation of Douglas-Peucker algorithm. Check the wikipedia page for algorithm and demonstration.

To understand this, suppose you are trying to find a square in an image, but due to some problems in the image, you didn’t get a perfect square, but a “bad shape” (As shown in first image below). Now you can use this function to approximate the shape. In this, second argument is called epsilon, which is maximum distance from contour to approximated contour. It is an accuracy parameter. A wise selection of epsilon is needed to get the correct output.

>>> for cnt in contours:
>>>     epsilon = 0.1*cv2.arcLength(cnt,True)
>>>     approx = cv2.approxPolyDP(cnt,epsilon,True)
>>>     print(epsilon, approx)
0.7656854152679444 [[[175 338]]

 [[176 337]]

 [[178 338]]

 [[177 339]]]
0.7656854152679444 [[[102 338]]

 [[103 337]]

 [[105 338]]

 [[104 339]]]
0.5656854152679444 [[[174 337]]

 [[175 336]]

 [[176 337]]

 [[175 338]]]
0.5656854152679444 [[[173 336]]

 [[174 335]]

 [[175 336]]

 [[174 337]]]
1.1071067690849306 [[[413 286]]

 [[416 285]]

 [[417 287]]

 [[415 288]]]
0.5656854152679444 [[[416 285]]

 [[417 284]]

 [[418 285]]

 [[417 286]]]
0.5656854152679444 [[[412 285]]

 [[413 284]]

 [[414 285]]

 [[413 286]]]
0.5656854152679444 [[[417 284]]

 [[418 283]]

 [[419 284]]

 [[418 285]]]
0.5656854152679444 [[[411 284]]

 [[412 283]]

 [[413 284]]

 [[412 285]]]
0.5656854152679444 [[[421 281]]

 [[422 280]]

 [[423 281]]

 [[422 282]]]
0.5656854152679444 [[[422 280]]

 [[423 279]]

 [[424 280]]

 [[423 281]]]
0.5656854152679444 [[[423 279]]

 [[424 278]]

 [[425 279]]

 [[424 280]]]
0.5656854152679444 [[[424 278]]

 [[425 277]]

 [[426 278]]

 [[425 279]]]
0.5656854152679444 [[[425 277]]

 [[426 276]]

 [[427 277]]

 [[426 278]]]
0.5656854152679444 [[[426 276]]

 [[427 275]]

 [[428 276]]

 [[427 277]]]
0.5656854152679444 [[[431 272]]

 [[432 271]]

 [[433 272]]

 [[432 273]]]
0.5656854152679444 [[[432 271]]

 [[433 270]]

 [[434 271]]

 [[433 272]]]
0.5656854152679444 [[[433 270]]

 [[434 269]]

 [[435 270]]

 [[434 271]]]
0.5656854152679444 [[[434 269]]

 [[435 268]]

 [[436 269]]

 [[435 270]]]
0.5656854152679444 [[[435 268]]

 [[436 267]]

 [[437 268]]

 [[436 269]]]
0.5656854152679444 [[[436 267]]

 [[437 266]]

 [[438 267]]

 [[437 268]]]
168.06875206232073 [[[639 316]]

 [[  0 399]]]

Below, in second image, green line shows the approximated curve for epsilon = 10% of arc length. Third image shows the same for epsilon = 1% of the arc length. Third argument specifies whether curve is closed or not.

4.2.5. 5. Convex Hull

Convex Hull will look similar to contour approximation, but it is not (Both may provide same results in some cases). Here, cv2.convexHull() function checks a curve for convexity defects and corrects it. Generally speaking, convex curves are the curves which are always bulged out, or at-least flat. And if it is bulged inside, it is called convexity defects. For example, check the below image of hand. Red line shows the convex hull of hand. The double-sided arrow marks shows the convexity defects, which are the local maximum deviations of hull from contours.

There is a little bit things to discuss about it its syntax:

hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]

Arguments details:

  • points are the contours we pass into.

  • hull is the output, normally we avoid it.

  • clockwise : Orientation flag. If it is True, the output convex hull is oriented clockwise. Otherwise, it is oriented counter-clockwise.

  • returnPoints : By default, True. Then it returns the coordinates of the hull points. If False, it returns the indices of contour points corresponding to the hull points.

So to get a convex hull as in above image, following is sufficient:

>>> hull = cv2.convexHull(cnt)

But if you want to find convexity defects, you need to pass returnPoints = False. To understand it, we will take the rectangle image above. First I found its contour as cnt. Now I found its convex hull with returnPoints = True, I got following values: [[[234 202]], [[ 51 202]], [[ 51 79]], [[234 79]]] which are the four corner points of rectangle. Now if do the same with returnPoints = False, I get following result: [[129],[ 67],[ 0],[142]]. These are the indices of corresponding points in contours. For eg, check the first value: cnt[129] = [[234, 202]] which is same as first result (and so on for others).

You will see it again when we discuss about convexity defects.

4.2.6. 6. Checking Convexity

There is a function to check if a curve is convex or not, cv2.isContourConvex(). It just return whether True or False. Not a big deal.

>>> k = cv2.isContourConvex(cnt)

4.2.7. 7. Bounding Rectangle

There are two types of bounding rectangles.

7.a. Straight Bounding Rectangle

It is a straight rectangle, it doesn’t consider the rotation of the object. So area of the bounding rectangle won’t be minimum. It is found by the function cv2.boundingRect().

Let (x,y) be the top-left coordinate of the rectangle and (w,h) be its width and height.

>>> x,y,w,h = cv2.boundingRect(cnt)
>>> img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

7.b. Rotated Rectangle

Here, bounding rectangle is drawn with minimum area, so it considers the rotation also. The function used is cv2.minAreaRect(). It returns a Box2D structure which contains following detals - ( top-left corner(x,y), (width, height), angle of rotation ). But to draw this rectangle, we need 4 corners of the rectangle. It is obtained by the function cv2.boxPoints()

>>> rect = cv2.minAreaRect(cnt)
>>> box = cv2.boxPoints(rect)
>>> box = np.int0(box)
>>> im = cv2.drawContours(img,[box],0,(0,0,255),2)
/tmp/ipykernel_111961/3423746700.py:3: DeprecationWarning: np.int0 is a deprecated alias for np.intp.  (Deprecated NumPy 1.24)
  box = np.int0(box)
>>> plt.imshow(im)
<matplotlib.image.AxesImage at 0x7f8126578550>
_images/sec02-contour-features_28_1.png

Both the rectangles are shown in a single image. Green rectangle shows the normal bounding rect. Red rectangle is the rotated rect.

4.2.8. 8. Minimum Enclosing Circle

Next we find the circumcircle of an object using the function cv2.minEnclosingCircle(). It is a circle which completely covers the object with minimum area.

>>> (x,y),radius = cv2.minEnclosingCircle(cnt)
>>> center = (int(x),int(y))
>>> radius = int(radius)
>>> img = cv2.circle(img,center,radius,(0,255,0),2)
>>> plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f81264a5610>
_images/sec02-contour-features_31_1.png

4.2.9. 9. Fitting an Ellipse

Next one is to fit an ellipse to an object. It returns the rotated rectangle in which the ellipse is inscribed.

>>> ellipse = cv2.fitEllipse(cnt)
>>> im = cv2.ellipse(im,ellipse,(0,255,0),2)
>>> plt.imshow(im)
<matplotlib.image.AxesImage at 0x7f8126509810>
_images/sec02-contour-features_34_1.png

4.2.10. 10. Fitting a Line

Similarly we can fit a line to a set of points. Below image contains a set of white points. We can approximate a straight line to it.

>>> rows,cols = img.shape[:2]
>>> [vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
>>> lefty = int((-x*vy/vx) + y)
>>> righty = int(((cols-x)*vy/vx)+y)
>>> img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
>>> plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f8127e70fd0>
_images/sec02-contour-features_37_1.png

4.2.11. Additional Resources

4.2.12. Exercises