넷마블 틀린그림찾기 매크로

넷마블 틀린그림찾기



악용하려고 만든건 아니고 원하는 프로그램을 만들기 위한 중간 과정이라서 공부할 겸 만들어봤습니다


흐름은 대충 그림이 바뀔 때마다 가로인지 세로인지 사용자가 지정해주는데, 지정할 때(그림이 바뀔때)마다 바탕화면 전체에서 템플릿 매칭으로 넷마블 프로그램을 찾아여. 매번 찾아주는 것보다 처음 한 번만 찾는게 구현도 쉽고 속도도 더 빠르지만 가끔 프로그램을 이동시켜야 할 일이 있고, 속도 차이도 요즘 같이 컴퓨터 성능이 상향평준화된 시대에서는 별의미가 없어서 매번 찾는 걸로 바꿨습니다.


넷마블 틀린그림찾기 프로그램을 찾으면 좌우 그림을 이진영상으로 바꾸고 정확도를 위해 차영상을 3개 만들고, 만들어진 3개의 차영상을 마지막에 더해주면 끝 영상처리는 끝. 그 다음 넷마블 틀린그림찾기 프로그램과 제가 만든 프로그램과 화면상에서 매핑하여 내 프로그램을 클릭하면 매핑된 넷마블 틀린그림찾기 프로그램을 클릭합니다.


아래 함수들은 제가 만든 자료형 및 매크로 상수들이 없으므로 다른 곳에서는 사용할 수 없지만 참고용으로 올려봅니다.



RGB영상을 이진영상으로 바꾸는 함수

void CIMAGE::RGB2BinaryImage( _IMAGE& oImgSrc, _IMAGE& oImgDst, int nThreshold )
{
	uchar ucGray;
	long lIdxSrc;	//oImgSrc.image의 인덱스
	long lIdxDst;	//oImgDst.image의 인덱스

	_IMAGE imgTemp;	//oImgSrc와 oImgDst가 같은 변수 일 수도 있으므로 Temp 변수 생성

	imgTemp.initialize( 
		cvSize( oImgSrc.image->width, oImgSrc.image->height ), 
		oImgSrc.image->depth, 
		1
		);

	for( int height=0 ; height < oImgSrc.image->height ; ++height )
	{
		for( int width=0 ; width < oImgSrc.image->width ; ++width )
		{
			lIdxDst = (height * imgTemp.image->widthStep) + (width * imgTemp.image->nChannels);
			lIdxSrc = (height * oImgSrc.image->widthStep) + (width * oImgSrc.image->nChannels);

			//RGB 값을 Gray 값으로 변환
			ucGray = uchar( (oImgSrc.image->imageData[lIdxSrc + 2] + oImgSrc.image->imageData[lIdxSrc + 1] + oImgSrc.image->imageData[lIdxSrc + 0]) / 3 );

			//1채널 영상에 대입
			imgTemp.image->imageData[ lIdxDst ] = ucGray;

			if( ucGray > nThreshold )
				imgTemp.image->imageData[ lIdxDst ] = (uchar)255;
			else
				imgTemp.image->imageData[ lIdxDst ] = (uchar)0;
		}
	}
	oImgDst.initialize( imgTemp.image );

	memcpy( oImgDst.image->imageData, imgTemp.image->imageData, imgTemp.image->imageSize );

	//DisplayWindow( "Binary Image", oImgDst, 10 );
}


차영상 구하는 함수

