Leaf Disease Classification using Deep Learning and Image Segmentation

Source: Deep Learning on Medium

Part-5: Deep Learning Models using CNN

As already described above, we experiment with 3 classification models.

  • Classification Model on Original Images
  • Classification Model on Cropped Images
  • Classification Model on Segmented Images
import keras
from keras import regularizers
from keras.optimizers import *
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers.normalization import BatchNormalization
from keras.layers import Conv2D, MaxPooling2D, MaxPooling2D
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint, TensorBoard
import warnings
warnings.filterwarnings('ignore')
import itertools
from sklearn.utils import resample
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix

Classification Model on Original Images

Data Preparation:

  • In this step, we store raw image data in numpy array format and create a csv file containing image names with corresponding disease names.
  • In this case study, all images are resized to 144×144. These resized images are used in building deep learning models.
path = '/My Drive/Leaf Disease Classification/'
dataset = '/My Drive/Leaf Disease Classification/Soyabean-Original/'
folders = os.listdir(dataset)
folders.sort()
# Storing image data in csv file
img_names = []
img_array = []
labels = []
for folder in folders:
imgs = os.listdir(dataset+folder+os.sep)
imgs.sort()
for img in imgs:
im = cv2.imread(dataset+folder+os.sep+img)
im_rgb = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
im_size = cv2.resize(im_rgb, (144, 144), interpolation=cv2.INTER_AREA)
img_names.append(img)
img_array.append(im_size)
labels.append(folder)
df = pd.DataFrame({'Image ID': img_names, 'Class': labels})
df.to_csv(path+'Files-Original/soyabean_original.csv', index=False)
# Saving image data in numpy array format
X = np.asarray(img_array)
np.save(path+'Files-Original/imgs_original', X)
  • Loading csv file into dataframe
data = pd.read_csv(path+'Files-Original/soyabean_original.csv')
data.head()
Fig 11: First five rows of csv file
# Mapping original class labels to integer values
# Ex: Bacterial Blight-0, Brown Spot-1, ..etc
labels = {}
val_cnt = 0
for folder in folders:
labels[folder] = val_cnt
val_cnt = val_cnt+1
X = np.load(path+'Files-Original/imgs_original.npy')
y = data['Class'].map(labels).values
data['Image Array'] = X.tolist()
data['Labels'] = y
data.head()
Fig 12: Final csv file
  • Now, we split the data into train and test data in the ratio of 85:15 to ensure suffient data for training, as dataset is very small.
X = data.drop(['Image ID', 'Class', 'Labels'], axis=1)
y = data['Labels']
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.15, stratify=y, random_state=0)
imbalance_train = X_tr.copy()
imbalance_train['Labels'] = y_tr
imbalance_train['Labels'].value_counts()
Fig 13: Distribution of training data
  • From above, label with 6 is majority class with 63 samples. So, we perform upsampling such that all the classes have same no. of samples.
# Reference: upsampling in python-https://elitedatascience.com/imbalanced-classesmajority_class = imbalance_train[imbalance_train.Labels == 6]# Upsampling for imbalance dataset in python
upsampled_classes = [majority_class]
minority_labels = [0,1,2,3,4,5,7,8,9]
for i in minority_labels:
minority_class = imbalance_train[imbalance_train.Labels == i]
minority_upsampled = resample(minority_class, replace = True, n_samples = majority_class.shape[0],random_state = 0)
upsampled_classes.append(minority_upsampled)train_upsampled = pd.concat(upsampled_classes)
train_shuffled = train_upsampled.sample(frac=1, random_state=0)
X_tr1 = train_shuffled['Image Array'].values
y_tr1 = train_shuffled['Labels'].values
y_train = keras.utils.to_categorical(y_tr1, num_classes=10)
y_test = keras.utils.to_categorical(y_te, num_classes=10)
train_X = X_tr1.tolist()
X_train = np.asarray(train_X)
X_te1 = X_te['Image Array'].values
test_X = X_te1.tolist()
X_test = np.asarray(test_X)
# Scaling pixel values - Normalization
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train = X_train/255
X_test = X_test/255

For every dataset, we build two deep learning models with different architectures and compare their performances with evaluation metric (accuracy).

Data augmentation is performed on training data to ensure sufficient data for training.

For all deep learning models, Convolutional layers with various depths, Maxpooling, Dropout and BatchNormalization are used. For all hidden layers, relu is used as activation function where as softmax is used as activation function for output classification layer.

For every model, we save best model weights to the disk.

Data Augmentation:

# Performing Data Augmentation on training data
datagen = ImageDataGenerator(horizontal_flip=True,vertical_flip=True)
datagen.fit(X_train)
it_train = datagen.flow(X_train, y_train, batch_size=4)
steps = int(X_train.shape[0]/4)

Model 1:

