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).
CodePudding user response:
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.
CodePudding user response:
let's say you have
src = np.uint8([
[ 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 255, 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, 255, 0, 0],
[ 0, 0, 0, 0, 0, 0, 255, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0]])
(you can figure out how to get that from your floats)
You are looking for nonzero pixels where not all neighbors are 0. You could construct the desired result from several operations.
You can use MORPH_HITMISS
for the positive condition (all neighbors 0), then combine with the negation.
You'd use this kernel:
kernel = np.int8([ # 0 means "don't care", all others have to match
[-1, -1, -1],
[-1, 1, -1],
[-1, -1, -1],
])
neighbors_all_zero = cv.morphologyEx(src=src, op=cv.MORPH_HITMISS, kernel=kernel)
result = src & ~neighbors_all_zero
Result:
array([[ 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, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 255, 0, 0],
[ 0, 0, 0, 0, 0, 0, 255, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
Be careful with the values themselves. OpenCV assumes masks to be 0 or 255 sometimes, and 0 and non-zero at other times. I got some funny results when I used just 0 and 1 instead of 0 and 255. I'm sure those can be worked with though.