Когда только вышла платформа OpenAI’s Universe и появилось множество статей с описанием того, что на ней может быть запущена даже GTA V, было очень интересно следить за всем этим. Однако потом, по непонятным причинам, GTA V была полностью исключена из списка возможных игр.
Хотя данная идея казалась крайне заманчивой, о ней пришлось на время забыть. Но затем мы решили вернуться к этому вопросу еще раз и подумать, а нужна ли вообще для подобных задач платформа Open AI. Конечно, для игр попроще, где можно тренироваться на нескольких тысячах итераций за один раз, это вполне реально, но вот для игр вроде GTA V это не вариант.
На самом деле не вполне очевидно: почему именно GTA V?
Grand Theft Auto (GTA) V по многим причинам является великолепным окружением для целого ряда практических задач моделирования. Это огромный мир с бесконечным количеством вещей, которые вы можете делать. Но давайте просто подумаем о самом простом: беспилотные автомобили. В GTA V мы можем использовать моды для контроля времени дня, погоды, скорости, происходящего при аварии и так далее — просто всего на свете. Это полностью настраиваемая среда.
Некоторые наши пособия тщательно спланированы, некоторые частично, а какие-то — абсолютно нет. Вот эта статья не планировалась совершенно, и мы просто стараемся работать над данной задачей. Конечно, мы понимаем, что не у всех есть GTA V, но мы считаем, что если вы играете в аналогичные игры и выполняете схожие задачи, то данный опыт может быть вам полезен. Поскольку, чтобы все заработало. вам придется самостоятельно настроить многоие вещи, эта серия статей не совсем для новичков.
Наша основная цель — это просто создать своего рода беспилотный автомобиль. Для этого подойдет любая игра с полосами движения и автомобилями. Метод, который мы будем использовать для доступа к игре, должен быть практически универсальным. Причем чем проще игра, тем проще это будет сделать. Такие штуки, как солнечные блики в GTA V, делают задачу компьютерного зрения гораздо сложней, но и намного реалистичней.
Также с таким подходом можно работать и с другими играми. Думаю, мы можем научить AI играть, просто показывая ему, как мы играем, передавая эту информацию в сверточную нейронную сеть, которая потом сама начнет играть.
Вот наши предварительные мысли. Несмотря на то, что у нас нет пока законченного решаная на Python, мы можем:
- Получить доступ к кадрам экрана.
- Имитировать нажатия клавиш (библиотеки sendkeys, pyautogui и вероятно некоторые другие).
Этого должно хватить для большинства элементарных задач, но как насчет глубокого обучения? На самом деле единственное, что нам может еще понадобиться, это то, что также сможет регистрировать различные события из игрового мира. Но большинство игр воспроизводятся почти полностью визуально, а мы уже можем успешно работать с этим, а также можем отслеживать положение мыши и нажатия клавиш. Именно это позволяет нам приступить к глубокому обучению.
Вряд ли на нашей дороге не будет ям и ухабов, но мы уверены, что задача решаема и из нее может выйти большой, интересный проект. Наша главная проблема в том, что мы должны все обрабатывать в реальном времени и достаточно быстро. Но мы думаем, что справимся, во всяком случае, стоит попробовать.
Так как это достаточно большой проект, нам надо его несколько структурировать и разбить на шаги, иначе мы можем запутаться. Мы думаем, что сначала надо сделать некий жестокий минимум. Итак, вот наши первоочередные цели:
- Получить доступ к игровому экрану с более-менее приличной кадровой частотой (FPS). Все, что больше 5-ти, нам пойдет. Это не очень приятно для глаз, но мы всегда можем смотреть на реальный игровой экран.
- Посылать клавиатурные команды на игровой экран. Мы уверены, что это можно сделать очень просто, но все равно надо убедиться.
- Попробовать посылать команды джойстика, если это возможно (особенно учитывая переключение скоростей и повороты).
- Использовать библиотеку OpenCV для обработки игровых фреймов, не слишком при этом затруднив обработку кадровой частоты.
- Создать простой беспилотный автомобиль, способный двигаться среди полос движения в простых условиях (чистое небо, день, нет дождя и нет трафика).
Итак, шаг первый. Как же нам в действительности получить доступ к экрану? Мы не сомневаемся, что это возможно, но вот как — пока не знаем. Но для этого есть Google! Там мы нашли несоклько ответов, остановимся вот на этом. Но в нем, кажется ошибка при импорте: ImageGrab
у них это часть PIL
.
import numpy as np import ImageGrab import cv2 while(True): printscreen_pil = ImageGrab.grab() printscreen_numpy = np.array(printscreen_pil.getdata(),dtype=uint8)\ .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) cv2.imshow('window',printscreen_numpy) if cv2.waitKey(25) & 0xFF == ord('q'): cv2.destroyAllWindows() break
Результат:
ImportError Traceback (most recent call last) <ipython-input-3-00f897cb4216> in <module>() 1 import numpy as np ----> 2 import ImageGrab 3 import cv2 4 5 while(True):
Ок, уговорили, и правда ImageGrab
— это часть PIL
.
import numpy as np from PIL import ImageGrab import cv2 while(True): printscreen_pil = ImageGrab.grab() printscreen_numpy = np.array(printscreen_pil.getdata(),dtype=uint8)\ .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) cv2.imshow('window',printscreen_numpy) if cv2.waitKey(25) & 0xFF == ord('q'): cv2.destroyAllWindows() break
Результат:
NameError Traceback (most recent call last) <ipython-input-4-545ecbe36422> in <module>() 5 while(True): 6 printscreen_pil = ImageGrab.grab() ----> 7 printscreen_numpy = np.array(printscreen_pil.getdata(),dtype=uint8) .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) 8 cv2.imshow('window',printscreen_numpy) 9 if cv2.waitKey(25) & 0xFF == ord('q'): NameError: name 'uint8' is not defined
Опять проблемы! Параметр dtype
должен быть строкового вида. Они там вообще свой код запускали?!
import numpy as np from PIL import ImageGrab import cv2 def screen_record(): while True: printscreen_pil = ImageGrab.grab() printscreen_numpy = np.array(printscreen_pil.getdata(),dtype='uint8')\ .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) cv2.imshow('window',printscreen_numpy) if cv2.waitKey(25) & 0xFF == ord('q'): cv2.destroyAllWindows() break
Отлично, в некоторой степени работает. Но слишком большой размер и слишком медленно. Давайте поработаем с размером изображения.
Примечание переводчика: модуль ImageGrab
работает только в Windows и Mac OS. Для работы в Linux замените строчку from PIL import ImageGrab
на import pyscreenshot as ImageGrab
. Не забыв, конечно, предварительно установить у себя pyscreenshot
(можно при помощи менеджера pip
). Правда, надо сказать, что GTA V работает только под Windows (и конечно, на игровых приставках), но под Linux тоже есть много игр, где ездят автомобили.
import numpy as np from PIL import ImageGrab import cv2 def screen_record(): while True: # 800x600 windowed mode printscreen_pil = ImageGrab.grab(bbox=(0,40,800,640)) printscreen_numpy = np.array(printscreen_pil.getdata(),dtype='uint8')\ .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) cv2.imshow('window',cv2.cvtColor(printscreen_numpy, cv2.COLOR_BGR2RGB)) if cv2.waitKey(25) & 0xFF == ord('q'): cv2.destroyAllWindows() break
Отлично, с размером решили вопрос, но все еще проблемы со скоростью: у нас только 2-3 фрейма в секунду. Давайте посмотрим, почему.
import numpy as np from PIL import ImageGrab import cv2 import time def screen_record(): last_time = time.time() while True: # 800x600 windowed mode printscreen_pil = ImageGrab.grab(bbox=(0,40,800,640)) printscreen_numpy = np.array(printscreen_pil.getdata(),dtype='uint8')\ .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) print('loop took {} seconds'.format(time.time()-last_time)) last_time = time.time() ## cv2.imshow('window',cv2.cvtColor(printscreen_numpy, cv2.COLOR_BGR2RGB)) ## if cv2.waitKey(25) & 0xFF == ord('q'): ## cv2.destroyAllWindows() ## break
По-прежнему 2-3 фрейма в секунду, стало быть, дело не в imshow
.
import numpy as np from PIL import ImageGrab import cv2 import time def screen_record(): last_time = time.time() while True: # 800x600 windowed mode printscreen_pil = ImageGrab.grab(bbox=(0,40,800,640)) ## printscreen_numpy = np.array(printscreen_pil.getdata(),dtype='uint8')\ ## .reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) print('loop took {} seconds'.format(time.time()-last_time)) last_time = time.time() ## ## cv2.imshow('window',cv2.cvtColor(printscreen_numpy, cv2.COLOR_BGR2RGB)) ## if cv2.waitKey(25) & 0xFF == ord('q'): ## cv2.destroyAllWindows() ## break
О, вот это уже кое-что:
loop took 0.05849909782409668 seconds loop took 0.044053077697753906 seconds loop took 0.04760456085205078 seconds loop took 0.04805493354797363 seconds loop took 0.05989837646484375 seconds
Теперь для метода imshow
библиотеки OpenCV
нам в действительности нужен массив numpy
. Что если вместо выполнения всех этих методов .getdata
и reshape
мы просто преобразуем ImageGrab.grab (bbox = (0,40,800,640))
в массив numpy
? Зачем менять форму? Это уже тот размер, который нам нужен, и возможно, метод .getdata
нам не потребуется.
import numpy as np from PIL import ImageGrab import cv2 import time def screen_record(): last_time = time.time() while(True): # 800x600 windowed mode printscreen = np.array(ImageGrab.grab(bbox=(0,40,800,640))) print('loop took {} seconds'.format(time.time()-last_time)) last_time = time.time() cv2.imshow('window',cv2.cvtColor(printscreen, cv2.COLOR_BGR2RGB)) if cv2.waitKey(25) & 0xFF == ord('q'): cv2.destroyAllWindows() break
Отлично, это дает нам 12-13 фреймов в секунду. Не бог весть что, но для нашей работы пойдет.
Размер параметра bbox
был выбран исходя из разрешения GTA V в оконном режиме.
Продолжение — Играем в GTA V c Python. Часть II: основы OpenCV.