Search code examples
pythonopencvimage-processingimage-morphology

How to get expected behavior from opencv's morhpologyEx with regard to image boundaries?


I'm using opencv (version 4.1.0, with Python 3.7) to perform morphological closing on binary images. I'm having trouble with boundaries when using big closing kernels.

My code is :

close_size = 20
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (close_size, close_size))
result = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel)

I read this question and this question, and also the the docs, which lead me to also try to change the borderValue argument in morphologyEx() like so

result = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel,borderValue=[cv2.BORDER_CONSTANT,0])

But both methods do not lead to what I want. I've summed up their behaviors in the image below.

enter image description here

My original image is on top. The behavior I expect is for the two dots to remain separate for small kernels (eg, kernel = 1), and merge together for big enough kernels.
As you can see, for the default border (left column of the image), the merge is correct when the kernel = 6, but as soon as it gets bigger, the dots start to merge with the boundary.
With constant border (right column of the image), bigger kernels can be used but an unexpected behavior occurs nevertheless with really bigger kernels (e.g. kernel = 20), where the points dissapear.

The closing kernel is left as a parameter for the user in the final software, in order to be able to merge dots which can be really far away. So ideally, I would need to be able to handle smoothly kernels which are really bigger than the distance between objects and the boundaries.

Original image : enter image description here


Solution

  • This answer explains how to use MORPH_CLOSE around the edge of an image by adding a buffer to the image.

    You can add a buffer by creating an image of zeros using numpy:

    # Import packages
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    # Read in the image
    img = cv2.imread('/home/stephen/Desktop/OKoUh.png', 0)
    # Create a black bufffer around the image
    h,w = img.shape
    buffer = max(h,w)
    bg = np.zeros((h+buffer*2, w+buffer*2), np.uint8)
    bg[buffer:buffer+h, buffer:buffer+w] = img
    

    Then you can iterate and check how it looks at different kernel sizes:

    for close_size in range(1,11):
        temp  = bg.copy()
        kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (close_size, close_size))
        result = cv2.morphologyEx(temp, cv2.MORPH_CLOSE, kernel)
        results = result[buffer:buffer+h, buffer:buffer+w]
        cv2.imshow('img', result)
        cv2.waitKey()
    

    My results: results