# Model parameters
input_shape = (144, 144, 3)
num_classes = 10
# Saving model weights and other important parameters
checkpoint = ModelCheckpoint(path+"Results/model_complex.h5", monitor="val_acc",mode="max", save_best_only=True, verbose=1)
tensorboard = TensorBoard(log_dir=path+"Results/graph_complex", histogram_freq=4, batch_size=4,update_freq='epoch')
callbacks = [checkpoint, tensorboard]# model architecture
model1 = Sequential()
model1.add(Conv2D(32,(3,3),activation='relu',padding='same',strides=(2,2),kernel_initializer='he_normal',input_shape=input_shape))
model1.add(Conv2D(64, (3, 3), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model1.add(MaxPooling2D(pool_size=(2, 2)))
model1.add(BatchNormalization())
model1.add(Dropout(0.2))
model1.add(Conv2D(128, (3, 3), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model1.add(Conv2D(128, (3, 3), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model1.add(Conv2D(256, (3, 3), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model1.add(MaxPooling2D(pool_size=(2, 2)))
model1.add(BatchNormalization())
model1.add(Dropout(0.3))
model1.add(Conv2D(256, (3, 3), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model1.add(Conv2D(256, (3, 3), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model1.add(MaxPooling2D(pool_size=(2, 2)))
model1.add(BatchNormalization())
model1.add(Dropout(0.3))
model1.add(Flatten())
model1.add(Dense(512,activation='relu',kernel_initializer='he_normal'))
model1.add(Dropout(0.5))
model1.add(Dense(128,activation='relu',kernel_initializer='he_normal'))
model1.add(Dropout(0.5))
model1.add(Dense(64,activation='relu',kernel_initializer='he_normal'))
model1.add(Dense(num_classes, activation='softmax'))
model1.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
model1.summary()
# fitting the model on training data.
history = model1.fit_generator(it_train,steps_per_epoch=steps,epochs=300, validation_data=(X_test, y_test),verbose=1, callbacks=callbacks)
sns.set()
plt.plot(history.history['acc'], 'r')
plt.plot(history.history['val_acc'], 'b')
plt.legend({'Train Accuracy': 'r', 'Test Accuracy':'b'})
plt.show()
plt.plot(history.history['loss'], 'r')
plt.plot(history.history['val_loss'], 'b')
plt.legend({'Train Loss': 'r', 'Test Loss':'b'})
plt.show()
Fig 14: Accuracy and Loss Plots
model1.load_weights(path+'Results/Result-Original/model_complex.h5')y_pred_tr = model1.predict(X_train)
y_tr2 = y_train.argmax(1)
y_pred_tr = y_pred_tr.argmax(1)
train_acc = accuracy_score(y_tr2, y_pred_tr)
y_pred = model1.predict(X_test)
y_te2 = y_test.argmax(1)
y_pred = y_pred.argmax(1)
test_acc = accuracy_score(y_te2, y_pred)
print("Train Accuracy : ", train_acc)
print("Test Accuracy : ", test_acc)

Model 2:

# model architecture
model = Sequential()
model.add(Conv2D(32,(3,3),activation='relu', padding='same',strides=(2, 2), kernel_initializer='he_normal', input_shape=input_shape))
model.add(Conv2D(64, (2, 2), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.3))
model.add(Conv2D(128, (2, 2), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model.add(Conv2D(64, (2, 2), activation='relu', padding='same', strides=(1, 1), kernel_initializer='he_normal'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(128,activation='relu',kernel_initializer='he_normal'))
model.add(Dropout(0.5))
model.add(Dense(64,activation='relu',kernel_initializer='he_normal'))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
model.summary()
# fitting model on training data
history = model.fit_generator(it_train,steps_per_epoch=steps, epochs=250,validation_data=(X_test, y_test),verbose=1, callbacks=callbacks)
sns.set()
plt.plot(history.history['acc'], 'r')
plt.plot(history.history['val_acc'], 'b')
plt.legend({'Train Accuracy': 'r', 'Test Accuracy':'b'})
plt.show()
plt.plot(history.history['loss'], 'r')
plt.plot(history.history['val_loss'], 'b')
plt.legend({'Train Loss': 'r', 'Test Loss':'b'})
plt.show()
Fig 15: Accuracy and Loss Plots
model1.load_weights(path+'Results/Results-Original/model_11.h5')y_pred_tr = model1.predict(X_train)
y_tr2 = y_train.argmax(1)
y_pred_tr = y_pred_tr.argmax(1)
train_acc = accuracy_score(y_tr2, y_pred_tr)
y_pred = model1.predict(X_test)
y_te2 = y_test.argmax(1)
y_pred = y_pred.argmax(1)
test_acc = accuracy_score(y_te2, y_pred)
print("Train Accuracy : ", train_acc)
print("Test Accuracy : ", test_acc)

Similarly, we repeat the above same steps for cropped and segmented image datasets also.

Instead of repeating the same above steps, only evaluation metrics of all the models are shown in the results and conclusion section.