프날 오토핫키 강좌

[프날 오토핫키] GDI+ (gdip) #7: 비활성 이미지서치

728x90

* 본 강좌는 오토핫키로 GDI+를 이용하여 이미지서치를 하는 것을 목표로 합니다. 내장 명령어인 ImageSearch 명령어를 찾으신다면 좌측 메뉴에서 강좌 본편을 참고해주세요.

 

* 본 강좌에서 설명하는 이미지서치 방법은 Haystack(추후 설명)이 될 창이 최소화 되어있지 않다는 것을 가정합니다.

 

* 기존 gdip 강좌를 모두 숙지하고 완벽히 다루실 수 있다는 가정 하에 진행되는 강좌입니다. 지난 강좌는 이 글의 최하단부에 목록으로 정리해두었습니다.

 

* 최근 이 글로 '프날 오토핫키 강좌'를 접하는 분들이 많습니다. 본 강좌는 본편 강좌가 따로 구성되어있으며, 해당 본편 강좌를 모두 배우신 후 보셔야 개념이 헷갈리지 않습니다. 또한, 본편의 서론에도 언급했듯이 이 강좌는 게임 매크로 강좌가 아닙니다! 오토핫키는 게임 매크로 만드는 툴이 아니며, 악용되어서도 안됩니다.


 gdip을 이용한 이미지서치 

사실 저는 비활성 이미지서치라는 용어를 좋아하지 않습니다. 비활성 창에 대한 이미지서치는 내장 명령어(ImageSearch)에서 Coordmode를 지정해주는 것만으로도 가능하기 때문입니다. 가려진 창은 인식이 되지 않지만, 절대좌표로 할 경우 "비활성 상태의 창"에서도 이미지서치가 되긴 됩니다.

 

다만 GDI+를 이용하면 가려진 창의 비트맵을 가져올 수 있고, 이를 이용하면 보이지 않는 부분 또한 이미지서치가 가능합니다. 아마 이 점이 국내 오토핫키 사용자층에게 gdip을 이용한 이미지서치가 '비활성 이미지서치'라는 이름으로 정착된 것에 한 몫을 한 것 같습니다. 해외 포럼에서 관련 글을 찾을 땐 gdip ImageSearch라는 키워드로 검색하셔야합니다.

 

이번 강좌에서는 이 gdip를 이용한 이미지서치를 가리키는 말로 "gdip 이미지서치"라는 용어를 사용하도록 하겠습니다. 다만 강좌의 제목은 대중적인 용어인 '비활성 이미지서치'를 사용했습니다.


 Gdip_ImageSearch 라이브러리 

gdip을 이용해서 이미지서치를 구현해놓은 라이브러리가 별도로 있습니다. 이번 강좌에서는 이 라이브러리를 사용합니다. 우리가 직접 로직을 구현하기엔 너무 복잡하고 어려운 과정이 되기 때문입니다.

 

라이브러리 원문은 (여기)서 확인 하실 수 있으며, 직접 전체를 복사하여 Gdip_ImageSearch.ahk라는 이름으로 저장해주시거나 아래 파일을 다운로드하셔서 보관해주시면 준비 끝입니다.

Gdip_ImageSearch.ahk
0.03MB


 전체 과정 

이번 강좌는 gdip의 기초적인 함수들을 배웠던 지난 강좌들과 달리, 응용적인 과정을 배우게 됩니다. 그렇기 때문에 한번 전체적인 gdip 이미지서치 과정을 짚어보려합니다.

1. Gdip와 Gdip_ImageSearch 라이브러리를 임포트합니다.
2. 이미지 서치를 할 범위의 비트맵과, 찾을 이미지의 비트맵을 가져옵니다.
3. Gdip_ImageSearch()라이브러리를 이용하여 이미지서치를 수행합니다.

지난 강까지 배웠던 내용들로 충분히 가능하니, 잘 따라와주세요.


 준비하기 

기존에 gdip 라이브러리만 임포트 했었는데, 같은 방법으로 Gdip_ImageSearch를 임포트합니다.

그 후, 원래 gdip의 함수를 사용할 때처럼 초기화(Initialize)와 정리(Clean up)함수를 써주고 시작하겠습니다.

스크립트 종료문(Exitapp) 또한 써주겠습니다.

 

이 과정은 이해 되시죠? 지난 강좌동안 이 과정은 빠지지 않고 사용했었으니까요. 따로 복잡한 설명은 하지 않겠습니다.

