Scikit Image − Li thresholding



Li thresholding, introduced by Li and Lee in 1993, offers a criterion for determining the optimal threshold to separate the foreground and background in an image. Their approach minimizes the cross-entropy between the foreground and its mean and the background and its mean, which would find the best threshold in most situations.

Before 1998, the common approach to finding this threshold was to test all possible thresholds and select the one with the lowest cross-entropy. However, in 1998, Li and Tam introduced an iterative method to more quickly find the optimum point by using the slope of the cross-entropy. This iterative method, known as the threshold_li() function, is implemented in the scikit-image library as part of its filters module.

Using the skimage.filters.threshold_li() function

The filters.threshold_li() function is used to compute a threshold value for a grayscale input image using Li's iterative Minimum Cross Entropy method −

Syntax

Following is the syntax of this function −

skimage.filters.threshold_li(image, *, tolerance=None, initial_guess=None, iter_callback=None)

Parameters

Here are the details of the function parameters −

  • image (N, M[, …, P]) ndarray: This parameter represents the grayscale input image. It should be a NumPy array.
  • tolerance (optional): This is a float value that specifies when the computation should finish. The algorithm stops when the change in the threshold in an iteration is less than this value. By default, this is the smallest difference between intensity values in the image.
  • initial_guess (optional): Li's iterative method uses gradient descent to find the optimal threshold. When an image's intensity histogram contains multiple modes (peaks), gradient descent can get stuck in a local optimum. Providing an initial guess for the threshold can help the algorithm find the globally optimal threshold. You can provide an initial guess as a float value or a callable function that takes an array of image intensities and returns a float value. Examples of valid callables include numpy.mean (default), lambda arr: numpy.quantile(arr, 0.95), or even skimage.filters.threshold_otsu().
  • iter_callback (optional): This is a callable function that will be called on the threshold at every iteration of the algorithm.

Return value

The function returns a single value, this is a float value representing the upper threshold value. All pixels with an intensity higher than this threshold are assumed to belong to the foreground, while those with an intensity lower than or equal to this threshold are considered part of the background.

Example

Here's a simple example of how to separate the foreground and background regions in the image using the filters.threshold_li() function −

import numpy as np
from skimage import io, filters
import matplotlib.pyplot as plt 

# Load a grayscale image
image = io.imread('Images/logo.png', as_gray=True)

# Compute the threshold using Li's method
threshold = filters.threshold_li(image)

# Use the threshold to separate the foreground and background regions in the image. 
# Pixel intensities higher than the threshold value is foreground
foreground = image > threshold

# Pixel intensities lower than or equal to the threshold value is background
background = image < threshold

# Display the original image, foreground, and background
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10))
ax = axes.ravel()

ax[0].imshow(image, cmap=plt.cm.gray)
ax[0].set_title('Original')

ax[1].imshow(foreground, cmap=plt.cm.gray)
ax[1].set_title('Foreground')

ax[2].imshow(background, cmap=plt.cm.gray)
ax[2].set_title('Background')

for a in ax:
    a.axis('off')

plt.tight_layout()
plt.show()

Output

Li Threshold

Example

This example demonstration, the concept of cross-entropy and its optimization using Li's iterative method. It's important to note that we are using the private function _cross_entropy(), which is not intended for production code as it may change in the future −

import numpy as np
import matplotlib.pyplot as plt
from skimage import io, filters
from skimage.filters.thresholding import _cross_entropy

# Load the input image
image = io.imread('Images/albert-einstein-15.jpg', as_gray=True)

# Possible threshold values
thresholds = np.arange(np.min(image) + 1.5, np.max(image) - 1.5)

# cross-entropy for the image at all possible thresholds
entropies = [_cross_entropy(image, t) for t in thresholds]

optimal_threshold = thresholds[np.argmin(entropies)]

fig, ax = plt.subplots(1, 3, figsize=(10, 8))

ax[0].imshow(image, cmap='gray')
ax[0].set_title('image')
ax[0].set_axis_off()

ax[1].imshow(image > optimal_threshold, cmap='gray')
ax[1].set_title('thresholded')
ax[1].set_axis_off()

ax[2].plot(thresholds, entropies)
ax[2].set_xlabel('thresholds')
ax[2].set_ylabel('cross-entropy')
ax[2].vlines(optimal_threshold,
             ymin=np.min(entropies) - 0.05 * np.ptp(entropies),
             ymax=np.max(entropies) - 0.05 * np.ptp(entropies))
ax[2].set_title('optimal threshold')

fig.tight_layout()

print('The brute force optimal threshold is:', optimal_threshold)
print('The computed optimal threshold is:', filters.threshold_li(image))

plt.show()

Output

The brute force optimal threshold is: 63.5
The computed optimal threshold is: 62.93757319319864
Li Threshold
Advertisements