Search code examples
pythonnumpyopencvimage-processingimage-morphology

Custom erosion results do not match with OpenCV erosion


I am new to image processing and was trying to write a custom method for erosion and dilation. I then tried to compare my results with OpenCV erosion and dilation function results. I give one padding of zeros to the input image and then overlap the kernel with padded image. Here is my function:

import numpy as np
import matplotlib.pyplot as plt

def operation(image, kernel, padding=0, operation=None):
    if operation:
        img_operated = image.copy() #this will be the image

        """
        The add_padding function below will simply add padding to the image, so the new array with one padding will
        look like ->

        [[0,0,0,0,0,0,0,0],
         [0,0,0,1,1,1,1,0],
         [0,0,0,1,1,1,1,0],
         [0,1,1,1,1,1,1,0],
         [0,1,1,1,1,1,1,0],
         [0,1,1,1,1,0,0,0],
         [0,1,1,1,1,0,0,0],
         [0,0,0,0,0,0,0,0]]
         )

        """
        image = add_padding(image, padding)

        print("Image is \n",  image)
        print("kernel is \n",kernel)
        print("="*40)

        vertical_window = padded.shape[0] - kernel.shape[0] #final vertical window position
        horizontal_window = padded.shape[1] - kernel.shape[1] #final horizontal window position

        print("Vertical Window limit: {}".format(vertical_window))
        print("Horizontal Window limit: {}".format(horizontal_window))
        print("="*40)

        #start with vertical window at 0 position
        vertical_pos = 0
        values = kernel.flatten() #to compare with values with overlapping element for erosion

        #sliding the window vertically
        while vertical_pos <= (vertical_window):
            horizontal_pos = 0

            #sliding the window horizontally
            while horizontal_pos <= (horizontal_window):
                dilation_flag = False
                erosion_flag = False
                index_position = 0

                #gives the index position of the box
                for i in range(vertical_pos, vertical_pos+kernel.shape[0]):
                    for j in range(horizontal_pos, horizontal_pos+kernel.shape[0]):

                        #First Case
                        if operation == "erosion":
                            if padded[i,j] == values[index_position]:
                                erosion_flag = True
                                index_position += 1
                            else:
                                erosion_flag = False
                                break

                        #Second Case
                        elif operation == "dilation":
                            #if we find 1, then break the second loop
                            if padded[i][j] == 1:
                                dilation_flag = True
                                break

                        else:
                            return  "Operation not understood!"

                    #if opertion is erosion and there is no match found, break the first 'for' loop
                    if opr == "erosion" and erosion_flag is False:
                        break

                    #if operation is dilation and we find a match, then break the first 'for' loop 
                    if opr == "dilation" and dilation_flag is True:
                        img_operated[vertical_pos, horizontal_pos] = 1
                        break

                #Check whether erosion flag is true after iterating over one complete overlap 
                if operation == "erosion" and erosion_flag is True:
                    img_operated[vertical_pos, horizontal_pos] = 1

                elif operation == "erosion" and erosion_flag is False:
                    img_operated[vertical_pos, horizontal_pos] = 0

                #increase the horizontal window position
                horizontal_pos += 1

            #increase the vertical window position
            vertical_pos += 1

        return img_operated

    return "Operation Required!"

array = np.array([[0,0,1,1,1,1],
               [0,0,1,1,1,1],
               [1,1,1,1,1,1],
               [1,1,1,1,1,1],
               [1,1,1,1,0,0],
               [1,1,1,1,0,0]], dtype=np.uint8)

kernel = np.array ([[0, 1, 0],
                    [1, 1, 1],
                    [0, 1, 0]], dtype = np.uint8)

#image will be padded with one zeros around
result_erosion = operation(array, kernel, 1, "erosion")
result_dilation = operation(array, kernel, 1, "dilation")

#CV2 Erosion and Dilation
cv2_erosion = cv2.erode(array, kernel, iterations=1) 
cv2_dilation = cv2.dilate(array, kernel, iterations=1)

The dilation result matches but the erosion result does not. I am not sure why this is the case. Is it because of some padding issues? Does OpenCV pad the image? Or am I implementing the erosion method incorrectly? Here is the image of the results:

final results


