OpenCV and Keras | Traffic Sign Classification for Self-Driving Car

27 Dec, 2019
IntroductionIn this article, we will learn how to classify some common traffic signs that we occasionally encounter in our daily lives on the road. While building a self-driving car, it is necessary to make sure it identifies the traffic signs with a high degree of accuracy, unless the results might be catastrophic. While travelling, you may have come across numerous traffic signs, like the speed limit signal, the left or right turn signal, the stop signal and so on. Classifying all these precisely can be a daunting task, and that is where this post is going to help you.
You can get the dataset from this link – Data. It contains 4 files –
signnames.csv – It has all the labels and their descriptors.
train.p – It contains all the training image pixel intensities along with the labels.
valid.p – It contains all the validation image pixel intensities along with the labels.
test.p – It contains all the testing image pixel intensities along with the labels.
The above files with extension .p are called pickle files, which are used to serialize objects into character streams. These can be deserialized and reused later by loading them using the pickle library in python.
Let’s implement a Convolutional Neural Network (CNN) using Keras in simple and easy-to-follow steps. A CNN consists of a series of Convolutional and Pooling layers in the Neural Network which map with the input to extract features. A Convolution layer will have many filters that are mainly used to detect the low-level features such as edges of a face. The Pooling layer does dimensionality reduction to decrease computation. Moreover, it also extracts the dominant features by ignoring the side pixels. To read more about CNNs, go to this link.
 Importing the librariesWe will be needing the following libraries. Make sure you install NumPy, Pandas, Keras, Matplotlib and OpenCV before implementing the following code.
import numpy as npimport matplotlib.pyplot as pltimport kerasfrom keras.models import Sequentialfrom keras.layers import Dense, Dropout, Flattenfrom keras.layers.convolutional import Conv2D, MaxPooling2Dfrom keras.optimizers import Adamfrom keras.utils.np_utils import to_categoricalfrom keras.preprocessing.image import ImageDataGeneratorimport pickleimport pandas as pdimport randomimport cv2  np.random.seed(0)
Here, we are using numpy for numerical computations, pandas for importing and managing the dataset, Keras for building the Convolutional Neural Network quickly with less code, cv2 for doing some preprocessing steps which are necessary for efficient extraction of features from the images by the CNN. Loading the datasetTime to load the data. We will use pandas to load signnames.csv, and pickle to load the train, validation and test pickle files. After extraction of data, it is then split using the dictionary labels “features” and “labels”.
# Read datadata = pd.read_csv("german-traffic-signs / signnames.csv")  with open('german-traffic-signs / train.p', 'rb') as f:    train_data = pickle.load(f)with open('german-traffic-signs / valid.p', 'rb') as f:    val_data = pickle.load(f)with open('german-traffic-signs / test.p', 'rb') as f:    test_data = pickle.load(f)  # Extracting the labels from the dictionariesX_train, y_train = train_data['features'], train_data['labels']X_val, y_val = val_data['features'], val_data['labels']X_test, y_test = test_data['features'], test_data['labels']  # Printing the shapesprint(X_train.shape)print(X_val.shape)print(X_test.shape)
Output:
(34799, 32, 32, 3)
(4410, 32, 32, 3)
(12630, 32, 32, 3)

 Preprocessing the data using OpenCVPreprocessing images before feeding into the model gives very accurate results as it helps in extracting the complex features of the image. OpenCV has some built-in functions like cvtColor() and equalizeHist() for this task. Follow the below steps for this task –
First, the images are converted to grayscale images for reducing computation using the cvtColor() function.
The equalizeHist() function increases the contrasts of the image by equalizing the intensities of the pixels by normalizing them with their nearby pixels.
At the end, we normalize the pixel values between 0 and 1 by dividing them by 255.
def preprocessing(img):    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    img = cv2.equalizeHist(img)    img = img / 255    return img  X_train = np.array(list(map(preprocessing, X_train)))X_val = np.array(list(map(preprocessing, X_val)))X_test = np.array(list(map(preprocessing, X_test)))  X_train = X_train.reshape(34799, 32, 32, 1)X_val = X_val.reshape(4410, 32, 32, 1)X_test = X_test.reshape(12630, 32, 32, 1)
After reshaping the arrays, it’s time to feed them into the model for training. But to increase the accuracy of our CNN model, we will involve one more step of generating augmented images using the ImageDataGenerator.
This is done to reduce overfitting the training data as getting more varied data will result in a better model. The value 0.1 is interpreted as 10%, whereas 10 is the degree of rotation. We are also converting the labels to categorical values, as we normally do.
datagen = ImageDataGenerator(width_shift_range = 0.1,                   height_shift_range = 0.1,                   zoom_range = 0.2,                   shear_range = 0.1,                   rotation_range = 10)datagen.fit(X_train)  y_train = to_categorical(y_train, 43)y_val = to_categorical(y_val, 43)y_test = to_categorical(y_test, 43)
 Building the modelAs we have 43 classes of images in the dataset, we are setting num_classes as 43. The model contains two Conv2D layers followed by one MaxPooling2D layer. This is done two times for the effective extraction of features, which is followed by the Dense layers. A dropout layer of 0.5 is added to avoid overfitting the data.
