GTAV智能驾驶源码详解(一)——制作数据集

项目介绍

场景足够丰富,操作足够简单,有大量的交通工具和驾驶视角可供选择,游戏《Grand Theft Auto 5》是一个相对廉价且适合初级人工智能探索的自动驾驶试验场。

本AI(暂且称之为ScooterV2)借鉴了美国死宅Harrison Kinsley的Charles方案,只使用截图捕捉的画面以模拟摄像头数据作为AI的输入,并没有真实的智能驾驶所涉及的传感器与雷达数据。AI的决策过程目前只停留在CNN(AlexNet)对单张图片进行分类选择操作的阶段,尚未引入记忆,无法处理时间序列数据,因而相比于引入循环神经网络,目前的ScooterV2任然需要大量的数据进行fit训练(目前已完成的ScooterV3采用了强化学习,不需要任何训练数据集,但是由于驾驶场景过于复杂,尚未设计出完美的奖励机制,虽然降低了训练成本但是效果不如目前的V2版本)。

但由于机能限制和存储能力限制(其实是因为不想花太多训练时间,以及经常改方案、丢数据、丢模型),ScooterV2相对于Charles做了一些简化:

Charles的驾驶载体为民用车(GTA5抢劫神车Kuroma装甲轿车),视角为引擎盖视角(为了模拟真实的摄像头),设计目标为保证在当前道路上保持车道行驶的同时尽量避开障碍物(由于Kuroma装甲车速度很快,避开障碍物主要以变道的形式完成),且仿制出了许多真实的智能驾驶模块(前碰撞预警、障碍物探测、行人检测)。

我做的ScooterV2驶载体为摩托车(DoubleT),视角为第三人称视角(为了看到更大的场景区域,为了捕捉到的车道线斜率范围更大,期望以更小的样本量在更少的时间训练出足够好的效果),设计目标为在当前道路上保持车道行驶(使用Mod屏蔽了所有交通和行人),且没有设计其他模块(由于机能限制,串联其它CNN模块会成倍增加单帧处理时间,使得模型的测试效果不美观)。

模型的训练分为三个部分:

  1. 数据集制作:监督式学习,数据集分为input data和label,其中输入数据为经过灰度处理、区域屏蔽和大小缩放的图像数据,截取自1280720分辨率的GTA5游戏画面,缩放为16090的大小;标签数据为每一个图像样本对应的1*3规格的操作向量,分别代表向左、向右、前进(Press A/W/D)。在游戏中人为驾驶5小时,将每一帧图片以及其所对应的操作向量记录在数据集张量中。数据集分5批录制完成,对不同操作所对应的图片进行数量平衡(W:A:D = 8:1:1)打乱数据集后取1000张图片作为测试集,其他的为训练集。
  2. 训练模型:创建AlexNet初始网络,对保存下来的数据集进行拟合。总共进行了约240000次权值更新,学习耗时3天左右。
  3. 测试模型,用getkey函数与keycheck函数定义操作向量与按键联系,用训练好的AlexNet对捕捉到的图片进行分类预测,执行当前类别对应的按键操作以进行驾驶。

制作数据集

导入依赖库:

import numpy as np
import cv2
import time
from grabscreen import grab_screen
from getkeys import key_check
import os

cv2进行图像处理;time用来记录单帧的处理时间;grab_screen从现有的py文件中导入,作用是截取屏幕上的一定区域;key_check用来处理当前帧的操作按键,将其转化为向量。

定义屏蔽函数:

vertices = np.array([[1,60],[1,89],[159,89],[159,60],[80,35],], np.int32)

def roi(img, vertices):
    mask = np.zeros_like(img)   
    cv2.fillPoly(mask, vertices, 255)
    masked = cv2.bitwise_and(img, mask)
    return masked

为了减少图像无用区域对训练过程的影响,需要将图像上方天空区域以及两侧街景进行涂黑屏蔽。
vertices定义了一个区域,roi函数将此区域外的像素用255灰度涂黑。

将操作转化为标签向量:

def keys_to_output(keys):
    output = [0,0,0]
    
    if 'A' in keys:
        output[0] = 1
    elif 'D' in keys: 
        output[2] = 1
    else:
        output[1] = 1
    return output

若按键A,则标签向量为[1,0,0];
若按键d,则标签向量为[0,0,1];
否则,则标签向量为[0,1,0];

数据集录制:

file_name = 'training_data_X.npy'

if os.path.isfile(file_name):
    print('File exists, loading previous data!')
    training_data = list(np.load(file_name))
else:
    print('File does not exist, starting fresh!')
    training_data = []

初始化空数据集,其中’training_data_X.npy’中的X应用数字表示当前录制批次。

def collect():
    
    for i in list(range(10))[::-1]:
        print(i+1)
        time.sleep(1)
        
        
    last_time = time.time()
    
    paused = False
    while True:
        if not paused:
            screenshot =  grab_screen(region=(0,32,1280,752))
            print('Frame took {} seconds'.format(time.time()-last_time))
            last_time = time.time()
            screen = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
            screen = cv2.resize(screen, (160,90))
            screen = roi(screen, [vertices])
            keys = key_check()
            output = keys_to_output(keys)
            training_data.append([screen,output])
            cv2.imshow('window2',screen)

            if cv2.waitKey(25) & 0xFF == ord('q'):
                cv2.destroyAllWindows()
                break

            if len(training_data) % 5000 == 0:
                print(len(training_data))
                np.save(file_name,training_data)
                
        keys = key_check()
        
        if 'T' in keys:
            if paused:
                paused = False
                print('unpaused!')
                time.sleep(1)
            else:
                print('Pausing!')
                paused = True
                time.sleep(1)
  1. 加入倒计时,在执行程序与开始录制数据集之间留下10秒空余,用以调整姿态与视角。
  2. 截取大小为1280*720的游戏区域,并对其进行缩小、灰化和屏蔽操作。
  3. 用key_check提取当前操作按键,并用函数转化为标签向量,与处理过的图片一起append到数据集中。
  4. 每5000帧保存一次数据集;设置T为暂停键。

平衡数据:

import numpy as np
import pandas as pd
from collections import Counter
from numpy.random import shuffle

train_1 = np.load('training_data_1.npy')
print('done1')
train_2 = np.load('training_data_2.npy')
print('done2')
train_3 = np.load('training_data_3.npy')
print('done3')
train_4 = np.load('training_data_4.npy')
print('done4')
train_5 = np.load('training_data_5.npy')
print('done5')

train = np.concatenate([train_1,train_2,train_3,train_4,train_5])

lefts = []
rights = []
forwards = []

shuffle(train)

for data in train:
    img = data[0]
    choice = data[1]

    if choice == [1,0,0]:
        lefts.append([img,choice])
    elif choice == [0,1,0]:
        forwards.append([img,choice])
    elif choice == [0,0,1]:
        rights.append([img,choice])
    else:
        print('no matches')


forwards = forwards[:8*len(lefts)][:8*len(rights)]
lefts = lefts[:len(forwards)]
rights = rights[:len(forwards)]

final_data = forwards + lefts + rights
shuffle(final_data)

np.save('training_data_after_shuffle.npy', final_data)

将5批数据集合并在一起后进行随机排序,并依据不同的标签划分为3个数组,按一定的比例进行截取后合并再进行随机排序,最后保存为training_data_after_shuffle.npy文件

    原文作者:JackieFang
    原文地址: https://segmentfault.com/a/1190000011732542
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