Майним крипто-коины с помощью Python и компьютерного зрения

После внезапного обогащения энтузиастов, которые поиграли в начале года в приложение Notcoin в телеграм, подобные проекты стали расти как грибы. Да и грибников заметно поприбавилось. Но в данной статье мы не будем касаться тем блокчейна или финансов, а рассмотрим простой пример применения компьютерного зрения для фарма поинтов в самом популярном, после Notcoin, проекте — хомяке комбате. Название явно на что-то намекает, но да ладно.

Это не первый проект, который я автоматизирую, и не самый нуждающийся в этом. Да и без компьютерного зрения с автоматизацией хомяка можно спокойно обойтись. Но с ним, во-первых, интереснее, а во-вторых — это просто хороший пример с минимумом строк кода для демонстрации возможностей библиотеки cv2. Статья, соответственно, предназначена для энтузиастов и начинающих специалистов.

Начнем с того, что мы установим все необходимые зависимости и импортируем их в свой проект. Вот они, слева направо.

import pyautogui
import keyboard
import cv2
import numpy as np
import time

С помощью pyautogui наш бот будет управлять мышью. Keyboard пригодится для назначения горячих клавиш, чтобы управлять работой бота. cv2 наградит бота зрением, пусть и компьютерным, с помощью которого тот будет находить совпадения с искомым изображением. А numpy пригодится для работы с большими массивами, но тут он почти для галочки, не бойтесь. Модуль time тоже понадобится, чтобы ставить таймауты в работе программы.

Далее напишем небольшую конструкцию- переключатель.

def change():
    global work
    work = not work

work = False

Функция change при вызове всего лишь меняет значение переменной work, которая будет использована в бесконечном цикле. И если work будет False, работа нашего кода будет останавливаться. И наоборот запускаться, в противоположном случае.

Кстати, забыл упомянуть, что разработчики проекта, над которым мы сейчас проводим эксперимент, большие молодцы, и убрали возможность пользоваться приложением на десктопных устройствах. Поэтому для его запуска понадобится эмулятор Android.

Теперь определим основную логику работы бота:

  1. Он ищет совпадение с изображением полностью заполненной энергии.

  2. Если находит совпадение, ищет изображение монеты и кликает на неё энное количество раз.

  3. И всё это работает в бесконечном цикле.

Значит со скриншота приложения необходимо вырезать две области, которые помечены красным, и разместить их в отдельные файлы, конечно же.

8cb7e3f9b8b40bd959a5e533eddda4fd.png

Теперь напишем функцию для кликов по монетке. Она будет принимать путь к исходному изображению, а так же порог чувствительности для компьютерного зрения и интервал (таймаут) в секундах.

def click(template_path, threshold=0.8, interval=0.0001):

    template = cv2.imread(template_path, 0)
    w, h = template.shape[::-1]

    screenshot = pyautogui.screenshot()
    screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)

    # Переводим скриншот в оттенки серого.
    gray_screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

    # Находим изображение на экране.
    result = cv2.matchTemplate(gray_screenshot, template, cv2.TM_CCOEFF_NORMED)
    loc = np.where(result >= threshold)

    # Кликаем по найденным координатам.
    for pt in zip(*loc[::-1]):
        center_x = pt[0] + w // 2
        center_y = pt[1] + h // 2
        for _ in range(260):
            pyautogui.doubleClick(center_x, center_y)
        break

Переменной template будет присвоено исходное изображение монетки, но в оттенках серого, так как мы указали в параметрах 0. Это необходимость, так как в оттенках серого компьютер зрит лучше. Сразу вычисляем высоту и ширину исходника, и присваиваем переменным. А далее по ходу исполнения кода он делает скриншот, сравнивает с исходником, получает координаты области с совпадением, и делает 260 даблкликов по ней. Координаты я ищу немного кривовато и в итоге loc содержит большой массив, из которого я использую лишь самые первые координаты, после чего цикл прерываю. Но лучше сделать не смог, извините.

А теперь напишем аналогичную функцию, но с задачей искать совпадение с картинкой полной энергии, после чего вызывать функцию click.

def find(template_path, threshold=0.9, interval=1):
    keyboard.add_hotkey('`', change)

    # Загружаем изображение, которое мы хотим найти на экране. 0- в градациях серого.
    template = cv2.imread(template_path, 0)
    w, h = template.shape[::-1]

    try:
        while True:
            if work:

                # Получаем скриншот экрана.
                screenshot = pyautogui.screenshot()
                screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)

                # Переводим скриншот в оттенки серого.
                gray_screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

                # Находим изображение на экране.
                result = cv2.matchTemplate(gray_screenshot, template, cv2.TM_CCOEFF_NORMED)
                loc = np.where(result >= threshold)

                for _ in zip(*loc[::-1]):
                    click('coin.bmp')
                    break
                time.sleep(interval)
    
    except KeyboardInterrupt:
        print('\nВыход из программы')

В целом всё аналогично. Добавил лишь ожидание горячей клавиши, чтобы можно было остановить программу в любое время нажатием на тильду (Ё). Ну и для красоты заключил в try-except.

И это всё. Пишем последние строки и запускаем скрипт (не забыв нажать на Ё для запуска логики в цикле).

if __name__ == "__main__":
    find('energy.bmp')

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

import pyautogui
import keyboard
import cv2
import numpy as np
import time


def change():
    global work
    work = not work

work = False

def find(template_path, threshold=0.9, interval=1):
    keyboard.add_hotkey('`', change)

    # Загружаем изображение, которое мы хотим найти на экране. 0- в градациях серого.
    template = cv2.imread(template_path, 0)
    w, h = template.shape[::-1]

    try:
        while True:
            if work:

                # Получаем скриншот экрана.
                screenshot = pyautogui.screenshot()
                screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)

                # Переводим скриншот в оттенки серого.
                gray_screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

                # Находим изображение на экране.
                result = cv2.matchTemplate(gray_screenshot, template, cv2.TM_CCOEFF_NORMED)
                loc = np.where(result >= threshold)

                for _ in zip(*loc[::-1]):
                    click('coin.bmp')
                    break
                time.sleep(interval)
    
    except KeyboardInterrupt:
        print('\nВыход из программы')


def click(template_path, threshold=0.8, interval=0.0001):

    template = cv2.imread(template_path, 0)
    w, h = template.shape[::-1]

    screenshot = pyautogui.screenshot()
    screenshot = cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)

    # Переводим скриншот в оттенки серого.
    gray_screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

    # Находим изображение на экране.
    result = cv2.matchTemplate(gray_screenshot, template, cv2.TM_CCOEFF_NORMED)
    loc = np.where(result >= threshold)

    # Кликаем по найденным координатам.
    for pt in zip(*loc[::-1]):
        center_x = pt[0] + w // 2
        center_y = pt[1] + h // 2
        for _ in range(260):
            pyautogui.doubleClick(center_x, center_y)
        break


if __name__ == "__main__":
    find('energy.bmp')

Не судите меня строго по этому скрипту, большую часть жизни я вообще бегал с пистолетиком, и пристрастился к разработке сравнительно недавно, поэтому всего лишь юный падаван в возрасте. Хотя могу писать и большие скучные штуки, но вот писать о них получится дольше, чем сам код. А если будут вопросы- добро пожаловать в телеграм. У меня там небольшой клуб по интересам.

© Habrahabr.ru