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?
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:
With edges:
Here's a potential approach
We begin by converting to grayscale and adaptive threshold
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
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
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
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)