Part 2: Studying Digital Image Processing with OpenCV-Python

12. Contours
Learn to find contours, draw contours.
12.1 What is Contours
Contour is a curve joining all the continuous points (along the boundary), having same color or intensity.
The contour is for shape analysis, object detection and recognition.
In OpenCV, finding contours is like finding white object from black background. So remember, object to be found should be white and background should be black.
OpenCV provides cv2.findContours() with:
Inputs:
- First one is source image.
- Second is contour retrieval mode.
- Third is contour approximation method:
  + cv2.CHAIN_APPROX_NONE : all the boundary points are stored.
  + cv2.CHAIN_APPROX_SIMPLE : only store necessary points, thereby saving memory (E.g: straight line only needs 2 points instead of all the boundary points).
Outputs:
- Contours is a Python list of all the contours in the image. Each individual contour is a Numpy array of (x,y) coordinates of boundary points of the object.
- Hierarchy
12.2 How to draw the contours?
OpenCV provides cv2.drawContours() to draw contours. Its input:
- First argument is source image.
- Second argument is the contours which should be passed as a Python list.
- Third argument is index of contours (useful when drawing individual contour. To draw all contours, pass -1).
- Remaining arguments are color, thickness etc.
Draw the contours for the image below:
Figure: input to draw the contours
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import cv2
im = cv2.imread('contour.png')
gray_img = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
cv2.imshow('img',im)
#uing binary threshold
ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY_INV)
#find contours
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#substitute cv2.CHAIN_APPROX_NONE with cv2.CHAIN_APPROX_SIMPLE to see how data points were stored
print(len(contours[0]))
#draw the contour (black color) on original image
cv2.drawContours(im, contours, -1, (0,0,0), 2)
#display it
cv2.imshow('contours',im)
cv2.waitKey(0)
cv2.destroyAllWindows()
Figure: Output with black contours
12.3 Contour Features
12.3.1 Moments
Support to calculate some features of the object such as area, mass, ...
OpenCV provides cv2.moments() to calculate moments with input is the contour need to be calculated.
Reuse the example above for contour at 0 index.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import cv2
im = cv2.imread('contour.png')
gray_img = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
cv2.imshow('im',im)
#uing binary threshold
ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY_INV)
#find contours
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#substitute cv2.CHAIN_APPROX_NONE with cv2.CHAIN_APPROX_SIMPLE to see how data points were stored
cnt = contours[0]
M = cv2.moments(cnt)
print (M)
Output: {'mu02': 72086346.66666663, 'mu03': 1.52587890625e-05, 'm11': 277955440.0, 'nu02': 0.08431372549019603, 'm12': 34232008746.666668, 'mu21': 2.86102294921875e-06, 'mu20': 70419666.66666663, 'nu20': 0.08236434108527127, 'm30': 47178681520.0, 'nu21': 1.9569418491858047e-17, 'mu11': 0.0, 'mu12': 4.76837158203125e-06, 'nu11': 0.0, 'nu12': 3.261569748643008e-17, 'm02': 352907306.6666666, 'm03': 48713840000.0, 'm00': 29240.0, 'm01': 2865520.0, 'mu30': 1.52587890625e-05, 'nu30': 1.0437023195657626e-16, 'nu03': 1.0437023195657626e-16, 'm10': 2836280.0, 'm20': 345538826.6666666, 'm21': 33862805013.333332}
Centroid is given by the formula: $C_{x}=\frac{M_{10}}{M_{00}},C_{y}=\frac{M_{01}}{M_{00}}$
1
2
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
Contour Area is given by cv2.contourArea(cnt)  or M['m00']
Contour perimeter or a curve length is given by cv2.arcLength(cnt, True) True is for closed contour.
12.3.2 Contour Approximation
As Wiki

"It is an implementation of Douglas-Peucker algorithm. The purpose of the algorithm is, given a curve composed of line segments (which is also called a Polyline in some contexts), to find a similar curve with fewer points. The simplified curve consists of a subset of the points that defined the original curve. The algorithm defines 'dissimilar' based on the maximum distance between the original curve and the simplified curve. The simplified curve consists of a subset of the points that defined the original curve."
To understand this, we make a simple example with the input image:
Figure: Input image - a broken object
And a track bar to control value of epsilon. After change the value of track bar, pressing the 'd' key to update result image.
Figure: GUI of example
The code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
import cv2 as cv
def nothing(x):
    pass

