코딩뉴비의 OCR 문제 해결기 - 1

최혜성·2024년 2월 5일
0

지난 12월부터 현재까지 어쩌다 보니 OCR 관련해서 백엔드 시스템 구축 + 로직 개발을 맡게 되었다.

백엔드야 늘상 밥먹다가도 하던거라 익숙했지만, OCR를 이용한 시스템이라는 점이 새롭게 느껴졌기도 하고, 잘 해보지 않았던 분야라 좀 어렵게 다가왔던게 있었다.

@Required

백엔드 프레임워크는 사실상 자유로웠지만, 핵심 로직을 구현하는게 가장 중요했다.
OCR로 문서를 찍어 올리면, 해당 문서를 각 나라에 맞게 번역하는 기능이였다.

쉽네?

그냥 클로바나 구글 비전같은거 써서 문자 쫙~ 긁어온 뒤, 번역기 넣고 돌리면 결과값 나올거고, 그거 그대로 반환하면 되는거 아닌가~? 해서 간단하게 생각하고 프로젝트를 시작했다.

@First

우선, 어떻게 보면 간단한 프로젝트지만, 서버에 있어서 가장 중요한것은 성능이므로, 핵심 로직중 번역에 있어 걸리는 시간을 측정하고, 번역 품질을 서로 비교해서 더 나은 API를 사용하기로 했다.

비교군은 다음과 같이 3개를 정했다.

  • Google Translation
  • Naver Papago
  • Deepl

여기서 구글이랑 파파고는 양대산맥이라 그럴 수 있어도, DeepL은 뭐냐? 라고 할 수 있다.
DeepL은 파파고에 뭍혀 국내에서는 인지도가 낮지만, 딥러닝이 적용된 번역기로 최근들어 다른 번역기보다 월등한 성능을 보여주고, 문맥에 따라 자연스러운 번역을 제공하는 기능을 갖고 있다.
https://www.etnews.com/20230222000243

그래서 이미지에서 텍스트를 추출하는 OCR도 중요하지만, 인식된 텍스트를 번역하는 부분도 비등하게 중요하기 때문에, 해당 부분을 고려하기로 했다.

Performance

영어 - 한글로 된 자료를 이용, 영어를 한글로 번역해 원본의 한글 번역본과 얼마나 유사한지, 속도는 몇 ms가 걸렸는지 측정하였다.

Papago - time : 0.39210081100463867
Google - time : 1.0491933822631836
Deepl - X

번역된 문장은 생략하였다.
속도 측면에서는 Papago가 가장 빨랐고, Google, Deepl 수준으로 순위가 매겨졌다.
번역된 문장의 정확성은 DeepL이 압도적이였고, 파파고, 구글순으로 좋았다.
마지막으로, 경제적인 비용은 Google, DeepL, Papago순으로 저렴했다.

따라서, 종합적으로 고려했을때 무난한 Google의 번역기를 사용하기로 했다.

다만.. 추후에 DeepL의 속도를 정확하게 측정하지 못한점(무료 API도 카드 등록이 필요했음..ㅜㅜ)
측정한 데이터셋의 신뢰성 + 측정을 한번밖에 안한점 + 다른 나라의 언어도 테스트해봐야 하는점
-> 최종적으로 정량적 수치 없이 정성적으로 평가되있는점
해당 부분에 있어서 지적대상이 되었다.

내가 크게 간과한점이
모든 성능 측정에 있어서 정량적인 정보를 보여줘야 그것을 평가하고, 확인할 수 있다는 점이 있었다.
또한, 측정한 자료의 신뢰성 또한 중요하여 만약 측정한 자료가 신뢰받지 못한다면, 측정된 정보또한 신뢰할 수 없다는것이였다.

해당 부분에 있어서 신경쓰지 못한게 한편으론 부끄러웠지만, 이러한 점들을 알게 되었고 더 발전하여 추후에는 같은 실수를 반복하지 않게 잘 새겨들었다.

@Second

우여곡절 끝에 OCR 개발 파트를 진행하게 되었다.
OCR이야 그냥 API써서 글자 다 가져오면 되는거 아닌가~ 라고 생각하며 API에 인식시킬 문서 형태를 본 순간 생각이 박살났다.

Expected..


                   요런 줄글을 기대했는데..

But..


               이것처럼 형식이 있는 문서가 나와버렸다.

https://en.wikipedia.org/wiki/Lorem_ipsum

각 API (Clova, Vision)이 제공하는 응답은 진짜 단순히 읽혀진 문자를 반환하기 때문에, 그냥 읽혀진 문자들을 다 이어 붙이고, .과 같이 문장이 끝나는 부분에서 개행 문자만 넣어서 반환하면 될것 같다고 생각했었다.

근데, 아래와 같은 형식은, 읽혀진 문자를 하나로 다 이어붙이는 순간, 문장 서순이고 뭐고 레이아웃을 고려하지 않았기 때문에 다 박살난다.

@Third

