自动驾驶 CNN 模型训练实战

使用 Keras/TensorFlow 训练卷积神经网络(CNN)模型,实现自动驾驶小车的车道跟随和行为克隆,涵盖数据采集、模型设计、训练技巧和部署优化。

深度学习 CNN 自动驾驶 TensorFlow Keras 行为克隆
自动驾驶 CNN 模型训练实战

自动驾驶 CNN 模型训练实战

在自动驾驶小车项目中,最核心的环节之一就是训练一个能够从摄像头图像预测转向角和油门的神经网络模型。本文将详细介绍如何使用卷积神经网络(CNN)和行为克隆(Behavioral Cloning)方法,训练一个可以自主巡线的自动驾驶模型。

什么是行为克隆?

行为克隆是一种模仿学习(Imitation Learning)方法。简单来说,你手动驾驶小车在赛道上行驶,同时记录摄像头图像和对应的控制指令(转向角、油门)。然后训练一个神经网络,学习从图像到控制指令的映射关系。训练完成后,模型可以根据实时摄像头画面自主驾驶。

数据采集

数据采集是训练过程中最重要的环节。数据质量直接决定模型性能。

使用 DonkeyCar 采集数据

DonkeyCar 框架自带数据采集功能。启动录制模式:

donkey createcar --path ~/mycar
cd ~/mycar
python manage.py drive

在 Web 界面中手动驾驶,系统会自动将图像和控制数据保存到 data/ 目录下的 tub 文件夹中。

数据采集技巧

  1. 多圈采集:至少采集 20-30 圈不同条件的数据
  2. 变化速度:包含不同速度的行驶数据
  3. 纠正数据:记录从偏离状态恢复到正确路线的过程
  4. 数据量:建议至少 5000-10000 条样本

模型架构设计

我们使用一个轻量级的 CNN 架构,适合在 Jetson Nano 上实时推理:

import tensorflow as tf
from tensorflow.keras import layers, models

def build_cnn_model(input_shape=(120, 160, 3)):
    model = models.Sequential([
        # 归一化层
        layers.Lambda(lambda x: x / 255.0 - 0.5, input_shape=input_shape),
        
        # 卷积块 1
        layers.Conv2D(24, (5, 5), strides=(2, 2), activation='relu'),
        layers.BatchNormalization(),
        
        # 卷积块 2
        layers.Conv2D(36, (5, 5), strides=(2, 2), activation='relu'),
        layers.BatchNormalization(),
        
        # 卷积块 3
        layers.Conv2D(48, (5, 5), strides=(2, 2), activation='relu'),
        layers.BatchNormalization(),
        
        # 卷积块 4
        layers.Conv2D(64, (3, 3), activation='relu'),
        layers.BatchNormalization(),
        
        # 卷积块 5
        layers.Conv2D(64, (3, 3), activation='relu'),
        layers.BatchNormalization(),
        
        # 全连接层
        layers.Flatten(),
        layers.Dense(100, activation='relu'),
        layers.Dropout(0.3),
        layers.Dense(50, activation='relu'),
        layers.Dropout(0.3),
        
        # 输出层:转向角和油门
        layers.Dense(2)  # [steering, throttle]
    ])
    
    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
                  loss='mse',
                  metrics=['mae'])
    return model

model = build_cnn_model()
model.summary()

数据加载与预处理

import numpy as np
import json
import cv2
from tensorflow.keras.utils import Sequence

class TubDataGenerator(Sequence):
    def __init__(self, tub_paths, batch_size=64, aug=True):
        self.records = []
        for path in tub_paths:
            with open(f'{path}/manifest.json') as f:
                manifest = json.load(f)
            for record in manifest['records']:
                self.records.append({
                    'image': f"{path}/{record['image']}",
                    'steering': record['steering'],
                    'throttle': record['throttle']
                })
        self.batch_size = batch_size
        self.aug = aug
    
    def __len__(self):
        return len(self.records) // self.batch_size
    
    def __getitem__(self, idx):
        batch = self.records[idx * self.batch_size:(idx + 1) * self.batch_size]
        images, labels = [], []
        
        for record in batch:
            img = cv2.imread(record['image'])
            img = cv2.resize(img, (160, 120))
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            
            # 数据增强
            if self.aug and np.random.random() > 0.5:
                img = cv2.flip(img, 1)
                steering = -record['steering']
            else:
                steering = record['steering']
            
            images.append(img)
            labels.append([steering, record['throttle']])
        
        return np.array(images), np.array(labels)

# 使用
train_gen = TubDataGenerator(['data/tub_1', 'data/tub_2'], batch_size=64)

训练模型

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

callbacks = [
    EarlyStopping(patience=10, restore_best_weights=True),
    ModelCheckpoint('best_model.h5', save_best_only=True, monitor='val_loss')
]

history = model.fit(
    train_gen,
    epochs=100,
    validation_split=0.2,
    callbacks=callbacks,
    verbose=1
)

# 保存最终模型
model.save('autopilot_model.h5')

训练技巧

1. 学习率调度

from tensorflow.keras.callbacks import ReduceLROnPlateau

lr_scheduler = ReduceLROnPlateau(
    factor=0.5, patience=5, min_lr=1e-6, verbose=1
)

2. 类别平衡

如果数据中直线行驶样本过多,可以对转弯数据进行过采样:

# 筛选高转向角数据并重复
high_turn_records = [r for r in records if abs(r['steering']) > 0.3]
records.extend(high_turn_records * 3)  # 重复 3 倍

3. 图像裁剪

去除图像中无关区域(如天空、车身),减少计算量:

# 裁剪顶部 40 像素(天空)和底部 20 像素(车身)
img = img[40:100, :, :]  # 输出 60x160

模型部署

训练完成后,在 Jetson Nano 上加载模型进行实时推理:

import tensorflow as tf

model = tf.keras.models.load_model('autopilot_model.h5')

# 实时推理
def predict(image):
    img = cv2.resize(image, (160, 120))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = np.expand_dims(img, axis=0)
    steering, throttle = model.predict(img)[0]
    return float(steering), float(throttle)

性能优化

对于 Jetson Nano,可以使用 TensorRT 加速推理:

# 转换为 TensorRT 引擎
converter = tf.experimental.tensorrt.Converter(
    input_saved_model_dir='saved_model',
    precision_mode='FP16'
)
converter.convert()
converter.save('tensorrt_model')

总结

训练自动驾驶 CNN 模型的关键在于:高质量的数据采集、合理的模型架构、以及充分的训练调优。通过行为克隆方法,你可以让小车学会在赛道上自主行驶。随着数据量和训练轮次的增加,模型的表现会越来越好。下一步可以尝试更复杂的场景,如多车竞速、障碍物避让等。