프날 오토핫키 강좌  v2

⚠ 이 강좌는 오토핫키 v2를 다룹니다

지금 보시는 강좌는 과거 오랜 시간동안 알려진 오토핫키(v1.1)의 차세대 버전인 오토핫키 v2를 다루고 있습니다.
만약 구버전인 '오토핫키 v1.1'의 강좌를 찾으신다면 프날 오토핫키 강좌(https://pnal.kr)를 봐주시면 되지만, 새로 오토핫키를 배우신다면 v2 버전을 배우시는 것을 강력히 추천드립니다.

41. ImageSearch (이미지 서치)


지금까지 배운 내용으로 키보드와 마우스 동작을 제어하여 간단한 프로그램을 만들 수 있었습니다. 그러나 아래와 같은 동작을 하려면 어떻게 해야할까요?

  • 특정 작업이 완료될 때까지 기다린 후, 키보드를 제어한다.
  • 특정 창이 뜨면 자동으로 확인 버튼을 눌러준다.

우리 인간이 느끼기에 '작업이 완료됐다', '특정 창이 뜬다'와 같은 것은 화면의 변화에 속합니다. 이러한 화면의 변화를 감지할 수 있으면 얼마나 유용할까요? 그 역할을 바로 흔히 '이미지 서치'라고 부르는, ImageSearch 함수가 합니다.

ImageSearch

ImageSearch 함수는 화상을 찾고, 찾았다면 그 위치를 변수에 담아주는 함수입니다.

프로그램 화면의 특정 부분에 네모가 쳐져있고, 그 네모 안의 내용과 같은 모습을 가진 이미지 옆에 '이미지가 존재함. 위치=(200, 500)이라고 적혀있는 모습 사진 1. ImageSearch 함수의 동작

ImageSearch 함수의 역할을 이해하셨나요? 이제 사용해보기 위해, 함수의 원형부터 보겠습니다.

ImageSearch(&OutputVarX, &OutputVarY, X1, Y1, X2, Y2, ImageFile)
ImageSearch 함수의 원형

매개변수

  • &OutputVarX: 찾은 화상의 x 좌표가 담길 변수의 참조
  • &OutputVarY: 찾은 화상의 y 좌표가 담길 변수의 참조
  • X1: 화상을 찾을 사각 영역의 좌상단 꼭짓점 x좌표
  • Y1: 화상을 찾을 사각 영역의 좌상단 꼭짓점 y좌표
  • X2: 화상을 찾을 사각 영역의 우하단 꼭짓점 x좌표
  • Y2: 화상을 찾을 사각 영역의 우하단 꼭짓점 y좌표
  • ImageFile: 찾을 화상이 담긴 이미지 파일의 경로 및 옵션

반환 값

  • 1 (true): 화상을 정상적으로 찾았을 때
  • 0 (false): 화상을 찾지 못했을 때

각각의 매개변수를 세부적으로 알아보면 아래와 같습니다.

1. &OutputVarX, &OutputVarY

범위 내에서 화상을 찾았을 경우, 찾은 화상 좌표가 담기는 변수를 참조로 적어줍니다. '참조로 적는다'라는 뜻은 추후에 설명드리기로 했죠? 지금으로선 '담기는 변수 앞에 & 기호를 붙여서 적는다' 라고 이해하시면 된다고 했습니다.

예를 들어서, 찾은 화상의 좌표가 outputX, outputY 변수에 각각 담기게 하고 싶다면 &outputX, &outputY 처럼 적어주면 되는 것이지요.

2. X1, Y1, X2, Y2

화상을 찾을 영역을 지정해줍니다. 왜 좌표가 두 쌍((x1, y1), (x2, y2))이나 필요하나면, 컴퓨터에서 '영역'은 흔히 가상의 사각형으로 표현하며, 사각형은 '왼쪽 위 꼭짓점'과 '오른쪽 아래 꼭짓점'으로 표현될 수 있기 때문입니다.

간단하게 아래 그림은 컴퓨터에서 영역을 지정하는 사각형에 왜 두 쌍의 좌표가 필요한지를 보여줍니다.

사진 2. 컴퓨터에서 사각형은 두 쌍의 좌표로 표현됩니다.

3. ImageFile

찾을 화상이 담긴 이미지 파일의 경로를 적어줍니다. 상대 경로와 절대 경로 중 원하는대로 적을 수 있습니다. 절대 경로와 상대 경로에 관한 내용은 Run 함수 강좌의 절대 경로와 상대 경로 부분을 참고하시길 바랍니다.

또한, 경로 앞에 함수의 옵션을 적어줄 수 있습니다. 옵션과 경로는 띄어 써줍니다. 적을 수 있는 옵션은 여러가지가 있지만, 기초 강좌인 이곳에서는 한 가지(*n)만 말씀드립니다.

  • *n: 색상 음영 오차 허용도를 의미합니다. *0부터 *255까지 적을 수 있습니다.
    예를 들어서 *2를 지정했다면 화상을 구성하는 어떤 화소가 #444444 색상을 가지고 있다면 #424242부터 #464646까지의 색상은 그 화소의 색상과 동일한 것으로 간주합니다.
1ImageSearch(&outputVarX, &outputVarY, 100, 100, 200, 200, "*10 Image.bmp")
*n 옵션의 사용

실습

ImageSearch의 전체 과정

기초적인 단계부터 실습해보겠습니다. ImageSearch는 범위 내에서 화상을 찾는 함수이기 때문에, 찾고자 하는 화상이 이미지 파일 형태로 존재해야합니다. (정확히는 파일 말고도 비트맵을 불러와서 사용할 수 있지만, 추가적인 옵션과 'GDI'라는 일종의 기능에 대해 이해해야하므로 생략합니다.)

1. 화상 캡처하기 (이미지 파일 만들기)

우선 화상을 파일 형태로 저장해야합니다. 시중에 있는 캡처 프로그램이나, Windows 기본 기능을 사용하여 찾고자 하는 화상을 캡처하여 이미지 파일로 만들어줍니다. 저는 제가 제작한 캡처 프로그램인 '흰캡처'를 이용하였습니다. (강좌를 잘 보고계시다면 써보시고 주변 사람들께 추천 한번씩 해주세요.)

새로고침 버튼의 모습 사진 3. 이미지 파일에 저장된 화상의 예

원활한 탐색를 위해, 파일 형식은 'BMP'혹은 'PNG'로 캡처하시면 됩니다. 이들은 무손실 파일 형식이기 때문에, 화상을 파일로 저장할 때 변형이 일어나지 않습니다. 반면 'JPEG'와 같은 형식은 (현재는 무손실로 저장할 수 있는 표준이 나왔지만,) 일반적으로 손실 저장되기 때문에 많이 변형되곤 합니다. 찾을 화상이 변형되어 저장되면 동작이 되지 않을 수 있겠죠?

또한, 이미지 파일은 최대한 작지만 특징이 드러나게 캡처합니다.

2. 함수 사용하기

캡처한 이미지 파일이 스크립트와 같은 경로에 'Image.png'로 저장되어있다고 합시다. 그리고 함수의 원형에 맞춰서 함수를 호출해줍시다.

1result := ImageSearch(&posX, &posY, 0, 0, 400, 400, "Image.png")
2MsgBox(result)
ImageSearch의 사용

기본 좌표 유형은 클라이언트 좌표죠? 즉, 활성 창의 클라이언트 영역을 기준으로 (0, 0)부터 (400, 400)까지 'Image.png'와 같은 화상이 있는지 찾게 되며, 만약 찾으면 1이 출력되고 찾지 못하면 0이 출력됩니다. 이미지 파일의 경로로 Image.png만 적어주었으므로 현재 스크립트 파일과 같은 위치에 있는 Image.png를 지정해준 것입니다.

⚠ 함수 실행 시 오류가 발생할 경우

여러분은 대부분 0이나 오류 메시지가 나타날 것입니다. 0은 화면에서 화상을 찾지 못했다는 것이고, 오류 메시지는 'Image.png' 파일이 없거나 인수를 잘 못 적어준 것입니다. 이미지 파일의 경로를 잘못 적어주었을 수도 있죠. 34강의 '좌표 알아오기'를 참고하여 클라이언트 좌표로 (0, 0)부터 (400, 400) 사이에 있는 화상을 잘 캡처했다면 1이 출력될 것입니다.

아, 그리고 SciTE4AutoHotkey에서 위 예제를 실행하면, 당연히 활성 창이 SciTE4AutoHotkey가 되겠죠? 이 점도 고려하시길 바랍니다.

3. 유의미하게 구현하기

ImageSearch 함수는 화상을 찾았는지 여부를 반환하고, 만약 찾았다면 &OutputVarX, &OutputVarY 매개변수에 찾은 화상의 좌표가 담긴다고 했습니다.

이런 기능을 이용해서, ①핫키를 누르면 화상을 찾아 그 부분을 더블클릭하기, ②화상이 나타날 때까지 기다렸다가 나타나면 메시지 박스를 띄우기 이렇게 두 가지 예제를 구현해보겠습니다.

① 화상을 찾아 더블클릭하기

1CoordMode("Pixel", "Screen")
2CoordMode("Mouse", "Screen")
3
4F1::
5{
6 if (ImageSearch(&vx, &vy, 0, 0, A_ScreenWidth, A_ScreenHeight, "Image\1.png"))
7 MouseClick("L", vx + 20, vy + 20, 2)
8 ExitApp
9}
예제 1. 핫키를 누르면 화상을 찾아 더블클릭합니다.

CoordMode를 사용하여 화상을 주 모니터의 왼쪽 위를 기준으로 찾도록 하였고, 이미지 파일은 Image 폴더 안에 1.png를 찾도록 하였습니다.

A_ScreenWidth와 A_ScreenHeight는 주 모니터의 너비와 높이가 들어있는 변수입니다. 1920x1080 해상도에 화면 배율을 100%로 놓는 일반적인 경우에는 A_ScreenWidth가 1920, A_ScreenHeight는 1080이 담겨있죠. 따라서, (0, 0)부터 (A_ScreenWidth, A_ScreenHeight)를 범위로 한다는 뜻은 '주 모니터 전체'가 되겠습니다.

그리고 찾은 화상의 좌표의 (vx + 20, vy + 20) 부분을 클릭하죠. 조건문에 의해 화상을 찾았을 때만 클릭할 것입니다. (vx, vy)는 찾은 화상의 왼쪽 위 부분인데, (vx + 20, vy + 20)는 그것보단 좀 오른쪽 아래(+20px)를 의미합니다.

비디오 1. '예제 2'를 실행하여 바탕화면의 아이콘을 더블클릭 하는 동작

② 화상이 나타날 때까지 기다리기

화상이 나타날 때까지 기다리는 예제를 만들어보겠습니다. '화상이 나타날 때까지'라는 것은 곧 'ImageSearch의 반환값이 '참'일때 까지'를 의미합니다. 따라서, ImageSearch의 반환값이 참이 될때까지 반복해서 찾으면 되겠네요.

1CoordMode("Pixel", "Screen")
2Loop
3{
4 if (ImageSearch(&vx, &vy, 0, 0, A_ScreenWidth, A_ScreenHeight, "Image\2.png"))
5 break
6 Sleep(1000)
7}
8MsgBox("찾았습니다! 좌표: (" vx ", " vy ")")
예제 2. 화상이 찾아지면 반복문 탈출

한 줄씩 따라가면 어렵지 않은 코드입니다. 화상을 매 반복마다 찾으며, 존재한다면 반복문을 탈출합니다. 8번 줄의 MsgBox는 화상을 찾았을 때만 실행되겠죠.

눈여겨 볼 부분은 6번 줄의 Sleep 함수입니다. 이와 같이 지연 시간이 없으면 스크립트는 ImageSearch를 지연 시간 없이 계속 수행할 것입니다. ImageSearch는 비용이 큰 함수이기 때문에, 이는 환경에 따라 프로그램이 버벅이거나 멈추는 원인이 되곤 합니다. 그러니 반복문을 쓸 땐 매 반복마다 적절한 지연 시간을 주는 것이 좋습니다.

Tip: 프로그램에서 '실행 비용이 크다'란?

실행 비용이 크다는 것은 컴퓨터의 자원을 많이 쓰는 것을 의미합니다. 꼭 금전적인 '비용'을 의미하는 것이 아니라, 하드웨어 자원을 과도하게 소모하여 프로그램이나 컴퓨터의 성능이 저하되는 정도가 크면 '비용이 크다'라고 합니다.

반복문에서 무거운 함수를 지속해서 수행하게 한다면 자원을 많이 쓰기 때문에 컴퓨터는 쉬지 못할 것입니다. ImageSearch는 CPU의 연산을 많이 사용하기 때문에, 빠른 속도로 반복하면 CPU를 과점하게 됩니다. 프로그램이 CPU를 무한정 과점할 수 없기 때문에, 가능한 한 반복문에는 지연 시간을 주는 것이 좋습니다.

ImageSearch와 화면 변화 감지

화면 상에서 주어진 이미지 파일과 일치하는 화상을 찾는다는 동작은 분명 유용하지만, 그만큼 변수가 많은 동작이기도 합니다. 컴퓨터 환경마다 화상이 조금씩 달라지기도 하고, 자칫하면 화상이 다르게 캡처되어 이미지 파일과 다를 수도 있습니다. 심지어 비용도 높습니다!

ImageSearch는 말 그대로 화상을 구분하기 위해서만 사용하는 것이 좋습니다. 화면의 변화는 가능하면 다른 기준을 사용하여 감지하는 것이 좋습니다. 특히, 그 변화가 정확히 화면의 한 지점에서만 일어난다면 다른 함수로 대체할 겨를이 많습니다. 한 프로그램 내에서만 일어나는 변화라면 아예 '찾기'와 관련된 함수를 쓰지 않고 그 프로그램의 변화를 감지할 수도 있습니다.

따라서 ImageSearch는 단순히 '화면의 변화 감지'가 아니라 '위치가 정해지지 않은 화상이 화면에 있을 때, 그 위치를 파악' 하는 때 사용하는 것이 적당합니다.

질문하러 가기