Generating a segmentation mask for circular particles from threshold mask?

Viewed 436

I am trying to find all the circular particles in the image attached. This is the only image I am have (along with its inverse)enter image description here.

I have read this post and yet I can't use hsv values for thresholding. I have tried using Hough Transform.

circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, dp=0.01, minDist=0.1, param1=10, param2=5, minRadius=3,maxRadius=6)

and using the following code to plot

names =[circles]
for nums in names:
  color_img = cv2.imread(path)
  blue = (211,211,211)
  for x, y, r in nums[0]:
    cv2.circle(color_img, (x,y), r, blue, 1)
    
  plt.figure(figsize=(15,15))
  plt.title("Hough")
  plt.imshow(color_img, cmap='gray')

The following code was to plot the mask:

for masks in names:
  black = np.zeros(img_gray.shape)
  for x, y, r in masks[0]:
    cv2.circle(black, (x,y), int(r), 255, -1)  # -1 to draw filled circles
plt.imshow(black, gray)
  

Yet I am only able to get the following mask which if fairly poor. result

This is an image of what is considered a particle and what is not.labeled image

2 Answers

My approach is based on a simple observation that most of the particles in your image have approximately same perimeter and the "not particles" have greater perimeter than them.

First, have a look at the RANSAC algorithm and how does it find inliers and outliers. It basically is for 2D data but we will have to transform it to 1D data in our case.

In your case, I am calling inliers to the correct particles and Outliers to incorrect particles.

Our data on which we have to work on will be the perimeter of these particles. To get the perimeter, find contours in this image and get the perimeter of each contour. Refer this for information about Contours.

Now we have the data, knowledge about RANSAC algo and our simple observation mentioned above. Now in this data, we have to find the most dense and compact cluster which will contain all the inliers and others will be outliers.

Now let's assume the inliers are in the range of 40-60 and the outliers are beyond 60. Let's define a threshold value T = 0. We say that for each point in the data, inliers for that point are in the range of (value of that point - T, value of that point + T).

Now first iterate over all the points in the data and count number of inliers to that point for a T and store this information. Find the maximum number of inliers possible for a value of T. Now increment the value of T by 1 and again find the maximum number of inliers possible for that T. Repeat these steps by incrementing value of T one by one.

There will be a range of values of T for which Maximum number of inliers are the same. These inliers are the particles in your image and the particles having perimeter greater than these inliers are the outliers thus the "not particles" in your image.

I have tried this algorithm in my test cases which are similar to your and it works. I am always able to determine the outliers. I hope it works for you too.

One last thing, I see that boundary of your particles are irregular and not smooth, try to make them smooth and use this algorithm if this doesn't work for you in this image.

One simple approach involves slightly eroding the image, to separate touching circular objects, then doing a connected component analysis and discarding all objects larger than some chosen threshold, and finally dilating the image back so the circular objects are approximately of the original size again. We can do this dilation on the labelled image, such that you retain the separated objects.

I'm using DIPlib because I'm most familiar with it (I'm an author).

import diplib as dip

a = dip.ImageRead('6O0Oe.png')
a = a(0) > 127        # the PNG is a color image, but OP's image is binary,
                      # so we binarize here to simulate OP's condition.

separation = 7        # tweak these two parameters as necessary
size_threshold = 500

b = dip.Erosion(a, dip.SE(separation))
b = dip.Label(b, maxSize=size_threshold)
b = dip.Dilation(b, dip.SE(separation))

output of code above

Do note that the image we use here seems to be a zoomed-in screen grab rather than the original image OP is dealing with. If so, the parameters must be made smaller to identify the smaller objects in the smaller image.

Related