基于卷积神经网络(CNN)的智能循迹小车

  • 上篇文章讲到了用树莓派和openCV库来做简单的循迹,在这篇文章里我们将尝试用树莓派做一些更酷的事情。我们不希望给小车具体的规则(explicitly programmed)去做循迹,而是让它自己从已有的数据集(包括图像与转向角)学习如何循迹,没错这就是机器学习。

### 概述 - 首要要提到的是,我还有一些坑没有填完,这条路并没有完全走通。还没有解决的问题是:我使用的Google Tensorflow不支持ARM架构的CPU,所以理论上没有办法在树莓派上训练和使用神经网络算法。
模型是在x64的ubuntu上训练出来的。在这篇文章里不会详细介绍机器学习以及卷积神经网络算法的原理,而是侧重于说明功能的实现。 #### 硬件要求 1. Raspberry Pi 3 2. 至少4GB内存的计算机 Intel x64架构 #### 软件部分 1. Python 2.7 2. Google Tensorflow/TFlearn 3. OS:ubuntu ARM 16.04/ubuntu x64 16.04 #### 总体思路 循迹赛道上篇文章介绍过这里不再附图解释。
第一部分是数据的采集,思路是利用Python的定时器按固定周期拍照,拍完照片做两件事情: - 处理图像,灰度化由阈值判断赛道边界,并求出合理转向角。 - 储存图像和转向角。
第二部分是由采集的数据训练神经网络,并观察误差情况。
第三部分则是使用模型,将摄像头实时拍到的图像传进模型,并有模型预测出转向情况,操纵舵机转向。

具体实现

训练数据采集

第一部分图像的处理并求转向角在前一篇文章中已经介绍。所以在这里只补充一下数据的存储规则。
数据的存储规则是我自己订的,独一无二。好吧,我承认它很傻:-I。机器学习界广泛使用的数据存储格式是.csv,可是我是一个Python渣呀,我的具体代码是这样子的:

1
2
fn = 'img/'+str(i)+'_'+str(err)+'.jpg'
cv2.imwrite(fn, im)

这两行代码每个处理周期跑一遍,会在img文件夹下创建一张图,命名规则为序号+下划线+转向偏差,图像格式为.jpg,到时候我们把数据输入网络的时候只要用正则表达式把它们分一下不就行了嘛。有了输入和输出我们就可以训练我们的模型了。 效果图

训练数据读取

首先我们通过正则表达式来提取转向角的数据,我们要做的就是获取这个文件夹里所以的文件名,然后按照下划线前面部分的序号对后面的转向数据排序。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
img_rows, img_cols, img_depth = 640, 480, 1
array_len = 1000 #图像总数
yArray = [0]* array_len
files=os.listdir("/home/hsm/Desktop/img")
files.sort()
for i in range(array_len):
files_re_a = re.split(r'\.', files[i])
files_re_b = re.split(r'\_', files_re_a[0])
yArray[int(files_re_b[0])-1] = int(files_re_b[1])
Y_data = np.asarray(yArray)
Y_train = Y_data.reshape(array_len,1);
Y_train = Y_train.astype('float32')
Y_train /= 240 #归一化处理数据

re.split函数可以切分字符串,我对文件名进行了两次切分,提取出了序号信息和转向信息。
完成转向信息的提取后,图像的数组就很容易构建,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
img_num = []
for i in range(array_len):
i = i + 1
fn = '/home/hsm/Desktop/imgonly/' + str(i) + '.jpg'
im=cv2.imread(fn,0)
img_num.append(im)
X_data=np.asarray(img_num)
#print X_data.shape
X_train= X_data.reshape(array_len,img_rows,img_cols,1)
X_train = X_train.astype('float32')
X_train /= 255

这里漏了一步,刚才提到我们图像命名非常的不科学,那么如何通过openCV按顺序读取它们并转换为灰度数组呢?我把刚才的图像文件夹复制了一份,并用macOS下的批量重命名工具将它们处理成按序号命名的图像了。

CNN卷积神经网络的建立与训练
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
net = tflearn.input_data(shape=[None, img_rows, img_cols, img_depth], name='input')
net = tflearn.conv_2d(net, 8, [4, 5], strides=2, activation='relu')
net = tflearn.conv_2d(net, 8, [4, 5], strides=2, activation='relu')
net = tflearn.conv_2d(net, 16, [4, 5], strides=2, activation='relu')
net = tflearn.conv_2d(net, 32, [2, 2], activation='relu')
net = tflearn.fully_connected(net, 128, activation='relu')
net = tflearn.dropout(net, 0.8)
net = tflearn.fully_connected(net, 64, activation='relu')
net = tflearn.dropout(net, 0.8)
net = tflearn.fully_connected(net, 32, activation='relu')
net = tflearn.dropout(net, 0.8)
net = tflearn.fully_connected(net, 8, activation='relu')
net = tflearn.dropout(net, 0.8)
net = tflearn.fully_connected(net, 1, activation='tanh')
net = tflearn.regression(net, loss='mean_square', metric='accuracy', learning_rate=1e-4, name='target')
model = tflearn.DNN(net)
model.fit(X_train, Y_train, n_epoch=10, batch_size=8,show_metric=True)
model.save("/home/hsm/Desktop/model1/model1.tfl")

这里就不多做解释了,有兴趣的读者可以参考TFlearn的官方文档,写的非常详细,最后将训练好的模型存到指定的位置方便下次使用。
训练的过程: 训练过程 这里显示的精确度为1并不可靠。至于之前为什么强调内存问题,由于模型的训练非常吃内存,实测训练2770张图片,batch_size参数设为16将占用大约12GB内存,事实上我是用32GB内存的电脑训练的。

模型的使用和检验

代码大概是这样,仍然需要先将要测试的图片读进来并且reshape一下,调用model.predict预测转向角。

1
2
3
4
5
6
7
8
9
result = []
for i in range(array_len):
pred = model.predict([X_in[i]])
result.append(pred[0][0])
print result
y = []
for i in range(0, array_len-1):
y.append(Y_train[i][0])
print y

为了更加方便的观察预测值和准确值的吻合情况,我使用了matplotlib库来绘图,画出来的曲线如下:
看起来有一些简陋,因为快期末考试了没时间画细致的图,所以先原谅我没有图例。横轴是图片的序号,纵轴是转向角,范围从-1到1。绿色的线是准确值,蓝色的线是模型的预测值,总体来看效果还不错。

存在的问题

我至今仍然不确定Tensorflow是否可以在ARM架构的CPU上运行,如果不行的话,我的解决方案大概有这么两个:

  1. 换用Intel的小型一体机,不但满足x64架构的条件,而且数据处理能力也比我们现在使用的树莓派强上百倍。
  2. Python网络编程,通过WLAN将Pi采集的图片发到我的笔记本上,调用模型计算出转弯角度再将数据发回去。这个方案不是不行,不过貌似比较复杂。

如果有碰巧读到这篇文章的读者,欢迎和我交流一下思路,感激不尽。