In this chapter, we are going to learn about shape analysis and image segmentation. We will learn how to recognize shapes and estimate the exact boundaries. We will discuss how to segment an image into its constituent parts using various methods. We will learn how to separate the foreground from the background as well.
By the end of this chapter, you will know:
Contour analysis is a very useful tool in the field of computer vision. We deal with a lot of shapes in the real world and contour analysis helps in analyzing those shapes using various algorithms. When we convert an image to grayscale and threshold it, we are left with a bunch of lines and contours. Once we understand the properties of different shapes, we will be able to extract detailed information from an image.
Let's say we want to identify the boomerang shape in the following image:
In order to do that, we first need to know what a regular boomerang looks like:
Now using the above image as a reference, can we identify what shape in our original image corresponds to a boomerang? If you notice, we cannot use a simple correlation based approach because the shapes are all distorted. This means that an approach where we look for an exact match won't work! We need to understand the properties of this shape and match the corresponding properties to identify the boomerang shape. OpenCV provides a nice shape matcher function that we can use to achieve this. The matching is based on the concept of Hu moment, which in turn is related to image moments. You can refer to the following paper to learn more about moments: http://zoi.utia.cas.cz/files/chapter_moments_color1.pdf. The concept of "image moments" basically refers to the weighted and power-raised summation of the pixels within a shape.
In the above equation, p refers to the pixels inside the contour, w refers to the weights, N refers to the number of points inside the contour, k refers to the power, and I refers to the moment. Depending on the values we choose for w and k, we can extract different characteristics from that contour.
Perhaps the simplest example is to compute the area of the contour. To do this, we need to count the number of pixels within that region. So mathematically speaking, in the weighted and power raised summation form, we just need to set w to 1 and k to 0. This will give us the area of the contour. Depending on how we compute these moments, they will help us in understanding these different shapes. This also gives rise to some interesting properties that help us in determining the shape similarity metric.
If we match the shapes, you will see something like this:
Let's take a look at the code to do this:
import sys import cv2 import numpy as np # Extract reference contour from the image def get_ref_contour(img): ref_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(ref_gray, 127, 255, 0) # find all the contours in the thresholded image # The values for the second and third parameters is restricted to a certain number of possible values. You can learn more 'findContours' function here: http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html contours, hierarchy = cv2.findContours(thresh, 1, 2) # Extract the relevant contour based on area ratio # We use the area ratio because sometimes the image boundary contour is extracted as well and we don't want that. This area ratio threshold will ensure that we only take the contour inside the image. for contour in contours: area = cv2.contourArea(contour) img_area = img.shape[0] * img.shape[1] if 0.05 < area/float(img_area) < 0.8: return contour # Extract all the contours from the image def get_all_contours(img): ref_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(ref_gray, 127, 255, 0) contours, hierarchy = cv2.findContours(thresh, 1, 2) return contours if __name__=='__main__': # boomerang reference image img1 = cv2.imread(sys.argv[1]) # input image containing all the different shapes img2 = cv2.imread(sys.argv[2]) # Extract the reference contour ref_contour = get_ref_contour(img1) # Extract all the contours from the input image input_contours = get_all_contours(img2) closest_contour = input_contours[0] min_dist = sys.maxint # finding the closest contour for contour in input_contours: # Matching the shapes and taking the closest one ret = cv2.matchShapes(ref_contour, contour, 1, 0.0) if ret < min_dist: min_dist = ret closest_contour = contour cv2.drawContours(img2, [closest_contour], -1, (0,0,0), 3) cv2.imshow('Output', img2) cv2.waitKey()