Contours are the lines or curves that connect all the points that lie on the boundary of the object. A specific contour refers to boundary pixels that have the same color and intensity. OpenCV provides an easy way to find and draw contours on images.
OpenCV provides two functions to find and draw contours.
cv2.findContour()
cv2.drawContour()
To get Contours we can use either the canny effect or threshold effect.
Contours using cv2.threshold
To draw the contours using threshold, we pass the threshold image into cv2.findContours()
. Threshold returns more number of contours.
cv2.threshold()
will modify the pixel values based on the min and max value we pass and assign them a different value based on the thresholding parameter we pass, it takes the parameters such as
- image( source )
- threshold 1, ( min value )
- threshold 2, ( max value )
- threshold type.
cv2.thresh()
looks for pixels below the range of threshold 1 and assigns them to black color, which means their color value is set to 0. Any pixel above Threshold 1 makes them to Threshold 2, thus we can represent the particular objects in the image. Since we binarize the image Threshold type is set to cv2.THRESH_BINARY).
For better results, we pass gray color image.
ret, thresh = cv2.threshold(imgray, 127, 255,
cv2.THRESH_BINARY)
We can pass the threshold image to cv2.findContours() to detect them and using cv2.drawContours we can draw the contours.
cv2.findContours()
takes parameters such as the
- Image,
- RETR_TYPE,
- Contour approximation method,
To retrieve all the Contours we set the return type to cv2.RETR_LIST.
For Contour approximation we can different methods such as:-
- cv2.CHAIN_APPROX_NONE:- It gives all the boundary points in the image.
- cv2.CHAIN_APPROX_SIMPLE:- It gives only the endpoints of a particular contour thus saves memory.
Since we require only the endpoints of a line to draw a line we use cv2.CHAIN_APPROX_SIMPLE.
contours, hierarchy = cv2.findContours(thresh,
cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
To draw all the contours, we create a blank image using numpy.zeros() and setting the datatype to ‘uint8′(represents object is of image type) and draw all the contours by passing the blank image to cv2.drawContours()
blank = np.zeros(image.shape, dtype='uint8')
To draw all the contours pass the blank image to cv2.drawContours()
and send the list of contours, To draw all contours, pass -1, set color, and pass thickness of contours.
cv2.drawContours(blank, contours, -1, (0, 0, 255), 1)
import cv2
import numpy as np
image = cv2.imread('C:\images\people.jpg')
imgray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blank = np.zeros(img.shape, dtype='uint8')
ret, thresh = cv2.threshold(imgray, 127, 255,
cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh,
cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(blank, contours, -1, (0, 0, 255), 1)
cv2.imshow('image', image)
cv2.imshow('contours', blank)
cv2.imshow('threshold', thresh)
Output:-
Contours using cv2.Canny
Instead of threshold, we can use cv2.Canny to get the contours and display them. The process remains the same, the only difference that makes is we use blur image to get a Canned image.
blur = cv2.GaussianBlur(imgray, (5, 5), cv2.BORDER_DEFAULT)
canny = cv2.Canny(blur, 150, 190)
Thus obtained image contains much fewer contours compared to the threshold since we use a blurred image.
import cv2
import numpy as np
image = cv2.imread('C:\images\people.jpg')
imgray = cv2.cvtColor(imag, cv2.COLOR_BGR2GRAY)
blank = np.zeros(img.shape, dtype='uint8')
blur = cv2.GaussianBlur(imgray, (5, 5), cv2.BORDER_DEFAULT)
canny = cv2.Canny(blur, 150, 190)
contours, hierarchy = cv2.findContours(canny, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(blank, contours, -1, (0, 0, 255), 1)
cv2.imshow('contours', blank)
Output:-
Contour Retrieval Techniques
Contour retrieval means how you would like to retrieve the contours. While detecting and drawing contours there are particular points that have objects one inside the other which we call it a hierarchy.
So based on the requirement of the hierarchy level we can choose different retrieval techniques. The different methods of retrieval are, RETR_TREE
, RETR_LIST
, RETR_EXTERNAL
and RETR_CCOMP
. Based on the retrieval techniques contours are returned either hierarchically or normally.
cv2.RETR_LIST
returns all the points in the form of a list without considering the hierarchy level or parent-child relationship.cv2.RETR_EXTERNAL
returns all the parent points or the external points without considering the child points. So the inner points won’t have any contours on them.cv2.RETR_CCOMP
retrieves all the parent points and child points with hierarchy up to level ‘2’.cv2.RET_TREE
returns all the contours in the image in a proper hierarchy manner, such as all the child and their corresponding parent points irrespective of the hierarchy level 2 or 3 are drawn.
Contour Approximations
Contours are the boundaries of a shape with the same intensity. It stores the (x,y) coordinates of the boundary of a shape. But does it store all the coordinates? That is specified by this contour approximation method.
Contour approximations provide the type we would like to draw the contours. There are two approximation methods known as cv2.CHAIN_APPROX_NONE
, cv2.CHAIN_APPROX_SIMPLE
.
cv2.CHAIN_APPROX_NONE
In this method, all the boundary points along the object are restored and contours are drawn on them.
contours1, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(blank1, contours1, -1, (0, 0, 255), 1)
cv2.imshow('CHAIN_APPROX_NONE', blank1)
Output:- as you can see all the points are having contours.
cv2.CHAIN_APPROX_SIMPLE
cv2.CHAIN_APPROX_SIMPLE
detects the endpoints of the object and draws contours on them. If we consider a line it’s a combination of several points from start point to endpoint.
So cv2.CHAIN_APPROX_NONE
draws contours on all the points that are on the line whereas cv2.CHAIN_APPROX_SIMPLE
draw contours only on the start and endpoint of the line thus use less memory.
contours2, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(blank2, contours2, -1, (0, 0, 255), 1)
cv2.imshow('CHAIN_APPROX_SIMPLE', blank2)
Output:- since we applied cv2.CHAIN_APPROX_SIMPLE only the endpoints are having contours.
Contour features
Contours features include finding out the features of a particular contour such as moment, area, perimeter, centroid.
Moment of a contour
The moment is a term bought from physics, its meaning remains the same, when it comes to calculating a moment for a particular contour we do it by weighing the average of the intensities of the pixels.
Using moments we can define the center, centroid, area, perimeter of a particular contour. cv2.moment() takes input as a contour and returns the moment of contour as a dictionary.
Centroid of a contour
The centroid of a contour is calculated by the arithmetic mean of all the points in a shape. In the context of image processing, Centroid is the mean of all the intensities that are along with the shape of the object.
Centroid(Cx, Cy) formula is given by Cx = int(M[‘m10’]/M[‘m00’]) , Cy = int(M[‘m01’]/M[‘m00’])
Finding Centroid and displaying it on image
Since we need to find out the moment to calculate the centroid of the shape, we calculate it by cv2.moment() and pass all the contours in the image one by one and looping over the whole process.
Once we find out the moment we calculate the centroid of that particular shape in the image. And place a text “C” on each centroid we find out in the image.
Code
import cv2
import numpy as np
image = cv2.imread('C:\Users\images\people.jpg’)
img = cv2.resize(image, (int(image.shape[1]*0.5),int(image.shape[0]*0.5)), interpolation = cv2.INTER_AREA)
blank = np.zeros(img.shape, dtype='uint8')
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(blank, contours, -1, (255, 255, 255), 2)
for c in contours:
M = cv2.moments(c)
cv2.contourArea(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
else:
cX, cY = 0, 0
cv2.putText(blank, "C", (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))
cv2.imshow("Image", blank)
Output: As we can see in the image we are displaying the centroid of each contour in the letter “C” in red color.
Contour Area
Contour area is the amount of area the particular contour or the shape it is taking in the image, and it returns a floating type with a number of pixels in the contour. Basically, we find an area for the closed object but If a contour in an image is not a closed type then it’s automatically regarded as a closed one and calculated for the area.
To find out the area of the contour or a shape in the image we can use cv2.contourArea() and pass a particular contour into it.
Since there are many contours in an image it would take some time to calculate and display them, so we are calculating the 3rd contour in the image and find the area.
c = contours[3]
cv2.contourArea(c)
Output
6.5
Contour Perimeter
Contour Perimeter is the total length of the arc that is surrounding the object or shape in the image. So the total length of the arc is the perimeter of the contour.
To calculate the length of an arc in OpenCV we can use the in-built method cv2.arclength(), We pass the contour object whose perimeter is to be found out.
The second argument we pass specifies whether the object is a closed one or just an arc.
cv2.arclength(contour, true)
Contour Properties
Contour properties explain how a particular contour is present such as its size, coordinates, etc. Contour properties include bounding rect, aspect ratio, Extent.
Bounding rectangle
It’s a rectangle usually known as “boundingrect” that is drawn along the shape of the binary object( contour ) and has dimensions such as width and height. We can find a bounding rectangle using the function cv2.boundingRect() and draw it using cv2.rectangle().
Since cv2.findContours returns a list of contours we randomly pick a contour such as the 250th contour, and as we used “blank” to draw all the contours we use the same over here.
cont = contours[250]
x, y, w, h = cv2.boundingRect(cont)
cv2.rectangle(blank, (x, y), (x+w, y+h), (0, 0, 255), 2)
Output: Since the contour itself is a very small shape drawing a rectangle on it looks almost like a circle but it’s a real rectangle.
Aspect Ratio
The aspect ratio of the contour is calculated by dividing the width by the height of the bounding rectangle of the particular shape.
If the aspect ratio is < 1, it means the width is less than the height and looks tall. If the aspect ratio is > 1, it means the width is greater than the height and looks broad
x, y, w, h = cv2.boundingRect(cont)
Aspect_ratio = float(w) / h
Extent
Extent is the ratio of counter area to bounding rectangle area. Extent formula is given by Object area / Bounding rectangle area.
In most cases, the Extent is < 1, since every time the number of pixels inside any contour is less than the number of pixels that are in the bounding rectangle.
Obj_area = cv2.contourArea(contour [ i ] )
x, y, w,h = cv2.boundingRect(contour [ i ] )
Rect_area = w*h
Extent = float(Obj_area) / Rect_area