void CIMAGE::SubImage( _IMAGE& oImgSrc1, _IMAGE& oImgSrc2, _IMAGE& oImgDst )
{
	long lIdxSrc;				//oImgSrc.image의 인덱스
	unsigned char	ucColor;
	_IMAGE imgTemp;				//oImgDst와 oImgSrc1 혹은 oImgSrc2가 같을 수 있으므로 Temp 

	imgTemp.initialize( oImgSrc1.image );

	for( int height=0 ; height < oImgSrc1.image->height ; ++height )
	{
		for( int width=0 ; width < oImgSrc1.image->width ; ++width )
		{
			lIdxSrc = (height * oImgSrc1.image->widthStep) + (width * oImgSrc1.image->nChannels);

			ucColor = (unsigned char)oImgSrc1.image->imageData[lIdxSrc] - (unsigned char)oImgSrc2.image->imageData[lIdxSrc]; 

			if( ucColor > 255 )		//일반적인 차영상 구할때 발생할 일이 없겠지만.. 
				ucColor = 255;
			else if( ucColor < 0 )
				ucColor = 0;		//빼기 연산하면 음수가 나올 수 있으므로

			imgTemp.image->imageData[lIdxSrc] = ucColor;	//보정된 ucColor 값을 입력
		}
	}
	oImgDst.initialize( imgTemp.image );

	memcpy( oImgDst.image->imageData, imgTemp.image->imageData, imgTemp.image->imageSize );

	//DisplayWindow( "Sub Image", oImgDst, 10 );
}


인자로 받은 위치, 사이즈 만큼 캡쳐하는 함수. 여기서는 바탕화면 전체를 캡쳐함

void CIMAGE::captureScreen( HWND hWnd, int x, int y, int width, int height )
{
	BYTE *pImageData;
	BITMAPINFO bmi;

	bmi.bmiHeader.biSize  = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biWidth = width;
	bmi.bmiHeader.biHeight = height;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 24;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biSizeImage = (((width * 24 + 31) & ~31) >> 3) * height;
	bmi.bmiHeader.biXPelsPerMeter = 0;
	bmi.bmiHeader.biYPelsPerMeter = 0;
	bmi.bmiHeader.biClrImportant = 0;
	bmi.bmiHeader.biClrUsed = 0;

	HDC hDC    = ::GetDC( hWnd );
	HDC memHDC = ::CreateCompatibleDC(hDC);

	HBITMAP hbmOrg = ::CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, (void**)&pImageData, 0, 0);
	HBITMAP hbmOld = (HBITMAP)::SelectObject(memHDC, hbmOrg);
	HDC desktopDC = ::GetDC(NULL);
	::BitBlt(memHDC, 0, 0, width, height, desktopDC , x, y, SRCCOPY);
	// end of screen capture.

	// make IplImage 
	IplImage *imgDesktop = cvCreateImage( cvSize(width, height), 8, 3 );
	imgDesktop->origin = IPL_ORIGIN_BL;
	memcpy( imgDesktop->imageData, pImageData, imgDesktop->imageSize );
	//...

	if( m_oDesktop.image != NULL )
	{
		cvReleaseImage( &m_oDesktop.image );
	}

	m_oDesktop.image = cvCreateImage( cvSize( width, height ), 8, 3 );
	m_oDesktop.image->origin = 1;

	cvCopyImage( imgDesktop, m_oDesktop.image );

	//DisplayWindow( "Desktop Image", m_oDesktop, 10 );

	if( imgDesktop != NULL )
	{
		cvReleaseImage( &imgDesktop );
	}

	//해제
	ReleaseDC( NULL, desktopDC);
	SelectObject(memHDC, hbmOld);

	DeleteObject(hbmOrg);
	DeleteDC(memHDC);
	ReleaseDC(hWnd, hDC);
}


바탕화면에서 넷마블 프로그램을 찾거나 좌우 이미지를 좀 더 정확하게 찾을 때 사용하는 함수