그래도 어떻게든 해결해보자 라고 생각하며 다음과 같은 방법을 우선적으로 생각해봤다.

  • ChatGPT를 이용, 인식된 텍스트를 다 제공하고 해당 텍스트를 기반으로 문맥에 맞게 정렬해달라 하기
  • 인식된 텍스트의 Polygon(좌표)값을 이용해서 어떻게든 유사한 문장끼리 이어붙이기

원래 첫번째 방안은 같이 하시던분이 생각하셔서 적용한 방법이였는데, 문제는 ChatGPT API가 너무 많이 느렸다. 한 답변 받는데 최소 20~30초까지 걸려서 OCR시간과 합치면 상당한 시간이 소요되었다.

이를 최대한 배제하고 다음으로 시도한 방법은 텍스트와 함께 반환되는 각 단어의 좌표값을 이용하는 방식이였다.

Polygon

Polygon은 x,y좌표로 구성되어 둘러싸고 있는 BoundingBox를 추론할 수 있도록 리턴되는 값이였다.
이를 이용해서 최소 x,y 좌표를 가지는 min Point, 최대 x,y좌표를 가지는 max Point를 구하였다.

이후, 이 값을 이용해 각 문장을 추출해보기로 했다.

  • Y는 같으나 X만 달라지는 경우
    이는 띄어쓰기와 같이 x좌표가 살짝 움직일때는 같은 문장으로 이어붙였고, 일정 크기 이상 넘어갈경우 다른 문장에 속하는 경우이므로 문장을 분리하였다.

오늘따라 날씨가 좋네 -> 오늘따라 / 날씨가 / 좋네

이때, x좌표가 크게 변하지 않으므로 이를 하나로 합친다.

오늘 하늘이 너무 우중충       내일부터는 한동안 장마가
해서 집에 빨리 가고싶어       지속될 예정입니다.

이 예시에서는 x좌표가 크게 변하는 부분이 있으므로 우중충 까지 한 문장, '내일부터 한동안..'한문장 이렇게 구성하였다.

  • X는 같으나 Y가 달라지는 경우
    이는 이제 줄바꿈이 일어나는 경우로, 줄바꿈이 일어나도 하나의 문장으로써 구성되어야 한다.
    따라서, y좌표가 크게 변화하지 않는경우 같은 문장으로 판단하고, 크게 변하는경우 다른 문장으로 분리하였다.

                                  &emsp안녕 하세요

대충 줄바꿈의 예시이다. 문장이 진행되는 도중 좌우 여백의 한계로 줄바꿈이 일어날때, 같은 문장이더라도 분리되는 경우가 있으니 이를 하나의 문장으로 파싱한다.

안녕하세요
                                            저도 반가워요~

이와 같은 문장에서는 y좌표가 일정 범위 이상 차이나므로 각기 다른 문장으로 취급한다.

좋아 완벽해..?


왜 안돼지..?ㅜㅜㅜㅜㅜㅜ

여기서 내가 또 간과했던점이 있었다. X좌표가 일정 길이가 넘어서 다른 문장으로 취급하고, 다음 문장을 읽고 있는데, 다음줄이 하필이면 기존 문장이랑 이어지던 부분이였던것이다.


                      이해 못함

예시를 들어서 설명하겠습니당..

오늘 하늘이 너무 우중충       내일부터는 한동안 장마가
해서 집에 빨리 가고싶어       지속될 예정입니다.

이 문장을 토대로 위 방식을 적용한다면.

오늘 하늘이 너무 우중충 - 정상파싱
<<X좌표 차이가 너무 많이남 - 새로운 문장>>
내일부터는 한동안 장마가 - 정상파싱
<<개행 - 기존 문장이랑 이어붙임>>
내일부터는 한동안 장마가 해서 집에 빨리 가고싶어
<<X좌표 차이가 너무 많이남 - 새로운 문장>>
지속될 예정입니다.

이렇게 파싱이 되어서 최종적으로는
오늘 하늘이 너무 우중충
내일부터는 한동안 장마가 해서 집에 빨리 가고싶어
지속될 예정입니다

이렇게 이상한 문장 3개가 나오게 된다...
이말인 즉슨, 문장이 쭈욱 이어지는게 아니라, 문서에 단이 정해져 있어 한 줄에 2개의 문장이 나올 수 있는 경우 문제가 되었다.

책 같은 경우는 그냥 문장으로 쭉 되어 있어 위 방식을 적용해도 문제가 없는데, 형식이 있는 문서라 생기는 문제였다.

이것에 대한 해결 방안으로 줄바꿈시 x좌표가 가장 가까운 문단을 찾고, 거기다 이어붙이는 방식도 생각했으나, 문서 형식이 워낙 제각기 달라 결과값이 일정하지 않았다.

어카지..

ChatGPT를 쓰기는 그렇고.. 문장 파싱은 안되고...
이렇게 고민하던중 Google Cloud Vision에서 각 문단별로 깔끔하게 분리해서 반환하는 Block형태가 있다는것을 알게 되었다.

2편에 계속

profile
KRW 채굴기

0개의 댓글