Contour Detection Using OpenCV

In the Previous Tutorial We talked about Binary Thresholding.

In this lecture we will continue what we have started by introducing Contour Detection.

What is contour Detection?


When we join all the points on the boundary of an object, we get a Contour. Typically, a specific contour refers to boundary pixels that have the same color and intensity. OpenCV makes it really easy to find and draw contours in images. It can be done in two steps:

  • findCountours()
  • drawContours()

Find Contours:



       contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE,
        method=cv2.CHAIN_APPROX_NONE)
      

Arguments:


  • image: The source image that we want to find Contours in.

  • mode:: This is related to the relationship between the contours we find. For example, here we return them as Parent-child relationship. Simply meaning that, the outer contour is the parent. And if we find any other inner contours, they will be children.

  • method: This is simply the algorithim method we will use to find the contours.

Draw Contours:


  cv2.drawContours(

  image=image_copy,
  contours= c[0],
  contourIdx= -1,
  color= (0,255,0),
  thickness= 2,
  lineType= cv2.LINE_AA
  )

Arguments:

  • img: The image source we want the contours to be drawn on.

  • controus: The contour list we want to use.

  • idx: The contour list index we want to draw. Since in every contour list we could have multiple contours stored, here we have the option to draw only one of them, Choosing a value of -1 would mean draw all contours in a list.

  • color: The color of the drawn contour.

  • thickness: The thickness of the drawn Contour.

  • lineType: This determines the style of the drawn line.

Why Thresholding before Finding contours is a good idea?


We have talked about Binary Thresholding which is the first step to contour detection.

This step is important, because it helps popping out our object that we wish to find contours for.


FindContour does not do a very good job when there is not strong contrast between objects. It does its best job when the background is black for example, and all objects are white and non-overlapping.

See the more the our target object is contrasted from the background the better the contour algorithim will yield out results.

Finding The contours:

steps:


  • Thresholding the image in a way that yields the most contrast of the object with respect to the background.
  • Apply findContours
opencv-bottle-contour
  
  import cv2

  #read the bottle image
  image = cv2.imread('pic_threshold.jpg')

  #Convert the thrsholded image to binary
  img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

  #threshold one more time for better results.
  ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)

  #find the contours
  contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)

  #make a copty of the image and use it to show the before and after result
  image_copy = image.copy()

  #draw controus on the copied image
  cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1 , color=(0,255,0), thickness=2, lineType=cv2.LINE_AA)

  #show the results
  cv2.imshow('None approximation', image_copy)
  cv2.waitKey(0)
  cv2.imwrite('contour.jpg', image_copy)
  cv2.destroyAllWindows()

  

Ok so by that we get the following result:

bottlecontoured

Improving the result:


This is not excatly what we want. We need only the outer contour of the bottle, and as we can see, we got multipe contours for things we don't need.

For example, we got a frame contour, inner light reflections on the bottle that are enclosed.

We are only interested in cropping out that bottle outer edge to proccess it further. So how can we do that?

Well:

  • With a little observation, we see that the largest contour area is the one that is surrounding the frame.

  • The small contours on the bottle body resulting from the light reflections can be simply ommited

  • So if we get rid of the very large frame contour, and the very small reflection contours, we should be getting good reulst!

The solution is by Filtering by area.

cv2.findArea() :


This function, is used to calculate the enclosed area of a certain contour.

So, we can simply do the following:

  • Loop through the contour list we obtained from findContours()
  • Calculate each Contour area in the contour list.
  • If this contour area is within a certain range, then we can say, this is our bottle. Otherwise, this is either a light reflection noise or the outer frame.
  • Store this target contour in a seperate list called c
  
  import cv2

  #Create a list to store only the target outer edge bottle contour
  c=[]


  image = cv2.imread('pic_threshold.jpg')

  #Convert the thrsholded image to binary
  img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

  #threshold one more time for better results.
  ret, thresh = cv2.threshold(img_gray, 150, 255, cv2.THRESH_BINARY)

  #find the contours
  contours, hierarchy = cv2.findContours(image=thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)

  #make a copty of the image and use it to show the before and after result
  image_copy = image.copy()
  for i in range(0,len(contours)):

          area=cv2.contourArea(contours[i])
          print(area)
          if( area<20000 and area>5000):
                  c.append(contours[i])

  #draw controus on the copied image
  cv2.drawContours(image=image_copy, contours=c[0], contourIdx=-1 , color=(0,255,0), thickness=2, lineType=cv2.LINE_AA)

  #show the 
  cv2.imshow('None approximation', image_copy)
  cv2.waitKey(0)
  cv2.imwrite('contour.jpg', image_copy)
  cv2.destroyAllWindows()
  

And here is our final bottle, cleaned, outer edge results!

opencv-bottle-contour

What's Next?


In the Next Tutorial we are going to see how we can crop out only that bottle object from the image to perform further processing.