Learn openCV3 (Python): Contours, Convex Contours, Bounding Rect, Min Area Rect, Min Enclosing Circle, Approximate Bounding Polygon.

Beside edges detection, contour detection is also one of the vital tasks in computer vision. One thing to notice here is that when find contours, we usually work with thresholded image. What is thresholding image? Just Google it, it’s simply putting a threshold value for pixels, if those pixels have value bigger than threshold value, they’re gonna be set to a new value.

This is a very simple example, when we find contour in an image that have black background and a white rectangle in its center.

First we create a 200×200 grayscale image having black background by using numpy.zeros method. Then we create a 100×100 white squares in that image. Next we threshold the image, every pixels that have values higher than 127 will be set to 255. Then we use opencv’s findContours() method to find all contours in the image. cv2.RETR_TREE basically means you want to get all contours. This method returns a tuple with 3 elements,  we only need to focus on the second one, contours. Keep in mind that findContours(), as well as many other methods in opencv, usually only work well with gray scale images. Finally we use drawContours() with green color to make all contours visible.

import cv2
import numpy as np 

ESC = 27

# create a black image with size 200x200 (in grayscale)
img = np.zeros((200, 200), dtype=np.uint8)
# set the center of image to be a 50x50 white rectangle
img[50:150, 50:150] = 255 

# threshold the image
# if any pixels that have value higher than 127, assign it to 255
ret, threshed_img = cv2.threshold(img, 127, 255, 0)

# find contour in image
# cv2.RETR_TREE retrieves the entire hierarchy of contours in image
# if you only want to retrieve the most external contour
# use cv.RETR_EXTERNAL
image, contours, hierarchy = cv2.findContours(threshed_img, cv2.RETR_TREE,
                            cv2.CHAIN_APPROX_SIMPLE)
# convert image back to BGR
color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# draw contours onto image
img = cv2.drawContours(color_img, contours, -1, (0, 255, 0), 2)

cv2.imshow("contours", img)

while True:
    keycode = cv2.waitKey()
    if keycode != -1:
        keycode &= 0xFF
        if keycode == ESC:
            break

cv2.distroyAllWindows

Result:

contours

Next, we will see how to find a bounding box, minimum area rectangle, and minimum enclosing circle. (There is a tutorial on opencv website talking about this, you can check it out now or later)

Let’s take this image as an example.

image.CUOVVY.png

You already know how to find contours in an image, so we won’t talk detailedly about that anymore. What we’re gonna talk about is finding bounding rect, min area rect, and enclosing circle of the ‘object’ in that image.

First let’s write code to find contours .

import cv2
import numpy as np 

# read and scale down image
img = cv2.pyrDown(cv2.imread('hammer.jpg', cv2.IMREAD_UNCHANGED))

# threshold image
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
                127, 255, cv2.THRESH_BINARY)
# find contours and get the external one
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_EXTERNAL,
                cv2.CHAIN_APPROX_SIMPLE)

cv2.drawContours(img, contours, -1, (255, 255, 0), 1)

cv2.imshow("contours", img)

ESC = 27
while True:
    keycode = cv2.waitKey()
    if keycode != -1:
        keycode &= 0xFF
        if keycode == ESC:
            break
cv2.destroyAllWindows()

There is a different here. Instead of retrieving all contours, we only retrieve the outermost contour. Here’s the result:

contour_external.png

The contour wrap the ‘object’ is drawn in (255, 255, 0) color. The thickness is only 1, you can change the thickness by altering the last argument in cv2.drawContours().
Here’s how cv2.RETR_TREE will look like:

contour_tree.png

It’s time to find the bounding rect, min area rect, and min enclosing circle.

import cv2
import numpy as np 

# read and scale down image
img = cv2.pyrDown(cv2.imread('hammer.jpg', cv2.IMREAD_UNCHANGED))

# threshold image
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
                127, 255, cv2.THRESH_BINARY)
# find contours and get the external one
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_TREE,
                cv2.CHAIN_APPROX_SIMPLE)

