Traffic Signs Detection & Recognition Using Neural Network | Live Traffic Signs Detection | By Ali Jakhar
According to the World Health Organization (WHO) approximately 1.3 million people die every year in road accidents. 93% of the world’s fatalities on the road occur in low-and middle-income countries. The number of accidents in these countries is high because people don’t recognize the traffic signs or follow the traffic signs. In this project I have proposed a solution for vehicle drivers and made a traffic sign recognizer which can inform the drivers about the traffic sign coming ahead. This project helps to reduce road accidents. This project also helps you in driving less cars to recognize the traffic signs.
Prerequisites
- Python
- Machine Learning
- Deep Learning
- Image Processing
- Neural Networks
Project Architecture
Requirements
- Python== 3.7
- Keras== 2.4.3
- Tensorflow== 2.4.1
- OpenCv== 4.5.1.48
- Sklearn== 0.24.2
- Pandas== 1.3.1
- Numpy== 1.19.5
- Matplotlib== 3.4.3
- Seaborn== 0.11.1
Traffic Sign Detection & Recognition
1. Dataset
I have used the “German Traffic Sign Recognition Benchmark” (GTSRB) dataset. I have used only the “Train Images” folder and train.csv file for training. Dataset contains 43 classes with 39,209 total images. You can download dataset by clicking on link.
2. Import Libraries
First of all import all necessary libraries such as opencv for read images from the local drive, keras, tensorflow for building neural network, sklearn for splitting data into training and testing and for confusion matrix and other libraries numpy, pandas, seaborn, os , random.
import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam
from keras.utils.np_utils import to_categorical
from keras.layers import Dropout, Flatten
from keras.layers.convolutional import Conv2D, MaxPooling2D
import cv2
from sklearn.model_selection import train_test_split
import os
import pandas as pd
import random
from keras.preprocessing.image import ImageDataGenerator
from sklearn.metrics import confusion_matrix
from tensorflow import keras
import seaborn as sn
3. Read Images
First of all read all read images from the local drive, resize images to (32x32) and store them into the list with their corresponding label. All images should have same size.
count = 0
images = []
label = []
classes_list = os.listdir(path)
print("Total Classes:",len(classes_list))
noOfClasses=len(classes_list)
print("Importing Classes.....")
for x in range (0,len(classes_list)):
imglist = os.listdir(path+"/"+str(count))
for y in imglist:
img = cv2.imread(path+"/"+str(count)+"/"+y)
img =cv2.resize(img,(32,32))
images.append(img)
label.append(count)
print(count, end =" ")
count +=1
print(" ")
Now, convert the list into array and reshape the image data to (32x32x3) for input of the model. (32x32) is the size of the image and 3 is the channel of the image; the channel is RGB (Red, Blue, Green).
images = np.array(images)
classNo = np.array(label)
data=np.array(images)
data= np.array(data).reshape(-1, 32, 32, 3)
4. Splitting dataset
Splitting dataset into train, test and validation. 60% train data, 20% test data and 20% for validation data.
X_train, X_test, y_train, y_test = train_test_split(images, classNo, test_size=0.2)
Y_tests=y_test
X_train, X_validation, y_train, y_validation = train_test_split(X_train, y_train, test_size=0.2)
print("Data Shapes")
print("Train",end = "");print(X_train.shape,y_train.shape)
print("Validation",end = "");print(X_validation.shape,y_validation.shape)
print("Test",end = "");print(X_test.shape,y_test.shape)
5. Preprocessing
Apply some image processing on training, validation and test images like convert image into grayscale, histogram equalization and image normalization etc.
def grayscale(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return img
def equalize(img):
img = cv2.equalizeHist(img)
return img
def preprocessing(img):
img = grayscale(img)
img = equalize(img)
img = img / 255 # image normalization
return img
X_train = np.array(list(map(preprocessing, X_train)))
X_validation = np.array(list(map(preprocessing, X_validation)))
X_test = np.array(list(map(preprocessing, X_test)))### reshape data into channel 1
X_train=X_train.reshape(-1,32,32,1)
X_validation=X_validation.reshape(-1,32,32,1)
X_test=X_test.reshape(-1,32,32,1)
6. Data Augmentation
I have performed data augmentation to generate more generic data. I have set different parameters for augmentation like width and height shift range is 10%, zoom range is 0.2 and rotation range is 10 degree. batch_size=20 means that 20 images are augmented at the same time.
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)
batches = dataGen.flow(X_train, y_train,batch_size=20)
X_batch, y_batch = next(batches)
Now convert the label class vector to binary class matrix.
y_train = to_categorical(y_train, noOfClasses)
y_validation = to_categorical(y_validation, noOfClasses)
y_test = to_categorical(y_test, noOfClasses)
7. Create Model
For creating the model I have used a sequential model from keras library. Then I have to add convolutional layers in the network. In the first two Conv2D layers I have used 60 filters and kernel size is (5,5). This kernel moves around the image and gets the image features. In MaxPooling2D I have used pool size (2,2).
I have added again two Conv2D layers with change some parameters use 30 filters and kernel size is (3,3) and then add MaxPooling2D layer and after that I have add Dropout layer with 0.5 (50%) drop rate that mean 50% of neurons are removed randomly after this layer.
After that I have added a Flatten layer with 500 nodes which is fully connected with the output layer. Basically the flatten layer is converting the data into a 1-D array for inputting it to the next layer. After that I have added a Dropout layer with 0.5 drop rate and then added an output layer. Output layer has only 43 nodes. For this model I have used Adam optimizer with 0.001 learning rate and categorical cross-entropy loss.
#CNN Model
def seq_Model():
no_Of_Filters = 60
size_of_Filter = (5, 5)
size_of_Filter2 = (3, 3)
size_of_pool = (2, 2)
no_Of_Nodes = 500
model = Sequential()
model.add((Conv2D(no_Of_Filters, size_of_Filter, input_shape=(32, 32, 1),
activation='relu')))
model.add((Conv2D(no_Of_Filters, size_of_Filter, activation='relu')))
model.add(MaxPooling2D(pool_size=size_of_pool))
model.add((Conv2D(no_Of_Filters // 2, size_of_Filter2, activation='relu')))
model.add((Conv2D(no_Of_Filters // 2, size_of_Filter2, activation='relu')))
model.add(MaxPooling2D(pool_size=size_of_pool))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(no_Of_Nodes, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(noOfClasses, activation='softmax'))
model.compile(Adam(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])
return modelmodel = seq_Model()print(model.summary())#####Print model summary
8. Train model
For the training model I have set some parameters like batch size, step per epoch and number of epochs.
batch_size_val=30
steps_per_epoch_val=500
epochs_val=40##Train the model##
history=model.fit(dataGen.flow(X_train,y_train,batch_size=batch_size_val),steps_per_epoch=steps_per_epoch_val,epochs=epochs_val,validation_data=(X_validation,y_validation),shuffle=1)
9. Plot Graph
After training model, training and loss graph shown in the figure below.
##Plot Graph##
plt.figure(1)
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.legend(['training','validation'])
plt.title('loss')
plt.xlabel('epoch')
plt.figure(2)
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['training','validation'])
plt.title('Acurracy')
plt.xlabel('epoch')
plt.show()
10. Evaluate Model
To evaluate the model pass 20% test data to the model for testing. I have achieved 99% test accuracy.
#model testing
score =model.evaluate(X_test,y_test,verbose=0)
print('Test Score:',score[0])
print('Test Accuracy:',score[1])
#####Confusion matrix code####
y_pred = model.predict(X_test)
y_pred = np.argmax(y_pred, axis=1)
cm=confusion_matrix(Y_tests,y_pred) # confusion matrix
plt.figure(figsize=(10,7))
sn.heatmap(cm, annot=True,cmap='Blues', fmt='g')
plt.xlabel('Predicted')
plt.ylabel('Truth')
plt.savefig('confusionmatrix.png', dpi=300, bbox_inches='tight')
11. Save Model
Save model in .h5 format for future use.
#save model
model.save('traffif_sign_model.h5')
Traffic sign detection and recognition in live video camera
1. Import libraries
import numpy as np
import cv2
from tensorflow import keras
2. Load model
Load model that I have already saved in the local drive after training. Also define some variable threshold, font etc.
threshold = 0.75 # THRESHOLD
font = cv2.FONT_HERSHEY_SIMPLEX
model = keras.models.load_model('traffif_sign_model.h5') #load model
3. Preprocessing for sign detection in image
def preprocess_img(imgBGR, erode_dilate=True): # pre-processing fro detect signs in image.
rows, cols, _ = imgBGR.shape
imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV)
Bmin = np.array([100, 43, 46])
Bmax = np.array([124, 255, 255])
img_Bbin = cv2.inRange(imgHSV, Bmin, Bmax)
Rmin1 = np.array([0, 43, 46])
Rmax1 = np.array([10, 255, 255])
img_Rbin1 = cv2.inRange(imgHSV, Rmin1, Rmax1)
Rmin2 = np.array([156, 43, 46])
Rmax2 = np.array([180, 255, 255])
img_Rbin2 = cv2.inRange(imgHSV, Rmin2, Rmax2)
img_Rbin = np.maximum(img_Rbin1, img_Rbin2)
img_bin = np.maximum(img_Bbin, img_Rbin)
if erode_dilate is True:
kernelErosion = np.ones((3, 3), np.uint8)
kernelDilation = np.ones((3, 3), np.uint8)
img_bin = cv2.erode(img_bin, kernelErosion, iterations=2)
img_bin = cv2.dilate(img_bin, kernelDilation, iterations=2)
return img_bin
4. Count traffic sings
This function is used to count the total number of traffic signs in a single frame (image).
def contour_detect(img_bin, min_area, max_area=-1, wh_ratio=2.0):
rects = []
contours, _ = cv2.findContours(img_bin.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if len(contours) == 0:
return rects
max_area = img_bin.shape[0] * img_bin.shape[1] if max_area < 0 else max_area
for contour in contours:
area = cv2.contourArea(contour)
if area >= min_area and area <= max_area:
x, y, w, h = cv2.boundingRect(contour)
if 1.0 * w / h < wh_ratio and 1.0 * h / w < wh_ratio:
rects.append([x, y, w, h])
return rects
5. Preprocessing of image before prediction
This is the same preprocessing step which can be done in traffic sign detection and recognition section 5 above before training the data.
def grayscale(img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return img
def equalize(img):
img = cv2.equalizeHist(img)
return img
def preprocessing(img):
img = grayscale(img)
img = equalize(img)
img = img / 255
return img
6. Define label name
Define label name with their corresponding id. 0 to 42 classes.
def getCalssName(classNo):
if classNo == 0:
return 'Speed Limit 20 km/h'
elif classNo == 1:
return 'Speed Limit 30 km/h'
elif classNo == 2:
return 'Speed Limit 50 km/h'
elif classNo == 3:
return 'Speed Limit 60 km/h'
elif classNo == 4:
return 'Speed Limit 70 km/h'
elif classNo == 5:
return 'Speed Limit 80 km/h'
elif classNo == 6:
return 'End of Speed Limit 80 km/h'
elif classNo == 7:
return 'Speed Limit 100 km/h'
elif classNo == 8:
return 'Speed Limit 120 km/h'
elif classNo == 9:
return 'No passing'
elif classNo == 10:
return 'No passing for vechiles over 3.5 metric tons'
elif classNo == 11:
return 'Right-of-way at the next intersection'
elif classNo == 12:
return 'Priority road'
elif classNo == 13:
return 'Yield'
elif classNo == 14:
return 'Stop'
elif classNo == 15:
return 'No vechiles'
elif classNo == 16:
return 'Vechiles over 3.5 metric tons prohibited'
elif classNo == 17:
return 'No entry'
elif classNo == 18:
return 'General caution'
elif classNo == 19:
return 'Dangerous curve to the left'
elif classNo == 20:
return 'Dangerous curve to the right'
elif classNo == 21:
return 'Double curve'
elif classNo == 22:
return 'Bumpy road'
elif classNo == 23:
return 'Slippery road'
elif classNo == 24:
return 'Road narrows on the right'
elif classNo == 25:
return 'Road work'
elif classNo == 26:
return 'Traffic signals'
elif classNo == 27:
return 'Pedestrians'
elif classNo == 28:
return 'Children crossing'
elif classNo == 29:
return 'Bicycles crossing'
elif classNo == 30:
return 'Beware of ice/snow'
elif classNo == 31:
return 'Wild animals crossing'
elif classNo == 32:
return 'End of all speed and passing limits'
elif classNo == 33:
return 'Turn right ahead'
elif classNo == 34:
return 'Turn left ahead'
elif classNo == 35:
return 'Ahead only'
elif classNo == 36:
return 'Go straight or right'
elif classNo == 37:
return 'Go straight or left'
elif classNo == 38:
return 'Keep right'
elif classNo == 39:
return 'Keep left'
elif classNo == 40:
return 'Roundabout mandatory'
elif classNo == 41:
return 'End of no passing'
elif classNo == 42:
return 'End of no passing by vechiles over 3.5 metric tons'
7. Define main function
The main program reads video frame by frame. First of all apply preprocessing on the image and detect single or multiple signs in an frame after that crop the detected part where the traffic sign exists and convert into grayscale and resize them into (32x32) and then reshape into (32x32x1) and then pass image to model for making prediction and after prediction the name of the sign shown on the screen.
if __name__ == "__main__":
cap = cv2.VideoCapture(0)
cols = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
rows = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
while (1):
ret, img = cap.read()
img_bin = preprocess_img(img, False)
cv2.imshow("bin image", img_bin)
min_area = img_bin.shape[0] * img.shape[1] / (25 * 25)
rects = contour_detect(img_bin, min_area=min_area) # get x,y,h and w.
img_bbx = img.copy()
for rect in rects:
xc = int(rect[0] + rect[2] / 2)
yc = int(rect[1] + rect[3] / 2)
size = max(rect[2], rect[3])
x1 = max(0, int(xc - size / 2))
y1 = max(0, int(yc - size / 2))
x2 = min(cols, int(xc + size / 2))
y2 = min(rows, int(yc + size / 2))
# rect[2] is width and rect[3] for height
if rect[2] > 100 and rect[3] > 100: #only detect those signs whose height and width >100
cv2.rectangle(img_bbx, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), (0, 0, 255), 2)
crop_img = np.asarray(img[y1:y2, x1:x2])
crop_img = cv2.resize(crop_img, (32, 32))
crop_img = preprocessing(crop_img)
cv2.imshow("afterprocessing", crop_img)
crop_img = crop_img.reshape(1, 32, 32, 1) # (1,32,32) after reshape it become (1,32,32,1)
predictions = model.predict(crop_img) # make predicion
classIndex = model.predict_classes(crop_img)
probabilityValue = np.amax(predictions)
if probabilityValue > threshold:
#write class name on the output screen
cv2.putText(img_bbx, str(classIndex) + " " + str(getCalssName(classIndex)), (rect[0], rect[1] - 10),
font, 0.75, (0, 0, 255), 2, cv2.LINE_AA)
# write probability value on the output screen
cv2.putText(img_bbx, str(round(probabilityValue * 100, 2)) + "%", (rect[0], rect[1] - 40), font, 0.75,
(0, 0, 255), 2, cv2.LINE_AA)
cv2.imshow("detect result", img_bbx)
if cv2.waitKey(1) & 0xFF == ord('q'): # q for quit
break
cap.release()
cv2.destroyAllWindows()
Live Video Demo
References
- https://towardsdatascience.com/recognizing-traffic-signs-with-over-98-accuracy-using-deep-learning-86737aedc2ab
- https://towardsdatascience.com/traffic-sign-recognition-using-deep-neural-networks-6abdb51d8b70
- https://www.kaggle.com/meowmeowmeowmeowmeow/gtsrb-german-traffic-sign
- https://medium.com/@sruthiir720/traffic-sign-detection-and-classification-800da3ed7b19
- https://github.com/ZhouJiaHuan/traffic-sign-detection
- https://towardsdatascience.com/the-most-intuitive-and-easiest-guide-for-convolutional-neural-network-3607be47480