//반환 값은 원본 이미지에서, 템플릿과 일치하는 좌하단 좌표와 가로, 세로 크기
CvRect CIMAGE::MatchTemplate( _IMAGE& oImgSrc, _IMAGE& oImgTemplate, bool bDesktop )
{
	CvRect cvRt;

	double min_val, max_val;	//상관관계 최대, 최소 값
	CvPoint min_loc, max_loc;	//left_Bottom, Right_top인듯 (윈도우와 OpenCV 좌표 체계가 달라서)

	int width = oImgSrc.image->width - oImgTemplate.image->width + 1;
	int height = oImgSrc.image->height - oImgTemplate.image->height + 1;

	IplImage* m_imgProc = cvCreateImage( cvSize( width, height ), IPL_DEPTH_32F, 1 );

	{//원본 이미지에서 템플릿 이미지와 유사?한 부분을 찾아 min_loc변수에 위치를 저장
		//상관계수를 구하여 m_imgProc에 그림, 이 함수의 특징은  유클리디안 거리를 사용하여 
		//소스 이미지 전체를 탐색하면서 템플릿 이미지와 얼마나 유사한지를 측정
		cvMatchTemplate( oImgSrc.image, oImgTemplate.image, m_imgProc, CV_TM_SQDIFF );

		//위에서 구한 상관계수 맵에서 최대의 유사성을 갖는 값(Max_val)을 cvMinMaxLoc( )를 이용하여 찾음
		cvMinMaxLoc( m_imgProc, &min_val, &max_val, &min_loc, NULL/*max_loc*/ );

		max_loc.x = oImgTemplate.image->width;		//사각형을 그릴때 템플릿 이미지 크기 만큼 그리기 위해
		max_loc.y = oImgTemplate.image->height;		//템플릿 이미지의 가로, 세로 사이즈를 넣어줌

		cvRt = cvRect( min_loc.x, min_loc.y, max_loc.x, max_loc.y );

		if( bDesktop == true )
		{//템플릿 이미지와 바탕화면을 비교해서 넷마블을 찾았는지 못 찾았는지 한 번만 확인하는 부분
			_IMAGE oImgCropNetmarble;	//바탕화면에서 넷마블이라 생각되는 부분을 잘라넣을 변수
			_IMAGE oImgSubTest;			//읽어온 템플릿과 oImgCropNetmarble변수를 빼서 저장할 변수

			long lIdxSrc;
			int nCount = 0;

			oImgSrc.cvRtCropROI = cvRt;

			//관심영역 지정
			cvSetImageROI( oImgSrc.image, oImgSrc.cvRtCropROI );

			oImgCropNetmarble.initialize( oImgTemplate.image );
			cvCopyImage( oImgSrc.image, oImgCropNetmarble.image );

			SubImage( oImgTemplate, oImgCropNetmarble, oImgSubTest );

			for( int height=8 ; height < oImgSubTest.image->height ; ++height )	//읽어온 템플릿 이미지에서 아래 부분을 8 빼느라고
			{
				for( int width=0 ; width < oImgSubTest.image->width ; ++width )
				{
					lIdxSrc = (height * oImgSubTest.image->widthStep) + (width * oImgSubTest.image->nChannels);

					if( uchar(oImgSubTest.image->imageData[ lIdxSrc ]) == 255 )
					{
						//읽어온 템플릿과 바탕화면에서 잘라낸 부분이 동일하다면 0에 가까운 수가 나온다.						
						if( ++nCount >= 5 )
						{
							m_bFoundNetmarble = false;
						}
					}
				}
			}
			//DisplayWindow( "Cropped Netmarble Image", oImgCropNetmarble, 10 );
		}

		if( m_imgProc != NULL )
		{
			cvReleaseImage( &m_imgProc );
		}
	}

	return cvRt;
}


작성자

Posted by 사용자 오뇽

태그

관련 글

댓글 영역

  • 프로필 사진
    키약
    2014.12.05 10:54

    우와 잘만드셨네요.ㅋㅋ
    저도 이번 프로젝트 하면서 딱 필요한 건데 저도 도전해봐야겠네요

    • 프로필 사진
      2014.12.05 11:07 신고

      어제 오늘 틀린그림 찾기 관련해서 세 분이나 글 남겨주셨네요.
      혹시 학교에서 과제로 받았나요!?
      아무튼 진행하시는 프로젝트 잘 되길 바랍니다 ㅎㅎ

  • 프로필 사진
    안녕하세요
    2014.12.21 10:09

    저도 공부하는 중입니다
    여기에서 세로의경우 좌우 그림의 위치를 지정해놨었는데요
    이게 세로그림의 위치가 1픽셀 2픽셀정도로 상하좌우로 조금씩 바뀌더군요
    이걸 어떻게 처리하셨나요?

  • 프로필 사진
    안녕하세요
    2014.12.21 11:15

    그리고 틀린부분을 찾았을때 그림은 5개지만 픽셀로만 따지면 수백개인데 그림으로 인식해서 5개만 클릭하게 하는 방법을 알수있을까요?