openCV

[C++ openCV 도형 인식] 게임 매직 타일 매크로 [2] (완)

 

[파이썬 openCV 도형 인식] 게임 매직타일 매크로(1)

매직타일 매크로  openCV로 뭘 만들까 고민하다가 옛날에 했던 게임 중 하나인 '매직 타일' 생각이 나서 매크로를 만들어 봤다. 매직 타일은 간단한 리듬 게임이다. 매직 타일 소개 영상 보다시피,

return-value.tistory.com

윗글을 읽고 오시면 이해가 빠릅니다.

 

결과 영상

 전의 파이썬과 비교했을 때, 확실히 검출해네는 속도가 빨라졌다! 심지어 C++은 의도적으로 딜레이를 30ms정도 준 상태인데도 말이다. 

 

 확실히 이런 실시간 싱크 맟추는 것이 중요하다면, C++이 강력한 것 같다. 하지만 C++로 코딩하기는 참 너무 귀찮다.. 해줄게 너무 많다! 그래도 속도만큼은 진짜 탁월한 것 같다.

 

 

 C++ 소스

화면 녹화 부분 참고 코드 출처 : github.com/sturkmen72/opencv_samples/blob/master/Screen-Capturing.cpp

 

sturkmen72/opencv_samples

sample source code of c++ using Opencv3. Contribute to sturkmen72/opencv_samples development by creating an account on GitHub.

github.com

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/videoio.hpp>
#include <Windows.h>
#include <iostream>

using namespace std;
using namespace cv;

class hwnd2Mat
{
public:
	hwnd2Mat(HWND hwindow, float scale = 1);
	virtual ~hwnd2Mat();
	virtual void read();
	Mat image;

private:
	HWND hwnd;
	HDC hwindowDC, hwindowCompatibleDC;
	int height, width, srcheight, srcwidth;
	HBITMAP hbwindow;
	BITMAPINFOHEADER  bi;
};

hwnd2Mat::hwnd2Mat(HWND hwindow, float scale)
{
	hwnd = hwindow;
	hwindowDC = GetDC(hwnd);
	hwindowCompatibleDC = CreateCompatibleDC(hwindowDC);
	SetStretchBltMode(hwindowCompatibleDC, COLORONCOLOR);

	RECT windowsize;    // get the height and width of the screen
	GetClientRect(hwnd, &windowsize);

	srcheight = 680;
	srcwidth = 550;
	height = (int)(srcheight * scale);
	width = (int)(srcwidth * scale);

	image.create(height, width, CV_8UC4);

	// create a bitmap
	hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);
	bi.biSize = sizeof(BITMAPINFOHEADER);    //http://msdn.microsoft.com/en-us/library/windows/window/dd183402%28v=vs.85%29.aspx
	bi.biWidth = width;
	bi.biHeight = -height;  //this is the line that makes it draw upside down or not
	bi.biPlanes = 1;
	bi.biBitCount = 32;
	bi.biCompression = BI_RGB;
	bi.biSizeImage = 0;
	bi.biXPelsPerMeter = 0;
	bi.biYPelsPerMeter = 0;
	bi.biClrUsed = 0;
	bi.biClrImportant = 0;

	// use the previously created device context with the bitmap
	SelectObject(hwindowCompatibleDC, hbwindow);
	read();
};

void hwnd2Mat::read()
{
	// copy from the window device context to the bitmap device context
	StretchBlt(hwindowCompatibleDC, 0, 0, width, height, hwindowDC, 1, 180, srcwidth, srcheight, SRCCOPY); //change SRCCOPY to NOTSRCCOPY for wacky colors !
	GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, image.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);  //copy from hwindowCompatibleDC to hbwindow
};

hwnd2Mat::~hwnd2Mat()
{
	// avoid memory leak
	DeleteObject(hbwindow);
	DeleteDC(hwindowCompatibleDC);
	ReleaseDC(hwnd, hwindowDC);
};

void make_binary(const Mat& img_src, Mat& img_binary)
{
	int thresh = 10;
	int maxval = 255;
	Mat img_gray;
	cvtColor(img_src, img_gray, COLOR_BGR2GRAY);
	threshold(img_gray, img_binary, thresh, maxval, THRESH_BINARY_INV);
}


void click_node(int cx, int cy)
{
	INPUT input;
	ZeroMemory(&input, sizeof(INPUT));
	input.type = INPUT_MOUSE;

	// 마우스 노드 좌표로 이동
	double screenRes_width = ::GetSystemMetrics(SM_CXSCREEN) - 2;
	double screenRes_height = ::GetSystemMetrics(SM_CYSCREEN) - 300;
	float dx = cx * (65535.0f / screenRes_width);
	float dy = cy * (65535.0f / screenRes_height);
	input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
	input.mi.dx = LONG(dx);
	input.mi.dy = LONG(dy);
	SendInput(1, &input, sizeof(INPUT));

	// 마우스 왼쪽 버튼 클릭 이벤트
	input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
	SendInput(1, &input, sizeof(INPUT));
	input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
	SendInput(1, &input, sizeof(INPUT));
	
	Sleep(35);
}

void detect_contours(Mat& img_src, Mat& img_binary)
{
	vector<vector<Point>> contours;
	findContours(img_binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);

	if (!contours.empty())
	{
		vector<Point2f> approx;
		for (size_t i = 0; i < contours.size(); i++)
		{
			// contour 근사
			approxPolyDP(Mat(contours[i]), approx,
				arcLength(Mat(contours[i]), true)*0.01, true);
			
			int size = approx.size();
			// 사각형일 경우
			if (size == 4)
			{
			line(img_src, approx[0], approx[approx.size() - 1], Scalar(0, 255, 0), 3);
				// line
				for (size_t j = 0; j < size -1; j++)
				{
					line(img_src, approx[j], approx[j + 1], Scalar(0, 255, 0), 3);
				}

				// 무게중심에 원 그리기
				Moments mu;
				mu = moments(contours[i]);
				int cx = static_cast<float>(mu.m10 / (mu.m00 + 1e-5));
				int cy = static_cast<float>(mu.m01 / (mu.m00 + 1e-5));
				cout << "노드 좌표 : " << cx << ' ' << cy << '\n';
				circle(img_src, Point(cx, cy), 15, Scalar(0, 255, 255), -1);

				// 원 클릭
				if (cy > 90)
				{
					click_node(cx, cy);
				}
			}
		}
	}
}

int main()
{
	Mat bgrImg;
	Mat img_binary;
	hwnd2Mat capDesktop(GetDesktopWindow());

	int tmp;
	cout << "자동 클릭 시스템을 구동하시겠습니까? (1)";
	cin >> tmp;
	while (tmp)
	{
		capDesktop.read();
		cvtColor(capDesktop.image, bgrImg, COLOR_BGRA2BGR);
		
		make_binary(bgrImg, img_binary);
		detect_contours(bgrImg, img_binary);

		imshow("cap win", bgrImg);
		int key = waitKey(5);

		if (key == 27)
			break;
	}
}