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:
There were two issues with your code:
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.
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)