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

최혜성·2024년 2월 5일
0

그림 그리기

그래서 파파고의 이미지 번역 기능을 한번 봤다.

https://www.venturesquare.net/817186

확실히 이미지위에 번역된 텍스트를 넣으니 가독성이 훨씬 좋고 레이아웃 또한 깨지지 않았다.

그리고 이걸 보고나니 표 문제도 해결할 수 있었다.

단순히 억지로 '문자열'로 결과값을 반환하는게 아닌, 위 파파고처럼 이미지를 그려 반환하고,
'표'를 어떻게든 아둥바둥 추출하는게 아니라, 표 내부의 셀만 잘 인식됐다면 해당 셀위에다 번역된 텍스트만 잘 넣어도 문제가 해결되지 않을까?
라는 생각이 들었다.

과목성적
국어100
수학95

위 표를 문자열로 반환하는 대신 이걸 그대로 번역해보자.

SubjectScore
Korean100
Math95

확실히 원본의 레이아웃을 깨지 않고, 이해하기 좋았다.

그래서 파파고처럼 이미지 위에 번역된 이미지를 덧씌워 번역된 이미지를 반환하기로 했다.

근데 애초부터 번역된 이미지 반환했으면 안됨?

갈! 자고로 기획을 잃는것은 죽음을 의미하는것이며..

원래 기획서에는 기존 문장과 번역된 문장만 제공하고 추가적인 데이터를 반환하는 부분이 없어
아~ OCR로 읽혀진 데이터를 개행문자 넣어서 문장단위로 반환하면 되겠구나 라고 생각해서 문자열에 얽매였다는점이 너무 컸다.

이는 하나의 핑계였을뿐, 내가 좀더 넓은 범주에서 생각하지 못한게 패인으로 볼 수 있었다.
그래서 일단 번역된 이미지 + OCR로 읽혀진 문자열을 반환하기로 했다. 이러면 기존 기획에서 크게 틀어지는 부분도 없었기 때문이다.

미술 7등급도 그림 그리는 방법

우선 응답값으로 넘어오는 문단의 polygon과 그 내용을 이용하기로 했다.
Azure의 한 문단을 구성하는 'Paragraph'는 해당 문단을 둘러싸고 있는 박스인 BoundingBox(x,y)와 해당 문단을 구성하는 내용으로 이루어져 있다.

그러면 BoundingBox 범위를 마스킹한뒤, 번역된 내용을 그 안에 써넣으면 되겠다!

그래서 자바의 Graphics를 이용하여 작업을 하기로 했다.

과정은 다음과 같다.
1. 이미지를 복제한다.
2. Paragraph의 영역을 마스킹해서 지운다.
3. Paragraph의 영역 내부에 알맞게 번역된 텍스트를 넣는다.
4. 반환한다.

일단 1,2,4 까지는 하겠는데 3에서 약간 어려웠던 점이 있었다.

  • 번역된 텍스트는 어떻게 구해올것인가.
  • 칸 내부에 알맞게 텍스트를 어떻게 집어넣을것인가.

텍스트 번역

번역된 텍스트는 각 Paragraph 순서에 맞게 하나의 문자열로 flatten 시킨후, 번역 api를 호출해 통째로 번역한 뒤, 다시 문자열을 분리시켜 각 Paragraph에 맞는 번역 문자열을 구하였다.

ex) Paragraph(1, "안녕하세요"), Paragraph(2, "반갑습니다"), Paragraph(3, "고마워요")
이를 하나로 flatten 시키면 "안녕하세요\n반갑습니다\n고마워요"가 되고, 다시 분리하면
"Hello\nNice to Meet you\nThank you"가 될것이고 이를 \n단위로 분리한뒤 순차적으로 집어넣으면 된다.

각 Paragraph 순차적으로 반복하면서 번역하고 집어넣고를 반복해도 되지만, 그러면 paragraph 수만큼 번역 api 호출이 일어나므로 최대한 묶어서 호출할 수 있도록 하였다.

