机器学习项目交通标志图片识别你做过吗?这个过程并不难
自动驾驶汽车现在可谓是“站在风口上的猪”,就连小米都加入了这一阵营。在国外很多人工智能和互联网大型公司如特斯拉,Uber,谷歌,梅赛德斯 - 奔驰,丰田,福特,奥迪等都早已在研究自动驾驶汽车。在我们可以预见的未来,或许真的可以实现这个目标,就像汽车刚出现的时候我们也不相信它能够跑得比马快,说不定若干年后我们的子孙见不到人开车的场景了。
自动驾驶级别分为L0到L5,其中0-2级为驾驶辅助类,3-5级为自动驾驶类。目前普遍都还在L2级,要实现L5级自动驾驶,车辆必须了解并遵守所有交通规则,也就是说为了在自动驾驶技术中实现准确性,车辆最基本应该能够识别交通标志并做出相应的决策。
对于机器学习初学者来说,即使学习了很多相关知识,但是没见过它的效用,会对自己产生怀疑,到底学的东西学会了吗?它有用吗?如果你学的扎实,看完这个案例,觉得你也可以做出来,你就能自信点,并且感受到这一个小小项目的用处。下面就是关于这个项目的演示,你给它上传一个交通标志的图片,它就能识别出这个标志的内容是什么。

在这个Python项目示例中,有几种不同类型的交通标志,如限速,禁止进入,交通信号灯,左转或右转,儿童交叉口,重型车辆不得通过等。我们把交通标志分类化简为识别交通标志属于哪个类别的过程,然后构建一个CNN神经网络模型,这个模型可以将图像中存在的交通标志分类为不同的类别。通过这个模型,我们能够读入和识别交通标志,然后给汽车后续3自动操作提供依据。
对于这个项目,我使用的是Kaggle提供的公共数据集: 交通标志数据集 。这个数据集包含超过 50000 张不同交通标志的图像。它进一步分为43个不同的类别。数据集的差异很大,有些类有很多图像,而有些类有很少的图像。数据集的大小约为316MB。数据集有一个"train"文件夹,其中包含每个类中的图像;还有一个“test”文件夹,用来测试我们的模型。
这个项目需要tensorflow,Matplotlib,Scikit-learn,Pandas,PIL和图像分类的先验知识。如果没有的话,需要先在终端中使用“pip install ”命令安装这些辅助包。
1.数据下载好后,将文件解压缩到一个文件夹中,包括train,test和meta文件夹。

2.创建一个 Python 脚本文件,并将其命名为项目文件夹中traffic_signs.py。构建这个交通标志分类模型的方法分为四个步骤:查看数据集;搭建CNN模型;训练和验证模型;使用测试数据集测试模型。下面就开始正式建模。
步骤 1:查看数据集
"train"文件夹包含43个文件夹,每个文件夹代表不同的类,每个类表示一种交通规则含义。文件夹的命名范围是从0到42。通过os模块来循环访问所有类,并将图像及其各自的标签(也就是父文件名)添加到数据和标签列表中。PIL库用于打开并读取图像内容转换为数组。
1.1 导入必要的库
#导入必要的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
import PIL
from PIL import Image
from Pillow import Image
import os
os.chdir(r"E:\app\archive" )
from sklearn.model_selection import train_test_split
#用于将图片数组转化为keras里的数据格式
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.layers import Conv2D, MaxPool2D, Dense, Flatten, Dropout
1.2 查看数据特征
data = []
labels = []
classes = 43
cur_path = os.getcwd()
#读取图片文件和它们对应的标签
for i in range(classes):
path = os.path.join(cur_path,'train',str(i))
images = os.listdir(path)
for a in images:
image = Image.open(path + '\\'+ a)
image = image.resize((30,30))
image = np.array(image)
#sim = Image.fromarray(image)
data.append(image)
labels.append(i)
except:
print("Error loading image")
#转换为numpy数组
data = np.array(data)
labels = np.array(labels)
print(data.shape, labels.shape)

