서양 고전을 연구하시는 분들에게는 Google Books나 Archive의 고전 문헌 스캔 및 공개 서비스는 꽤나 큰 도움이 됩니다. 해당 문헌을 보기 위해 직접 산넘고 물건너 비행기 타고 먼 외국의 대학까지 가는 대신, 집에 앉아서 인터넷으로 클릭 몇 번으로 고문헌들을 살펴볼 수 있으니깐요. 게다가 단순히 스캔한 이미지 파일 뿐만 아니라 텍스트를 스캔하여두었기 때문에 텍스트 검색도 지원한다는 것이 큰 장점입니다. 전체 문헌을 일일히 살펴보지 않아도 검색을 통해 필요한 페이지만 찾아볼 수 있으니깐요!
그런데 사실 약간의 문제가 있습니다. 고전 문헌에 쓰인 글자체가 오늘날의 글자 모양과는 조금 달라서 OCR시 잘못 인식되는 경우가 많다는 겁니다. 활자본은 그나마 낫습니다. 수기로 작성한 문헌은... 글자 크기나 모양이 일정하지 않고, 굵기가 달라지거나, 줄을 맞추고 쓰지 못해서 점점 밀려올라가는 경우도 있죠. 가장 어려운 경우는 자신만의 줄여쓰는 약자를 만들어 쓰는 거구요. 당연히 현대의 OCR는 현대 텍스트를 인식하는데 초점이 맞춰져 있기 때문에, 성능이 잘 안 나올수 밖에 없습니다.
사실 예전부터 Google Books를 사용하면서 이런 문제를 느껴왔기 때문에 할수만 있다면 한 번 오래된 글꼴에 맞춘 OCR 툴을 만들고 싶었습니다만, 그 당시에는 데이터도 없고, 제 능력도 부족하여 그럴 수가 없었습니다. 최근 Archive.org 에서 대량의 고전 라틴어 문헌 스캔 이미지를 구해서 갑자기 이 분야에 대한 흥미가 생겨서 며칠 동안 삽질을 해보았는데요, 그 결과를 이렇게 포스팅으로 공유하고자 합니다.
https://archive.org/details/tractatusquistel00cail/page/n9 의 일부분입니다
위의 사진은 1487년에 출판된 고문헌의 한 페이지를 캡쳐를 뜬 것입니다. 라틴어를 배우셨다 해도 이 글꼴을 잘 모르신다면 읽기에 어려우실 겁니다. 제일 윗줄을 현대에 쓰는 글자로 옮겨보자면,
verbo et bono exemplo. sed ut pasceris ab eo. i. ut negliges ...
와 같습니다. 위와 같은 글씨체를 Textualis(텍스투알리스)라고 부릅니다. 14~15세기 프랑스, 영국, 독일 등에서 널리 쓰였던 글씨체이구요, 이 글씨체의 영향은 근대를 거쳐 현대까지 이어져 Blackletter 혹은 고딕체라는 폰트로까지 이어지게 됩니다. 나머지 글자들은 대체로 현대와 비슷한 모양을 하고 있지만, s는 유독 모양이 다릅니다. 단어 끝의 s는 현대와 동일하게 생겼지만 어두나 어중의 s는 ∫처럼 길게 생겼는데요, 이는 f와 헷갈리기 매우 쉽습니다.
그래서 ABBYY와 같이 아무리 훌륭한 OCR 소프트웨어일지라도, 이 텍스트를 제대로 해독해내지는 못합니다. ABBYY의 경우 윗줄의 텍스트를 아래와 같이 인식해냅니다(...)
l>etbo f bono eirempto.feb bf pafc^tio ab eo»{.bt negligfe
위에서 보여드린 ABBYY의 문자 인식 결과는 누가봐도 이상하다는 것을 잘 알수 있습니다. 왜냐하면 문장 중간에 >나 ^, { 등이 끼어드는 경우는 거의 없거든요. 그리고 라틴어를 배워보신 분들은 f가 단독으로 쓰이지 않는다는 것이나 feb라는 단어가 없다는 것, pafc는 잘못된 단어라는 것을 압니다. 즉 우리는 암묵적인 언어지식을 바탕으로 이게 잘못된 결과인지 아닌지를 알 수 있습니다. 예를 들어 다음과 같은 한국어 OCR 결과가 있다고 해봅시다.
나는 빕을 먹있다.
한국인이라면 누구나 저 결과가 잘못됐음을 알 수 있고, 빕은 아마 밥이 잘못 인식된 것이고, 먹있다의 있 역시 었이 잘못 인식된 것이라고 파악할 수 있을 겁니다. 이는 우리의 암묵적 한국어 지식에 의한 것으로 먹다라는 단어에는 빕이라는 단어보다는 밥이라는 단어가 더 자주 쓰이고, 먹 다음에는 있 보다는 었이 나올 가능성이 높다는 걸 알기 때문입니다.
컴퓨터에게도 이런 지식을 가르쳐 줄 수 있을까요? 답부터 말하자면 Yes입니다. 위의 문제는 결국 특정한 문자열 다음에 어떤 문자가 등장할 지를 예측하는 조건부 확률 문제입니다. 제일 간단하게는 한국어 문장에서 먹 다음에 있이 등장하는 횟수와 었이 등장하는 횟수를 카운팅하여 그 비율을 비교하면 됩니다. 조금 더 복잡하게는, 전혀 등장하지 않은 패턴에 대해 smoothing을 적용할 수도 있고, 딥러닝을 활용해 RNN(Recurrent Neural Network)과 같은 기법으로 다음에 나올 글자의 확률을 추론할 수 있습니다. 이를 언어 모델이라고 합니다.
따라서 특정 언어에 대해 잘 학습된 언어 모델을 가지고 있다면, OCR 결과가 애매하게 나와서 헷갈릴 때, 이 언어모델을 이용하여 먹 다음에는 있보다는 었이 나올거다! 라는 사실을 귀띔해줌으로써 좀더 올바른 결과를 얻게 할 수 있겠죠.
다행히도 저는 이미 살아있는 라틴어 사이트를 구축하면서 40여만개의 라틴어 문장을 수집했기 때문에, 이를 가지고 쉽게 언어 모델을 학습할 수 있었습니다. 언어 모델은 Kneser-Ney smoothing이 적용된 6-gram language model을 선택했습니다. 구현도 간단하면서 성능도 높게 나오기 때문이죠. 이 모델에 대한 설명은 https://bab2min.tistory.com/603를 참조하시길 바랍니다.
이미지에서 문자를 인식하는 작업은 이제 인공지능이라 부르기도 뭣할 정도로 대중화되고 정말 간단한 작업이 되어버렸습니다. (중학생도 잘 구현된 라이브러리 가져다 쓰면 하루 이틀이면 만들수 있을겁니다) 저도 그 시류를 타고 tensorflow를 통해 히든 레이어가 2층인 간단한 OCR 모델을 만들어서 학습시키기로 했습니다. 이 부분 코드에 대해서는 다음 포스팅에 공개하였으니 이를 참조하시길 바랍니다.
사실 글자 하나만 따로 떼어놓고 보면 글자를 인식하는건 간단한 작업입니다. 까다로운 부분은 전체 스캔본에서 어디가 글자영역이고, 어디가 글자가 아닌 영역인지 구분하고, 글자가 여러개 붙어있을때 얘네들을 잘 분리해내는 작업입니다. 사실 이 부분이 많이 걱정이 됐는데, 다행히도 Archive.org에서 스캔본을 제공할때 abbyy.xml 파일을 같이 제공해주는 것을 확인했습니다. 이 xml파일에는 OCR 결과로 어느 지점에서 어떤 결과가 발견되었는지에 관련된 정보가 모두 포함되어 있었기 때문에 글자 영역을 따로 잡는 작업을 할 필요는 없었습니다.
다만 이 ABBYY 친구가 하나의 글자를 둘로 쪼개서 인식하거나, 둘 이상의 글자를 하나로 합쳐서 인식한 경우가 있었는데, 이 경우를 대비해 다음과 같이 여러 종류의 후보를 사용해야 했습니다.
1. ABBYY의 글자 영역이 실제 글자 1개와 일치하는 경우
2. ABBYY의 글자 영역이 실제 글자 2개 이상과 일치하는 경우
3. ABBYY의 글자 영역 2개 이상이 실제 글자 1개와 일치하는 경우
2번의 경우 ABBYY의 글자 영역을 Y축으로 스캔하여 평균 밝기 값이 높은 극대점을 찾습니다. 극대점을 기준으로 자른 것과 자르지 않은 것 양쪽을 후보에 다 넣고 언어 모델 상에서 확률이 더 높은 쪽을 선택하게 합니다.
3번의 경우 연속한 2개 이상의 글자 영역이 서로 겹치는 경우, 합친 것과 합치지 않은 것을 모두 후보에 넣고 언어 모델 상에서 확률이 더 높은 쪽을 고르게 합니다.
이렇게 생성된 여러 후보들은 언어 모델을 통해 얻은 확률과 조합하여 전체 OCR 결과의 score를 구성하는데에 쓰입니다. score가 가장 높은 문자열을 출력하도록 하면 그게 OCR의 최종 결과가 되는거죠. score가 높은 경로를 탐색하기 위해 Kiwi에서도 썼었던 비터비 알고리즘의 변형 버전이 사용되었습니다.
이상적으로 하얀 배경에 검은 글자가 나오면 좋겠지만, 스캔한 이미지가 그렇게 깔끔하게 나오는 경우는 없습니다. 누런 배경에 반대편 쪽의 글자가 비치는 경우도 많고, 전반적인 노이즈도 큰 편입니다. 이를 흰 배경에 검은 글자로 바꾸기 위해서는 High Pass 필터를 이용하는게 좋습니다. (예전 글 참조) 이를 Python 코드를 통해 이런 필터를 적용시키는 것은 의외로 간단합니다. pillow와 같은 이미지 라이브러리를 이용하여 전체 이미지에 Gaussian Blur를 걸고, 원본 이미지에서 blur된 이미지를 빼 버리면 high pass 필터와 동일한 결과를 냅니다. 여기서 회색부분(RGB값이 128이상인 부분)을 다 흰색으로 바꿔버리면 깔끔한 이미지를 얻을 수 있습니다.
각 글자 이미지에 대해 라벨링을 해야 이를 통해 문자 인식 모델도 학습하고, 성능 평가도 진행할 수 있겠죠. 그래서 샘플 문서를 가져와서 라벨링해주는 웹 인터페이스를 후다닥 만들었습니다. 이런 거 만드는데에 시간을 많이 낭비하면 정작 실제 중요한 일을 하는 시간이 줄어들테니 늘 쓰던걸로 후다닥 개발해야죠. Python3 http서버에서 API를 제공하고, PHP쪽에서 처리하는 식으로 해서
이런식으로 구성했고, 약 5000글자를 라벨링했습니다. 의외로 인터페이스가 편해서 금방 라벨링하게 되더라구요. (하다 보니 재미도 있고...) 문제는 5000글자중에서 모음 aeiou가 거의 각각 500~600개를 넘게 차지해 절반 이상이 aeiou고 나머지 자음들이 적은 비중을 차지하고 있다는 겁니다. 전형적인 데이터 불균형인데요, 특히 지프의 법칙을 따르는 텍스트 데이터에서 강하게 나타나죠. 이를 해결하기 위해 갯수가 적은 녀석들은 augmentation을 실시하여 400개까지 불려주었습니다.
5-fold cross validation을 실시해본 결과 모델의 accuracy는 약 91%정도 되었습니다. 즉 글자 1개를 따로 떼어놓고 보면 91%정도의 정확도로 이를 정확하게 맞춘다는 뜻입니다.
위에서 만든 문자 인식 모델에 앞서 설명한 언어 모델을 결합하여 비터비 알고리즘으로 최고 점수를 가지는 결과를 추려내면 어떤 결과가 나올까요? 아주 흡족한 정도는 아니지만, 꽤나 높은 정확도를 보였습니다.
tractatusquistel00cail (해상도 높음) | OEXV745_P3 (해상도 낮음) | |
---|---|---|
ABBYY 결과 | 0.715613 | 0.593506 |
제시한 방법 | 0.937548 | 0.871371 |
테스트 데이터가 크지는 않지만, 두 문헌에 있어서 모두 ABBYY의 결과보다 높은 정확도(맞춘 글자수 / 정답셋의 글자수)를 보였습니다. 해상도가 높은 경우 93%정도의 높은 성능을 보였고, 해상도가 좀 구질구질한 경우는 87%정도의 성능을 보이는군요. 사실 라틴어 특성상 단어 내에서 한 두 글자가 바뀌어도 단어 뜻이 휙휙 바뀌는 경우가 많아서, 이것으로도 부족한 성능이라고 여겨지지만, 기존의 ABBYY의 결과를 개선했다는 것만으로도 충분히 가치가 있는 일이라 생각합니다.
좀더 디테일한 결과는 아래와 같습니다.
제시한 방법 | ABBYY 결과 |
---|---|
verbo et bono exemplo.sed ut pasceris ab eo.i ut negliges | l>etbo f bono eirempto.feb bf pafc^tio ab eo»{.bt negligfe |
et ociosus et vivens liiicuriose:et obmutescente dicat diis | et ociofuo et b(aen@ liijcnnofeiCt obmutetcmre otcat Ono |
Ligatis manibus ne possit se deradere.et pedibus ne pos | fixatio manibue.ne poCTit fe beteOere.et peOibus ne pof |
sit fugere.proiicite eu in tenebras exteriores.i. qtinuo per | fitfu^eee.p^oitciteeu in tcnebmsejrterio^eo.f. stinuo per |
petuevrentes.qr plus dilexit lucru trale * aiailia pro sibus | petueb^entes.q} pluo Otleirlt luctu fpate $ afai;:. pio i|bu« |
filius dei morte sustinuit. Et qr hic virit deliciose et totus | filiuoDei' inoiteCufi'nuit.i6tq2btcbtntbeliciofe‘; tofuo |
좀 더 개선이 필요하겠지만, 일단 현재 방법을 통해 교정된 OCR 결과들은 살아있는 라틴어 사이트에 차례로 업로드될 예정입니다. Archive.org에서 제공하는 라틴어 문헌이 10만개가 넘어가는지라, 이 모든 결과를 처리하여 제공하려면 시간이 좀 걸릴것 같군요.
+ 사실 고문헌용 OCR 툴로 ocular라는 소프트웨어가 발표되긴 했습니다. 고문헌들의 폰트가 제각각이고 글자가 다른 경우도 많으니 이를 수작업으로 만든 라벨링 데이터를 바탕으로 학습시켜서 사용하기가 어려운게 사실입니다. 따라서 이미지 내에서 비슷한 글자들을 모으고 언어 모델을 바탕으로, 각 형태들이 어떤 글자에 알맞을지 추론해서, 적절하게 OCR를 해낸다는게 이 알고리즘의 개요입니다. 1
처음엔 이 툴로 OCR을 해보려 했는데 textualis 글꼴을 잘 잡아내지 못하더라구요. 좋은 툴인데 적용하지 못한다는게 아쉬웠습니다.
슈퍼 마리오 갤럭시 한글판 HD 텍스처 팩 (2) | 2020.03.08 |
---|---|
Seq2seq를 이용한 텍스트 Autoencoder + 이를 이용한 클러스터링 (4) | 2019.02.16 |
한국어 고문헌 검색기 '어듸메' 개발기 (22) | 2018.11.22 |
스타크래프트2 종족전쟁 저그 소개영상 (0) | 2014.11.22 |
페북 앱 좋아요b - 버그 제보 및 건의 사항 (0) | 2014.04.08 |
(스타크래프트2 2.1 패치를 기념하며) Brood War 확장 모드 게시 (4) | 2014.01.23 |
댓글 영역