4

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

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
Anil Yadav
  • 149
  • 1
  • 14
  • What is rhe magic behind index_position? Why it is not used in dilation? I guess this is where the problem originates – tstanisl Nov 24 '19 at 00:00
  • Erosion is the exact opposite of dilation. Dilation starts with false, if any pixel is 1 then you set it to true. Erosion will be starting with true, if any pixel is 0 then set it to false. Also, you might want to pad with 1s for the erosion. – Cris Luengo Nov 24 '19 at 00:11
  • @tstanisl I assume that for erosion we need to have an exact match with the kernel. The 'values' array has all the values of the kernel. Every time the for loop runs, i check whether the value from image is same as the value from kernel at the same position. The 'index_position' variable simply gives me the value of kernel at certain position (starting with 0 and can go all the way upto 8) – Anil Yadav Nov 24 '19 at 04:18
  • @CrisLuengo I am kind of confused on your explanation about erosion. What i am assuming is that, if there is an exact match with the kernel, i replace the value in the new image at that position with 1 else i replace the value with 0. can you elaborate on what you mean by 'if any pixel is 0 then set it to false'. Thank you. – Anil Yadav Nov 24 '19 at 04:22

1 Answers1

3

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)
Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • Just a quick question. When we do erosion, is there a general strategy on how to select the structuring element ? Thanks again. – Anil Yadav Nov 26 '19 at 00:59