image = 'broken_object1.png'
im = cv.imread(image)
cv.namedWindow('image')
# create trackbars for color change
cv.createTrackbar('epsilon','image',0,80,nothing)
cv.imshow('image',im)
while(1):  
 k = cv.waitKey(0) & 0xFF
 if k == 27:
  break
 if k == ord('d'):
  im = cv.imread(image)
  gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
  ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
  contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
  cnt = contours[0]
  epsilon = cv.getTrackbarPos('epsilon','image')
  approx = cv.approxPolyDP(cnt,epsilon,True)
  cv.drawContours(im, [approx], 0, (0,0,0), 2)
  cv.imshow('image',im)
cv.destroyAllWindows()
According to the values of epsilon, the new results were generated.


So you can see when epsilon ('dissimilar' ) is small the contour fits object perfectly. But when epsilon ('dissimilar' ) is large the contour just fits the outbound.
You can use this function to approximate the shape. As example above, the object is a square but somehow, its shape was distort. This function support to recover the square.
12.3.3 Convex Hull
OpenCN provides function cv2.convexHull() to checks a curve for convexity defects and corrects it. Convex curves are the curves which are always bulged out, or at-least flat. Convexity defects are curves which are bulged inside.
For example, check the image:
Figure: image to check convex hull
The code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import numpy as np
import cv2 as cv

image = 'broken_object1.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
hull = cv.convexHull(cnt, False)
cv.drawContours(im, [hull], 0, (0,0,0), 2)
cv.imshow('convexHull',im)
cv.waitKey(0)
cv.destroyAllWindows()
After using convex hull the image:
Figure: the black line shows the convex hull of object

Figure: the green lines shows the convexity defects,
the local maximum deviations of hull from contours
OpenCV provides cv.isContourConvex(contour) to check whether an object is convexity or not.
12.3.4 Bounding Rectangle
There are two types of bounding rectangles:
+ Straight Bounding Rectangle: it doesn't consider the rotation of the object. The area of the bounding rectangle is not minimum. Use cv.boundingRect(contour).
+ Rotated Rectangle: it considers the rotation of the object. The area of the rotated rectangle is minimum. Use cv.minAreaRect(cnt). This function goes with "cv.boxPoints(rect)" or "cv2.cv.BoxPoints(rect)"
to find the four vertices of a rotated rectangle to draw rotated rectangle.
Draw boundary rectangle for object below:
Figure: Input to draw bounding rectangle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
import cv2 as cv
import cv2

image = 'test9.png'
im = cv.imread(image)
im1 = im.copy()
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#Straight Bounding Rectangle
x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
#Rotated Rectangle
rect = cv.minAreaRect(cnt)
box = cv2.cv.BoxPoints(rect) 
box = np.int0(box)
cv.drawContours(im1,[box],0,(255,0,0),2)
#
cv.imshow('Straight Bounding Rectangle',im)
cv.imshow('Rotated Rectangle',im1)
cv.waitKey(0)
cv.destroyAllWindows()
Figure: output image, green box is Straight Bounding Rectangle, blue box is Rotated Rectangle
12.3.5 Minimum Enclosing Circle
It is a circle which covers the object with minimum area. Use cv.minEnclosingCircle(contour).
Reuse image above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import numpy as np
import cv2 as cv
import cv2