Solution

  • There were two issues with your code:

    1. You weren't checking the value of the kernel. For the dilation this happened to not matter, but you'd see the difference with a different input image.

    2. The erosion was confused. As I mentioned in a comment, the erosion is the complete logical inverse of the dilation. You can think of the erosion as the dilation of the background: erosion(image) == ~dilation(~image) (with ~ the logical negation of the image). Therefore, you should be able to use exactly the same code and logic for the erosion as you use for the dilation, but check if you see a background pixel (0) within the kernel, in which case you set that pixel in the output to background (0). To replicate the results of the OpenCV erosion, the padding has to be with foreground (1).

    This is the corrected code. I wrote a add_padding function using OpenCV, since it was missing in the OP. The code could be simplified significantly, for example by using a single flag for both operations; by checking the operation string only once at the top of the function and setting a variable with the value 0 or 1 to be used when comparing the input and modifying the output; and by using for loops instead of while loops to iterate over the image. I'll leave those changes to the interested reader.

    import numpy as np
    import matplotlib.pyplot as plt
    import cv2
    
    def add_padding(image, padding, value):
        return cv2.copyMakeBorder(image, padding, padding, padding, padding, cv2.BORDER_CONSTANT, value=value)
    
    def operation(image, kernel, padding=0, operation=None):
        if operation:
            img_operated = image.copy() #this will be the image
    
            padding_value = 0           # <<< ADDED
            if operation == "erosion":  # <<< ADDED
                padding_value = 1       # <<< ADDED
            padded = add_padding(image, padding, padding_value)  # <<< MODIFIED
    
            vertical_window = padded.shape[0] - kernel.shape[0] #final vertical window position
            horizontal_window = padded.shape[1] - kernel.shape[1] #final horizontal window position
    
            #start with vertical window at 0 position
            vertical_pos = 0
    
            #sliding the window vertically
            while vertical_pos <= vertical_window:
                horizontal_pos = 0
    
                #sliding the window horizontally
                while horizontal_pos <= horizontal_window:
                    dilation_flag = False
                    erosion_flag = False
    
                    #gives the index position of the box
                    for i in range(kernel.shape[0]):      # <<< MODIFIED
                        for j in range(kernel.shape[1]):  # <<< MODIFIED
                            if kernel[i][j] == 1:         # <<< ADDED
                                #First Case
                                if operation == "erosion":
                                    #if we find 0, then break the second loop
                                    if padded[vertical_pos+i][horizontal_pos+j] == 0:  # <<< MODIFIED
                                        erosion_flag = True                            # <<< MODIFIED
                                        break
                                #Second Case
                                elif operation == "dilation":
                                    #if we find 1, then break the second loop
                                    if padded[vertical_pos+i][horizontal_pos+j] == 1:  # <<< MODIFIED
                                        dilation_flag = True
                                        break
                                else:
                                    return  "Operation not understood!"
    
                        #if opertion is erosion and there is no match found, break the first 'for' loop
                        if operation == "erosion" and erosion_flag:         # <<< MODIFIED
                            img_operated[vertical_pos, horizontal_pos] = 0  # <<< ADDED
                            break
    
                        #if operation is dilation and we find a match, then break the first 'for' loop 
                        if operation == "dilation" and dilation_flag:       # <<< FIXED
                            img_operated[vertical_pos, horizontal_pos] = 1
                            break
    
                    # !!! Removed unnecessary checks here
    
                    #increase the horizontal window position
                    horizontal_pos += 1
    
                #increase the vertical window position
                vertical_pos += 1
    
            return img_operated
    
        return "Operation Required!"
    
    array = np.array([[0,0,1,1,1,1],
                   [0,0,1,1,1,1],
                   [1,1,1,1,1,1],
                   [1,1,1,1,1,1],
                   [1,1,1,1,0,0],
                   [1,1,1,1,0,0]], dtype=np.uint8)
    
    kernel = np.array ([[0, 1, 0],
                        [1, 1, 1],
                        [0, 1, 0]], dtype = np.uint8)
    
    #image will be padded with one zeros around
    result_erosion = operation(array, kernel, 1, "erosion")
    result_dilation = operation(array, kernel, 1, "dilation")
    
    #CV2 Erosion and Dilation
    cv2_erosion = cv2.erode(array, kernel, iterations=1)
    cv2_dilation = cv2.dilate(array, kernel, iterations=1)