'Why opencv 'cv2.morphologyEx' operations shift images in one direction during iterations?

I am currently using "morphologyEx" operations in OpenCV to eliminate some noise in the image. It's working successfully but for some strange reason, roi keeps moving south during iterations. The original image is : enter image description here

The image wth scale bars : enter image description here

The python script that I am running is

test_image = r"C:/test/test.bmp"
image = cv2.imread(test_image,cv2.COLOR_BAYER_BG2RGB)
blurred = cv2.medianBlur(image, 3) 
ret,binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)
img_adj = cv2.morphologyEx(blurred, cv2.MORPH_OPEN,(3,11),iterations=25)
#imshow(binary)
imshow(img_adj)

But after iterations it is as follows : enter image description here

Image roi has shifted south which is proportional to iterations. How can I prevent shifting ?



Solution 1:[1]

The main issue is the (3,11) argument passed to cv2.morphologyEx.

According to the documentation of morphologyEx, kernel is a Structuring element, and not the size of the kernel.

Passing (3,11) is probably like passing np.array([1, 1]) (or just undefined behavior).

The correct syntax is passing 3x11 NumPy a array of ones (and uint8 type):

img_adj = cv2.morphologyEx(blurred, cv2.MORPH_OPEN, np.ones((3, 11), np.uint8), iterations=25)

Using large kernel with 25 iterations is too much, so I reduced it to 3x5 and 5 iterations.

The following code sample shows that the image is not shifted:

import cv2
import numpy as np

test_image = "test.bmp"
#image = cv2.imread(test_image, cv2.COLOR_BAYER_BG2RGB) # cv2.COLOR_BAYER_BG2RGB is not in place
image = cv2.imread(test_image, cv2.IMREAD_GRAYSCALE)  # Read image as grayscale
blurred = cv2.medianBlur(image, 3) 
ret, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)
#img_adj = cv2.morphologyEx(blurred, cv2.MORPH_OPEN, (3, 11), iterations=25)
img_adj = cv2.morphologyEx(blurred, cv2.MORPH_OPEN, np.ones((3, 5), np.uint8), iterations=5)

montage_img = np.dstack((255-image, 0*image, 255-img_adj)) # Place image in the blue channel and img_adj in the red channel

# Show original and output images using OpenCV imshow method (instead of using matplotlib)
cv2.imshow('image', image)
cv2.imshow('img_adj', img_adj)
cv2.imshow('montage_img', montage_img)
cv2.waitKey()
cv2.destroyAllWindows()

image:
enter image description here

img_adj:
enter image description here

montage_img:
enter image description here


A better solution would be finding the largest connected component (that is not the background):

import cv2
import numpy as np

test_image = "test.bmp"
image = cv2.imread(test_image, cv2.IMREAD_GRAYSCALE)  # Read image as grayscale
ret, binary = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)

nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(binary, 8)  # Finding connected components

# Find the largest non background component.
# Note: range() starts from 1 since 0 is the background label.
max_label, max_size = max([(i, stats[i, cv2.CC_STAT_AREA]) for i in range(1, nb_components)], key=lambda x: x[1])

res = np.zeros_like(binary) + 255
res[output == max_label] = 0

cv2.imshow('res', res)
cv2.waitKey()
cv2.destroyAllWindows()

Result:
enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rotem