#include Gdip_All.ahk
#include Gdip_ImageSearch.ahk

pToken := Gdip_StartUp()

; 이 곳에서 gdip 이미지서치를 수행할 예정입니다.

Gdip_Shutdown(pToken)
Exitapp

 Haystack과 Needle준비 

이 개념을 드디어 설명하네요. 서양에서는 "a needle in a haystack"라는 말이 있습니다. 직역하면 '건초더미 안 바늘'정도이고요, 한국에는 '서울에서 김서방 찾기' 정도가 있습니다. 갑자기 이 말을 왜 설명드리냐면, 프로그래밍에서 "어떤 전체적인 것에서 작은 것을 찾는" 과정을 설명할 때 주로 Haystack(건초더미;전체적인 것)과 Needle(바늘;작은 것)을 사용하기 때문입니다.

 

Gdip_ImageSearch함수도 이 Haystack과 Needle개념을 사용합니다.

  • Haystack: 이미지를 찾을 영역의 비트맵
  • Needle: 찾을 이미지의 비트맵

 

Haystack은 Gdip_BitmapFromHWND() 함수를 이용하여 특정 창의 비트맵을 가져오고

Needle은 Gdip_CreateBitmapFromFile() 함수를 이용하여 이미지 파일을 비트맵으로 가져오시면 됩니다.

모두 지난 시간 동안 배웠던 함수입니다.

 

그러면 Haystack에서 Needle을 찾는, 즉 특정 창 안에서 이미지를 찾을 수 있는 것이지요. 물론, 그 '특정 창'은 다른 창에 의해 가려져 있어도 됩니다. (일부 창은 비트맵을 가져올 수 없으니 참고해주세요.)

#include Gdip_All.ahk
#include Gdip_ImageSearch.ahk

pToken := Gdip_StartUp()

pHaystack := Gdip_BitmapFromHWND(WinExist("제목 없음 - 그림판"))
pNeedle := Gdip_CreateBitmapFromFile("pencil.png")

Gdip_Shutdown(pToken)
Exitapp

위와 같이 Haystack과 Needle을 가져와주었습니다.

저는 그림판에서 연필 아이콘을 찾기 위해 위와 같이 써주었습니다. Needle은 찾을 이미지의 비트맵이기에, Gdip_CreateBitmapFromFile()을 이용하여 가져와주려면 우선 이미지 파일로 저장되어있어야 합니다. 캡처를 재량껏 하셔서 저장해두시면 되겠습니다. (제가 만든 캡처 프로그램을 써보셔도 좋습니다.. ^^ (바로가기))

Haystack으로 쓸 비트맵입니다. (1/2배로 축소)
Needle로 쓸 비트맵입니다. (5배 확대)


 Gdip_ImageSearch() 

이제, 준비한 Haystack과 Needle로 이미지서치를 수행해볼 것입니다. 이 과정에선 Gdip_ImageSearch()함수를 이용하는데, 매개변수는 아래와 같습니다. (원문을 조금 수정했습니다.)

Gdip_ImageSearch(pHaystack, pNeedle, OutputList [, x1, y1, x2, y2, Variation, Trans, SearchDirection, Instances, LineDelim, CoordDelim])

매개변수가 너무 많습니다. 우리는 주로 쓰는 Variation 정도까지만 알아보겠습니다. 

Gdip_ImageSearch(pHaystack, pNeedle, OutputList [, x1, y1, x2, y2, Variation])
  • pHaystack: Haystack의 메모리 주소입니다.
  • pNeedle: Needle의 메모리 주소입니다.
  • OutputList: 출력 변수입니다. 찾은 이미지의 X, Y 좌표가 들어갑니다.
  • x1, y1, x2, y2: 이미지서치 영역입니다. 생략하거나 0으로 채울 시 Haystack 전체에서 찾습니다.
  • Variation: 음영도입니다. 오차 허용 값이라고도 합니다. 0부터 255사이에서 쓰시면 됩니다. 생략시 0입니다.

내장 되어있는 ImageSearch 명령어와 흡사합니다. Haystack을 따로 지정해준다는 것과, 출력 변수를 OutputList 하나로 두었다는 점만 다릅니다.

 

해당 함수는 서치 결과를 반환하며, 찾으면 1, 못찾으면 0을 반환합니다. 서치 오류 시 음수값을 반환합니다. 내장 이미지서치 명령어가 찾을 때 0, 못 찾을 때 1을 ErrorLevel로 반환하는 것과 반대라는 점도 눈여겨 볼만합니다.

 

