OCR로 사과게임 보드 추출하기
문제: 실제 보드 데이터가 필요했다
사과게임 보드가 어떻게 생성되는지 연구하려면, 먼저 많은 양의 실제 보드 데이터가 필요했다. 하지만 원본 게임은 단순히 플래시 게임을 html로 포팅한 것으로, 각 보드를 구성하는 숫자를 그대로 내보내주지 않는다. 조회할 수 있는 API도 없고, 분석할 세이브 파일도 없다. 화면에 보이는 것, 즉 초록색 프레임 안에 들어 있는 한 자리 숫자 그리드가 전부다.
그래서 남은 방법은 스크린샷에서 숫자를 직접 추출하는 것. 게다가 의미 있는 통계 분석을 하려면 보드 몇 개로는 부족했고, 수백 개는 필요했다. 현실적으로 이걸 손으로 옮겨 적는 건 말이 안 된다고 생각했다.


왜 OCR이 유일한 방법이었나
우리 연구에는 최소 500개의 보드가 필요했다. 각 보드는 17 x 10 그리드로, 총 170개의 셀을 가진다. 스크린샷 한 장에서 숫자 170개를 손으로 옮겨 적으려면 보드 하나당 몇 분은 걸릴 수 있고, 500개면 지루하고 실수하기 쉬운 작업이 엄청나게 쌓인다.
실질적인 선택지는 자동화된 파이프라인뿐이었다. OCR을 쓰면 스크린샷 한 장을 1초도 안 되어 처리할 수 있고, 500개 전체도 몇 분이면 끝난다. 게다가 Deterministic한 파이프라인은 같은 입력에 대해 항상 같은 출력을 내놓는다.
추출 파이프라인
파이프라인은 원본 스크린샷을 받아 각 셀에 대응하는 170개의 정수 배열을 출력한다. 전체 과정은 다섯 단계로 이루어진다.
- Frame detection. 사과게임 보드는 특유의 초록색 테두리 안에 있다. HSV Space에서 Color Thresholding을 적용해 이 테두리를 찾고, 가장 큰 Contour를 이용해 스크린샷 안에서 보드 영역의 위치를 잡는다.
- Board cropping. 초록색 프레임의 위치를 찾고 나면, 이미지에서 보드 영역만 잘라낸다. 점수 표시, 배경, 각종 UI 요소는 모두 이 단계에서 제거된다.
- Grid alignment. 보드는 17 x 10 그리드, 즉 17열 10행으로 고정되어 있다. 잘라낸 영역의 크기를 기준으로 행과 열 경계를 계산해 170개의 셀로 나눈다.
- Cell isolation. 170개의 각 셀을 별도의 Sub-image로 추출한다. 이 시점에서는 각 이미지에 숫자 하나만 들어 있다.
- OCR. 각 셀 이미지를 전처리한 뒤 Tesseract에 넣어 숫자를 인식한다.




전처리: OCR을 안정적으로 만드는 과정
원본 셀 이미지는 그대로는 OCR에 적합하지 않다. 숫자는 색이 있는 배경 위에 있고 대비도 일정하지 않으며, 셀 이미지 자체도 작다. 전처리 없이 바로 넣으면 Tesseract가 숫자를 잘못 읽거나 아예 결과를 내지 못하는 경우가 자주 생긴다.
각 셀은 다음 여섯 단계의 전처리를 거친다.
- Margin cropping. 네 변에서 작은 여백을 잘라낸다. 그리드 선의 흔적이나 인접 셀의 잔여물이 섞이지 않도록 하기 위해서다.
- Grayscale conversion. 이미지를 RGB에서 단일 채널 Grayscale로 변환해 노이즈를 줄이고 입력을 단순화한다.
- Thresholding. Otsu's method를 사용한 Global Thresholding으로 이진화 임계값을 자동 선택한다. 그 결과 숫자와 배경이 잘 분리된 흑백 이미지가 만들어진다.
- Inversion. Tesseract는 밝은 배경 위의 어두운 글자를 더 잘 처리한다. 게임의 색 구성에 따라 이미지를 반전시켜, 숫자가 항상 흰 배경 위 검은 글자로 보이도록 맞춘다.
- Padding and resizing. 숫자 주변에 균일한 흰색 여백을 추가하고 이미지를 키운다. 이미지가 너무 작으면 OCR이 실패하는 경우가 많기 때문이다.
- Tesseract with digit-only config. 이 부분은 조금 의외였다. 각 셀에 숫자 하나만 들어 있으니 처음에는
--psm 10(Single Character Mode)이 가장 잘 맞을 거라고 생각했다. 그런데 우리 파이프라인에서는 psm 10의 인식 결과가 불안정했다. 특정 숫자를 반복해서 잘못 읽거나 빈 결과를 내는 일이 있었다. 여러 설정을 시험해본 끝에,--psm 6(Uniform Block of Text 가정)이 우리 데이터에서는 훨씬 더 안정적으로 동작한다는 것을 확인했다. 왜 이 경우 psm 6이 psm 10보다 나았는지는 명확하지 않지만, 적어도 500개 보드 전체에서 그 차이는 일관됐다. 여기에 더해 Character Whitelist를 1-9 숫자로 제한해 Tesseract가 문자나 특수문자를 추측하지 않도록 했다.


각 셀은 OCR에 들어가기 전에 Crop, Grayscale, Threshold, Inversion, Padding 단계를 거친다.
검증
사과게임 보드에는 알려진 제약이 하나 있다. 170개 숫자의 전체 합은 반드시 10으로 나누어떨어져야 한다.
이 제약은 자연스러운 Checksum 역할을 한다. OCR이 숫자 하나를 잘못 읽으면, 전체 합이 이 조건을 만족하지 못할 가능성이 높다. 우연히 통과할 확률은 대략 10분의 1 정도다. 추출한 500개 보드는 모두 이 검사를 통과했다.
물론 Checksum만으로 완벽한 것은 아니다. 여러 개의 오류가 우연히 서로 상쇄되면, 합은 여전히 맞을 수 있다. 그래서 일부는 수동 검증도 했다. 전체 데이터셋의 5%에 해당하는 25개 보드를 무작위로 골라, 총 4,250개의 숫자를 눈으로 직접 원본 스크린샷과 하나씩 대조했다. 다 맞았다.
Rule of Three에 따르면, 25개 보드에서 오류 0건이 관측되었을 때 보드 단위 오류율의 95% 신뢰 상한은 다음과 같다.
Checksum과 수동 검사는 어느 정도 상호보완적이다. Checksum은 단일 오류가 들어간 대부분의 경우를 잡아내고, 수동 검사는 서로 상쇄되는 경우까지 포함해 나머지를 확인해준다. 둘을 합치면 파이프라인이 Systematic Error를 넣지 않았다는 데 대해 어느 정도 신뢰를 가질 수 있다. 엄밀하게 말하면 두 검증 방법이 독립이라는 것부터 증명해야 하겠지만, 독립이라 가정하면 미검출 오류의 결합 확률은 1.2%이다.
파이프라인 자체도 보수적으로 설계했다. 전처리 결과가 애매하면 억지로 추측하지 않고 입력을 거부하도록 했다. 500개 보드 전체에서 추출 실패나 불일치는 관찰되지 않았다.
전체 방법론과 데이터는 AppleGame+의 보드 생성 연구 전문에서 확인할 수 있다.
전체 글