Skip to content
Advertisement

Erode x pixels from side of mask with hole in OpenCV

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:

Original mask

These grey areas will be eroded:

result

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:

  1. Compute cumulative sum along horizontal lines.
  2. Threshold cumulative sum at your desired distance n.
  3. 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()

output of code above

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement