Nghiên cứu ứng dụng công nghệ xử lý ảnh để nhận diện biển báo và đèn giao thông. Thực thi 1 hệ thống xử lý ảnh đơn giản trên vi điều khiển, ứng dụng nhận diện biển báo và đèn giao thông. Qua đó đánh giá khả năng ứng dụng vào thực tế. Hiểu các tính chất của hệ thống biển báo cũng như đèn báo giao thông Việt Nam và trích xuất các đặc trưng ứ ng dụng xử lý ả nh phục vụ yêu cầu nhận diện. Phát triển 1 thuật toán nhận diện biển báo giao thông Thư viện Open cv. Ngôn ngữ lập trình P ython. Phương pháp xử lý ả nh. Một số loại biển báo giao thông tại Việt Nam. Máy tính nhúng Raspberry Pi.
HỌC VIỆN NÔNG NGHIỆP VIỆT NAM KHOA CƠ ĐIỆN - ĐỒ ÁN TỐT NGHIỆP ĐỀ TÀI : “NGHIÊN CỨU, ỨNG DỤNG CÔNG NGHỆ XỬ LÝ ẢNH ĐỂ NHẬN DIỆN BIỂN BÁO VÀ ĐÈN GIAO THÔNG” Hà Nội - 2022 HỌC VIỆN NÔNG NGHIỆP VIỆT NAM KHOA CƠ ĐIỆN - ĐỒ ÁN TỐT NGHIỆP ĐỀ TÀI : “NGHIÊN CỨU, ỨNG DỤNG CÔNG NGHỆ XỬ LÝ ẢNH ĐỂ NHẬN DIỆN BIỂN BÁO VÀ ĐÈN GIAO THÔNG” Sinh viên thực LÊ QUANG NAM Mã sinh viên 612299 Lớp K61TDH Ngành KỸ THUẬT ĐIỆN, ĐIỆN TỬ Chuyên ngành TỰ ĐỘNG HÓA Giảng viên hướng dẫn ThS ĐẶNG THỊ THÚY HUYỀN Hà Nội - 2022 Lời cam đoan Em – Lê Quang Nam xin cam đoan kết nghiên cứu trình bày đồ án trung thực, khách quan chưa dùng để bảo vệ cho đồ án môn học Em xin cam đoan giúp đỡ cho việc thực đồ án cảm ơn, thông tin trích dẫn đồ án rõ nguồn gốc Hà Nội, ngày… tháng ….năm 2021 Tác giả đồ án LÊ QUANG NAM i Lời cảm ơn Với tất tình cảm chân thành, em xin bày tỏ lòng biết ơn sâu sắc tới Ban Giám đốc, Ban Quản lý đào tạo, Khoa điện - Học viện Nông nghiệp Việt Nam, thầy cô, giảng viên tham gia giảng dạy khoá học 2016-2021, tạo điều kiện thuận lợi cho em học tập nghiên cứu, làm sở việc nghiên cứu đề tài Đặc biệt, em xin chân thành cảm ơn cô giáo ThS Đặng Thị Thúy Huyền dành nhiều thời gian, công sức để dẫn, hướng dẫn em tận tình chu đáo mặt chun mơn để em thực hồn thành đồ án tốt nghiệp Em xin bày tỏ lòng biết ơn sâu sắc đến thầy cô phản biện, thầy cô hội đồng chấm đồ án đồng ý đọc duyệt góp ý kiến quý báu để em hồn chỉnh đồ án định hướng nghiên cứu tương lai Mặc dù có nhiều cố gắng, song chắn không tránh khỏi thiếu sót định Rất mong đóng góp ý kiến Hội đồng chấm đồ án tốt nghiệp, đọc giả quan tâm đến đề tài đồ án Em xin chân thành cảm ơn! Hà Nội, ngày tháng năm 2021 Sinh viên thực LÊ QUANG NAM ii MỤC LỤC Lời cam đoan i Lời cảm ơn ii MỤC LỤC iii Danh mục hình ảnh vi Danh mục bảng biểu ix LỜI MỞ ĐẦU 1) Đặt vấn đề 2) Mục đích đề tài 3) Đối tượng phạm vi nghiên cứu 4) Phương pháp nghiên cứu 5) Giới hạn đề tài Chương 1: TỔNG QUAN 1.1 Khái quát xử lý ảnh 1.1.1 Khái niệm xử lý ảnh 1.1.2 Quá trình xử lý ảnh 1.2 Deep learning 1.3 Neural Network 1.4 Convolutional Neural Network 1.4.1 Tổng quát Convolutional Neural Network 1.4.2 Lớp tích chập( Convolutional Layer) 1.4.3 Lớp tổng hợp(Pooling layer) 1.4.4 Lớp kết nối đầy đủ( Fully Connected Layer) 1.4.5 Softmax 10 1.4.6 Batch Normalization 10 1.5 Thuật toán YOLO 11 1.5.1 Các phiên YOLO 17 1.5.2 Phiên YOLO sử dụng đề tài 17 iii 1.6 Một số phương pháp nhận diện biển báo đèn giao thông 21 1.6.1 Lọc màu biển báo HSV 21 1.6.2 Huấn luyện máy học SVM 22 Chương : NỘI DUNG VÀ PHƯƠNG PHÁP NGHIÊN CỨU 25 2.1 Biển báo giao thơng tín hiệu đèn giao thông 25 2.1.1 Một số biển báo giao thông đường Việt Nam 25 2.1.2 Biển báo đèn báo sử dụng đề tài 28 2.2 Máy tính nhúng Raspberry Pi 4B 28 2.3 Module camera Pi 29 2.4 Thư viện mã nguồn OpenCV 31 2.4.1 Khái niệm 31 2.4.2 Ứng dụng OpenCV 31 2.4.3 Tính module phổ biến OpenCV 31 2.4.4 Ngôn ngữ dùng để lập trình OpenCV 32 2.5 Ngôn ngữ Python sử dụng đề tài 33 2.5.1 Khái niệm ngôn ngữ Python 33 2.5.2 Một số tính chất Python 33 2.6 Lựa chọn mơ hình CNN nhận diện biển báo đèn giao thông 34 2.7 Xây dựng thuật toán nhận diện biển báo đèn giao thông 35 2.7.1 Huấn luyện, mô nhận diện phần mềm Pycharm máy tính laptop 35 2.7.2 Huấn luyện google colab đưa vào mơ hình nhận diện máy tính nhúng Raspberry Pi 40 Chương 3: KẾT QUẢ VÀ THẢO LUẬN 49 3.1 Kết 49 3.1.1 Kết thử nghiệm mô laptop phần mềm Pycharm 49 3.1.2 Kết nhận diện chạy máy tính nhúng Raspberry Pi 61 3.1.3 Khảo sát khoảng cách nhận diện 74 iv 3.1.4 Khảo sát ảnh hưởng ánh sáng tới khả nhận diện đối tượng 76 3.2 Thảo luận 78 KẾT LUẬN VÀ KIẾN NGHỊ 80 PHỤ LỤC 82 v Danh mục hình ảnh Hình 1 Các giai đoạn xử lý ảnh .3 Hình Cấu trúc mạng Neural Hình Mối quan hệ ngõ vào ngõ node Hình Đồ thị hàm sigmoid (a) hàm Tanh (b) Hình Đồ thị hàm ReLU .7 Hình Đồ thị hàm Mish Hình Cấu trúc CNN Hình Max pooling .9 Hình Phép flatten đưa tensor thành vector Hình 10 Ví dụ đầu vào đầu hàm softmax 10 Hình 11 Phép biến đổi batch normalization .11 Hình 12 Mơ hình mạng YOLO 11 Hình 13 Kiến trúc mạng YOLO 12 Hình 14 Biểu diễn hàm tính IOU 13 Hình 15 Xác định anchor box 14 Hình 16 Các lớp mạng YOLO .16 Hình 17 Kết so sánh YOLOv4 với SOTA(state-of-the-art) thời điểm 18 Hình 18 Cấu trúc YOLOv4 19 Hình 19 Các Backbone sử dụng YOLOv4 19 Hình 20 Quá trình đào tạo để tinh chỉnh kích thước anchor box cho giống với vật thể .20 Hình 21 Ví dụ nhận diện biển báo đường ưu tiên 21 Hình 22 Lọc màu biển báo 22 Hình 23 Kết nhận diện 22 vi Hình 24 Kết nhận dạng bảng hiệu điều khiển giao thông: (a) Ảnh đầu vào; (b) Ảnh không gian màu IHLS; (c) Ảnh kết phân đoạn; (d) Ảnh kết phát vùng ứng viên 23 Hình 25 Kết nhận dạng tín hiệu đèn giao thơng: (a) Ảnh đầu vào; (b) Ảnh kết phân đoạn; (c) Ảnh kết xác định Ellipse khoanh vùng ứng viên; (d) Ảnh kết nhận dạng tín hiệu đèn 24 Hình Các loại biển báo cấm 25 Hình 2 Các loại biển báo nguy hiểm 26 Hình Các loại biển báo hiệu lệnh 27 Hình Các loại biển phụ .27 Hình Biển báo đèn giao thông sử dụng đề tài 28 Hình Sơ đồ tổng quát Raspberry Pi .28 Hình Module camera Raspberry Pi CSI 5MP 1080P .29 Hình Sơ đồ tổng qt q trình huấn luyện dự đốn đối tượng 34 Hình Tạo tên nhãn cho đối tượng 37 Hình 10 Máy tính thống kê số lượng ảnh mẫu dùng để huấn luyện .38 Hình 11 Gán nhãn tự động vào ảnh mẫu 39 Hình 12 Biểu đồ thể tỉ lệ huấn luyện 39 Hình 13 Kẻ khung, tạo nhãn cho biển báo Stop 41 Hình 14 Kẻ khung, tạo nhãn cho biển báo rẽ phải .42 Hình 15 Kẻ khung, tạo nhãn cho biển báo thẳng .42 Hình 16 Kẻ khung, tạo nhãn cho biển báo rẽ trái 42 Hình 17 Kẻ khung, tạo nhãn cho đèn xanh 43 Hình 18 Kẻ khung, tạo nhãn cho đèn đỏ 43 Hình 19 Bộ ảnh mẫu sau gán nhãn 43 Hình 20 Cài đặt GPU google colab .44 vii Hình 21 Thay đổi thơng số tệp Makefile 45 Hình 22 Thay đổi thơng số tệp yolov4-custom.cfg 45 Hình 23 Tạo file tên nhãn cho đối tượng .46 Hình 24 Quá trình huấn luyện google colab 46 Hình 25 Lưu đồ trình nhận diện đối tượng Raspberry Pi 48 Hình Kết nhận diện Raspberry Pi 62 viii classNo = [] myList = os.listdir(path) print("Total Classes Detected:",len(myList)) noOfClasses=len(myList) print("Importing Classes ") for x in range (0,len(myList)): myPicList = os.listdir(path+"/"+str(count)) for y in myPicList: curImg = cv2.imread(path+"/"+str(count)+"/"+y) images.append(curImg) classNo.append(count) print(count, end =" ") count +=1 print(" ") images = np.array(images) classNo = np.array(classNo) X_train, X_test, y_train, y_test = train_test_split(images, classNo, test_size=testRatio) X_train, X_validation, y_train, y_validation = train_test_split(X_train, y_train, test_size=validationRatio) 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) assert(X_train.shape[0]==y_train.shape[0]), "The number of images in not equal to the number of lables in training set" 83 assert(X_validation.shape[0]==y_validation.shape[0]), "The number of images in not equal to the number of lables in validation set" assert(X_test.shape[0]==y_test.shape[0]), "The number of images in not equal to the number of lables in test set" assert(X_train.shape[1:]==(imageDimesions))," The dimesions of the Training images are wrong " assert(X_validation.shape[1:]==(imageDimesions))," The dimesionas of the Validation images are wrong " assert(X_test.shape[1:]==(imageDimesions))," The dimesionas of the Test images are wrong" data=pd.read_csv(labelFile) print("data shape ",data.shape,type(data)) num_of_samples = [] cols = num_classes = noOfClasses fig, axs = plt.subplots(nrows=num_classes, ncols=cols, figsize=(5, 300)) fig.tight_layout() for i in range(cols): for j,row in data.iterrows(): x_selected = X_train[y_train == j] axs[j][i].imshow(x_selected[random.randint(0, len(x_selected)- 1), :, :], cmap=plt.get_cmap("gray")) axs[j][i].axis("off") if i == 2: axs[j][i].set_title(str(j)+ "-"+row["Name"]) num_of_samples.append(len(x_selected)) 84 print(num_of_samples) plt.figure(figsize=(12, 4)) plt.bar(range(0, num_classes), num_of_samples) plt.title("Distribution of the training dataset") plt.xlabel("Class number") plt.ylabel("Number of images") plt.show() 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 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))) X_train=X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[2],1) X_validation=X_validation.reshape(X_validation.shape[0],X_validation.shape[1],X _validation.shape[2],1) X_test=X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[2],1) dataGen= ImageDataGenerator(width_shift_range=0.1, 85 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) fig,axs=plt.subplots(1,15,figsize=(20,5)) fig.tight_layout() y_train = to_categorical(y_train,noOfClasses) y_validation = to_categorical(y_validation,noOfClasses) y_test = to_categorical(y_test,noOfClasses) def myModel(): 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=(imageDimesions[0 ],imageDimesions[1],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)) 86 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_v2.Adam(learning_rate=0.001),loss='categorical_crossentrop y',metrics=['accuracy']) return model ############################### TRAIN model = myModel() print(model.summary()) 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_validatio n,y_validation),shuffle=1) 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') 87 plt.show() score =model.evaluate(X_test,y_test,verbose=0) print('Test Score:',score[0]) print('Test Accuracy:',score[1]) model.save("my_model.h5") cv2.waitKey(0) Phụ lục Code mô nhận diện Pycharm import cv2 import numpy as np import tensorflow as tf frameWidth= 640 frameHeight = 480 brightness = 180 threshold = 0.85 font = cv2.FONT_HERSHEY_SIMPLEX cap = cv2.VideoCapture(0) cap.set(3, frameWidth) cap.set(4, frameHeight) cap.set(10, brightness) model= tf.keras.models.load_model('my_model.h5') def grayscale(img): img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) return img def equalize(img): img =cv2.equalizeHist(img) return img 88 def preprocessing(img): img = grayscale(img) img = equalize(img) img = img/255 return img def getCalssName(classNo): if classNo == 0: return 'stop' elif classNo == 1: return 're phai' elif classNo == 2: return 're trai' elif classNo == 3: return 'di thang' elif classNo == 4: return 'den do' elif classNo == 5: return 'den xanh' while True: success, imgOrignal = cap.read() img = np.asarray(imgOrignal) img = cv2.resize(img, (32, 32)) img = preprocessing(img) cv2.imshow("Processed Image", img) img = img.reshape(1, 32, 32, 1) cv2.putText(imgOrignal, "CLASS: " , (20, 35), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA) cv2.putText(imgOrignal, "PROBABILITY: ", (20, 75), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA) predictions = model.predict(img) classesIndex = np.argmax(model.predict(img), axis=-1) probabilityValue =np.amax(predictions) 89 if probabilityValue > threshold: cv2.cv2.putText(imgOrignal,str(classesIndex)+" "+str(getCalssName(classesIndex)), (120, 35), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA) cv2.putText(imgOrignal, str(round(probabilityValue*100,2) )+"%", (180, 75), font, 0.75, (0, 0, 255), 2, cv2.LINE_AA) cv2.imshow("Result", imgOrignal) if cv2.waitKey(1) and 0xFF == ord('q'): break Phụ lục Code chụp ảnh mẫu camera Raspberry Pi import cv2 import numpy as np cam = cv2.VideoCapture(0) cv2.namedWindow("test") img_counter = while True: ret, frame = cam.read() cv2.imshow("test", cv2.resize(frame,(640,480))) if not ret: break k = cv2.waitKey(1) if k%256 == 27: print("Escape hit, closing ") break if k%256 == 32: # SPACE pressed ## ấn nút space để chụp ảnh 90 img_name = "{}.jpg".format(img_counter) cv2.imwrite(img_name, frame) print("{} written!".format(img_name)) img_counter += cam.release() cv2.destroyAllWindows() Phụ lục Code huấn luyện google colab #Liên kết google colab với tài khoản google drive có liệu ảnh nhãn from google.colab import drive drive.mount('/content/drive') # tải mã nguồn yolov4 drive !rm -rf darknet %cd /content/drive/MyDrive !git clone https://github.com/AlexeyAB/darknet %cd /content/drive/MyDrive/darknet !rm -rf data !mkdir data # giải nén file data.zip để lấy liệu train %cd /content/drive/MyDrive/darknet/data !unzip data.zip # Tạo file YOLO.NAMES chứa tên class %cd /content/drive/MyDrive/darknet !echo "stop" > yolo.names !echo "re phai" >> yolo.names !echo "re trai" >>yolo.names !echo "di thang" >> yolo.names 91 !echo "den do" >>yolo.names !echo "den xanh" >> yolo.names # Tạo file train.txt val.txt %cd /content/drive/MyDrive/darknet import glob2 import math import os import numpy as np files = [] for ext in ["*.png", "*.jpeg", "*.jpg"]: image_files = glob2.glob(os.path.join("data/data/", ext)) files += image_files nb_val = math.floor(len(files)*0.2) rand_idx = np.random.randint(0, len(files), nb_val) with open("train.txt", "w") as f: for idx in np.arange(len(files)): if (os.path.exists(files[idx][:-3] + "txt")): f.write(files[idx]+'\n') with open("val.txt", "w") as f: for idx in np.arange(len(files)): if (idx in rand_idx) and (os.path.exists(files[idx][:-3] + "txt")): f.write(files[idx]+'\n') # Tạo file yolo.data %cd /content/drive/MyDrive/darknet !mkdir backup !echo classes=6 > yolo.data 92 !echo train=train.txt >> yolo.data !echo valid=val.txt >> yolo.data !echo names=yolo.names >> yolo.data !echo backup=backup >> yolo.data # Biên dịch mã nguồn DARKNET %cd /content/drive/MyDrive/darknet !rm darknet !make # Tải PRETRAIN WEIGHTS %cd /content/drive/MyDrive/darknet !wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_o ptimal/yolov4.conv.137 # Train %cd /content/drive/MyDrive/darknet !./darknet detector train yolo.data cfg/yolov4-custom.cfg yolov4- trainning_last.weights -dont_show Phụ lục Code nhận diện đối tượng Raspberry Pi import numpy as np import cv2 import serial import time net = cv2.dnn.readNet('yolov4_training_last.weights', 'yolov4_testing.cfg') arduino = serial.Serial('/dev/ttyUSB0', 9600) time.sleep(2) classes = [] with open("classes.txt", "r") as f: 93 classes = f.read().splitlines() cap = cv2.VideoCapture(0) cap.set(3,640) cap.set(4,480) starting_time = time.time() frame_id = font = cv2.FONT_HERSHEY_PLAIN while True: ret, frame = cap.read() frame_id += height = 480 width = 640 frame = cv2.resize(frame, (640,480)) value = arduino.read_all() if value == b'1': blob = cv2.dnn.blobFromImage(frame, 1/255, (320, 320), (0, 0, 0), True, crop=False) net.setInput(blob) output_layers_names = net.getUnconnectedOutLayersNames() layerOutputs = net.forward(output_layers_names) boxes = [] confidences = [] class_ids = [] for output in layerOutputs: for detection in output: scores = detection[5:] 94 class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.8: center_x = int(detection[0]*width) center_y = int(detection[1]*height) w = int(detection[2]*width) h = int(detection[3]*height) x = int(center_x - w/2) y = int(center_y - h/2) boxes.append([x, y, w, h]) confidences.append((float(confidence))) class_ids.append(class_id) number_obj = len((boxes)) indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.8, 0.4) colors = np.random.uniform(0, 255, size=(len(boxes), 3)) for i in range(number_obj): if i in indexes: x, y, w, h = boxes[i] label = str(classes[class_ids[i]]) confidence = str(round(confidences[i], 2)) color = colors[i] cv2.rectangle(frame, (x, y), (x + w, y + h), color, 3) cv2.putText(frame, label + " " + confidence, (x, y + 20), font, 2, (255, 0, 0), 2) if class_ids[i] == 0: print("Stop") 95 arduino.write(b'0') time.sleep(2) elif class_ids[i] == 1: print("Re Phai") arduino.write(b'1') time.sleep(2) elif class_ids[i] == 2: print("Re Trai") arduino.write(b'2') time.sleep(2) elif class_ids[i] == 3: print("Di Thang") arduino.write(b'3') time.sleep(2) elif class_ids[i] == 4: print("Den Do") arduino.write(b'4') time.sleep(2) elif class_ids[i] == 5: print("Den Xanh") arduino.write(b'5') time.sleep(2) key1 = cv2.waitKey(1) if key1 == 27: break elapsed_time = time.time() - starting_time 96 fps = frame_id/elapsed_time cv2.putText(frame, "FPS: " + str(round(fps,2)), (10,50), font, 4, (255,0,0),3) cv2.imshow("Real Time", frame) cap.release() cv2.destroyAllWindows() 97