pythonmatlabopencvscikit-imagemathematical-morphology

Is there a Python function equivalent to a Matlab bwmorph?


I recently transferred Matlab code to Python, and I've tried OpenCV's cv2.morphologyEx and skimage's skimage.morphology, but the result is not the same as in Matlab. I can't find the equivalent function for the following Matlab code:

output = bwmorph(input, 'shrink', Inf)

For example, if the input array is:

[1, 1, 1, 0, 1;
 1, 1, 0, 0, 0;
 0, 0, 1, 0, 0;
 1, 0, 1, 0, 1;
 1, 1, 1, 0, 1]

Then the output array will be:

[0, 0, 0, 0, 1;
 0, 0, 0, 0, 0;
 0, 0, 1, 0, 0;
 0, 0, 0, 0, 1;
 0, 0, 0, 0, 0]

In Matlab's documentation, I thought that the operation is similar to erosion, so I applied OpenCV to this array but got a different output:

output = cv2.erode(input, np.ones([3,3]))

output =
[[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]]

Could anybody please point me in the right direction? Thank you.


Solution

  • The "shrink" operation is called in different ways, including "thinning" and "ultimate thinning". Thinning is related to the skeleton, in that an approximation to the skeleton can be computed by thinning. But if you continue the thinning operation not keeping the end points (i.e. repeatedly removing the pixel at the end of branches) then eventually you'll end up with an image that has only isolated pixels and loops. This is what "shrink" does.

    Scikit-image, in the morphology module, has thin(), skeletonize() and medial_axis(). They’re all similar, different approximations to the skeleton. There is no option to remove the end points.

    OpenCV has thinning() in the cv::ximgproc module. It also cannot do your "shrink".

    DIPlib has a function dip.EuclideanSkeleton that does remove skeleton end points when choosing the option "loose ends away".

    The function has a downside in that it doesn't process a 2-pixel border around the image. So to get results all the way to the edges of the image, we need to add two pixels to each side of the image, which we then crop from the result.

    DIPlib uses its own objects to store images, but they're compatible with NumPy. Inputs can be NumPy arrays, but the outputs will always be dip.Image objects. We can easily convert them back to a NumPy array. Note that this particular function requires a binary image as input, so we cast the NumPy array to Boolean first.

    import numpy as np
    import diplib as dip
    
    input = np.array([[1, 1, 1, 0, 1],
                      [1, 1, 0, 0, 0],
                      [0, 0, 1, 0, 0],
                      [1, 0, 1, 0, 1],
                      [1, 1, 1, 0, 1]])
    h, w = input.shape
    
    tmp = input.astype(np.bool_)
    tmp = dip.ExtendImage(tmp, 2, ["add zeros"])
    output = dip.EuclideanSkeleton(tmp, endPixelCondition="loose ends away")
    output.Crop((w, h))
    
    output = np.asarray(output, dtype=np.int_)
    
    array([[0, 0, 0, 0, 1],
           [0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0],
           [0, 0, 1, 0, 0],
           [0, 0, 0, 0, 1]])
    

    Disclosure: I'm an author of DIPlib, but did not implement dip.EuclideanSkeleton().