Перейти к содержанию

[C++, OpenCV] Решение капчи на OpenCV


Рекомендуемые сообщения

Всем доброго времени суток.

Решил поботоводить известным ботом в известной игре:). Бот умеет решать капчу, но на данном сервере возникла проблема) Байпасы не перехватываются, а при попытке отправить какой-то из них (при чем верный), спустя пару попыток клиент закрывается, либо дисконнектит с сервера.

 

Чтобы сильно не заморачиваться и не влезать в клиент, решил накидать прототип "решалки" на OpenCV. Применять можно абсолютно везде (в любой игре).

 

Вид капчи:

Спойлер

image.png

 

Софт:

- VisualStudio 2019

- OpenCV 3.4.10

 

Итак, сначала нам надо получить handle окна, с которого мы будем снимать скрин:

Для этого у нас будет 2 функции: GetPID и EnumWindowsProc

Спойлер

HWND hWindow = NULL;

int GetPID()
{
	HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);;
	PROCESSENTRY32 pentry32;

	if (snap == INVALID_HANDLE_VALUE) return 0;
	pentry32.dwSize = sizeof(PROCESSENTRY32);

	if (!Process32First(snap, &pentry32)) { CloseHandle(snap); return 0; }
	do
	{
		if (!lstrcmpi("L2.exe", &pentry32.szExeFile[0]))
		{
			CloseHandle(snap);
			return pentry32.th32ProcessID;
		}
	} while (Process32Next(snap, &pentry32));
	CloseHandle(snap);
	return 0;
}

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
	DWORD lpdwProcessId;
	GetWindowThreadProcessId(hwnd, &lpdwProcessId);
	if (lpdwProcessId == lParam)
	{
		hWindow = hwnd;
		return FALSE;
	}
	return TRUE;
}

 

 

Далее описание в коде:

Спойлер

BOOL antibot_state = false;

EnumWindows(EnumWindowsProc, GetPID());