소스코드에 써주시면 아래와 같은 형태입니다.

#include Gdip_All.ahk
#include Gdip_ImageSearch.ahk

pToken := Gdip_StartUp()

pHaystack := Gdip_BitmapFromHWND(WinExist("제목 없음 - 그림판"))
pNeedle := Gdip_CreateBitmapFromFile("pencil.png")
result := Gdip_ImageSearch(pHaystack, pNeedle, outputVar)

Gdip_Shutdown(pToken)
Exitapp

서치 범위를 지정하지 않아서 Haystack 전체에서 찾고, 음영도 또한 지정해주지 않아서 Needle과 정확히 일치하는 이미지만 찾습니다.

 

만약 음영값을 준다면 아래와 같이 쓰시면 되겠지요.

result := Gdip_ImageSearch(pHaystack, pNeedle, outputVar, 0, 0, 0, 0, 15)

 

결과값이 result에 담겼으니, 한번 result 변수를 출력해보세요.

0이 나오면 못 찾은 것이고

1이 나오면 찾은 것입니다.

음수 값이 나오면 구문 오류이니 다시 확인해주세요.

 

그리고 outputVar는 출력 변수입니다. 이 또한 출력해보세요.

정상적으로 이미지를 찾았다면, outputVar에 찾은 이미지의 좌표가 (X, Y)의 형태로 담겨있는 것을 볼 수 있습니다.


 출력 변수의 좌표 쪼개기 

출력 변수엔 찾은 이미지의 좌표가 (X, Y)의 형태로 담겨있다고 했습니다. 다만 우리가 실제 오토핫키에서 이 좌표를 이용하려면 X와 Y를 쪼개서 담아줄 필요가 있겠지요.

이 부분은 문자열 파싱의 영역이니 강좌에서 생략하겠습니다. 저는 정규식을 이용하여 out1, out2 변수로 쪼개보았습니다.

RegExMatch(outputVar, "(.*),(.*)", out)
MsgBox, X: %out1% Y: %out2%

참고: 정규식 강좌 바로가기 >


 전체 소스 

제가 작성한 gdip 이미지서치 소스입니다. 

 

#Include Gdip_All.ahk
#Include Gdip_ImageSearch.ahk

pToken := Gdip_Startup()
pHaystack := Gdip_BitmapFromHwnd(WinExist("제목 없음 - 그림판"))
pNeedle := Gdip_CreateBitmapFromFile("Pencil.png")
result := Gdip_ImageSearch(pHaystack, pNeedle, outputVar)

Gdip_DisposeImage(pHaystack)
Gdip_DisposeImage(pNeedle)
Gdip_Shutdown(pToken)

if (result = 1)
{
    RegExMatch(outputVar, "(.*),(.*)", out)
    MsgBox, X: %out1% Y: %out2%
}

ExitApp

Test.ahk
0.00MB

ExitApp을 제외하고 개행으로 4개 부분으로 나누었습니다.

 

  • 첫번째 부분: 라이브러리 임포트
  • 두번째 부분: GDI+를 초기화 하고, Haystack과 Needle을 준비한 뒤 이미지서치를 해서 결과를 result 변수에 담았습니다.
  • 세번째 부분: 사용된 리소스를 제거하고, GDI+를 정리했습니다.
  • 네번째 부분: 아까 받아둔 result변수를 통해 조건 분기를 하고, 출력 변수에 저장된 좌표값을 콤마를 기준으로 끊어서 출력했습니다.

크게 헷갈릴만한 부분은 없으니 천천히 봐주세요. 보이는 것만 복잡할 뿐입니다.

 

result 변수와 출력변수 outputVar는 GDI+가 정리된 뒤에도 살아있으니, 정리를 먼저 해주고 이용했다는 점 또한 알아주셨으면 합니다. 전에도 한 번 말씀드렸지만, GDI+가 정리된 뒤에도 우리가 사용했던 변수는 살아있습니다.

잘 출력 됩니다.

 


 이번 강에서의 gdip 함수 

Gdip_ImageSearch()

매개변수: 찾을 영역의 비트맵, 찾을 이미지의 비트맵, 출력변수, X1, Y1, X2, Y2, 음영도
반환 값: 1(찾음), 0(못 찾음), 음수 값(오류)

 

 

 

반응형