What’s That Leaf? Image Processing and Classification in Python

Misha Ysabel
Data Caffeine
Published in
4 min readFeb 2, 2021

In this article, we will demonstrate several topics in this image processing series. Here, we will be identifying different kind of leaves. Below is a sample of the raw leaf images.

To begin, we first get an overview of the whole dataset. We have 27 images; and 5 classes of leaves.

# Reading all the leaf image
image_path = os.path.join('*.jpg')
image_list = []
for file in glob(image_path):
img = cv2.imread(file)
image_list.append(img)
# Converted the image to grayscale
gray_scale = [rgb2gray(x) for x in image_list]
# Displaying the leaves images
fig, axes = plt.subplots(3, 9, figsize = (25, 10))
for i in range (0, 9):
axes[0, i].imshow(gray_scale[i])
axes[1, i].imshow(gray_scale[i+9])
axes[2, i].imshow(gray_scale[i+18])

Before anything else, we first clean the images by creating a mask of each image using the Otsu method. This way, the background is removed.

# Image Segmentation with Otsu's Method
fig, axes = plt.subplots(3, 9, figsize = (25, 10))
for i in range (0, 9):
freq, bins = histogram(gray_scale[i])
axes[0, i].step(bins, freq*1.0/freq.sum())

freq, bins = histogram(gray_scale[i+9])
axes[1, i].step(bins, freq*1.0/freq.sum())

freq, bins = histogram(gray_scale[i+18])
axes[2, i].step(bins, freq*1.0/freq.sum())
# Segment the Image with Otsu Method
fig, axes = plt.subplots(3, 9, figsize = (25, 10))
for i in range (0, 9):
th_1 = threshold_otsu(gray_scale[i])
axes[0, i].imshow(gray_scale[i]<th_1)
th_2 = threshold_otsu(gray_scale[i+9])
axes[1, i].imshow(gray_scale[i+9]<th_2)
th_3 = threshold_otsu(rgb2gray(image_list[i+18]))
axes[2, i].imshow(gray_scale[i+18]<th_3)
Otsu Masks

Now that we have masks, our next step is to segment the individual leaves in each image. However, before segmenting we cropped the image by 5 pixels on both axes to remove border pixels.

To segment, we use regionprops, selecting regions with an are greater than or equal to 1200. To standardize the dimension of all leaves, we resize it to 500px by 500px.

We loop through all the photos creating a total of 258 individual leaf photos.

leaves = []fig, ax = plt.subplots(27, figsize=(10, 100))for i, image in enumerate(gray_scale):

image1 = image[5:, 5:]
image1 = st.resize(image1, (876,876))

image2 = ((image1 < threshold_otsu(image1))*image1*255).astype(int)

thr_label = label((image1 < threshold_otsu(image1)))

image_label_overlay = label2rgb(thr_label, image=image1, bg_label=0)

ax[i].imshow(image_label_overlay)

for region in regionprops(thr_label):
if region.area >= 1200:
# draw rectangle around segmented coins
minr, minc, maxr, maxc = region.bbox
rect = mpatches.Rectangle((minc, minr), maxc - minc, maxr - minr,
fill=False, edgecolor='red', linewidth=2)
ax[i].add_patch(rect)
ax[i].set_axis_off()

a = region.bbox
b = image2[a[0]:a[2],a[1]:a[3]]
b = st.resize(b, (500,500), preserve_range=True).astype(int)
leaves.append(b)
plt.tight_layout()
plt.show()

Since the dataset is small, we manually created the labels for the data [0,1,2,3,4]

y = np.concatenate((np.repeat(0,51), np.repeat(1,49), np.repeat(2,55), np.repeat(3,50), np.repeat(4,53)))
y

We now have our X data and y data, we can begin the machine learning process. Train test split is performed with a 10% test size. Next, we one-hot encoded the labels.

#Train Test split of 80% training and 20% test data
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size = 0.1,
random_state=42)
#one-hot encode target columny_train = to_categorical(y_train)
y_test = to_categorical(y_test)

The data is now ready for

We create the model. Here, we implement a simple Convolutional Neural Network using an Adam optimizer, and categorical crossentropy loss as shown below.

#create model
model = Sequential()#add model layers
model.add(Conv2D(8, kernel_size=3, activation='relu', input_shape=(500,500,1)))
model.add(Conv2D(8, kernel_size=3, activation='relu'))
model.add(Flatten())
model.add(Dense(15))
model.add(Dense(5, activation='softmax'))
model.summary()
Model: "sequential_19"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_41 (Conv2D) (None, 498, 498, 8) 80
_________________________________________________________________
conv2d_42 (Conv2D) (None, 496, 496, 8) 584
_________________________________________________________________
flatten_19 (Flatten) (None, 1968128) 0
_________________________________________________________________
dense_26 (Dense) (None, 15) 29521935
_________________________________________________________________
dense_27 (Dense) (None, 5) 80
=================================================================
Total params: 29,522,679
Trainable params: 29,522,679
Non-trainable params: 0
_________________________________________________________________
#compile model using accuracy to measure model performance
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

We trained the model over 3 epochs with a batch size of 10, and validation split of 10%

# Training the Model
model.fit(X_train, y_train, epochs=3, batch_size=10, validation_split=0.1)

Training the CNN…

Epoch 1/3
21/21 [==============================] - 1s 49ms/step - loss: 4055.8989 - accuracy: 0.2231 - val_loss: 644.2237 - val_accuracy: 0.4167
Epoch 2/3
21/21 [==============================] - 1s 37ms/step - loss: 134.0834 - accuracy: 0.8138 - val_loss: 32.3694 - val_accuracy: 0.7083
Epoch 3/3
21/21 [==============================] - 1s 36ms/step - loss: 1.3805 - accuracy: 0.9665 - val_loss: 20.7465 - val_accuracy: 0.6250
<tensorflow.python.keras.callbacks.History at 0x7f1359130490>

Evaluating the model, we get a 61% accuracy in identifying the leaves.

model.evaluate(X_test, y_test)1/1 [==============================] - 0s 55ms/step - loss: 39.7163 - accuracy: 0.6154[39.71626281738281, 0.6153846383094788]

--

--