How to accurately segment overlapping rice grains in an image using OpenCV

I am working on an image processing task using opencv python where I need to segment individual rice grains from an image. The challenge is that many grains are overlapping with each other.

I tried watershed algorithm but this is the best result I’ve got so far.


Still there are grains whose boundaries are not detected properly.

I have also tried different edge detection methods like Canny, Sobel, Laplacian, etc and also morphological operations like erosion, dilation, opening and closing. But none of it seems to give a complete solution.

So, I would appreciate any suggestions or techniques that might perform better in these scenario.

Given below is the code I used to perform watershed algorithm.

import cv2
import numpy as np

image = cv2.imread('images/155.png')

#extract the grains from the blue background
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_blue = np.array([110, 50, 50])
upper_blue = np.array([130, 255, 255])
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_grains = cv2.bitwise_not(mask_blue)

# Morphological processing
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
mask_grains = cv2.morphologyEx(mask_grains, cv2.MORPH_OPEN, kernel, iterations=1)

# Distance transform
sure_bg = cv2.dilate(mask_grains, kernel, iterations=3)
dist_transform = cv2.distanceTransform(mask_grains, cv2.DIST_L2, 5)
_, sure_fg = cv2.threshold(dist_transform, 0.3 * dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)
sure_fg = cv2.dilate(sure_fg, kernel, iterations=1)
sure_fg = cv2.morphologyEx(sure_fg, cv2.MORPH_OPEN, kernel, iterations=2)
unknown = cv2.subtract(sure_bg, sure_fg)

# Connected components and watershed
num_markers, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
markers[unknown == 255] = 0
markers = cv2.watershed(image, markers)
image[markers == -1] = [0, 0, 255]  # Mark watershed boundaries

# Save segmented image
segmented_img = image.copy()

cv2.imshow('segment',segmented_img)

cv2.waitKey(0)
cv2.destroyAllWindows()
1 Like

related:

After reading the question and your comment on stackoverflow, it seems that you don’t need to count them but you want to measure them. So is it really necessary to measure ALL rice grains? Why don’t you just put all good grains into the dish and the bad you may throw away if you wish.

Seperation is a difficult thing if elements are overlapped. There’s a reason why all good captchas have overlapping elements. Your single grains seem to be found very well and overlapping grains can’t be accurately measured anyway.

As far as I see the single rice grains are all convex, the overlapping contours are not. Measure the convex contours and give all others some extra treatment

My ultimate goal is to classify the grains based on their size. For that I wanted to separate the grains because overlapped grains will lead to misclassification.