基于深度学习的Udacity无人驾驶系统
基于深度学习的 Udacity 无人驾驶系统
一、引言
在科技迅猛发展的当下,无人驾驶技术成为了人工智能领域极具潜力的发展方向。它不仅有望彻底改变我们的出行方式,还能极大地提高交通效率、减少交通事故。本文将深入介绍基于深度学习的 Udacity 无人驾驶系统的完整实现过程,包含数据预处理、模型构建、模型训练以及最终的自动驾驶实现,同时会为代码添加详细解释,方便初学者理解并运行。
二、数据预处理
数据预处理是深度学习项目的基石,直接关系到模型的训练效果和性能。本项目的数据预处理涵盖数据扩充、丰富数据类型和数据归一化等关键步骤。
2.1 导入必要的第三方库
import cv2
import numpy as np
cv2
:OpenCV 库,用于图像处理,如读取、裁剪、调整大小和颜色空间转换等操作。numpy
:用于数值计算,处理数组和矩阵。
2.2 设置初始化变量
image_height, image_width, image_channels = 66, 200, 3
center, left, right = 'test/center.jpg', 'test/left.jpg', 'test/right.jpg'
steering_angle = 0.0
image_height
、image_width
、image_channels
:定义图像的高度、宽度和通道数。center
、left
、right
:分别表示中心、左、右摄像头拍摄的图像路径。steering_angle
:初始转向角。
2.3 图像选择
def image_choose(center, left, right, steering_angle):
choice = np.random.choice(3)
if choice == 0:
image_name = center
bias = 0.0
if choice == 1:
image_name = left
bias = 0.2
if choice == 2:
image_name = right
bias = -0.2
image = cv2.imread(image_name)
steering_angle = steering_angle + bias
return image, steering_angle
np.random.choice(3)
:随机选择 0、1、2 中的一个数字。- 根据选择的数字,决定使用中心、左或右摄像头的图像,并相应地调整转向角。
cv2.imread(image_name)
:读取选定的图像。
2.4 图像翻转
def image_flip(image, steering_angle):
if np.random.rand() < 0.5:
image = cv2.flip(image, 1)
steering_angle = -steering_angle
return image, steering_angle
np.random.rand() < 0.5
:以 50% 的概率进行图像翻转。cv2.flip(image, 1)
:水平翻转图像。- 翻转图像时,同时改变转向角的符号。
2.5 图像平移
def image_translate(image, steering_angle):
range_x, range_y = 100, 10
tran_x = int(range_x * (np.random.rand() - 0.5))
tran_y = int(range_y * (np.random.rand() - 0.5))
tran_m = np.float32([[1, 0, tran_x], [0, 1, tran_y]])
image = cv2.warpAffine(image, tran_m, (image.shape[1], image.shape[0]))
steering_angle = steering_angle + tran_x * 0.002
return image, steering_angle
range_x
、range_y
:分别定义 x 和 y 方向的平移范围。tran_x
、tran_y
:随机生成 x 和 y 方向的平移量。tran_m
:平移矩阵。cv2.warpAffine(image, tran_m, (image.shape[1], image.shape[0]))
:对图像进行平移操作。- 根据 x 方向的平移量调整转向角。
2.6 图像归一化
def image_normalized(image):
image = image[60:-25, :, :]
image = cv2.resize(image, (image_width, image_height), cv2.INTER_AREA)
image = cv2.cvtColor(image, cv2.COLOR_RGB2YUV)
return image
image[60:-25, :, :]
:裁剪图像,去除上方的天空和下方的车头部分。cv2.resize(image, (image_width, image_height), cv2.INTER_AREA)
:将图像调整为指定的大小。cv2.cvtColor(image, cv2.COLOR_RGB2YUV)
:将图像从 RGB 颜色空间转换为 YUV 颜色空间,有助于提高模型的训练效果。
2.7 图像预处理函数
def image_preprocessing(center, left, right, steering_angle):
image, steering_angle = image_choose(center, left, right, steering_angle)
image, steering_angle = image_flip(image, steering_angle)
image, steering_angle = image_translate(image, steering_angle)
return image, steering_angle
将图像选择、翻转、平移操作组合成一个预处理函数。
三、模型构建
本项目构建了三个不同的卷积神经网络模型,分别为build_model1
、build_model2
和build_model3
。
3.1 导入必要的第三方库
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Dropout, Flatten, Lambda, MaxPool2D
from preprocessing_2 import image_height, image_width, image_channels
Sequential
:用于构建顺序模型。Conv2D
:卷积层,用于提取图像特征。Dense
:全连接层,用于 “降维” 和输出结果。Dropout
:防止过拟合,随机丢弃一部分神经元。Flatten
:将多维数据扁平化为一维数据。Lambda
:自定义层,用于对输入数据进行归一化处理。MaxPool2D
:最大池化层,降低输出维度。
3.2 构建模型
Input_size = (image_height, image_width, image_channels)
def build_model1():
model = Sequential()
model.add(Lambda(lambda x: x / 127.5 - 1, input_shape=Input_size))
model.add(Conv2D(filters=24, kernel_size=(5, 5), strides=(2, 2), activation='relu'))
model.add(MaxPool2D())
model.add(Dropout(0.25))
model.add(Conv2D(filters=32, kernel_size=(5, 5), strides=(2, 2), activation='relu'))
model.add(MaxPool2D())
model.add(Flatten())
model.add(Dense(32))
model.add(Dropout(0.20))
model.add(Dense(16))
model.add(Dense(1))
model.summary()
return model
def build_model2():
model = Sequential()
model.add(Lambda(lambda x: x / 127.5 - 1, input_shape=Input_size))
model.add(Conv2D(filters=24, kernel_size=(5, 5), strides=(2, 2), activation='elu'))
model.add(Conv2D(filters=36, kernel_size=(5, 5), strides=(2, 2), activation='elu'))
model.add(Conv2D(filters=48, kernel_size=(5, 5), strides=(2, 2), activation='elu'))
model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), activation='elu'))
model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), activation='elu'))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(100, activation='elu'))
model.add(Dense(50, activation='elu'))
model.add(Dense(10, activation='elu'))
model.add(Dense(1))
model.summary()
return model
def build_model3():
model = Sequential()
model.add(Lambda(lambda x: x / 127.5 - 1, input_shape=Input_size))
model.add(Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='elu'))
model.add(Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='elu'))
model.add(MaxPool2D((2, 2), padding='same'))
model.add(Dropout(0.5))
model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='elu'))
model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='elu'))
model.add(MaxPool2D((2, 2), padding='same'))
model.add(Dropout(0.5))
model.add(Conv2D(filters=128, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='elu'))
model.add(Conv2D(filters=128, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='elu'))
model.add(MaxPool2D((2, 2), padding='same'))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(64, activation='elu'))
model.add(Dense(16, activation='elu'))
model.add(Dense(1))
model.summary()
return model
Lambda(lambda x: x / 127.5 - 1, input_shape=Input_size)
:对输入数据进行归一化处理,将数据范围缩放到 [-1, 1] 之间。Conv2D
:卷积层,通过不同数量的卷积核和不同大小的卷积核提取图像特征。MaxPool2D
:最大池化层,降低特征图的维度,减少计算量。Dropout
:防止过拟合,随机丢弃一部分神经元。Flatten
:将多维的特征图扁平化为一维向量。Dense
:全连接层,用于 “降维” 和输出最终结果。
四、模型训练
4.1 导入必要的第三方库
import cv2
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, TensorBoard
from preprocessing_2 import image_height, image_width, image_channels
from preprocessing_2 import image_preprocessing, image_normalized
from build_model_3 import build_model1, build_model2, build_model3
pandas
:用于读取和处理 CSV 文件。train_test_split
:将数据集划分为训练集和测试集。Adam
:优化器,用于更新模型的参数。ModelCheckpoint
:在训练过程中保存最佳模型。EarlyStopping
:当验证损失不再下降时,提前停止训练,防止过拟合。TensorBoard
:可视化训练过程,监控模型的性能。
4.2 设置初始化变量
data_path = 'data_mountain/'
test_ration = 0.1
batch_size = 100
batch_num = 200
epoch = 50
- data_path`:数据集的路径。
test_ration
:测试集的比例,这里表示将数据集的 10% 作为测试集。batch_size
:每个批次的样本数量。batch_num
:每个训练轮次的批次数量。epoch
:训练的轮数。
4.3 导入数据
def load_data(data_path):
pd_read_csv = pd.read_csv(data_path + 'driving_log.csv', names=['center', 'left', 'right', 'steering', '_', '__', '___'])
X = pd_read_csv[['center', 'left', 'right']].values
Y = pd_read_csv['steering'].values
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=test_ration, random_state=0)
return X_train, X_test, Y_train, Y_test
X_train, X_test, Y_train, Y_test = load_data(data_path)
pd.read_csv
:读取 CSV 文件,包含中心、左、右摄像头的图像路径和对应的转向角。train_test_split
:将数据集划分为训练集和测试集。
4.4 创建数据生成器
def batch_generator(data_path, batch_size, X_data, Y_data, train_flag):
image_container = np.empty([batch_size, image_height, image_width, image_channels])
steer_container = np.empty(batch_size)
while True:
ii = 0
for index in np.random.permutation(X_data.shape[0]):
center, left, right = data_path + X_data[index]
steering_angle = Y_data[index]
if train_flag and np.random.rand() < 0.6:
image, steering_angle = image_preprocessing(center, left, right, steering_angle)
else:
image = cv2.imread(center)
image_container[ii] = image_normalized(image)
steer_container[ii] = steering_angle
ii += 1
if ii == batch_size:
break
yield image_container, steer_container
image_container
:用于存储一批图像数据。steer_container
:用于存储一批转向角数据。np.random.permutation(X_data.shape[0])
:随机打乱数据的索引。image_preprocessing
:对图像进行预处理,增加数据的多样性。image_normalized
:对图像进行归一化处理。yield
:生成器函数,每次返回一批数据。
4.5 训练模型
model = build_model2()
checkpoint = ModelCheckpoint(
'zkk_mountain_model2_{epoch:03}.h5',
monitor='val_loss',
verbose=1,
save_best_only=True,
mode='auto'
)
stopping = EarlyStopping(
monitor='val_loss',
min_delta=0.001,
patience=200,
verbose=1,
mode='auto'
)
tensor_board = TensorBoard(
log_dir='./logs',
histogram_freq=1,
write_graph=1,
write_images=0
)
model.compile(optimizer=Adam(learning_rate=0.0001), loss='mse', metrics=['accuracy'])
model.fit(
batch_generator(data_path, batch_size, X_train, Y_train, True),
steps_per_epoch=batch_num,
epochs=epoch,
verbose=1,
validation_data=batch_generator(data_path, batch_size, X_test, Y_test, False),
validation_steps=1,
max_queue_size=1,
callbacks=[checkpoint, stopping, tensor_board]
)
ModelCheckpoint
:在每个训练轮次结束后,根据验证损失保存最佳模型。EarlyStopping
:当验证损失在 200 个轮次内没有下降超过 0.001 时,提前停止训练。TensorBoard
:将训练过程的日志保存到./logs
目录下,方便可视化。model.compile
:编译模型,指定优化器、损失函数和评估指标。model.fit
:训练模型,指定训练数据生成器、训练轮次、验证数据生成器等参数。
4.6 保存模型
model.save('./mountain_model/zkk_mountain_model2.h5')
将训练好的模型保存到指定的文件中。
五、自动驾驶实现
5.1 导入必要的第三方库
import socketio
import eventlet.wsgi
from flask import Flask
import base64, cv2
from io import BytesIO
from PIL import Image
import numpy as np
from tensorflow.keras.models import load_model
from preprocessing_2 import image_normalized
socketio
:用于与模拟器进行实时通信。eventlet.wsgi
:处理网络请求。Flask
:轻量级 Web 框架,用于搭建服务器。base64
:用于解码图像数据。cv2
:OpenCV 库,用于图像处理。PIL
:Python Imaging Library,用于处理图像。load_model
:加载训练好的模型。
5.2 设置初始化变量
class SimplePIControl:
def __init__(self, KP, KI):
self.KP = KP
self.KI = KI
self.error = 0.0
self.set_point = 0.0
self.integral = 0.0
self.throttle = 0.0
def set_desired(self, desired):
self.set_point = desired
def updated(self, measurement):
self.error = self.set_point - measurement
self.integral += self.error
self.throttle = self.error * self.KP + self.integral * self.KI
return self.throttle
controller = SimplePIControl(0.1, 0.002)
set_speed = 20
controller.set_desired(set_speed)
steering_angle = -0.02
throttle = 0.3
SimplePIControl
:定义一个简单的 PI 控制器,用于控制车速。controller
:创建 PI 控制器对象。set_speed
:设定目标车速。steering_angle
:初始转向角。throttle
:初始油门值。
5.3 创建网络连接
sio = socketio.Server()
app = Flask(__name__)
app = socketio.WSGIApp(sio, app)
socketio.Server()
:创建一个 Socket.IO 服务器。Flask(__name__)
:创建一个 Flask 应用。socketio.WSGIApp(sio, app)
:将 Socket.IO 服务器和 Flask 应用结合起来。
5.4 传递参数,控制汽车运行
@sio.on('connect')
def on_connect(sid, environ):
print('与模拟器连接成功')
@sio.on('telemetry')
def on_telemetry(sid, data):
if data:
speed = float(data['speed'])
image = Image.open(BytesIO(base64.b64decode(data['image'])))
image = np.array(image)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
cv2.imshow('Image from Udacity Simulator', image)
cv2.waitKey(1)
image = image_normalized(image)
steering_angle = float(model.predict(np.array([image])))
throttle = controller.updated(speed)
send_control(steering_angle, throttle)
else:
sio.emit('manual', data={})
@sio.on('disconnect')
def on_connect(sid):
print('与模拟器断开连接')
def send_control(steering_angle, throttle):
sio.emit('steer', data={
'steering_angle': steering_angle.__str__(),
'throttle': throttle.__str__()
})
@sio.on('connect')
:当与模拟器建立连接时,触发该事件。@sio.on('telemetry')
:当接收到模拟器发送的遥测数据时,触发该事件。base64.b64decode(data['image'])
:解码图像数据。model.predict(np.array([image]))
:使用训练好的模型预测转向角。controller.updated(speed)
:根据当前车速更新油门值。send_control(steering_angle, throttle)
:将转向角和油门值发送给模拟器。
5.5 启动运行
eventlet.wsgi.server(eventlet.listen(('', 4567)), app)
启动服务器,监听 4567 端口,等待模拟器的连接。
六、总结
通过以上步骤,我们实现了基于深度学习的 Udacity 无人驾驶系统。从数据预处理到模型构建,再到模型训练和最终的自动驾驶实现,每个环节都至关重要。初学者可以按照上述步骤,逐步理解和运行代码,深入学习无人驾驶技术的实现原理。同时,无人驾驶技术仍面临着许多挑战,如安全性、法律法规等问题,需要我们不断地探索和研究。