%matplotlib inline
Morphological operations¶
Morphology is the study of shapes. In image processing, some simple operations can get you a long way. The first things to learn are erosion and dilation. In erosion, we look at a pixel’s local neighborhood and replace the value of that pixel with the minimum value of that neighborhood. In dilation, we instead choose the maximum.
import numpy as np
from matplotlib import pyplot as plt, cm
import skdemo
image = np.array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
plt.imshow(image);
The documentation for scikit-image’s morphology module is here.
Importantly, we must use a structuring element, which defines the local
neighborhood of each pixel. To get every neighbor (up, down, left, right, and
diagonals), use morphology.square
; to avoid diagonals, use
morphology.diamond
:
from skimage import morphology
sq = morphology.square(width=3)
dia = morphology.diamond(radius=1)
print(sq)
print(dia)
[[1 1 1]
[1 1 1]
[1 1 1]]
[[0 1 0]
[1 1 1]
[0 1 0]]
The central value of the structuring element represents the pixel being considered, and the surrounding values are the neighbors: a 1 value means that pixel counts as a neighbor, while a 0 value does not. So:
skdemo.imshow_all(image, morphology.erosion(image, sq), shape=(1, 2))
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-5-7101991a2e67> in <module>
----> 1 skdemo.imshow_all(image, morphology.erosion(image, sq), shape=(1, 2))
~/work/skimage-tutorials/skimage-tutorials/lectures/skdemo/_skdemo.py in imshow_all(*images, **kwargs)
83 fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(width, size))
84 for ax, img, label in zip(axes.ravel(), images, titles):
---> 85 ax.imshow(img, **kwargs)
86 ax.set_title(label)
87
/opt/hostedtoolcache/Python/3.8.10/x64/lib/python3.8/site-packages/matplotlib/__init__.py in inner(ax, data, *args, **kwargs)
1359 def inner(ax, *args, data=None, **kwargs):
1360 if data is None:
-> 1361 return func(ax, *map(sanitize_sequence, args), **kwargs)
1362
1363 bound = new_sig.bind(ax, *args, **kwargs)
/opt/hostedtoolcache/Python/3.8.10/x64/lib/python3.8/site-packages/matplotlib/axes/_axes.py in imshow(self, X, cmap, norm, aspect, interpolation, alpha, vmin, vmax, origin, extent, filternorm, filterrad, resample, url, **kwargs)
5603 aspect = rcParams['image.aspect']
5604 self.set_aspect(aspect)
-> 5605 im = mimage.AxesImage(self, cmap, norm, interpolation, origin, extent,
5606 filternorm=filternorm, filterrad=filterrad,
5607 resample=resample, **kwargs)
/opt/hostedtoolcache/Python/3.8.10/x64/lib/python3.8/site-packages/matplotlib/image.py in __init__(self, ax, cmap, norm, interpolation, origin, extent, filternorm, filterrad, resample, **kwargs)
898 self._extent = extent
899
--> 900 super().__init__(
901 ax,
902 cmap=cmap,
/opt/hostedtoolcache/Python/3.8.10/x64/lib/python3.8/site-packages/matplotlib/image.py in __init__(self, ax, cmap, norm, interpolation, origin, filternorm, filterrad, resample, **kwargs)
255 self._imcache = None
256
--> 257 self.update(kwargs)
258
259 def __getstate__(self):
/opt/hostedtoolcache/Python/3.8.10/x64/lib/python3.8/site-packages/matplotlib/artist.py in update(self, props)
1060 func = getattr(self, f"set_{k}", None)
1061 if not callable(func):
-> 1062 raise AttributeError(f"{type(self).__name__!r} object "
1063 f"has no property {k!r}")
1064 ret.append(func(v))
AttributeError: 'AxesImage' object has no property 'shape'
and
skdemo.imshow_all(image, morphology.dilation(image, sq))
and
skdemo.imshow_all(image, morphology.dilation(image, dia))
Erosion and dilation can be combined into two slightly more sophisticated operations, opening and closing. Here’s an example:
image = np.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, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 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],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], np.uint8)
plt.imshow(image);
What happens when run an erosion followed by a dilation of this image?
What about the reverse?
Try to imagine the operations in your head before trying them out below.
skdemo.imshow_all(image, morphology.opening(image, sq)) # erosion -> dilation
skdemo.imshow_all(image, morphology.closing(image, sq)) # dilation -> erosion
Exercise: use morphological operations to remove noise from a binary image.
from skimage import data, color
hub = color.rgb2gray(data.hubble_deep_field()[350:450, 90:190])
plt.imshow(hub);
Remove the smaller objects to retrieve the large galaxy.