Is there a way to perform a process similar to erosion in OpenCV that retains a given pixel if ANY of its neighbors are non-zero, instead of requiring all of its neighbors to be non-zero?
Here, by neighbors, I mean any pixel with abs(x1-x2)+abs(y1-y2)==1
, but that is easy to control via the erosion kernel.
Of course, I can always use for loops and implement this behavior from scratch, but I prefer the speed that OpenCV can provide with its libraries.
Will it work to invert the image, perform an erosion, and then invert it back?
The other idea I had would be to convolve with a kernel with an empty center and then clip all values to the range 0 to 1. I would use scipy.ndimage.convolve
for this.
I am working with a binary NumPy array with type np.float32
(i.e., values of 0.0 or 1.0) with shape (512,512).
One easy way to accomplish your goal would be to convolve with a square 3x3 kernel of ones. For each pixel, you now know how many foreground pixels there are in its neighborhood (including itself). Threshold this at 2 (>= 2
) to get all pixels where there are at least 2 foreground pixels in the neighborhood. Finally, the logical AND with the original image will give all foreground pixels that have at least one foreground neighbor.
Here's an example:
import scipy.ndimage
import numpy as np
img = np.array([[0., 0., 0., 0., 0., 0., 0., 1., 1., 1.],
[0., 1., 0., 0., 0., 0., 0., 1., 1., 1.],
[0., 0., 0., 0., 0., 0., 0., 0., 1., 1.],
[0., 0., 0., 0., 0., 0., 1., 0., 1., 1.],
[0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 1., 0., 1., 0., 0., 0., 0., 0.],
[0., 1., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 1., 1., 0.],
[0., 0., 0., 0., 1., 1., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 0., 0., 1., 0., 0.]], dtype=np.float32)
tmp = scipy.ndimage.convolve(img, np.ones((3,3)), mode='constant')
out = np.logical_and(tmp >= 2, img).astype(np.float32)
The output is:
[[0. 0. 0. 0. 0. 0. 0. 1. 1. 1.]
[0. 0. 0. 0. 0. 0. 0. 1. 1. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 1.]
[0. 0. 0. 0. 0. 0. 1. 0. 1. 1.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 1. 1. 0.]
[0. 0. 0. 0. 1. 1. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
Of course some imaging libraries will have functions designed specifically for this purpose. I don't know if OpenCV or ndimage or scikit-image have such a function, I don't know these libraries well enough. But DIPlib does (disclosure: I'm an author):
import diplib as dip
out = img - dip.GetSinglePixels(img > 0)
The img > 0
part is to convert the floating-point array into a logical array, which DIPlib expects for binary images. This is about 5 times as fast as the other solution for a 512x512 image.