• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Переносим пушистого в майнкрафт через нейросети.

raoulduke666

RAID-массив
Пользователь
Регистрация
08.10.2024
Сообщения
74
Реакции
102
Автор: raoulduke666
Написано специально для xss.pro (c)

Привет, читатель.

В этой статье описано краткое руководство по переносу любых объектов из реальной жизни в майнкрафт через модели компьютерного зрения yolo. Попробуем перенести кошку!

Изначально я хотел собрать компьютер в майнкрафте, но было лень его строить. Поэтому стал искать инструмент, который сможет каким либо образом спавнить блоки, желательно через код. Таким оказался mcpi. Эта библиотека позволяет взаимодействовать с запущенным миром в майнкрафте через python код и мод "Raspberry Jam Mod" . Сборку компьютера я бы мог перекинуть на llm, но это было бы слишком просто. Поэтому я сместил свой фокус внимания на что то интереснее, например CV (Computer Vision) в контексте майнкрафта. Через yolo сегменетацию можно получить координаты маски и заспавнить соответствующие блоки. Осталось соединить mcpi + yolo11n-seg и посмотреть что из этого может получиться.

kotik.gif
syka.gif


Лица деревенских жителей представили ?
IMG_8234.gif

yolo можно запускать на CPU, но он будет загружен генерацией блоков в майнкрафте, поэтому будет хорошо если у вас GPU nvidia 30-50 серия, для инференса yolo 8гб видеопамяти хватит с головой. Если вы будете обучать yolo-seg на своем датасете, то это нужно делать на GPU, на CPU это займет несколько суток.

Окружение
  • Python: 3.12.7
  • YOLO: yolo11n-seg
https://docs.ultralytics.com/ru/tasks/segment/ офф сайт ultralytics с моделями сегментации. Скачивайте самую маленькую yolo11n-seg.
  • Minecraft: 2.12.2 forge
  • Raspberry Jam Mod: 2.12.2
https://github.com/arpruss/raspberryjammod/releases нужно скачать mods. В папке 2.12.2 лежит RaspberryJamMod, его нужно перенести в "C:\Users\"Имя"\AppData\Roaming\.minecraft\mods"

  • CUDA: 12.4
https://developer.nvidia.com/Cuda-downloads CUDA дает возможность использовать GPU для вычислений.
  • PyTorch с поддержкой CUDA: 2.5.1+cu124
Выбираем параметры под систему и скачиваем, это необходимо чтобы запустить на GPU https://pytorch.org/get-started/locally/
PyTorch позволяет работать с вычислениями на GPU за счет CUDA.

Используйте этот код чтобы проверить CUDA:
Python:
import torch

if torch.cuda.is_available():
    print("CUDA доступен")
    print(f"Количество доступных GPU: {torch.cuda.device_count()}")
    for i in range(torch.cuda.device_count()):
        print(f"GPU {i}: {torch.cuda.get_device_name(i)}")
else:
    print("CUDA не доступен.")
print(torch.__version__)

pip install opencv-python
pip install numpy
pip install ultralytics
pip install mcpi

Я не ставлю opencv-python потому что в этой версии меньше инструментов для трекинга, opencv-contrib-python в этом смысле жирнее, но для текущего проекта opencv-python достаточно. Кому будет интересно углубиться в CV используйте opencv-contrib-python.

Руководство
Python:
import cv2
import numpy as np
from ultralytics import YOLO
from mcpi.minecraft import Minecraft
import time

#Путь к видео
video_path = "Путь/к/видео.mp4"

#Параметры сегментации
DETECTION_CLASSES = [0]  # класс / классы
DETECTION_CONF = 0.75    # порог уверенности сегментации
INPUT_WIDTH = 640        # Ширина для обработки (сохраняет пропорции)
DETECTION_IMGSZ = 480    # Размер изображения для ИИ.
MASK_THRESHOLD = 0.4     # Порог маски (чем выше, тем четче края)
SMOOTH_MASK = True       # Сглаживание маски (улучшает качество в Minecraft)

# Параметры вокселизации
height_blocks = 100
width_blocks = 70
depth_blocks = 1


# Подключение к minecraft
try:
    mc = Minecraft.create()
    print("Успешное подключение к Minecraft")
except Exception as e:
    print(f"Ошибка подключения: {e}")
    exit()

# Спавн проекции
x0, y0, z0 = 0, 4, 10

