Search code examples
pythonnumpyopencvimage-processingscikit-image

How to remove the noise on the border without losing data in the middle of mask


I am trying to segment out the tissue blobs from the image. I did some initial preprocessing and obtained the following result. My concern is the noise on the borders. If i erode with horizontal/vertical kernel, i loose some data in the middle as well. I am not sure what is a better to achieve the results, or should i go through a different approach for segmentation.

Here is the sample image:

import numpy as np 
import os
import matplotlib.pyplot as plt
from skimage import io
from skimage.filters import threshold_mean
from skimage.exposure import adjust_sigmoid, adjust_gamma
from skimage.morphology import opening
from skimage import morphology
import scipy.ndimage as ndi

def create_binary_mask(path_to_file):
    file = io.imread(path_to_file)

    #APPLY FILTERS FOR BETTER THRESHOLD
    img_med = ndi.median_filter(file, size=20) #REDUCE NOISE
    adjusted_gamma = adjust_gamma(img_med, 1.8, 2) #INCREASE GAMMA
    adjusted_sigmoid = adjust_sigmoid(adjusted_gamma, 0.01) #INCREASE CONTRAST

    #DO THE MEAN THRESHOLD
    thresh = threshold_mean(adjusted_sigmoid)
    binary = adjusted_sigmoid > thresh

    #REMOVE SMALL NOISE WITHIN THE IMAGE
    disk = morphology.disk(radius=7)
    opening = morphology.binary_opening(binary, disk)

    fig, axis = plt.subplots(1,4, figsize=(10,10))
    axis[0].imshow(file, cmap='gray')
    axis[0].set_title('Original File')
    axis[1].imshow(adjusted_sigmoid, cmap='gray')
    axis[1].set_title('Adjusted Sigmoid')
    axis[2].imshow(binary, cmap='gray')
    axis[2].set_title('Mean Threshold')
    axis[3].imshow(opening, cmap='gray')
    axis[3].set_title('After opening')
    plt.savefig('results.png')
    plt.show()


path_to_file = "sample_img.png"
create_binary_mask(path_to_file)

Results


Solution

  • A simple approach is to perform morphological closing on the binary image to connect the noise together into a single contour. From here we can find contours and filter using contour area. We can effectively remove the noise on the border by drawing in the contour. Here's the result with the noise colored in with the background color. If you wanted to remove it on the mask you could simply paint it in with black (0,0,0).

    import cv2
    import numpy as np
    
    # Load image, grayscale, Gaussian blur, adaptive threshold
    image = cv2.imread('1.png')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (7,7), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,61,5)
    
    # Morph close to connect noise
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=5)
    
    # Filter using contour area and fill in contour to remove noise
    cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        area = cv2.contourArea(c)
        if area > 250000:
            cv2.drawContours(image, [c], -1, (43,43,43), -1)
    
    cv2.imwrite('image.png', image)
    cv2.waitKey()