Thresholding

In this post we will experiment applying global (histogram based) and local (considering values of neighboring pixels) thresholding to images.

This post is based on the PluralSight course Building Image Processing Applications Using scikit-image by Janani Ravi. She uses the Jupyter notebook for the exercises. I decided to use the VSCode IDE to experiment with GitHub Copilot. At this point in time such a feature does not seem to be available.

I would like to disclose that I am a Microsoft employee and have been using VSCode and Visual Studio IDEs for many years. Like to follow the KISS principle (https://en.wikipedia.org/wiki/KISS_principle) and when needed, challenge common assumptions that do not make sense to me i.e., this is how we always do it, or this is how it is done.

We will be using the Anaconda prompt to call Python to run a script as we edit it.

# **** folder of interest ****
cd C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos

# **** open file of interest using VSCode ****
(base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>code Thresholding.py

# **** execute python script of interest ****
(base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>python Thresholding.py

We start by opening an Anaconda prompt and getting to the folder of interest. The path you choose for it would probably be different. Once there we invoke the VSCode IDE to create the first pass of our Python script. After the IDE opens, make sure you save the script. As we edit the script of interest we will save and then run the script. To run the script we will use the third command in the screen capture. Also note that you may choose a different name for the script of interest if needed.

# **** imports ****
import matplotlib.pyplot as plt

from skimage import data, io, color
from skimage.filters import threshold_otsu, threshold_local

# **** additional import used in second pass ****
from skimage.filters import try_all_threshold

As usual we start by importing all the libraries of interest. You may note that the last line contains a library that is only used in the second pass of the script. In this example, we run a first pass and then go back and make some changes and run it a second time. Please note that the pieces of code that is added for the second pass is clearly labeled. BTW, the last line may or may not be included in the first pass of the Python script.

# **** read cathedral image (convert to grayscale) ****
cathedral = color.rgb2gray(io.imread('./images/pexels-painting.jpg'))

# **** display cathedral ****
plt.figure(figsize=(8, 8))
plt.imshow(cathedral, cmap='gray')
plt.title('Cathedral')
plt.show()

We start by reading an image of the interior of a cathedral. The image is in RGB JPG format. As we read the image we convert it to grayscale. The image is then displayed.

# **** Convert values from 0 to 1 to 0 to 255 ****
cathedral = cathedral * 255


# **** Apply Otsu thresholding (first pass)
#      Calculates an optimal threshold so that the intra-class
#      variance is minimal and inter-class variance is maximal
# thresh = threshold_otsu(cathedral)

# **** Apply local thresholding (second pass)
#      Local filtering is useful when there is a wide variation
#      in the illumination across the image
#      Local because neighboring pixels are used to calculate the threshold
thresh = threshold_local(   cathedral, 
                            block_size=25)

# **** display threshold ****
print(f'thresh: {thresh}')

Convert the values in the cathedral from [0 : 1] to [0 : 255]. In other words, the binary values have been extended to grayscale values in the range [0 : 255]. In the first pass we use the threshold_otsu. We then display the values from the variable thresh on the console.

# **** create binary image ****
cathedral_binary = cathedral > thresh

# **** display the values in cathedral_binary ****
print('cathedral_binary:\n',  cathedral_binary)

We create a binary version of the cathedral image and display the values in the cathedral_binary. The values are displayed on the Anaconda window.

On the first pass…

(base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>python Thresholding.py
thresh: [[53.7701056  53.77013395 53.7701829  ... 55.88719677 55.34227413
  55.07282776]
 [53.77007435 53.77010222 53.7701503  ... 55.9008656  55.35228714
  55.0809978 ]
 [53.77000913 53.77003644 53.77008349 ... 55.92715806 55.37152518
  55.09667052]
 ...
 [53.77464511 53.77513026 53.77607099 ... 53.77000143 53.77008874
  53.7700928 ]
 [53.77291169 53.77322797 53.7738405  ... 53.76998722 53.7700822
  53.7700927 ]
 [53.77210944 53.77234543 53.77280206 ... 53.76997982 53.77007868
  53.77009273]]
cathedral_binary:
 [[False False False ... False False False]
 [ True False False ... False False False]
 [ True  True  True ... False False False]
 ...
 [False False False ...  True  True  True]
 [False False False ...  True  True  True]
 [False False False ...  True  True  True]]

On the second pass…

(base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>python Thresholding.py
thresh: [[53.7701056  53.77013395 53.7701829  ... 55.88719677 55.34227413
  55.07282776]
 [53.77007435 53.77010222 53.7701503  ... 55.9008656  55.35228714
  55.0809978 ]
 [53.77000913 53.77003644 53.77008349 ... 55.92715806 55.37152518
  55.09667052]
 ...
 [53.77464511 53.77513026 53.77607099 ... 53.77000143 53.77008874
  53.7700928 ]
 [53.77291169 53.77322797 53.7738405  ... 53.76998722 53.7700822
  53.7700927 ]
 [53.77210944 53.77234543 53.77280206 ... 53.76997982 53.77007868
  53.77009273]]
cathedral_binary:
 [[False False False ... False False False]
 [ True False False ... False False False]
 [ True  True  True ... False False False]
 ...
 [False False False ...  True  True  True]
 [False False False ...  True  True  True]
 [False False False ...  True  True  True]]

We now move to display the two images side by side.

# **** display images ****
fig, axes = plt.subplots(   nrows=1,
                            ncols=2, 
                            figsize=(14, 8),
                            sharex=True,
                            sharey=True)

# **** flatten axes ****
ax = axes.ravel()

# **** display original image ****
ax[0].imshow(cathedral, cmap='gray')
ax[0].set_title('Original Image')
ax[0].axis('off')

# **** display binary image ****
ax[1].imshow(cathedral_binary, cmap='gray')
ax[1].set_title('Thresholded Image')
ax[1].axis('off')

fig.tight_layout()
plt.show()

On the first pass:

On the second pass:

On the second pass the script terminates due to the following code:

# **** On second pass exit here! ****
exit(0)

The rest of the code is intended to work for the first pass, this is why on the second pass the script ends.

# **** ****
fig, ax = plt.subplots(figsize=(6, 6))

ax.hist(cathedral.ravel())

# **** display histogram ****
ax.set_title('Histogram of Grayscale Image')
ax.set_xlabel('Pixel Intensity')
ax.set_ylabel('Number of Pixels')

# **** display histogram ****
ax.axvline(thresh, color='y')
plt.show()

We will now display the histogram for the data in the cathedral image after it was converted from value in the range [0 : 1] to [0 : 255].

# **** generates a number of threshold images ****
fig, ax = try_all_threshold(cathedral, 
                            figsize=(16, 12), 
                            verbose=False)

# **** display images ****
plt.show()

With this code snippet we will generate a set of images processed using different algorithms. The resulting figure is then displayed.

Hope you enjoyed this post. It provides insights into what is available in scikit-image.

Remember that one of the best ways to learn is to read, experiment, and repeat as needed. If you do not have a current need regarding the topic of this post, you might just do some experimentation. I am sure that if you have the need, you will spend additional time.

If interested in the code for this post, you can find it in my GitHub repository under the name Thresholding.

Enjoy;

John

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.