I have a mask which may have holes inside. I want to erode from the outside of the mask (so not the holes), horizontally in, a certain number of pixels.
The trick being if I’m eroding inwards 5px and at a certain point there is a hole 3px in from the edge, I want to erode those 3px and then the remaining 2px past the hole. So I’m always eroding 5px each side, essentially skipping over any holes.
For example with this mask:
These grey areas will be eroded:
I can see how to do this by looping over each row, something like:
erode_px = 5 for y, row in enumerate(mask): idxs = np.nonzero(row)[0] if idxs.size: if idxs.size < erode_px * 2: mask[y] = 0 else: mask[y, :idxs[erode_px]] = 0 mask[y, idxs[-erode_px]:] = 0
But I am dealing with very large masks and this needs to be efficient. Is there a way to achieve this without looping over each row in Python? Preferably just using OpenCV / numpy.
Advertisement
Answer
You can accomplish what you want using the cumulative sum:
- Compute cumulative sum along horizontal lines.
- Threshold cumulative sum at your desired distance
n
. - Logical AND with input.
This will unset the first n
set pixels along each line. To apply the operation from the right edge, flip the matrix, apply the operation above, and flip the result.
The following code demonstrates the operation:
import numpy as np import matplotlib.pyplot as plt def erode_left(mask, n): return np.logical_and(np.cumsum(mask, axis=1) > n, mask) def erode_both(mask, n): mask = erode_left(mask, n) mask = np.fliplr(erode_left(np.fliplr(mask), n)) return mask mask = np.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,1,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,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,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,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]], dtype=bool) f, axarr = plt.subplots(2,2) axarr[0,0].imshow(mask) axarr[0,0].title.set_text('Input') axarr[0,1].imshow(erode_left(mask, 5)) axarr[0,1].title.set_text('Eroded left side') axarr[1,0].imshow(erode_both(mask,5)) axarr[1,0].title.set_text('Eroded both sides') axarr[1,1].imshow(erode_both(mask,5) + 2*mask) axarr[1,1].title.set_text('Overlay') plt.show()