# Загрузка YOLO
try:
    model = YOLO("yolo11n-seg.pt")  # Можно использовать yolov8s-seg.pt для лучшего качества
    print("Модель YOLO загружена")
except Exception as e:
    print(f"Ошибка загрузки модели: {e}")
    exit()
 

# Цвета шерсти
wool_colors = {
    0: (241, 241, 241), 1: (228, 228, 68), 2: (210, 125, 220),
    3: (160, 165, 255), 4: (255, 255, 97), 5: (160, 255, 160),
    6: (255, 160, 160), 7: (90, 90, 90), 8: (180, 180, 180),
    9: (40, 110, 140), 10: (130, 50, 170), 11: (40, 40, 140),
    12: (100, 70, 40), 13: (80, 110, 50), 14: (180, 70, 70),
    15: (20, 20, 20)
}

# Векторизованная функция поиска ближайшего цвета
def vectorized_closest_wool(bgr_colors):
    colors_array = np.array(list(wool_colors.values()))
    bgr = bgr_colors[:, [2, 1, 0]]  # BGR to RGB
    diff = bgr[:, np.newaxis, :] - colors_array[np.newaxis, :, :]
    distances = np.sum(diff**2, axis=2)
    return np.argmin(distances, axis=1)

# Открытие видео
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
    print("Ошибка открытия видео")
    exit()

# Пропускаем первых кадров
for _ in range(4):
    cap.read()

# Основной цикл
frame_count = 0
last_time = time.time()
prev_wool_ids = np.zeros((height_blocks, width_blocks), dtype=np.uint8)

try:
    while True:
        # Чтение кадра
        ret, frame = cap.read()
        if not ret:
            break
 
        # Пропуск каждого второго кадра
        frame_count += 1
        if frame_count % 2 != 0:
            continue
 
        # 1. Замена размера перед обработкой YOLO (сохраняя пропорции)
        height, width = frame.shape[:2]
        new_height = int(INPUT_WIDTH * height / width)
        resized_frame = cv2.resize(frame, (INPUT_WIDTH, new_height))
 
        # 2. Обрабатка через YOLO
        results = model(resized_frame,
                       classes=DETECTION_CLASSES,
                       conf=DETECTION_CONF,
                       imgsz=DETECTION_IMGSZ,
                       verbose=False)
 
        if results[0].masks is None:
            continue
 
        # 3. Обработка полученной маски
        mask = results[0].masks.data[0].cpu().numpy()
        mask = cv2.resize(mask, (width_blocks, height_blocks))
 
        if SMOOTH_MASK:
            mask = cv2.GaussianBlur(mask, (3,3), 0.5)  # Сглаживание
 
        mask_binary = mask > MASK_THRESHOLD
 
        if not np.any(mask_binary):
            continue
 
        # 4. Получение цветов оригинального изображения, до потери качества
        small_frame = cv2.resize(frame, (width_blocks, height_blocks))
        wool_ids = np.zeros((height_blocks, width_blocks), dtype=np.uint8)
        wool_ids[mask_binary] = vectorized_closest_wool(small_frame[mask_binary])
 
 
        # Очистка измененных блоков
        to_clear = (prev_wool_ids > 0) & (wool_ids == 0)
        for y in range(height_blocks):
            clear_start = None
            for x in range(width_blocks + 1):
                if x < width_blocks and to_clear[y, x]:
                    if clear_start is None:
                        clear_start = x
                elif clear_start is not None:
                    mc.setBlocks(
                        x0 + clear_start, y0 + height_blocks - y - 1, z0,
                        x0 + x - 1, y0 + height_blocks - y - 1, z0, 0)
                    clear_start = None
 
        # Установка новых блоков
        for y in range(height_blocks):
            start_x = None
            prev_color = None
            for x in range(width_blocks + 1):
                color = wool_ids[y, x] if x < width_blocks else None
                if color != prev_color:
                    if prev_color and prev_color > 0:
                        mc.setBlocks(
                            x0 + start_x, y0 + height_blocks - y - 1, z0,
                            x0 + x - 1, y0 + height_blocks - y - 1, z0,
                            35, prev_color)
                    start_x = x
                    prev_color = color
 
        prev_wool_ids = wool_ids.copy()
        fps = 1 / (time.time() - last_time)
        print(f"Кадр {frame_count}: FPS = {fps:.1f}")
        last_time = time.time()