上述代码的目的是读进图像文件,把所有图像及其标签存储到列表(数据和标签)中,然后转换为numpy数组,以便喂给模型进行训练。数据的形状为(39209,30,30,3),这意味着有39209张大小为30×30像素的图像,第四维的“3”表示数据包含彩色图像(RGB值)。
1.3 切分数据集
通过sklearn包,我们使用 train_test_split()方法来拆分训练和测试数据。
#切分训练集和验证集
X_train, X_test, y_train, y_test = train_test_split(data, labels, test_size=0.2, random_state=42)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)

1.4 从tensorflow.keras.utils包中,我们使用to_categorical方法将y_train和y_test编码为独热码。
#把训练集和测试集的标签都转换为keras里的独热码
y_train = to_categorical(y_train, 43)
y_test = to_categorical(y_test, 43)
步骤 2:构建CNN模型
为了将图像分类到各自的类别中,我们将构建一个CNN模型(卷积神经网络),CNN最适合用于图像分类。这个模型的体系结构是:
- 2 Conv2D layer (filter=32, kernel_size=(5,5), activation=”relu”)
- MaxPool2D layer ( pool_size=(2,2))
- Dropout layer (rate=0.25)
- 2 Conv2D layer (filter=64, kernel_size=(3,3), activation=”relu”)
- MaxPool2D layer ( pool_size=(2,2))
- Dropout layer (rate=0.25)
- Flatten layer to squeeze the layers into 1 dimension
- Dense Fully connected layer (256 nodes, activation=”relu”)
- Dropout layer (rate=0.5)
- Dense layer (43 nodes, activation=”softmax”)
我们使用Adam优化器来训练模型,如果你看了我之前的优化算法的话,应该知道神经网络模型一般都是用“Adam”,损失评估函数是"categorical_crossentropy"(类别交叉熵),因为我们有多个类需要分类。
model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(5,5), activation='relu', input_shape=X_train.shape[1:]))
model.add(Conv2D(filters=32, kernel_size=(5,5), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(rate=0.25))
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(rate=0.25))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(rate=0.5))
model.add(Dense(43, activation='softmax'))
#把上面设置的网络组建编译到模型实例中来
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
步骤 3:训练和验证模型
构建模型架构后,我们使用 model.fit()训练模型。我尝试了Batch size为32和64,其中在Batch size为64大小下表现更好。在15个epoches之后,模型精度逐渐趋于稳定。
epochs = 15
history = model.fit(X_train, y_train, batch_size=32, epochs=epochs, validation_data=(X_test, y_test))
model.save("my_model.h5")

