Shape Detection using OpenCV

2 minute read

Procedure

  • Read an image
    image = cv2.imread("/assetsimages/shapes.jpg")
    

    input

  • Let’s see edges using canny edge detection
    • First convert image to gray scale for better detection and add blur to reduce noise
grayImage = (cv2.cvtColor(image, cv2.COLOR_BGR2GRAY))
blurredImage = cv2.GaussianBlur(grayImage, (3, 3), 0) 
 # check with different blur types for better output

gray-blur

  • now apply canny
    cannyImage = cv2.Canny(blurredImage, 95,90)
     # threshold1 and threshold2 values depends on images , try with different values
    

    edges

findContours method will give all the contours present in image

provide cannyImage for finding contours

contours, hierarchy = cv2.findContours(image=cannyImage,mode=cv2.RETR_EXTERNAL,method=cv2.CHAIN_APPROX_NONE)

see this for details about contours and hierarchy

method = CHAIN_APPROX_NONE or CHAIN_APPROX_SIMPLE

CHAIN_APPROX_NONE : this will give all the points on the boundaries CHAIN_APPROX_SIMPLE : It removes all redundant points and compresses the contour, thereby saving memory.

nonevssimple

Here with CHAIN_APPROX_SIMPLE it gave only 4 corner points but with CHAIN_APPROX_NONE we got each and every point on the borders

choose according to your requirement

I am willing to draw entire boundary here so, I am using CHAIN_APPROX_NONE

Tip:- we can draw boundaries by getting corner points also using line function in OpenCV

for contour in contours:
    area = cv2.contourArea(contour)
    # gives area of contour
    perimeter = cv2.arcLength(contour, closed=True)
    # gives perimeter of contour

    borders = cv2.approxPolyDP(curve=contour,epsilon=0.05*perimeter,closed=True)
    # approximate boundaries for given polygon contour

epsilon : (x*perimeter) try with different values of x for better result when I used x=0.1, it detected circle as square

0.1

  • For 0.05 I got it perfect

by using len(boundaries) determine polygon type
Now the conflict is when, no. of boundaries is 4, it may be a square or rectangle

  • I used aspect ratio to differentiate square and rectangle aspectratio square aspectratio
    if  len(borders) == 3:
        shape = 'Tri'
    elif len(borders) == 4:
        aspectratio=w/h
        print(aspectratio)
        if round(aspectratio,1)==1:
            shape = "Square"
        else:
            shape="Rectangle"
    elif len(borders) > 4:
        shape = "Circle"
    else:
        shape = "nothing"

Printing Polygon type on image using putText method

    cv2.putText(image,shape,(round(x+w//2)-15,round(y+h//2)+10),fontFace=cv2.FONT_HERSHEY_COMPLEX,fontScale=0.3,color=(0,0,0))

final

Additional

drawContours

we can also draw contours we get on images using drawContours

 for contour in contours:
    cv2.drawContours(grayImage,contour,-1,color=(0,0,0),thickness=3)
    cv2.imshow("edges drawn",grayImage)
  • for CHAIN_APPROX_NONE

    none

  • for CHAIN_APPROX_SIMPLE

    simple

boundingRect

min rectangle over the image

borders = cv2.approxPolyDP(curve=contour,epsilon=0.05*perimeter,closed=True)
x, y, w, h = cv2.boundingRect(borders)
cv2.rectangle(image,(x,y),(x+w,h+y),(255,0,0))

boundingrect

minEnclosingCircle

min circle that fits the shape

(x1,y1),r=cv2.minEnclosingCircle(contour)
# now draw circle (x1,y1) as center and r as radius

minCircle

see this for more..

contours and hierarchy

  • Contours (boundaries) 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 : Incase of nested Figures,hierarchy will define the relation between contours. It can specify how one contour is connected to each other, like, is it child of some other contour, or is it a parent etc
    more about contours and hierarchy

Updated: