92 lines
4.6 KiB
Markdown
92 lines
4.6 KiB
Markdown
|
# CNN Plates Classification
|
||
|
Author: Weronika Skowrońska, s444523
|
||
|
|
||
|
As my individual project, I decided to perform a classification of plates images using a Convolutional Neural Network. The goal of the project is to classify a photo of the client's plate as empty(0), dirty(1) or full(2), and assign an appropriate value to the given instance of the "Table" class.
|
||
|
|
||
|
# Architecture
|
||
|
|
||
|
Architecture of my CNN is very simple. I decided to use two convolutions, each using 32 feature detectors of size 3 by 3, followed by the ReLU activation function and MaxPooling of size 2 by 2.
|
||
|
```sh
|
||
|
classifier = Sequential()
|
||
|
|
||
|
classifier.add(Convolution2D(32, (3, 3), input_shape =(256, 256, 3), activation = "relu"))
|
||
|
classifier.add(MaxPooling2D(pool_size = (2,2)))
|
||
|
|
||
|
classifier.add(Convolution2D(32, 3, 3, activation = 'relu'))
|
||
|
classifier.add(MaxPooling2D(pool_size = (2, 2)))
|
||
|
|
||
|
classifier.add(Flatten())
|
||
|
```
|
||
|
After flattening, I added a fully connected layer of size 128 (again with ReLU activation function). The output layer consists of 3 neurons with softmax activation function, as I am using the Network for multiclass classification (3 possible outcomes).
|
||
|
```sh
|
||
|
classifier.add(Dense(units = 128, activation = "relu"))
|
||
|
classifier.add(Dense(units = 3, activation = "softmax"))
|
||
|
```
|
||
|
The optimizer of my network is adam, and categorical cross entropy was my choice for a loss function.
|
||
|
```sh
|
||
|
classifier.compile(optimizer = "adam", loss = "categorical_crossentropy", metrics = ["accuracy"])
|
||
|
```
|
||
|
# Library
|
||
|
|
||
|
I used keras to implement the network. It let me add some specific features to my network, such as early stopping and a few methods of data augmentation.
|
||
|
```sh
|
||
|
train_datagen = ImageDataGenerator(
|
||
|
rescale=1./255,
|
||
|
shear_range=0.2,
|
||
|
zoom_range=0.2,
|
||
|
horizontal_flip=True,
|
||
|
width_shift_range=0.2,
|
||
|
height_shift_range=0.1,
|
||
|
fill_mode='nearest')
|
||
|
```
|
||
|
This last issue was very important to me, as I did not have many photos to train the network with (altogether there were approximately 1200 of them).
|
||
|
|
||
|
# Project implementation
|
||
|
|
||
|
After training the Network, I firstly saved the model which gave me the best results (two keras callbacks, EarlyStopping and ModelCheckpoint were very useful) to a file named "best_model.h5".
|
||
|
```sh
|
||
|
# callbacks:
|
||
|
es = EarlyStopping(monitor='val_loss', mode='min', baseline=1, patience = 10)
|
||
|
mc = ModelCheckpoint('best_model.h5', monitor='val_loss', mode='min', save_best_only=True, verbose = 1, period = 10)
|
||
|
```
|
||
|
It occured though, that the file is to big to upload it to git, so I modified the code a little bit, and instead of saving the model, I saved the weights:
|
||
|
```sh
|
||
|
mc = ModelCheckpoint('best_model.h5', monitor='val_loss', mode='min', save_best_only=True, verbose = 1, period = 10, save_weights_only = True)
|
||
|
```
|
||
|
To be honest, it was not a very good idea either, as the new file is also to big to upload it. I managed to solve the probem in another way: I added the h5 file to my google drive, and added a link to download it to the project files.
|
||
|
|
||
|
To use the saved weights, I created the CNN model inside our project:
|
||
|
```sh
|
||
|
#initializing:
|
||
|
classifier = Sequential()
|
||
|
#Convolution:
|
||
|
classifier.add(Convolution2D(32, (3, 3), input_shape =(256, 256, 3), activation = "relu"))
|
||
|
#Pooling:
|
||
|
classifier.add(MaxPooling2D(pool_size = (2,2)))
|
||
|
|
||
|
# Adding a second convolutional layer
|
||
|
classifier.add(Convolution2D(32, 3, 3, activation = 'relu'))
|
||
|
classifier.add(MaxPooling2D(pool_size = (2, 2)))
|
||
|
|
||
|
#Flattening:
|
||
|
classifier.add(Flatten())
|
||
|
|
||
|
#Fully connected layers::
|
||
|
classifier.add(Dense(units = 128, activation = "relu"))
|
||
|
classifier.add(Dense(units = 3, activation = "softmax"))
|
||
|
|
||
|
# loading weigjts:
|
||
|
classifier.load_weights('s444523/best_model_weights2.h5')
|
||
|
#Making CNN:
|
||
|
classifier.compile(optimizer = "adam", loss = "categorical_crossentropy", metrics = ["accuracy"])
|
||
|
```
|
||
|
After coming to each table, the Agent (the waiter) evaluates a randomly selected photo of a plate using the provided model, and assigns the number of predicted class into the "state" attribute of a given table. This information will let perform further actions, based on the predicted outcome.
|
||
|
|
||
|
I noticed that my program has difficulties in distinguishing a full plate from a dirty one - interestingly, this was also a problem for me and my friends when we worked as real waiters in the restaurant. Therefore, if the plate is classified by the waiter as dirty, he asks politely if the client already has done eating, and acts accordingly to his answer:
|
||
|
```sh
|
||
|
if result[1] == 1:
|
||
|
result[1] = 0
|
||
|
x = int(input("Excuse me, have You done eating? 1=Yes, 2 = No \n"))
|
||
|
result[x] = 1
|
||
|
```
|