Search code examples
pythonopencvimage-processingedge-detectionblurry

Find the edge strength/magnitude of a contour to find and exclude blurry objects


I'd like to find an edge strength value for each of the objects in a greyscale image using python. I'm detecting objects via thresholding to create a binary image and then opencv findContours to give me the edges. Some of these objects I detect in each image are blurry and I would like to exclude them based on the magnitude of the edge gradient for the contour (See image below for an example of an in focus object and a blurry object). What's the best way to go about processing the edge strength of each contour to give a value for the edge strength of each object such that I can exclude blurry ones based on some threshold I can work out?

Edge magnitude

contours, hierarchy = cv2.findContours(binary_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
edges = cv2.drawContours(original_image, contours, -1, (255, 255, 255), 1)

I use the above code on a binary image generated via thresholding to plot edges on the original image. The next step is to send the objects detected for processing, but I wish to exclude the blurry ones as they don't require further analysis. The pictures below show an image with the edges drawn on, I would like to find some value describing the average edge gradient from each edge pixel for each object I find, and only further process those who's edge magnitude is above some threshold, aka are in focus.

Original image:

Original Image

With edges:

Image with Edges


Solution

  • Here's a potential approach

    • Convert image to grayscale
    • Adaptive threshold to obtain binary image
    • Dilate to enhance contour
    • Find contour and extract ROI
    • Perform variation of the Laplacian for blur detection

    We begin by converting to grayscale and adaptive threshold

    enter image description here

    import cv2
    import numpy as np
    
    image = cv2.imread('1.jpg')
    
    result = image.copy()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    

    Next we dilate to enhance the contour

    enter image description here

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
    dilate = cv2.dilate(thresh, kernel, iterations=3)
    

    Now we find contours and extract each ROI. We perform blur detection on this ROI contour using the variation of the Laplacian.

    cv2.Laplacian(image, cv2.CV_64F).var()
    

    Essentially we take a single channel of an image and convolve it with the following 3x3 kernel and take the standard deviation squared of the response. If the variance falls below a defined threshold then the ROI is blurry otherwise the ROI is not blurry. Take a look at this blog post for more details

    [0  1  0]
    [1 -4  1]
    [0  1  0]
    

    Here's the result

    enter image description here

    ROI_Number: 1, Value: 27.655757845590053

    ROI_Number: 2, Value: 7.385658155007905

    ROI_num = 0
    for c in cnts:
        x,y,w,h = cv2.boundingRect(c)
        ROI = image[y:y+h, x:x+w]
        value = cv2.Laplacian(ROI, cv2.CV_64F).var()  
        cv2.rectangle(result, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.putText(result, "{0:.2f}".format(value), (x,y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (36,255,12), 2)
        cv2.imshow("ROI_{}".format(ROI_num), ROI)
        ROI_num += 1
        
        print('ROI_Number: {}, Value: {}'.format(ROI_num, value))
    

    Here's the results for the other image

    enter image description here enter image description here enter image description here

    ROI_Number: 1, Value: 23.96665214233842

    ROI_Number: 2, Value: 67.59560601952461


    Full code

    import cv2
    import numpy as np
    
    image = cv2.imread('1.jpg')
    
    result = image.copy()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
    dilate = cv2.dilate(thresh, kernel, iterations=3)
    
    cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    
    ROI_num = 0
    for c in cnts:
        x,y,w,h = cv2.boundingRect(c)
        ROI = image[y:y+h, x:x+w]
        value = cv2.Laplacian(ROI, cv2.CV_64F).var()  
        cv2.rectangle(result, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.putText(result, "{0:.2f}".format(value), (x,y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (36,255,12), 2)
        cv2.imshow("ROI_{}".format(ROI_num), ROI)
        ROI_num += 1
        
        print('ROI_Number: {}, Value: {}'.format(ROI_num, value))
    
    cv2.imshow('thresh', thresh)
    cv2.imshow('dilate', dilate)
    cv2.imshow('result', result)
    cv2.waitKey(0)