我们的模型在训练数据集上达到了99.02% 的准确率。 下面使用matplotlib绘制accuracy和loss随着迭代次数的增加而变化的图。
plt.figure()
plt.plot(history.history['accuracy'], label='training accuracy')
plt.plot(history.history['val_accuracy'], label='val accuracy')
plt.title('Accuracy')
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.legend()
plt.show()
plt.figure(1)
plt.plot(history.history['loss'], label='training loss')
plt.plot(history.history['val_loss'], label='val loss')
plt.title('Loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.show()

步骤 4: 用测试集测试模型
我们的数据集包含一个test文件夹,在test.csv文件中,有与图像路径和它们各自的类标签相关的详细信息。 使用pandas提取图像路径和标签, 然后为了预测模型,同样也需要把图像大小调整为30×30像素,并转化成一个包含所有图像数据的numpy数组。 从sklearn.metrics中导入了accuracy_score评估方法模型的好坏。
#在测试集上测试模型准确性
from sklearn.metrics import accuracy_score
y_test = pd.read_csv('Test.csv')
labels = y_test["ClassId"].values
imgs = y_test["Path"].values
data=[]
for img in imgs:
image = Image.open(img)
image = image.resize((30,30))
data.append(np.array(image))
X_test=np.array(data)
pred = model.predict_classes(X_test)
#评估准确性
from sklearn.metrics import accuracy_score
print(accuracy_score(labels, pred))

可以看到这个模型在测试集中实现了94.8% 的准确率,效果已经很不错了。我尝试肉眼自己去识别,有些图案比较暗或者不认识的图案,它基本都能快速识别出来。
最后使用Keras model.save() 函数保存训练好的模型:
model.save(‘traffic_classifier.h5’)
步骤 5:创建 图形用户界面APP
保存了这个模型之后我们该怎么使用呢?接下来就使用一个python的GUI包tkinter把交通标志模型制作成一个可以交互使用的图形用户界面APP,方便大家使用。Tkinter是标准python库中的GUI工具包。你可以在这个项目文件夹中创建一个新文件并复制以下代码。将其另存为traffic_classifications.py,你可以像文章开头动图里那样通过在命令行中键入python traffic_classifications.py来运行代码。
在这个文件中,我们首先使用Keras.models.load_model加载了训练的模型"traffic_classifier.h5"。然后,我们构建用于上传图像的GUI,并使用一个按钮对调用classize()函数的按钮进行分类。classify() 函数将图像转换为为(1, 30, 30, 3)形状的数组,这是因为要预测交通标志,必须提供与构建模型时相同的维度。然后我们预测类,model.predict_classes(image)返回一个介于(0-42)之间的数字,表示它所属的类。我们使用字典来获取有关该类的信息。下面是traffic_classifications.py文件的代码。
import tkinter as tk
from tkinter import filedialog
from tkinter import *
from PIL import ImageTk, Image
import numpy
#加载训练模型的分类标志
from keras.models import load_model
model = load_model('traffic_classifier.h5')
#把所有交通标志分类放进一个字典中
classes = { 1:"限速(20km/h)",
2:"限速(30km/h)",
3:"限速(50km/h)",
4:"限速(60km/h)",
5:"限速(70km/h)",
6:"限速(80km/h)",
7:"限速结束(80公里/小时)",
8:"限速(100公里/小时)",
9:"限速(120公里/小时)",
10:"禁止通行",
11:"超过3.5吨的车辆不得通过",
12:"十字路口的路权",
13:"优先道路",
14:"让道",
15:"停止",
16:"禁止车辆",
17:"禁止>3.5吨",
18:"禁止入境",
19:"一般警告",
20:"危险的连续转弯左",
21:"危险的连续转弯右",
22:"双弯道",
23:"道路颠簸",
24:"路滑",
25:"道路在右边变窄",
26:"道路工程",
27:"交通信号灯",
28:"行人",
29:"儿童穿越",
30:"自行车穿越",
31:"当心冰雪",
32:"野生动物杂交",
33:"结束速度 + 通过限制",
34:"前方右转",
35:"前方左转",
36:"只可直行",
37:"直走或右走",
38:"直走或左转",
39:"保持右行",
40:"保持左行",
41:"环岛强制",
42:"禁止通行结束",
43:'> 3.5 吨车辆禁止通行结束'}
#初试话GUI实例
top=tk.Tk()
top.geometry('800x600')
top.title('交通标志识别')
top.configure(background='#46A3FF')
label=Label(top,background='#46A3FF', font=('arial',15,'bold'))
sign_image = Label(top)
def classify(file_path):
global label_packed
image = Image.open(file_path)
image = image.resize((30,30))
image = numpy.expand_dims(image, axis=0)
image = numpy.array(image)
pred = model.predict_classes([image])[0]
sign = classes[pred+1]
print(sign)
label.configure(foreground='#011638', text=sign)
def show_classify_button(file_path):
classify_b=Button(top,text="识别图片",command=lambda: classify(file_path),padx=10,pady=5)
classify_b.configure(background='#364156', foreground='white',font=('arial',10,'bold'))
classify_b.place(relx=0.45,rely=0.926)
def upload_image():
file_path=filedialog.askopenfilename()
uploaded=Image.open(file_path)
uploaded.thumbnail(((top.winfo_width()/3),(top.winfo_height()/4)))
im=ImageTk.PhotoImage(uploaded)
sign_image.configure(image=im)
sign_image.image=im
label.configure(text='')
show_classify_button(file_path)
except:
upload=Button(top,text="上传新图片",command=upload_image,padx=5,pady=5)
upload.configure(background='#364156', foreground='white',font=('arial',10,'bold'))
upload.pack(side=BOTTOM,pady=50)