import numpy as np
import matplotlib.pyplot as plt
from skimage.filters import threshold_otsu
from skimage.morphology import area_opening, disk, binary_closing
from skimage.measure import find_contours, label
from skimage.color import rgb2gray, label2rgb
78 Count seeds
In this exercise we will learn how to use image analysis to identify and count seeds from an image collected with a mobile device. In this process we will learn about the concept of image thresholdding, histograms, erosion, dilation, area opening, and binary closing.
Otsu’s thresholding is an image segmentation method used to automatically determine the optimal threshold value that maximizes the between-class variance and minimizes the intra-class variance of the pixel intensities in a grayscale image.
Erosion is used to smooth the boundaries of foreground objects (i.e., objects in white on a black background) in an image. It works by moving a structuring element (a small “kernel” or “mask”) over the image and only keeping the pixel in the output image if all the pixels under the structuring element are white. Erosion can be used to remove small white noise from a binary image.
Dilation is used to expand the boundaries of foreground objects in an image. It works by moving the structuring element over the image and setting the output pixel to white if at least one pixel under the structuring element is white. Dilation can be used to fill small gaps in a binary image or to merge adjacent objects.
Area opening is used to remove small objects or noise from an image while preserving the larger structures. It involves eroding the image and then dilating it with the same structuring element. This operation can be used, for example, to remove small white specks from a black background.
Binary closing is used to fill small holes or gaps in objects in an image. It involves dilating the image and then eroding it with the same structuring element. This operation can be used to close small gaps between letters in a text image, for instance.
# Read color image
= mpimg.imread('../datasets/images/rice_seeds.jpg') image_rgb
# Inspect image size
print(image_rgb.shape)
(1622, 1563, 3)
# Convert image to grayscale
= rgb2gray(image_rgb) image_gray
# Visualize rgb and grayscale images
=(8,4))
plt.figure(figsize
1,2,1)
plt.subplot(
plt.imshow(image_rgb)'off')
plt.axis('RGB')
plt.title(
plt.tight_layout()
1,2,2)
plt.subplot(='gray')
plt.imshow(image_gray, cmap'off')
plt.axis('Gray scale')
plt.title(
plt.tight_layout()
plt.show()
# Segment seeds using a global automated threshold
= threshold_otsu(image_gray)
global_thresh = image_gray < global_thresh image_binary
# Display classified seeds and grayscale threshold
=(12,4))
plt.figure(figsize
1,3,1)
plt.subplot(='gray')
plt.imshow(image_gray, cmap'off')
plt.axis('Original grayscale')
plt.title(
plt.tight_layout()
1,3,2)
plt.subplot(=256)
plt.hist(image_gray.ravel(), bins='r', linestyle='--')
plt.axvline(global_thresh, color'Otsu threshold')
plt.title('Grayscale')
plt.xlabel('Counts')
plt.ylabel(
1,3,3)
plt.subplot(='gray')
plt.imshow(image_binary, cmap'off')
plt.axis('Binary')
plt.title(
plt.tight_layout()
plt.show()
# Area opening to remove small areas (smaller than the typical seed size)
= 1000 # area to remove in total pixels
pixel_area = area_opening(image_binary,
image_binary =pixel_area,
area_threshold=2) connectivity
# Closing to fill in holes or connect small, nearby patches
= 5
kernel_size = binary_closing(image_binary, disk(kernel_size))
image_binary
# Let's inspect the structuring element
print(disk(kernel_size))
[[0 0 0 0 0 1 0 0 0 0 0]
[0 0 1 1 1 1 1 1 1 0 0]
[0 1 1 1 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 1 1 1 0]
[1 1 1 1 1 1 1 1 1 1 1]
[0 1 1 1 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 1 1 1 0]
[0 0 1 1 1 1 1 1 1 0 0]
[0 0 0 0 0 1 0 0 0 0 0]]
# Display inverted and denoised binary image
=(4,4))
plt.figure(figsize
='gray')
plt.imshow(image_binary, cmap'off')
plt.axis('Binary')
plt.title(
plt.tight_layout()
plt.show()
# Identify seed boundaries
= find_contours(image_binary)
contours
# Print number of seeds in image
print('Image contains',len(contours),'seeds')
Image contains 41 seeds
# Plot seed contours
=(4,4))
plt.figure(figsize='gray')
plt.imshow(image_binary, cmap'off')
plt.axis(
plt.tight_layout()
for contour in contours:
1], contour[:, 0], '-r', linewidth=1.5)
plt.plot(contour[:,
# Label image regions
= label(image_binary)
label_image = label2rgb(label_image, image=image_binary) image_label_overlay
# Display image regions on top of original image
=(4, 4))
plt.figure(figsize
plt.imshow(image_label_overlay)
plt.tight_layout()'off')
plt.axis( plt.show()
# Display contour for a single seed
=(12, 8))
plt.figure(figsize
for seed in range(36):
6,6,seed+1)
plt.subplot(1],
plt.plot(contours[seed][:, 0],
contours[seed][:, '-r', linewidth=2)
plt.tight_layout() plt.show()