if (hWindow)
	{
    RECT windowRect;
    GetWindowRect(hWindow, &windowRect);

  	// Получаем размеры и позицию окна
	int left = windowRect.left;
	int top = windowRect.top;
	int right = windowRect.right;
	int bot = windowRect.bottom;

	int width = right - left;
	int height = bot - top;

	HDC hwndDC = GetWindowDC(hWindow);
	HDC hdcMem = CreateCompatibleDC(hwndDC);
	
    // Полный путь к папке в которой лежин паттерн с помощью которого мы определяем, что вылезло окно с капчей
  	// здесь - текущая папка с проектом
	TCHAR dirBuffer[256];
	DWORD ProjectPath = GetCurrentDirectory(256, dirBuffer);
	const char* FILE_NAME = "\\antibot.jpg\0";
	const char* FILE_PATH = strcat((char*)dirBuffer, FILE_NAME);

  	// Считываем паттерн из файла и прифодим к формату цвета RGB
	Mat antibot = imread(FILE_PATH);
	cvtColor(antibot, antibot, BI_RGB);

	if (!antibot.data)
	{
		std::cout << "Image not loaded";
		return -1;
	}

	// Далее сам цикл отработки
	while (true)
	{
	HBITMAP hBitmap = CreateCompatibleBitmap(hwndDC, width, height);
	SelectObject(hdcMem, hBitmap);
	BitBlt(hdcMem, 0, 0, width, height, hwndDC, 0, 0, SRCCOPY);

	BITMAPINFO BMI;
	BMI.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	BMI.bmiHeader.biWidth = width;
	BMI.bmiHeader.biHeight = -height;
	BMI.bmiHeader.biPlanes = 1;
	BMI.bmiHeader.biBitCount = 32;
	BMI.bmiHeader.biCompression = BI_RGB;
	
	Mat img = Mat(height, width, CV_8UC4);			// Сам скриншот
	Mat result = Mat(height, width, CV_8UC4);		// Переменная для записи результата сравнения
	Mat antibot_result = Mat(544, 60, CV_8UC4);		// Для записи результата сравнения паттерна окна капчи
	Mat cropped_img, pattern, find_field;			// Обрезанное изображение без рамок

	Rect cropp, roi, find_cropp;
	
    // Rect обрезанного изображения. Здесь мы корректируем смещение координат по толщине рамки 
    // width - 16 это толщина рамки в 8 пикселей слева и справа
    // height - 39 толщина рамки 8 пикселей снизу и 31 пиксель сверху
	cropp.x = 8;
	cropp.y = 31;
	cropp.width = width - 16;
	cropp.height = height - 39;
	
    // Для паттерна "Примера" картинки которую нужно найти и кликнуть (чтобы не искать по всему скриншоту)
	roi.x = 561;
	roi.y = 280;
	roi.width = 25;
	roi.height = 25;
	
    // Область поиска в которой мы ищем нужную картинку (поле "Выбор")
	find_cropp.x = 400;
	find_cropp.y = 348;
	find_cropp.width = 340;
	find_cropp.height = 340;

	cropped_img = img(cropp);		// Обрезаем скриншот (не знаю зачем конечно, но мне так больше нравится))
	pattern = cropped_img(roi);		// Выделяем область из скриншота, в которой находится наш пример
	find_field = cropped_img(find_cropp);	// Выделяем область поиска

	GetDIBits(hwndDC, hBitmap, 0, height, img.data, &BMI, DIB_RGB_COLORS);

	// Сравнение скриншота и паттерна антибота
	matchTemplate(cropped_img, antibot, antibot_result, CV_TM_SQDIFF);
	double    minval_a, maxval_a;
	Point     minloc_a, maxloc_a;
	minMaxLoc(antibot_result, &minval_a, &maxval_a, &minloc_a, &maxloc_a);
	
    // Далее проверка. Необходимо вывести в консоль значения minval и maxval и посмотреть в каком диапазоне
    // находятся значения когда появляется наш паттерн на скриншоте. Я брал только по minval.
    // Также для удобства можно обводить область в которой нашелся наш паттерн
    if (minval_a >= 6.77e7 && minval_a <= 6.84e7)
    {
      char timeBuffer[80];
      time_t seconds = time(NULL);
      tm* timeinfo = localtime(&seconds);
      const char* format = "%H:%M:%S";
      strftime(timeBuffer, 80, format, timeinfo);

      cout << timeBuffer << " Finded ANTIBOT!" << endl;
      antibot_state = TRUE;
    }
	else { antibot_state = FALSE; }
	
    // Поиск паттерна "Примера" в нашей заданной области поиска и выполнение обводки первого попавшегося паттерна
    matchTemplate(find_field, pattern, result, CV_TM_SQDIFF);
    double    minval, maxval;
    Point     minloc, maxloc;
    minMaxLoc(result, &minval, &maxval, &minloc, &maxloc);
	cv::rectangle(find_field, cvPoint(minloc.x, minloc.y), cvPoint(minloc.x + roi.width - 1, minloc.y + roi.height - 1), CV_RGB(255, 0, 0), 1, 8);

    if (antibot_state)
    {
      // Сохраняем координаты курсора
      POINT cur;
      GetCursorPos(&cur);
      
      // Центр квадрата найденной картинки
      int center_x = (minloc.x + minloc.x + roi.width - 1) / 2;
	  int center_y = (minloc.y + minloc.y + roi.height - 1) / 2;
	  
      // handle текущего активного окна
      HWND current_window = GetForegroundWindow();
      
      // Выбираем окно с игрой и совершаем клик в центре найденного квадрата с картинкой
      SetForegroundWindow(hWindow);
      Sleep(200);
      SetCursorPos(windowRect.left + find_cropp.x + center_x, windowRect.top + find_cropp.y + center_y + 29);
      Sleep(200);
      mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
      Sleep(200);
      mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
      mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
      Sleep(200);
      mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
	  
      // Возвращаем окно с которого ушли
      SetForegroundWindow(current_window);

      // Восстановливаем координаты курсора
      SetCursorPos(cur.x, cur.y);
      ShowCursor(TRUE);
      Sleep(2000);
    }
	
    // Для удобства подгона зоны в которой нужно искать паттерны можно вывести картинку
	imshow("window", resized);

	waitKey(1);
	DeleteObject(hBitmap);
  }

ReleaseDC(NULL, hwndDC);
DeleteDC(hdcMem);	

 

 

Скрины и описание:

Спойлер

Тут видим зеленый квадрат и красный. Подгоняем окно внутри игры так, чтобы "Пример" был в зеленом квадрате

image.png

 

Подогнали и видим, что программа нашла такую-же картинку. Кстати, несмотря на то, что наш пример темнее чем остальные картинки и там заштрихован фон.

image.png

 

В зависимости от позиции окна капчи внутри окна игры будет меняться и minval\maxval. Поэтому нужно следить за этим)

Как только программа найдет картинку, выведет в лог сообщение и произведет необходимые действия :)

image.png

 

Пока что быстродействие сего чуда хромает.

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

Жду критики\предложений. Возможно буду дорабатывать программу под разные виды капч и сделаю GUI к ней (это не точно).

Может кому пригодится)

Ссылка на комментарий
Поделиться на другие сайты

Посмотри в сторону Sikuli IDE. Там и без OpenCV такая решалка есть. Причем быстрая. Точнее, не решалка, а поисковик картинки по паттерну. И даже можно задать уровень точности совпадения.

Ссылка на комментарий
Поделиться на другие сайты

12 минут назад, Xipho сказал:

Посмотри в сторону Sikuli IDE. Там и без OpenCV такая решалка есть. Причем быстрая. Точнее, не решалка, а поисковик картинки по паттерну. И даже можно задать уровень точности совпадения.

Хм, спасибо очень даже интересно и код при чем пишется на Python'е, что много проще, возьму в копилку.

 

upd: Видео, и статейка на хабре:

 

Ссылка на комментарий
Поделиться на другие сайты

Помню, для жены делал на Sikuli бота для игры браузерной ВКшной. В ней каждую минуту нужно было собирать ресурсы с домов. Фармить. Вот я и сделал бота, который ждал появления определенной картинки  на экране и по ней кликал. Нормально она в сутки ресурсов поднимала ))

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.