OverlordGameDev
Light Weight
- Депозит
- $0
Предисловие:
После написания огромной статьи о разработке игры мне захотелось отвлечься и написать что-то более легкое. Так как я геймдев-разработчик, то и сегодняшняя статья будет о создании софта для игр, а точнее, о построении аима на базе нейронной сети, используя YOLO из библиотеки Ultralytics.Что такое Yolo:
YOLO — это метод из Python-библиотеки, который позволяет распознавать объекты на изображениях и видео. Проще говоря, это нейронная сеть, которая занимается распознаванием объектов. Код, написанный в статье, будет работать с распознанными объектами и информацией о них. В данной статье будет использоваться последняя стабильная версия YOLOv8.Создание обученной модели для определения объектов:
Для того чтобы нейронная сеть могла определять объекты, ее нужно обучить, а именно предоставить файл с материалом, содержащим заранее подготовленные объекты. Для обучения понадобится достаточно большое количество скриншотов из игры, в которой должна работать нейросеть. Минимальное количество скриншотов — примерно 2 тысячи, но чем больше, тем лучше.Скриншоты желательно должны быть размером 640 на 640 пикселей. Около 10% скриншотов должны быть пустыми, то есть без игроков, но в естественной среде игры, например, на карте. Это нужно для того, чтобы нейросеть не определяла случайные объекты как игроков, а имела примеры как игрока, так и всего остального. После подготовки скриншотов необходимо создать разметку на этих изображениях, выделяя объекты игроков. Для этого есть несколько программ и сервисов, таких как: Label Studio, LabelImg и Roboflow. В данной статье будет использован сервис Roboflow.
Чтобы начать разметку, нужно перейти на сайт https://roboflow.com и создать новый проект.
После создания проекта с подготовленными ранее скриншотами нужно начать делать разметку. Это довольно простое, но времязатратное занятие.
Вот и всё. Нужно просто выбрать инструмент выделения и начать обводить объекты игроков. После того как все скриншоты будут обработаны таким способом, нужно получить датасет с этими скриншотами и файлами разметки.
В итоге скачанный датасет с сайта должен выглядеть так же, как и на скриншоте:
Теперь нужно запустить обучение на данном датасете. Для этого потребуется достаточно мощный компьютер. Например, на компьютере с 12-ядерным Xeon и 32 GB ОЗУ, система просто зависала намертво при обучении на датасете с 16K скриншотов. Обучение удалось завершить лишь на компьютере с Ryzen 7, 64 GB ОЗУ и файлом подкачки в 100 GB. Если у вас есть компьютер подобной мощности, для обучения нужно лишь установить библиотеку Ultralytics, затем открыть консоль и ввести следующую команду:
Код: Скопировать в буфер обмена
yolo task=detect mode=train imgsz=640 data=Полный путь до файла data.yaml epochs=45 batch=8 name=Имя для готового файла
Единственное, что вы можете изменить для себя, это количество эпох.
Что такое эпохи при обучении модели:
Эпохи — это количество раз, сколько нейросеть пройдётся по всем данным датасета от начала до конца. С каждой эпохой у неё будет всё больше данных, и определение объектов будет становиться лучше. Однако, если задать слишком большое количество эпох, нейросеть может начать определять случайные объекты, поэтому с этим лучше не перебарщивать.После того как обучение закончится, в папке с датасетом появится файл best.pt. Это и есть готовый файл модели. После его получения можно приступать к написанию логики на Python.
Написание логики на Python:
Инструкции по созданию проекта на Python и открытию его в PyCharm не будет, поэтому сразу можно приступить к написанию кода. Первым делом нужно добавить необходимые библиотеки:Python: Скопировать в буфер обмена
Code:
import os
import torch
from ultralytics import YOLO
import dxcam
Что за библиотека torch и cuda:
Torch — это библиотека, работающая с моделями машинного обучения. В коде она будет использована для определения устройства (CPU или GPU), а также YOLO будет использовать Torch для загрузки обученной модели.CUDA — это технология, которая позволяет использовать видеоядра не только для обработки графики, но и для выполнения других вычислительных задач. В случае текущего софта, на видеоядрах будут обрабатываться скриншоты, что значительно быстрее и эффективнее, чем обработка на процессоре, так как видеоядра значительно более многочисленны. Однако CUDA работает только на видеокартах Nvidia, поэтому производительность нейронной сети может значительно снизиться, если в компьютере не будет соответствующей видеокарты.
С установкой библиотеки Torch совместно с CUDA могут возникнуть некоторые сложности, особенно при установке по отдельности или через PyCharm. Для упрощения процесса можно создать BAT-файл. Но для начала необходимо создать в папке с проектом папку requirements. В этой папке создайте текстовый файл с названием requirements_gpu.txt. Внутри этого файла укажите следующие библиотеки:
Код: Скопировать в буфер обмена
Code:
torch==2.4.0+cu124
torchvision==0.19.0+cu124
--extra-index-url https://download.pytorch.org/whl/cu124
После сохранения файла нужно перейти в корневую папку проекта и создать BAT-файл, внутри которого будет следующая команда:
Код: Скопировать в буфер обмена
Code:
python -m venv venv && call .\venv\Scripts\activate.bat && pip install -r requirements\requirements_gpu.txt
pause
После этого необходимо запустить BAT-файл и дождаться полной установки. Остальные библиотеки можно установить обычным способом. Теперь можно продолжить написание кода. Первое, что нужно сделать, — это создать переменные, в которых будут храниться размеры экрана и размеры FOV (зона, в которой будут делаться скриншоты и детектить объекты).
Python: Скопировать в буфер обмена
Code:
screen_width = 1366
screen_height = 768
fov_width = 500
fov_height = 500
Далее нужно инициализировать CUDA.
Python: Скопировать в буфер обмена
device = 'cuda' if torch.cuda.is_available() else 'cpu'
P.S. device = 'cuda' — если найдена библиотека torch, то функция is_available() проверяет наличие CUDA. Если CUDA доступна, функция возвращает значение больше нуля, и device устанавливается как 'cuda'. Если CUDA недоступна, функция возвращает False, и device устанавливается как 'cpu'.
Теперь нужно открыть модель обучения, используя устройство, которое было определено, то есть CPU или GPU.
Python: Скопировать в буфер обмена
Code:
model = YOLO(os.path.abspath("models\\best.pt"))
model.to(device)
Далее нужно вычислить центр экрана и левый верхний угол FOV, чтобы центрировать его.
Для вычисления центра экрана по координате X берётся ширина экрана, указанная в переменной screen_width, и делится на 2. Полученное значение будет центром по ширине. Например, если ширина экрана 1366 пикселей, то деление на 2 даёт 683 пикселя, и это будет центр по ширине.
Python: Скопировать в буфер обмена
center_x = screen_width // 2
Аналогично ширине, для вычисления центра экрана по координате Y берётся значение из переменной screen_height и делится на 2. Полученное значение будет центром по высоте экрана.
Python: Скопировать в буфер обмена
center_y = screen_height // 2
Теперь переходим к вычислению левого верхнего угла FOV. Ширина прямоугольника FOV (x) делится на 2, затем из центра ширины экрана вычитается разделённая ширина прямоугольника. В итоге получается, что половина прямоугольника находится левее центра экрана.
Python: Скопировать в буфер обмена
fov_x = center_x - fov_width // 2
Также по аналогии нужно делать и с высотой FOV. Высота прямоугольника FOV делится на 2, затем из центра высоты экрана вычитается разделённая высота прямоугольника.
Python: Скопировать в буфер обмена
fov_y = center_y - fov_height // 2
В итоге получается, что половина прямоугольника находится выше центра экрана. Ниже представлен график для большей ясности (надеюсь, он понятен):
Теперь нужно создать скриншоты, которые будут отправляться обученной модели для определения объектов на них.
Функция скриншота:
Для этого первым делом нужно создать функцию screenshot. Внутри функции необходимо инициализировать объект камеры из библиотеки. Для скриншотов в данном случае будет использоваться библиотека dxcam.P.S. Данная библиотека показала себя намного лучше, чем популярная mss, и скорость создания скриншота была в 2 раза выше.
Python: Скопировать в буфер обмена
Code:
import dxcam
def screenshot():
camera = dxcam.create()
Затем нужно создать цикл while True и в нем создать переменную, которая будет захватывать скриншот, начиная с точки (x, y) FOV.
Python: Скопировать в буфер обмена
frame = camera.grab(region=(fov_x, fov_y, fov_x + fov_width, fov_y + fov_height))
Также нужно добавить проверку, чтобы если скриншот пуст, цикл начинал выполнение заново.
Python: Скопировать в буфер обмена
Code:
if frame is None:
continue
Далее нужно вызывать функцию (которой пока еще нет), которая будет обрабатывать полученные скриншоты и определять на них объекты (в качестве аргумента frame передается объект скриншота).
Python: Скопировать в буфер обмена
detection(frame)
С функцией создания скриншота закончено. Теперь нужно создать функцию для обработки скриншота и определения на нем объектов.
Функция обработки и детекции объектов на скриншотах:
Функция будет называться detection. Внутри созданной функции первым делом нужно инициализировать объект с информацией о первом обработанном скриншоте.Python: Скопировать в буфер обмена
results = model.predict(frame, verbose=True)[0]
model.predict — это функция, которая возвращает список с найденными объектами на обработанных скриншотах. verbose=True — это флаг, который означает, что будет выводиться информация о результате обнаружения. [0] означает, что будет извлечен объект с информацией о первом скриншоте. В данном коде в любом случае будет только один объект, так как в обработку всегда идет только один скриншот, но все же это нужно уточнять.
Далее нужно написать условие if, в котором будет проверяться, чтобы в объекте скриншота были объекты игроков.
Python: Скопировать в буфер обмена
if len(results.boxes) > 0:
Работа продолжается, если в объекте boxes (который находится внутри объекта первого скриншота) есть хотя бы один объект с информацией о найденных объектах игрока..
Далее, если объекты игроков найдены, необходимо инициализировать первый объект игрока.
Python: Скопировать в буфер обмена
box = results.boxes[0]
Затем нужно инициализировать координаты первого найденного игрока.
Python: Скопировать в буфер обмена
box_cord = box.xyxy[0].tolist()
Далее нужно вычислить центр объекта игрока по координатам x и y.
Python: Скопировать в буфер обмена
Code:
center_x_obj = (box_cord[0] + box_cord[2]) / 2
center_y_obj = (box_cord[1] + box_cord[3]) / 2
Теперь нужно вычислить центр объекта по координатам x и y относительно экрана.
Python: Скопировать в буфер обмена
Code:
center_x_obj += fov_x
center_y_obj += fov_y
Пример:
Код: Скопировать в буфер обмена
Code:
center_x_obj += fov_x эквивалентно center_x_obj = center_x_obj + fov_x.
center_y_obj += fov_y эквивалентно center_y_obj = center_y_obj + fov_y.
Далее можно добавить настройку высоты по y, чтобы была возможность стрелять не в центр объекта, а в голову, если есть желание, но это не обязательно.
Python: Скопировать в буфер обмена
center_y_obj -= (box_cord[3] - box_cord[1]) * aim_target
В выражении box_cord[3] - box_cord[1] вычисляется высота объекта по y, после чего это значение умножается на aim_target.
Теперь необходимо вычислить расстояние объекта от центра экрана.
Python: Скопировать в буфер обмена
Code:
dx = center_x_obj - center_x
dy = center_y_obj - center_y
После этого нужно вызвать функцию наводки (которой пока что нет) и передать в нее переменные dx и dy.
Python: Скопировать в буфер обмена
aim(dx, dy)
Функция наводки на найденный объект:
Теперь можно приступить к написанию функции наводки на найденный объект игрока. Для этого нужно создать функцию с названием aim, принимающую переменные dx и dy, и внутри этой функции добавить проверку if на нажатие левой кнопки мыши. Для работы с мышью будет использоваться библиотека win32api.Python: Скопировать в буфер обмена
Code:
import win32api
import win32con
def aim(dx, dy):
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
Внутри проверки if нужно разделить расстояния до объекта на несколько равных частей, указанных в переменной aim_step (которую тоже нужно создать).
Python: Скопировать в буфер обмена
Code:
aim_step = 2
aim_step_x = dx / aim_step
aim_step_y = dy / aim_step
Далее нужно создать цикл, который будет повторяться столько раз, сколько указано в переменной aim_step. Внутри цикла нужно перемещать мышь, используя библиотеку win32api, на координаты, указанные в aim_step_x и aim_step_y.
Python: Скопировать в буфер обмена
Code:
for i in range(aim_step):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(aim_time_sleep)
P.S. Для time.sleep нужно импортировать библиотеку time.
На этом вся нейронная сеть готова. Осталось лишь задать запуск функции создания скриншота при запуске программы.
Python: Скопировать в буфер обмена
Code:
if __name__ == "__main__":
screenshot()
Весь код нейронной сети:
Python: Скопировать в буфер обмена
Code:
import os
import time
import win32api
import torch
import win32con
from ultralytics import YOLO
import dxcam
screen_width = 1366
screen_height = 768
fov_width = 500
fov_height = 500
aim_step = 2
aim_time_sleep = 0.017
aim_target = 0.25
device = 'cuda' if torch.cuda.is_available() else 'cpu'
if torch.cuda.is_available():
print("Используется CUDA")
else:
print("Используется CPU")
model = YOLO(os.path.abspath("models\\best.pt"))
model.to(device)
center_x = screen_width // 2
center_y = screen_height // 2
fov_x = center_x - fov_width // 2
fov_y = center_y - fov_height // 2
def screenshot():
camera = dxcam.create()
while True:
frame = camera.grab(region=(fov_x, fov_y, fov_x + fov_width, fov_y + fov_height))
if frame is None:
continue
detection(frame)
def aim(dx, dy):
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
aim_step_x = dx / aim_step
aim_step_y = dy / aim_step
for i in range(aim_step):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(aim_time_sleep)
def detection(frame):
results = model.predict(frame, verbose=True)[0]
if len(results.boxes) > 0:
box = results.boxes[0]
box_cord = box.xyxy[0].tolist()
center_x_obj = (box_cord[0] + box_cord[2]) / 2
center_y_obj = (box_cord[1] + box_cord[3]) / 2
center_x_obj += fov_x
center_y_obj += fov_y
center_y_obj -= (box_cord[3] - box_cord[1]) * aim_target
dx = center_x_obj - center_x
dy = center_y_obj - center_y
aim(dx, dy)
if __name__ == "__main__":
screenshot()
Статья в виде документа: https://docs.google.com/document/d/1ny3V6XrqH89oT2YxsPbTk_scHVG0Yqv9PRrklAyFSjk/edit?usp=sharing
Вывод:
По итогу вся нейронная сеть вышла в 70 строк и уже работает достойно, позволяя эффективно стрелять по игрокам. Основная сложность в написании этой нейронной сети — это сделать правильные математические вычисления, а в остальном все понятно. Достаточно лишь прочитать документацию по использованию библиотеки ultralytics.Сделано OverlordGameDev специально для форума XSS.IS