Search code examples
numpymask

Extracting boundary of a numpy array


Let A be a numpy array representing a mask. I would like to extract the boundary corresponding to this mask i.e, make everything zero except for the boundary.

eg:

In [22]: A
array([[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, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)

The required output is:

array([[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, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)

Further, the mask in A could be non-linear as well.

So, my question is, what's the most efficient way get this boundary?

Edit 1: What I mean by non-linear? Consider an image in which there is a person. The mask corresponding to this person is non-linear.


Solution

  • One trick to get the contour would be to use binary dilation with 3x3 ones array as the kernel on the negated mask and look for the common ones between it and input. For 4-connected boundary, it would be all ones array and for 8-connected a plus-shaped ones array -

    from scipy.ndimage.morphology import binary_dilation
    
    k = np.ones((3,3),dtype=int) # for 4-connected
    k = np.zeros((3,3),dtype=int); k[1] = 1; k[:,1] = 1 # for 8-connected
    out = binary_dilation(a==0, k) & a
    

    Sample run -

    Input array :

    In [384]: a
    Out[384]: 
    array([[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, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
           [0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
           [0, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0]])
    
    In [385]: from scipy.ndimage.morphology import binary_dilation
    

    Solve for 4-connected :

    In [386]: k = np.ones((3,3),dtype=int)
    
    In [390]: binary_dilation(a==0, k) & a
    Out[390]: 
    array([[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, 0, 0, 0, 0],
           [0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
           [0, 0, 1, 0, 1, 1, 0, 0, 0, 0],
           [0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
           [0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
           [0, 1, 0, 1, 0, 0, 0, 0, 0, 0],
           [0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    

    Solve for 8-connected :

    In [411]: k = np.zeros((3,3),dtype=int); k[1] = 1; k[:,1] = 1
    
    In [412]: k
    Out[412]: 
    array([[0, 1, 0],
           [1, 1, 1],
           [0, 1, 0]])
    
    In [413]: binary_dilation(a==0, k) & a
    Out[413]: 
    array([[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, 0, 0, 0, 0],
           [0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
           [0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
           [0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
           [0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
           [0, 1, 0, 1, 0, 0, 0, 0, 0, 0],
           [0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
    

    We could also use binary_erosion :

    from scipy.ndimage.morphology import binary_erosion
    out = a-binary_erosion(a,k)