pythonopencvwatershed

Watershed analysis of coins - wrong output


For the counting of round objects in an image I want to use the watershed algorithm. In order to learn how it works and how I can use it for my needs, I have searched some working examples in python (https://docs.opencv.org/3.1.0/d3/db4/tutorial_py_watershed.html ; http://scikit-image.org/docs/dev/auto_examples/segmentation/plot_label.html)

I finaly found a working solution, which works more or less out-of-the-box for my own purposes (How to define the markers for Watershed in OpenCV?)

With this code I get nice results, both with the example file as with my own images. I do get a strange behavior though after the watershed analysis. For some reason, the watershed step also adds a border around the image. So next to the objects that are detected, also the whole edge of the image gets detected and colored.

My guess is that I should change a parameters in the code to stop this from happening, but so far I'm unable to find what I should do.

this is the code:

import cv2
import numpy as np
from scipy.ndimage import label
def segment_on_dt(a, img):
    border = cv2.dilate(img, None, iterations=3)
    border = border - cv2.erode(border, None)
    dt = cv2.distanceTransform(img, cv2.DIST_L2, 3)
    dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(np.uint8)
    _, dt = cv2.threshold(dt, 200, 255, cv2.THRESH_BINARY)
    lbl, ncc = label(dt)
    # Completing the markers now. 
    lbl[border == 255] = 255 
    lbl = lbl.astype(np.int32)
    cv2.watershed(a, lbl)
    lbl[lbl == -1] = 0
    lbl = lbl.astype(np.uint8)
    return 255 - lbl

# Load image file
img = cv2.imread('coins.jpg')
# Pre-processing.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gray = cv2.GaussianBlur(img_gray,(5,5),0)  
width, height = img_gray.shape
_, img_bin = cv2.threshold(img_gray, 0,  255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN,np.ones((5, 5),     dtype=int))
result = segment_on_dt(img, img_bin)
result[result != 255] = 0
result = cv2.dilate(result, None)
img[result == 255] = (0, 0, 255)
cv2.imwrite('Img_output.png',img)

Running this code will give this result (at least on my pc)

Coins detected after watershed, edge of image also colored

The result for detecting the coins is good enough for my purposes, but I'm a bit puzzled about the image edge that is also detected. From what I see during debugging, the watershed adds this edge, but it is unclear to me why this happens.


Solution

  • You can fix this by adding a background label using the tutorial provided by openCV. https://docs.opencv.org/3.1.0/d3/db4/tutorial_py_watershed.html

    They added an extra step to insert sure background and sure foreground region to help the watershed algorithm to properly segment the coin regions.

    *********** edit**************

    After reading your code again. I found that your original code has no problem. Background label was set using the variable border.

    You will probably get the same result by executing the code found in OpenCV tutorial. The problem is in the way your draw the results. Since this is a display problem, there are many ways we can tackle the problem. One of the many is to use the information of the sure-background

    Here are the modification to the function segment_on_dt

    def segment_on_dt(a, img):
        sure_background = cv2.dilate(img, None, iterations=3)
        border = sure_background - cv2.erode(sure_background, None)
    
    
        dt = cv2.distanceTransform(img, cv2.DIST_L2, 3)
        dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(np.uint8)
        _, dt = cv2.threshold(dt, 200, 255, cv2.THRESH_BINARY)
        lbl, ncc = label(dt)
    
    
        # Completing the markers now. 
        lbl[border == 255] = 255 
    
    
        lbl = lbl.astype(np.int32)
        cv2.watershed(a, lbl)
        lbl[lbl == -1] = 0
        # Only draw red line if its not in sure background
        lbl[sure_background == 0] = 255
    
        lbl = lbl.astype(np.uint8)
        cv2.imshow('lbl_2',lbl)
    
        return 255 - lbl
    

    I have added a new condition for the red lines to be drawn. The line are only drawn if its not in sure background region.

    Your final result should look like this.

    enter image description here