Sunny
Sunny

Reputation: 63

Extracting objects after watershed segmentation

I need to segment the seeds in the image below and crop them.

https://i.sstatic.net/ndOkX.jpg

They can be pretty close to each other and sometimes overlap, so I chose to use the watershed algorithm for this task.

My results are in the image below, after drawing the contours of the markers that are returned, and as you can see I'm having problems defining good markers for applying it. The individual seeds are outlined but there are many inner lines that I do not want.

https://i.sstatic.net/BtOfj.jpg

How would I go about removing them or defining better markers?

The code I'm running:

from skimage.feature import peak_local_max
from skimage.segmentation import watershed
import matplotlib.pyplot as plt
from scipy import ndimage
import cv2 as cv
import imutils
import numpy as np

img = cv.imread("image.jpg");
blur = cv.GaussianBlur(img,(7,7),0)


#color space change
mSource_Hsv = cv.cvtColor(blur,cv.COLOR_BGR2HSV);
mMask = cv.inRange(mSource_Hsv,np.array([0,0,0]),np.array([80,255,255]));
output = cv.bitwise_and(img, img, mask=mMask)

#grayscale
img_grey = cv.cvtColor(output, cv.COLOR_BGR2GRAY)

#thresholding
ret,th1 = cv.threshold(img_grey,0,255,cv.THRESH_BINARY + cv.THRESH_OTSU)

#dist transform
D = ndimage.distance_transform_edt(th1)

#markers
localMax = peak_local_max(D, indices=False, min_distance=20, labels=th1)
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]

#apply watershed
labels = watershed(-D, markers, mask=th1)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))

# loop over the unique labels

for label in np.unique(labels):
    if label == 0:
        continue

    # draw label on the mask
    mask = np.zeros(img_grey.shape, dtype="uint8")
    mask[labels == label] = 255

    # detect contours in the mask and grab the largest one
    cnts = cv.findContours(mask.copy(), cv.RETR_EXTERNAL,
        cv.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    c = max(cnts, key=cv.contourArea)

    cv.drawContours(img, cnts, -1, (0, 255, 0), 2)


cv.imshow("segmented",img)
cv.waitKey(0)

Upvotes: 1

Views: 2168

Answers (1)

Rotem
Rotem

Reputation: 32094

You can merge every two contours that applies the following condition:

  • Area of convex hull of the merged contours is close to the sum of areas of the two contours.

The following solution uses a kind of "brute force" approach that tries merging every contour with all other contours (not very efficient).

Here is a working code sample (please read the comments):

from skimage.feature import peak_local_max
from skimage.segmentation import watershed
import matplotlib.pyplot as plt
from scipy import ndimage
import cv2 as cv
import imutils
import numpy as np

img = cv.imread("image.jpg");
blur = cv.GaussianBlur(img,(7,7),0)


#color space change
mSource_Hsv = cv.cvtColor(blur,cv.COLOR_BGR2HSV);
mMask = cv.inRange(mSource_Hsv,np.array([0,0,0]),np.array([80,255,255]));
output = cv.bitwise_and(img, img, mask=mMask)

#grayscale
img_grey = cv.cvtColor(output, cv.COLOR_BGR2GRAY)

#thresholding
ret,th1 = cv.threshold(img_grey,0,255,cv.THRESH_BINARY + cv.THRESH_OTSU)

#dist transform
D = ndimage.distance_transform_edt(th1)

#markers
localMax = peak_local_max(D, indices=False, min_distance=20, labels=th1)
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]

#apply watershed
labels = watershed(-D, markers, mask=th1)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))


contours = []

# loop over the unique labels, and append contours to all_cnts
for label in np.unique(labels):
    if label == 0:
        continue

    # draw label on the mask
    mask = np.zeros(img_grey.shape, dtype="uint8")
    mask[labels == label] = 255

    # detect contours in the mask and grab the largest one
    cnts = cv.findContours(mask.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    c = max(cnts, key=cv.contourArea)

    ## Ignore small contours
    #if c.shape[0] < 20:
    #    continue

    # Get convex hull of contour - it' going to help when merging contours
    hull = cv.convexHull(c)

    #cv.drawContours(img, c, -1, (0, 255, 0), 2)
    cv.drawContours(img, [hull], -1, (0, 255, 0), 2, 1)

    # Append hull to contours list
    contours.append(hull)


# Merge the contours that does not increase the convex hull by much.
# Note: The solution is kind of "brute force" solution, and can be better.
################################################################################
for i in range(len(contours)):
    c = contours[i]

    area = cv.contourArea(c)

    # Iterate all contours from i+1 to end of list
    for j in range(i+1, len(contours)):
        c2 = contours[j]

        area2 = cv.contourArea(c2)

        area_sum = area + area2

        # Merge contours together
        tmp = np.vstack((c, c2))
        merged_c = cv.convexHull(tmp)

        merged_area = cv.contourArea(merged_c)

        # Replace contours c and c2 by the convex hull of merged c and c2, if total area is increased by no more then 10%
        if merged_area < area_sum*1.1:
            # Replace contour with merged one.
            contours[i] = merged_c
            contours[j] = merged_c
            c = merged_c
            area = merged_area
################################################################################


# Draw new contours in red color
for c in contours:
    #Ignore small contours
    if cv.contourArea(c) > 100:
        cv.drawContours(img, [c], -1, (0, 0, 255), 2, 1)


cv.imshow("segmented",img)
cv.waitKey(0)
cv.destroyAllWindows()

Result:
enter image description here

Upvotes: 3

Related Questions