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()`.