image = 'test9.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#Minimum Enclosing Circle 
(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(im,center,radius,(0,255,0),2)
#
cv.imshow('Minimum Enclosing Circle ',im)
cv.waitKey(0)
cv.destroyAllWindows()

Figure: Minimum Enclosing Circle
12.3.6 Fitting an Ellipse
Fit a rotated ellipse to an object. Use cv.fitEllipse(cnt)
Reuse image above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import numpy as np
import cv2 as cv
import cv2

image = 'test9.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#Fitting an Ellipse  
ellipse = cv.fitEllipse(cnt)
cv.ellipse(im,ellipse,(0,255,0),2)
#
cv.imshow('Fitting an Ellipse  ',im)
cv.waitKey(0)
cv.destroyAllWindows()
Figure: Fitting an Ellipse
12.3.7 Fitting a Line
Fit a line to a set of points. Use cv.fitLine().
Reuse image above.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
import cv2 as cv
import cv2

image = 'test9.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#Fitting a Line 
rows,cols = im.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv2.cv.CV_DIST_L2 ,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(im,(cols-1,righty),(0,lefty),(0,255,0),2)
#
cv.imshow('Fitting a Line   ',im)
cv.waitKey(0)
cv.destroyAllWindows()
Figure: Fitting a Line
12.4 Contour Properties
12.4.1 Aspect Ratio
It is the ratio of width to height of bounding rect of the object.
x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
12.4.1 Extent
It is the ratio of contour area to bounding rectangle area.
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
12.4.2 Solidity
Solidity is the ratio of contour area to its convex hull area.
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
12.4.3 Equivalent Diameter
It is the diameter of the circle whose area is same as the contour area.
$EquivalentDiameter = \sqrt{\frac{4*ContourArea}{\Pi }}$
area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
12.4.4 Orientation
It is the angle at which object is directed. Following method also gives the Major Axis and Minor Axis lengths.
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)
12.4.5 Mask and Pixel Points
Support to create mask from the contour of object.
Find mask for the object below.
Figure: Input image to find mask
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import numpy as np
import cv2 as cv

image = 'broken_object.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#Mask and Pixel Points
#init mask with black
mask = np.zeros(gray_img.shape,np.uint8)
#fill the contour with white
cv.drawContours(mask,[cnt],0,255,-1)
#just get white points
pixelpoints = cv.findNonZero(mask)
cv.imshow('Mask and Pixel Points',mask)
cv.waitKey(0)
cv.destroyAllWindows()
Figure: mask of input image
12.4.6 Maximum, Minimum Intensity and their locations
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
Find Maximum, Minimum Intensity and their locations of the image below
Figure: Image with intensity from low to high

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import cv2 as cv

image = 'minmax.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
mask = np.zeros(gray_img.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = cv.findNonZero(mask)
#Maximum, Minimum Intensity
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(gray_img,mask = mask)
#max intensity location in red
cv.circle(im,min_loc, 10, (0,0,255), -1)
#min intensity location in blue
cv.circle(im,max_loc, 10, (255,0,0), -1)
cv.imshow('Maximum, Minimum Intensity',im)
cv.waitKey(0)
cv.destroyAllWindows()
Figure: output image
12.4.7 Mean Color or Mean Intensity
mean_val = cv2.mean(im,mask = mask)
12.4.8 Extreme Points
Extreme Points are topmost, bottommost, rightmost and leftmost points of the object.
Find extreme points of object in image below.
Figure: image to find extreme points
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np
import cv2 as cv

image = 'extremepoints.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#extreme points
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
#draw points
cv.circle(im,leftmost, 10, (255,0,255), -1)
cv.circle(im,rightmost, 10, (255,0,0), -1)
cv.circle(im,topmost, 10, (0,255,0), -1)
cv.circle(im,bottommost, 10, (255,255,0), -1)
cv.imshow('Extreme Points ',im)
cv.waitKey(0)
cv.destroyAllWindows()
Figure: extreme points in different colors
12.4.9 Convexity Defect
Convexity defect is the deviation of the object from this hull.
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
Remember to pass returnPoints = False to find convexity defects
It returns an array where each row contains these values - [ start point, end point, farthest point, approximate distance to farthest point ].
Figure: object to find convex defects
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
import cv2 as cv

image = 'condef.png'
im = cv.imread(image)
gray_img = cv.cvtColor(im,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(gray_img,130,255,cv.THRESH_BINARY_INV)
contours, hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_NONE)
cnt = contours[0]
#draw points
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv.line(im,start,end,[255,0,0],2)
    cv.circle(im,far,5,[0,255,0],-1)
cv.imshow('img',im)
cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(0)
cv.destroyAllWindows()
12.4.10 Point Polygon Test
This function finds the shortest distance between anypoint in the image and a contour.
The distance is:
+ negative if point is outside the contour.
+ positive when point is inside.
+ zero if point is on the contour.
Figure: find the distance between point (0,0) to the contour of the object
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import cv2
im = cv2.imread('distance.png')
gray_img = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
#uing binary threshold
ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY_INV)
#find contours
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(im, contours, -1, (255,0,0), 2)
dist = cv2.pointPolygonTest(contours[0],(0,0),True)
print(dist)
print(contours[0])
#display it
cv2.imshow('contours',im)
cv2.waitKey(0)
cv2.destroyAllWindows()
Figure: (0,0) is outside the contour with distance
12.4.11 Match Shapes
OpenCV provides function cv2.matchShapes() to compare two shapes, or two contours. If the result value is small, two shapes are more similar. The algorithm is based on the hu-moment (cv2.HuMoments()) values.
Compare 4 objects below;
 Figure: m1 object
