3

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

TylerH
  • 20,799
  • 66
  • 75
  • 101
James
  • 45
  • 7
  • can you add some intermediate outputs(contours,edges images) and code samples ? – venkata krishnan Aug 28 '19 at 09:28
  • Draw the white contour on a black background and use as a mask to get the value of the image under it. Then average the pixel values for each masked contour. – fmw42 Aug 28 '19 at 16:45

3 Answers3

2

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)
Community
  • 1
  • 1
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • Why the Laplacian ? This doesn't reflect the gradient strength. –  Aug 29 '19 at 10:38
  • It's a method for blur detection detailed in [diatom autofocusing in brightfield microscopy: a comparative study](http://optica.csic.es/papers/icpr2k.pdf). From my understanding, the reason this method works is because the Laplacian highlights regions of the image containing rapid intensity changes so if there is high variance of both edge-like and non-edge like then it is representative of an in-focus image. Similarly, if there is low variance, there are little edges in the image. The more blurred an image is, the less edges there are. Essentially the Laplacian can be used for edge detection – nathancy Aug 29 '19 at 19:56
1

If I understand your question right, you can try to use some derivative kernels to get some threshold. For example [-1 0 1] kernel may work. In addition you can check the Canny edge detection algorithm, maybe it will help.

Daniel Nudelman
  • 401
  • 5
  • 11
0

A simple way is to compute a gradient magnitude image and evaluate the gradient either along the contours that you detected or inside the whole blob. Probably better, inside a ring obtained by dilation of the contours.