num_classes = 43  def cnn_model():    model = Sequential()    model.add(Conv2D(60, (5, 5),                      input_shape =(32, 32, 1),                      activation ='relu'))          model.add(Conv2D(60, (5, 5), activation ='relu'))    model.add(MaxPooling2D(pool_size =(2, 2)))          model.add(Conv2D(30, (3, 3), activation ='relu'))    model.add(Conv2D(30, (3, 3), activation ='relu'))    model.add(MaxPooling2D(pool_size =(2, 2)))          model.add(Flatten())    model.add(Dense(500, activation ='relu'))    model.add(Dropout(0.5))    model.add(Dense(num_classes, activation ='softmax'))          # Compile model    model.compile(Adam(lr = 0.001),                   loss ='categorical_crossentropy',                   metrics =['accuracy'])    return model  model = cnn_model()history = model.fit_generator(datagen.flow(X_train, y_train,                             batch_size = 50), steps_per_epoch = 2000,                             epochs = 10, validation_data =(X_val, y_val),                             shuffle = 1)
Output:
Epoch 1/10
2000/2000 [==============================] - 129s 65ms/step - loss: 0.9130 - acc: 0.7322 - val_loss: 0.0984 - val_acc: 0.9669
Epoch 2/10
2000/2000 [==============================] - 119s 60ms/step - loss: 0.2084 - acc: 0.9352 - val_loss: 0.0609 - val_acc: 0.9803
Epoch 3/10
2000/2000 [==============================] - 116s 58ms/step - loss: 0.1399 - acc: 0.9562 - val_loss: 0.0409 - val_acc: 0.9878
Epoch 4/10
2000/2000 [==============================] - 115s 58ms/step - loss: 0.1066 - acc: 0.9672 - val_loss: 0.0262 - val_acc: 0.9925
Epoch 5/10
2000/2000 [==============================] - 116s 58ms/step - loss: 0.0890 - acc: 0.9726 - val_loss: 0.0268 - val_acc: 0.9925
Epoch 6/10
2000/2000 [==============================] - 115s 58ms/step - loss: 0.0777 - acc: 0.9756 - val_loss: 0.0237 - val_acc: 0.9927
Epoch 7/10
2000/2000 [==============================] - 132s 66ms/step - loss: 0.0700 - acc: 0.9779 - val_loss: 0.0327 - val_acc: 0.9900
Epoch 8/10
2000/2000 [==============================] - 122s 61ms/step - loss: 0.0618 - acc: 0.9812 - val_loss: 0.0267 - val_acc: 0.9914
Epoch 9/10
2000/2000 [==============================] - 115s 57ms/step - loss: 0.0565 - acc: 0.9830 - val_loss: 0.0146 - val_acc: 0.9957
Epoch 10/10
2000/2000 [==============================] - 120s 60ms/step - loss: 0.0577 - acc: 0.9828 - val_loss: 0.0222 - val_acc: 0.9939

After successfully compiling the model, and fitting in on the train and validation data, let us evaluate it by using Matplotlib. Evaluation and testingPlotting the loss function.
plt.plot(history.history['loss'])plt.plot(history.history['val_loss'])plt.legend(['training', 'validation'])plt.title('Loss')plt.xlabel('epoch')
Output:


Plotting the accuracy function.
plt.plot(history.history['acc'])plt.plot(history.history['val_acc'])plt.legend(['training', 'validation'])plt.title('Accuracy')plt.xlabel('epoch')
Output:


As you can see, we have fitted the data well keeping both the training and validation loss at a minimum. Time to evaluate how our model performs on the test data.
score = model.evaluate(X_test, y_test, verbose = 0)print('Test Loss: ', score[0])print('Test Accuracy: ', score[1])
Output:
Test Loss:  0.16352852963907774
Test Accuracy:  0.9701504354899777

Let us check one test image by feeding it into the model. The model gives a prediction of class 0 (Speed limit 20), which is correct.
plt.imshow(X_test[990].reshape(32, 32))print("Predicted sign: "+ str(        model.predict_classes(X_test[990].reshape(1, 32, 32, 1))))
Output:
Predicted sign: [0]


Image-Processing
Machine Learning
Python
Machine Learning
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
ML | Monte Carlo Tree Search (MCTS)
Introduction to Recurrent Neural Network
Markov Decision Process
Support Vector Machine Algorithm
DBSCAN Clustering in ML | Density based clustering
Read JSON file using Python
Python map() function
Adding new column to existing DataFrame in Pandas
Python Dictionary
How to get column names in Pandas dataframe