Figure: m2 object

Figure: m3 object
Figure: m4 object
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import cv2
im1 = cv2.imread('m1.png')
im2 = cv2.imread('m2.png')
im3 = cv2.imread('m3.png')
im4 = cv2.imread('m4.png')

gray_img1 = cv2.cvtColor(im1,cv2.COLOR_BGR2GRAY)
gray_img2 = cv2.cvtColor(im2,cv2.COLOR_BGR2GRAY)
gray_img3 = cv2.cvtColor(im3,cv2.COLOR_BGR2GRAY)
gray_img4 = cv2.cvtColor(im4,cv2.COLOR_BGR2GRAY)
#uing binary threshold
ret1,thresh1 = cv2.threshold(gray_img1,127,255,cv2.THRESH_BINARY_INV)
ret2,thresh2 = cv2.threshold(gray_img2,127,255,cv2.THRESH_BINARY_INV)
ret3,thresh3 = cv2.threshold(gray_img3,127,255,cv2.THRESH_BINARY_INV)
ret4,thresh4 = cv2.threshold(gray_img4,127,255,cv2.THRESH_BINARY_INV)
#find contours
contours1, hierarchy1 = cv2.findContours(thresh1,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours2, hierarchy2 = cv2.findContours(thresh2,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours3, hierarchy3 = cv2.findContours(thresh3,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours4, hierarchy4 = cv2.findContours(thresh4,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

ret1 = cv2.matchShapes(contours1[0],contours2[0],1,0.0)
ret2 = cv2.matchShapes(contours1[0],contours3[0],1,0.0)
ret3 = cv2.matchShapes(contours1[0],contours4[0],1,0.0)

print("m1 vs m2 " + str(ret1));
print("m1 vs m3 " + str(ret2));
print("m1 vs m4 " + str(ret3));

#display it
cv2.imshow('m1',im1)
cv2.imshow('m2',im2)
cv2.imshow('m3',im3)
cv2.imshow('m4',im4)
cv2.waitKey(0)
cv2.destroyAllWindows()
Figure: m1 and m4 are more similar than others
12.5 Contours Hierarchy
12.5.1 Concept
When using the cv2.findContours() function to find contours. The returned contours in an image have relationship to each other. It specify how one contour is connected to each other. It can be child of some other contour, or it is parent. This relationship is called the Contours Hierarchy.
Figure: An example from OpenCV
0,1,2 are external or outermost. They are in hierarchy-0 and same hierarchy level.
contour-2a can be considered as a child of contour-2  and in hierarchy-1.
contour-3 is child of contour-2 and it comes in next hierarchy.
contours 4,5 are the children of contour-3a
12.5.2 Hierarchy Representation in OpenCV
OpenCV represents it as an array of four values:  
[Next, Previous, First_Child, Parent]
Next denotes next contour at the same hierarchical level.
E.g: For contour-0, its Next is contour-1. So Next = 1. For contour-1, next is contour-2. So Next = 2.
Previous denotes previous contour at the same hierarchical level.
E.g: Previous of contour-1 is contour-0. For contour-2, it is contour-1. And for contour-0, there is no Previous, so put it as -1.
First_Child denotes its first child contour.
E.g: For contour-2, child is contour-2a. contour-3a has two children. But we take only first child. And it is contour-4. So First_Child = 4 for contour-3a.
Parent denotes index of its parent contour.
E.g: contour-3a is Parent of contour-4 and contour-5. contour-3is Parent of contour-3a.
Note: If there is no child or parent, that field is taken -1.
12.5.3 Contour Retrieval Mode 
It is represented through flags like
+ cv.RETR_LIST
+ cv.RETR_TREE
+ cv.RETR_CCOMP
+ cv.RETR_EXTERNAL
- RETR_LIST
It retrieves all the contours. But parents and kids are same hierarchical level (they have no relationship). So First_Child, Parent are -1.
We take the image below for demonstration:
Figure: demonstration image
1
2
3
4
5
6
7
8
9
import cv2

img = cv2.imread('images/hierarchy.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,125,255,0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

print('hierarchy:')
print(hierarchy)
Output:
hierarchy:
[[[ 1 -1 -1 -1]
  [ 2  0 -1 -1]
  [ 3  1 -1 -1]
  [ 4  2 -1 -1]
  [ 5  3 -1 -1]
  [ 6  4 -1 -1]
  [ 7  5 -1 -1]
  [-1  6 -1 -1]]]

- RETR_EXTERNAL
It returns only extreme outer flags. All child contours are left.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import cv2

img = cv2.imread('images/hierarchy.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,125,255,0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1,(255,0,0), 2)

cv2.imshow('RETR_EXTERNAL',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Figure: RETR_EXTERNAL blue boundary
- RETR_CCOMP
It arranges contours to a 2-level hierarchy:
+ external contours of the object (boundary) are placed in hierarchy-1.
+ the contours of holes inside object (if any) is placed in hierarchy-2.
Figure: RETR_CCOMP

1
2
3
4
5
6
7
8
import cv2

img = cv2.imread('images/ccomp_hierarchy.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,125,255,0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1,(255,0,0), 2)
print(hierarchy)
Output:
[[[ 3 -1  1 -1]
  [ 2 -1 -1  0]
  [-1  1 -1  0]
  [ 5  0  4 -1]
  [-1 -1 -1  3]
  [ 7  3  6 -1]
  [-1 -1 -1  5]
  [ 8  5 -1 -1]
  [-1  7 -1 -1]] 

Figure: Example of RETR_CCOMP from OpenCV
The order of contours in brown color and the hierarchy they belongs to, in green color (1 or 2). Let 's explain:
contour-0 - hierarchy-1: It has two holes, contours 1&2 with hierarchy-2. Next contour in same hierarchy level is contour-3. And there is no Previous one. And its first child is contour-1 in hierarchy-2. It has no parent. So its hierarchy array is [3,-1,1,-1]
contour-1 - hierarchy-2: Next is countor-2, Previous is -1, First_Child is -1, Parent is 0. So its hierarchy array is [2,-1,-1,0]
contour-2 - hierarchy-2: Next is -1, Previous is contour-1, First_Child is -1, Parent is 0. So its hierarchy array is [-1,1,-1,0]
contour-3 - hierarchy-1: Next is contour-5, Previous is contour-0, First_Child is contour-4, Parent is -1. So its hierarchy array is [5,0,4,-1]
contour-4 - hierarchy-2: Next is -1, Previous is -1, First_Child is -1, Parent is contour-3. So its hierarchy array is [-1,-1,-1,3]
Do the same way for remaining.

- RETR_TREE
It retrieves all the contours and creates a full family hierarchy list.
1
2
3
4
5
6
7
8
import cv2

img = cv2.imread('images/ccomp_hierarchy.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray,125,255,0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1,(255,0,0), 2)
print(hierarchy)
Outout:
[[[ 7 -1  1 -1]
  [-1 -1  2  0]
  [-1 -1  3  1]
  [-1 -1  4  2]
  [-1 -1  5  3]
  [ 6 -1 -1  4]
  [-1  5 -1  4]
  [ 8  0 -1 -1]
  [-1  7 -1 -1]]]


Figure: Example of RETR_TREE from OpenCV
contour-0 - hierarchy-0: Next contour in same hierarchy is contour-7. No Previous contours. Child is contour-1. And no Parent. So array is [7,-1,1,-1].
contour-1 - hierarchy-1: Next is -1. Previous is -1. Child is contour-2. And Parent is contour-0. So array is [-1,-1,2,0].
contour-2 - hierarchy-2: Next is -1. Previous is -1. Child is contour-3. And Parent is contour-1. So array is [-1,-1,3,1].
Do the same way for remaining.

Post a Comment

1 Comments

  1. This is the one of the best explained on contours

    ReplyDelete
Emoji
(y)
:)
:(
hihi
:-)
:D
=D
:-d
;(
;-(
@-)
:P
:o
:>)
(o)
:p
(p)
:-s
(m)
8-)
:-t
:-b
b-(
:-#
=p~
x-)
(k)