基于深度学习的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_heightimage_widthimage_channels:定义图像的高度、宽度和通道数。
  • centerleftright:分别表示中心、左、右摄像头拍摄的图像路径。
  • 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_xrange_y:分别定义 x 和 y 方向的平移范围。
  • tran_xtran_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_model1build_model2build_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 无人驾驶系统。从数据预处理到模型构建,再到模型训练和最终的自动驾驶实现,每个环节都至关重要。初学者可以按照上述步骤,逐步理解和运行代码,深入学习无人驾驶技术的实现原理。同时,无人驾驶技术仍面临着许多挑战,如安全性、法律法规等问题,需要我们不断地探索和研究。

posted @ 2025-05-30 14:17  Lovcsy  阅读(62)  评论(0)    收藏  举报