안녕하세요, 대장코린이입니다.
오늘은 매크로를 위한 이미지 전처리 2번째 입니다.
- 1편
매크로 과정에서 이미지 처리하는 과정에 대해 잘 보여줄 수 있는 예시에 대해 고민한 결과...
오늘은 아주 간단한 메이플 스토리 거짓말 탐지기를 한번 풀어보도록 하겠습니다.
참고로 저는 메이플을 하지 않습니다. 일전에 지인에게 질문이 들어왔어서 그걸 해결해주었는데 생각해보니 예시로 딱 좋겠더군요
뭐 워낙 간단한 부분이니 ㅎㅎ 후딱 해보겠습니다.
1. 원본 이미지
위 사진은 그당시 작업했던 이미지입니다.
만약 저렇게 간단한 연산이 필요한 상황이라고 가정해봅시다.
방법은 여러가지가 있습니다.
첫번째로는 Tesseract(테서렉트)와 같은 OCR을 사용하는 것입니다.
간단하게 설명드리자면, 머신러닝을 이용하여 숫자 혹은 글씨 "이미지"를 실제 숫자(float) 혹은 문자열(string)으로 변환하는 것이라고 생각하시면 되겠습니다.
이 방법의 경우 설치만 하신다면, 굳이 복잡하게 처리할 필요 없이 간단하게 사용이 가능하다는 장점이 있습니다.
하지만 외국 학습 데이터기 때문에 한글 인식이 잘 안된다는 문제와 정확도의 문제가 있을 수 있습니다.
두번째로는 본인이 직접 작업하여 인식시키는 방법입니다.
위의 예시의 경우, 매우매우매우매우~~~ 간단한 형식의 테스트입니다.
(솔직히... 이 거짓말 탐지기가 매크로 방어체계라고 형식만 갖췄을 뿐 실제 효과가 있는지는 모르겠습니다 ㅋㅋ... 옛날꺼라 지금은 어떤지 모르겠습니다만...)
문자가 포함되지 않고 숫자로만 구성된 방식이며, 숫자 폰트도 동일하고 색도 동일하며 크기도 동일합니다.
또한 회전도 들어가있지 않습니다.
사실 이런 부분을 처리할 때는 직접 작업하는 방식이 사실상 성공률 100프로라고 생각할 수 있겠습니다.
2. 이미지 전처리
자 이제 본격적으로 이미지 전처리를 해보도록 하겠습니다.
먼저 저 숫자들을 인식시켜야하는데, 배경이 매우 불편하군요
다행히도 숫자들은 검정색으로 색상이 통일되어있으니, 간단하게 배경을 제거해봅시다.
import pyautogui as gui
import numpy as np
img = gui.screenshot(region = (480,343,350,80))
img_arr = np.array(img)
img_arr[img_arr != 0] = 255
먼저 pyautogui의 screenshot을 이용하여 숫자가 나타나있는 영역에 대해서만 추출해줍시다.
그리고 array로 변환해주고 0이 아닌 값들에 대해 모두 255로 변환해주겠습니다.
그럼 아래와 같이 결과물이 나오네요.
정말 간단한 작업이죠?
이제 이 그림의 숫자들을 모두 문자열로 바꾸어봅시다.
3. 이미지 -> 숫자 변환
사실 작업들이 번거로울 수 있지만
저는 적중률 100프로를 목표로 하기 위해선 필요한 작업이라 생각합니다.
이런식으로 0~9까지의 숫자들을 모두 추출한 뒤, 배경을 삭제하여 저장해줍니다.
이제 이 숫자들을 돌려가며 판단해보도록 하겠습니다.
num_path = r'./' # 전처리 해놓은 숫자들 경로
numbers=glob.glob(num_path+'*.png')
catch = [] # 읽은 숫자를 저장할 변수 지정
catch_x=[] # 읽은 숫자들의 순서를 기록해놓을 변수 지정
result = np.array(list(zip(catch,catch_x)))
for n,npath in enumerate(numbers):
num_str = npath.split('.')[1][-1] # 숫자들의 파일 이름을 통해 숫자가 몇인지 저장
num = Image.open(npath)
num_arr = np.array(num)
num_sz = num_arr.shape
num_pad = num_arr[::-1] # 순서 뒤집어주기
new_pad = pad[::-1]
## (0,0)부터 차례로 옮겨가며 숫자 비교해주기
for i in range(sz[0]-num_sz[0]):
for j in range(sz[1]-num_sz[1]):
comp_pad = new_pad[i:i+num_sz[0],j:j+num_sz[1]]
comp = np.sum(comp_pad * num_pad) ## 픽셀이 겹치는 부분은 1x1 해서 1, 겹치지 않으면 0
score = comp/np.sum(num_pad) ## normalize 시켜서 점수를 0~1까지 매김. 0 : 겹치는 부분이 전혀 없음, 1: 모든 픽셀이 일치함.
if num_str == '1': # 숫자 1의 경우 가장 간단하고 4, 9와 같이 1을 포함하는 숫자들이 있어 점수컷을 높게 주려 따로 설정함.
threshold = 0.9
else:
threshold = 0.8
if score > threshold: # 만약 score가 threshold 보다 높으면 숫자를 찾았다고 생각함.
#print('catch')
catch.append(num_str) # 숫자 저장
catch_x.append(j) # 숫자 순서 저장
catch = np.array(catch)
catch = list(catch[np.argsort(catch_x)]) # 저장한 숫자들을 순서에 맞게 배열함.
string = ''.join(catch) # 숫자들을 합쳐서 나열함.
res = eval(string)
print(f'{string} = {res}')
전체적인 코드 내용은 이렇습니다.
기본적인 구성은 변환하려는 저 수식에서 0~9까지 숫자들을 돌려가며 비교해줍니다.
자세한 내용은 코드와 주석을 참고해주세요.
어찌어찌 결과를 확인해보면, string은 우리가 처음에 변환하려 했던 문자열 '85+38'이 될겁니다.
그럼 이제 이걸 실제 수식으로 변환하여 계싼하면 되는데, 여기서 eval 함수를 사용하면 편하더라구요.
eval 함수는 문자열을 그대로 수식화하여 결과를 내주는 기능입니다.
예를들어 eval("x = 1+1")을 하면 x에 2가 저장되는 형식이죠.
이렇게 처리를 하면 정답인 85+ 38 = 123이 출력되는 것을 확인할 수 있을거에요~
직접 비교를 할 땐 이런 방법도 있다는 것과, 이런식으로 작성하면 정형화된 패턴에서는 적중률 100프로에 가깝게 구현될 수 있습니다.
다만, 만약 패턴이 복잡하거나 다양하면 이런방법으로는 불가능하고 머신러닝을 이용하는 방법이 더 좋겠죠.
그건 상황에 맞게 사용하시면 될 것 같습니다~