except Exception as e:
    print(f"Ошибка: {e}")
finally:
    cap.release()
    mc.setBlocks(x0, y0, z0,
                x0 + width_blocks - 1,
                y0 + height_blocks - 1,
                z0, 0)
    print("Программа завершена")

Python:
from ultralytics import YOLO
model = YOLO('yolo11n-seg.pt')
classes = model.names
for idx, class_name in classes.items():
    print(f"{idx}: {class_name}")

На самом деле, эту идею можно реализовать в строчек 30, большая часть кода это работа над производительностью / качеством вывода в майнкрафте.
Чтобы запустить код, нужно указать путь к видео, зайти в майнкрафт, начать игру и после этого можно стартовать в плане кода. Над вашим персонажем появится анимация

Основные параметры. В DETECTION_CLASSES нужно указать номер класса, который переносится в майнкрафт. Выше есть спойлер со всеми доступными классами.
Если ИИ плохо справляется с сегментацией, то нужно выкрутить параметр DETECTION_CONF пониже, можно вообще до 0.1 опустить его, но тогда по краям будут жирные, черные контуры

Перед обработкой нейронкой изображение сжимается до 480*480 самой нейронкой. Но перед этим я еще раз сжимаю изображение по ширине до 640 (остальные стороны тоже меняются чтобы сохранить соотношение оригинальное). Да, качество ухудшилось, но зато получилось добиться очень хорошей оптимизации, нагрузка на GPU меньше + порядочное качество. Если нужно максимальное качество, то убирайте из кода первое уменьшение размера и оставляйте только уменьшение самой нейронкой. Это должно быть актуально для тех, кто запускает все на CPU, но это лишь предположение, на GPU нагрузка заметно уменьшилась.

DETECTION_IMGSZ должен быть кратен 32, это связано с архитектурой модели. Этот параметр содержит размер, который нейронка подгоняет под себя, когда принимает изображение, 480*480 / 512*512 / 544*544 ... И должен быть не больше INPUT_WIDTH, иначе смысла от этих преобразований нет. Если что изображение не становится квадратным, соотношение сторон такое же, просто по краям черные полосы.

Параметры области спавна блоков.
height_blocks = высота
width_blocks = ширина
depth_blocks = количество слоев.

Python:
#Параметры сегментации
DETECTION_CLASSES = [0]  # класс / классы
DETECTION_CONF = 0.75    # порог уверенности сегментации
INPUT_WIDTH = 640        # Ширина для обработки (сохраняет пропорции).
DETECTION_IMGSZ = 480    # Размер изображения для ИИ.
MASK_THRESHOLD = 0.4     # Порог маски (чем выше, тем четче края, не влияет на черные контуры при плохой сегментации)
SMOOTH_MASK = True       # Сглаживание маски (улучшает качество в minecraft)

# Параметры вокселизации
height_blocks = 100
width_blocks = 70
depth_blocks = 1

В остальной части код работа в основном с оптимизацией вывода в майнкрафте.

Цвет шерсти подбирается через numpy.
Массив пикселей превращается в 3д матрицу для векторизированного сравнения, далее все тоже самое но с шерстью.
Расчтиывается квадрат разницы между каждым пикселем и цветом. В итоге получаем самый близкий цвет для каждого пикселя.

Касательно генерации блоков.
Получилось добиться плавной анимации за счет генерации только меняющихся блоков относительно предыдущего кадра.
Блоки спавнятся не по 1, а горизонтальной пачкой, если несколько одинаковых цветов. И так же удаляются соответственно.

Половина кадров пропускается, чтобы сделать нагрузку на GPU меньше и ускорить анимацию соответственно. Поэтому в идеальных условиях у вас в консоли во время обработки каждого кадра будет отображаться FPS = 2. Значит yolo уверенно сегментирует все изображения. 1 кадр пропускается 2 прогоняется.

Поэтому если в консоли видите большое значение у FPS значит сегментация справляется плохо и кадры пропускаются, соответственно пробуйте выкрутить DETECTION_CONF (уверенность) ниже, если не помогает увеличивайте размер INPUT_WIDTH, если это тоже не помогает увеличивайте размер DETECTION_IMGSZ. Если тоже не помогает, то обучайте сегментацию сами.

Это самое малое и простое что можно реализовать в майнкрафте через нейросети.
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх