from skimage import io
from skimage.color import rgb2grey
= io.imread('three-circles.png')
img_original = rgb2grey(img_original)
img_grey
= plt.subplots(nrows=1, ncols=2, figsize=(8, 4))
fig, ax = ax
ax_original, ax_grey
ax_original.imshow(img_original)f'Original: shape={img_original.shape}')
ax_original.set_title(
='gray')
ax_grey.imshow(img_grey, cmapf'Grayscale: shape={img_grey.shape}')
ax_grey.set_title(
for a in ax.flat:
'off') a.axis(
Image Analysis (Good)
What to expect in this chapter
In this chapter, I will show you how to perform segmentation analysis, where you try to identify features of an image. It is one of the most important sub-fields under image analysis. It is the starting point of most detection algorithms, be it an image of a cell, an MRI scan of a brain, or an aerial photograph from a military drone. In this chapter, I will use the tools from scikit-image to discuss the basics of segmentation analysis.
1 scikit-image
scikit-image is a powerful image analysis package. You can install it by:
-image conda install anaconda::scikit
2 Segmentation Analysis
Some typical steps go into extracting information from an image. These are:
- Convert the image to grayscale.
- Find and apply a threshold to binarise
- Identify and label objects
- Apply measurement tools to extract information about the objects
I will use the image three-circles.png
and the tools from scikit-image to demonstrate these.
2.1 Convert the image to grayscale
Grayscale images (with just one layer) are more straightforward to analyze than RGB ones (with three layers). This is because a grayscale image has the same RGB values for each pixel. So, the first step is to convert the image into grayscale.
2.2 Binarise
Thresholding, or binarising, converts an image into black and white depending on what features you want to analyse. For example, to separate the foreground (our subject, such as a cell) from the background.
The threshold(\(t\)) separates the image into background and foreground. I.e., everything below (\(<t\)) will be black, and those values equal or above (\(\geq t\)) will be white.
Finding a threshold
scikit-image offers some functions to help us find a threshold. You can pick one (I will try Otsu and Yen) or try all. Here is how it works.
from skimage.filters import threshold_otsu, threshold_yen
= io.imread('three-circles.png')
img_original = rgb2grey(img_original)
img_grey
print('{threshold_otsu(img_grey)=}')
print('{threshold_yen(img_grey)=}')
We can also try all the available methods using try_all_threshold()
!
from skimage.filters import try_all_threshold
= try_all_threshold(img_grey, figsize=(10, 8), verbose=False)
fig, ax plt.show()
Yen seems to be the best.
Applying a threshold
Once we have a threshold, we can apply it to binarise the image.
= threshold_yen(img_grey)
threshold = img_grey < threshold
img_binarised
= plt.subplots(nrows=1, ncols=3, figsize=(9, 3))
fig, ax = ax
ax_original, ax_grey, ax_binarised
ax_original.imshow(img_original)f'Original: shape= {img_original.shape}')
ax_original.set_title(
='gray')
ax_grey.imshow(img_grey, cmapf'Grayscale: shape= {img_grey.shape}')
ax_grey.set_title(
='gray')
ax_binarised.imshow(img_binarised, cmapf'Binarised (Using Otsu)') ax_binarised.set_title(
2.3 Labelling
Note that skimage function measure.label() requires an image of type int
Once you have a binarised image, we can label features. More specifically, labeling ‘fills’ connected areas with an integer. For example, if you have three distinct regions, one will be filled with 1s, the next with 2s, and the third with 3s. This enables us to visualize the segments by a simple heat map.
Let me first show you what happens. I will then explain a bit more.
# measure.label() requires an image of type int
= measure.label(img_binarised.astype('uint8'))
img_labelled
= plt.subplots(nrows=1, ncols=3, figsize=(9, 3))
fig, ax = ax
ax_original, ax_binarised, ax_labelled
ax_original.imshow(img_original)f'Original: shape= {img_original.shape}')
ax_original.set_title(
='gray')
ax_binarised.imshow(img_binarised, cmapf'Binarised (Using Yen)')
ax_binarised.set_title(
# Using jet to colour the different regions
='jet')
ax_labelled.imshow(img_labelled, cmapf'Labelled Objects') ax_labelled.set_title(
Making sense of labelling
I mentioned that labeling simply ‘fills’ distinct regions with integer numbers. This means we should be able to use masking to isolate these regions.
1st region
= img_labelled == 1
img_masked plt.imshow(img_masked)
2nd region
= img_labelled == 2
img_masked plt.imshow(img_masked)
3rd region
= img_labelled == 3
img_masked plt.imshow(img_masked)
2.4 Measuring
Now that we have separated and labelled the regions, we can measure important characteristics (e.g. area, centroid) of these regions. Let me show you how.
---------- Region 0 ----------
Centre : (87.0, 168.0)
Area : 20565
---------- Region 1 ----------
Centre : (317.0, 283.0)
Area : 15193
---------- Region 2 ----------
Centre : (317.0, 53.0)
Area : 6793
# measure.label() requires an image of type int
= measure.label(img_binarised.astype('uint8'))
img_labelled = measure.regionprops(img_labelled)
region_info
= len(region_info)
no_of_regions
for count, region in enumerate(region_info):
print('-'*10, f'Region {count}', '-'*10)
print(f'Centre\t: {region.centroid}')
print(f'Area\t: {region.area}') # What is the area
print('\n')
There are many more properties you can measure. Please see here for more information.