# with each contour, draw boundingRect in green
# a minAreaRect in red and
# a minEnclosingCircle in blue
for c in contours:
    # get the bounding rect
    x, y, w, h = cv2.boundingRect(c)
    # draw a green rectangle to visualize the bounding rect
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)

    # get the min area rect
    rect = cv2.minAreaRect(c)
    box = cv2.boxPoints(rect)
    # convert all coordinates floating point values to int
    box = np.int0(box)
    # draw a red 'nghien' rectangle
    cv2.drawContours(img, [box], 0, (0, 0, 255))

    # finally, get the min enclosing circle
    (x, y), radius = cv2.minEnclosingCircle(c)
    # convert all values to int
    center = (int(x), int(y))
    radius = int(radius)
    # and draw the circle in blue
    img = cv2.circle(img, center, radius, (255, 0, 0), 2)

print(len(contours))
cv2.drawContours(img, contours, -1, (255, 255, 0), 1)

cv2.imshow("contours", img)

ESC = 27
while True:
    keycode = cv2.waitKey()
    if keycode != -1:
        keycode &= 0xFF
        if keycode == ESC:
            break
cv2.destroyAllWindows()

Use cv2.boundingRect to get the bounding rectangle (in green), cv2.minAreaRect to get the minimum area rectangle (in red), and cv2.minEnclosingCircle to get minimum enclosing circle (in blue).

Result:

contour_all.png
More on contours, convex hull.

import cv2
import numpy as np 

# downscale and read image
img = cv2.pyrDown(cv2.imread('hammer.jpg', cv2.IMREAD_UNCHANGED))
# threshold image
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
                    127, 255, cv2.THRESH_BINARY)
# get contours from image
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_EXTERNAL,
                    cv2.CHAIN_APPROX_SIMPLE)

# for each contour
for cnt in contours:
    # get convex hull
    hull = cv2.convexHull(cnt)
    # draw it in red color
    cv2.drawContours(img, [hull], -1, (0, 0, 255), 1)

cv2.imshow("contours", img)

ESC = 27
while True:
    keycode = cv2.waitKey(25)
    if keycode != -1:
        keycode &= 0xFF
        if keycode == ESC:
            break

cv2.destroyAllWindows()

Result:

convex_hull.png

With the demonstration, you can figure out what ‘convex hull’ is.

Visit OpenCV Website to get more information as well as example code about contours.

More about opencv drawing functions

Besides convex hull, there is one more thing you need to know is ‘approximate polygon’. I consider an approximate polygon is a basic shape of an object. In OpenCV, approximate bounding polygon can be calculated by using cv2.approxPolyDP. This method use Douglas-Peucker algorithm.

import numpy
import cv2 

# read and downscale image
img = cv2.pyrDown(cv2.imread('hammer.jpg', cv2.IMREAD_UNCHANGED))
# threshold image
# this step is neccessary when you work with contours
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
                        127, 255, cv2.THRESH_BINARY)
# find contours in image
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    # calculate epsilon base on contour's perimeter
    # contour's perimeter is returned by cv2.arcLength
    epsilon = 0.01 * cv2.arcLength(cnt, True)
    # get approx polygons
    approx = cv2.approxPolyDP(cnt, epsilon, True)
    # draw approx polygons
    cv2.drawContours(img, [approx], -1, (0, 255, 0), 1)

    # hull is convex shape as a polygon
    hull = cv2.convexHull(cnt)
    cv2.drawContours(img, [hull], -1, (0, 0, 255))

cv2.imshow('contours', img)
ESC = 27

while True:
    keycode = cv2.waitKey()
    if keycode != -1:
        keycode &= 0xFF
        if keycode == ESC:
            break

cv2.destroyAllWindows()

Result:

ssssss.png

 

The approx bounding polygon is in green, and the convex hull is in red.

Again, you can find code example on OpenCV website, I post this because I want to share what I’ve learn. My explanations might be different than things that you get on opencv website, so you will not read the same thing twice.

Advertisements