글씨 넣기

이제 번역된 텍스트를 칸 내부에 집어넣을 방법을 생각해야 했다.
어떻게 보면 간단한 방법이지만 문제는 텍스트를 표현하는 방식이 '그리는' 방식인 점이였다.

번역된 텍스트의 경우 기존 길이보다 더 길어진 경우가 있어 칸 내부에 줄바꿈을 포함해서 적절하게 나타낼 방법이 필요했다.

어디서
대한민국, 대구광역시 동성로 12-1...

이러한 형태의 표가 있다고 가정하자. 이를 이제 번역하면 "Where\nRepublic of Korea..."의 형태로 번역이 될것이다.
다시 표에 넣어보자

Where
Dongseong-ro, Jung-gu, Daegu, Republic of Korea 12-1...

위의 원본의 표보다 좀더 길어졌다. 이게 단순히 문자열을 다루는 방식이면 괜찮지만, 이미지위에 그리는 방식이기 때문에 원본 이미지의 Paragraph 영역을 벗어나버리게 된다.

그래서 폰트 크기를 축소하고, 줄바꿈을 넣고.. 하는 과정이 필요했는데 해당 방식에 대한 정보가 없어서 직접 구현이 필요했다.

고민끝에 다음과 같은 방법을 찾아냈다.

폰트 크기 = 50 //가장 큰 폰트 사이즈
while (true) {
	폰트 하나당 너비, 폰트 하나당 높이 = 폰트 크기 구하기(폰트크기)
    한줄에 들어가는 폰트수 = Paragraph의 너비 / 폰트 하나당 너비
    총 줄수 = 전체 문자열 수 / 한줄에 들어가는 폰트 수
    적절한 폰트 여부 = Paragraph의 높이 > 총 줄수 * 폰트 하나당 높이
    if (적절한 폰트 여부)
    	break
    else
    	폰트크기--
}

위와 같은 방식으로 구현했다.
가장 큰 폰트부터 시작해서 폰트 크기를 하나씩 줄여나가면서 해당 영역에 맞는 줄바꿈과, 너비를 구할 수 있는 방식이다.

위 방식대로 했더니 얼추 결과가 잘 나와서 살짝씩 미세 조정을 하였고, 이미지를 write한뒤 클라이언트에 게 반환하여 해당 문제를 해결하였다.

 fun findSuggestedFontInArea(graphics: Graphics, area: Area, content: String): FontData {
        val width = area.width.toInt()
        val height = area.height.toInt()
        var font = Font("Arial", Font.PLAIN, 50)
        var data = findFontData(graphics, font, width, height, content)
        for (i in 0..50000) {
            data = findFontData(graphics, font, width, height, content)
            val ascentHeight = height - graphics.getFontMetrics(font).ascent
            if (data.maxLine * data.charHeight <= ascentHeight || font.size == 1)
                break

            font = Font(graphics.font.name, graphics.font.style, font.size - 1)
        }
        return data
    }

while문에서 문제가 생기더라도 어떻게든 멈출 수 있게 for문으로 대체했다. (사실상 50~1까지 반복하므로 50정도만 해도 적합하다.)

while문이 안끝나는 순간 공포가 생겨서.. 개인적으론 조금 크더라도 멈출 수 있는 for문을 선호하는 편이다.
진짜 코드 잘짜는 분들은 while문을 신뢰하고 구성할 수 있겠지만 아직까지는 좀 무섭다.

아무튼 이렇게 해서 이미지 위에 덧그려서 반환을 했고, 잘 반환되는 모습을 확인했다.

Result

원본

번역

https://www.fnnews.com/news/202010071043165178

파파고에 비하면 좀 좋지 않은 퀼리티지만, 그래도 번역된 데이터가 기존 이미지 위에 잘 그려져 있는 모습을 볼 수 있다.

아직도 현재 진행형인 프로젝트라 끝나기 전까진 쭉 개선해보고자 한다.

profile
KRW 채굴기

0개의 댓글