이미지 파일 자동 번역 프로그램 개발 기록 (2) OCR 단어 좌표를 문장 위치로 맞추기와 번역 검증

OCR 결과는 문장 단위로 오지 않는다 1편에서 정리했듯이 두 번째 프로그램의 첫 목표는 사람이 영역을 그리지 않게 하는 것이었다. Google Cloud Vision의 text_detection 은 좌표를 함께 돌려주니 그대로 쓰면 될 거라고 생각했다. 실제로 받아 보니 쓸 수 있는 형태가 아니었다. 응답의 첫 번째 항목은 이미지 전체 텍스트이고, 줄바꿈으로 나뉘어 있지만 좌표가 이미지 전체 범위다. 두 번째 항목부터는 단어 하나하나와 그 좌표다. 내가 필요한 것은 그 중간인 "문장 하나와 그 문장이 차지하는 박스"였다. 번역은 문장 단위로 해야 자연스럽고, Photoshop에 텍스트 레이어를 놓을 위치와 글자 크기도 문장 박스에서 나오기 때문이다. 이번 편에서는 단어 좌표를 문장 위치로 다시 맞춘 과정과, GPT-4o 번역이 가끔 원문을 남기는 문제를 어떻게 걸러냈는지를 정리한다. 단순한 방법이 틀리는 이유 가장 단순한 방법은 전체 텍스트를 줄 단위로 쪼개 문장 목록을 만들고, 각 문장의 단어를 단어 목록 전체에서 찾아 그 좌표들의 최소·최대값으로 박스를 만드는 것이다. 상세페이지에서는 이 방법이 맞지 않는다. 같은 단어나 숫자가 이미지 여러 곳에 반복되기 때문이다. 문장의 단어를 이미지 전체에서 찾으면 다른 줄에 있는 같은 단어까지 잡혀서 박스가 세로로 늘어난다. 박스 높이로 글자 크기를 계산하는 구조라, 박스가 틀리면 번역문이 비정상적으로 크게 들어가거나 엉뚱한 곳에 놓인다. 바꾼 방법: 줄 그룹을 먼저 만들고 그 안에서만 찾는다 단어를 먼저 "줄"로 묶었다. 단어 시작 y좌표 차이가 30px 이내면 같은 줄로 보고, 줄 안에서는 x좌표로 정렬해 줄 텍스트와 줄 전체 박스를 만든다. 그다음 문장이 어느 줄에 속하는지를 정한 뒤, 그 줄 안의 단어로만 박스를 만든다. 다른 줄에 같은 단어가 있어도 섞이지 않는다. 문장과 줄을 비교할 때는 공백과 하이픈을 빼고 소문자로 바꿨다. OCR은 띄어쓰기를 원문과 다르게 돌려주는 경우가 많아서, 그대로 비교하면 같은 문장도 다르다고 판단했다. 그래도 한 번에 다 맞지는 않아서 매칭을 세 단계로 나눴다. 정확히 일치하는 줄 : 정규화한 문장과 줄 텍스트가 같으면 바로 채택한다. 포함 관계 : 문장이 줄 안에 포함되면, 문장이 줄에서 차지하는 비율이 가장 큰 줄을 고른다. 짧은 문장이 긴 문장의 일부로 잘못 붙는 것을 막기 위해서다. 줄 안에서 단어 매칭이 안 되면 문자 위치 비율로 x좌표를 추정한다. 유사도 : 위 두 방법이 모두 실패하면, 문장과 줄 사이의 가장 긴 공통 부분 문자열 길이를 문장 길이로 나눠 점수를 매기고 50% 이상인 줄 중 최고점을 쓴다. 세 단계가 모두 실패하면 경고를 남기고 기본 위치(좌상단)에 놓는다. 문장을 버리지 않은 이유는, 사용자가 PSD에서 레이어를 옮기는 편이 번역문이 아예 빠지는 것보다 낫기 때문이다. # 같은 줄 판정: 시작 y좌표가 30px 이내 for wc2 in valid_word_coords: if wc2['idx'] in used_word_indices: continue if abs(wc2['start_y'] - word_y) < 30: line_words.append(wc2) # 2단계: 문장이 줄에서 차지하는 비율이 가장 큰 줄 선택 for lg in line_groups: line_no_space = lg['text'].replace(' ', '').replace('-', '').lower() if sentence_no_space in line_no_space: ratio = len(sentence_no_space) / len(line_no_space) if ratio > best_match_ratio: best_match_ratio = ratio best_match_lg = lg 디버깅을 위해 매칭 과정은 이미지 옆에 text_matching_log.txt 로 남겼다. 감지된 줄 그룹 목록, OCR 전체 텍스트, 문장마다 어느 단계에서 어떤 줄에 붙었는지가 기록된다. 위치가 이상하게 나온 이미지는 이 로그로 어느 단계에서 엉뚱한 줄에 붙었는지 확인할 수 있다. 그래도 자동 매칭을 믿기 어려운 이미지가 있어서 "문장 편집기" 옵션을 남겼다. 켜면 번역 직후 팝업이 떠서 문장별 번역문을 고치고 좌표 후보를 고를 수 있다. 기본값은 꺼짐이다. 번역 결과에 원문이 남는 문제 GPT-4o로 번역하면서 대부분은 만족스러웠는데, 가끔 결과에 원문 글자가 그대로 섞여 나왔다. 영어로 번역했는데 한글 단어 하나가 남아 있거나, 한국어로 번역했는데 일본어 가나가 남는 식이었다. 이미지 안에 들어가 버리면 사람이 PSD를 다 읽어 보기 전에는 알기 어렵다. 그래서 번역 결과를 문자 범위로 검사하는 함수를 만들었다. 대상 언어에 따라 "남아 있으면 안 되는 문자"를 정한다. 한국어로 번역할 때: 히라가나·가타카나, 한자가 있으면 실패 일본어로 번역할 때: 한글이 있으면 실패 유럽어·동남아 언어로 번역할 때: 가나, 한글, 한자가 있으면 실패 검사에 실패하면 프롬프트를 바꿔 다시 요청한다. 첫 시도는 일반 번역 지시와 브랜드명 규칙, 줄바꿈 유지 규칙만 담고 temperature 0.3으로 보낸다. 재시도에서는 "이전 번역이 불완전했다, 모든 글자를 번역하라"는 문장을 앞에 넣고 temperature를 0.5로 올린다. 같은 프롬프트를 그대로 다시 보내면 같은 결과가 나올 가능성이 높아서 조건을 바꾼 것이다. for attempt in range(max_attempts): # 최대 3회 system_content = base_prompt if attempt == 0 else strict_prompt response = openai.chat.completions.create( model="gpt-4o", messages=[{"role": "system", "content": system_content}, {"role": "user", "content": text}], temperature=0.3 if attempt == 0 else 0.5) translated = response.choices[0].message.content.strip() if not contains_foreign_characters(translated, target_language): return translated # 세 번 모두 실패하면 마지막 결과를 쓴다 return translated 세 번 모두 실패하면 마지막 결과를 그대로 쓴다. 번역을 비워 두는 것보다는 사람이 PSD에서 고치는 편이 낫다고 봤다. API 호출 자체가 마지막까지 예외로 끝나면 원문을 돌려준다. 브랜드명과 줄바꿈 규칙 회사 이름도 문제였다. "마더케이"를 그냥 번역시키면 "Mother Kay"처럼 매번 다른 표기가 나왔다. 프롬프트에 "마더케이는 항상 Mother-K", "Mother-K, MotherK 등은 번역하지 말 것", "㈜마더케이는 Mother-K Co., Ltd."를 규칙으로 넣어 고정했다. 줄바꿈도 규칙으로 넣었다. 원문이 두 줄이면 번역문도 비슷한 위치에서 두 줄이 되도록 요구한다. PSD 레이어 번역에서는 Photoshop이 줄바꿈으로 \r 을 쓰기 때문에, 아예 줄 단위로 나눠 한 줄씩 번역한 뒤 \r 로 다시 이었다. 이렇게 하면 모델이 줄을 합쳐 버려도 레이어 모양이 무너지지 않는다. 회고 OCR 좌표 문제는 "API가 주는 단위"와 "내가 필요한 단위"가 다르다는 데서 시작됐다. 처음부터 단어 → 줄 → 문장의 계층을 직접 만들어야 한다는 걸 알았다면 첫 시도의 시행착오를 줄일 수 있었을 것이다. 지금 구현은 같은 줄 판정을 30px 고정값으로 하는데, 글자가 큰 배너에서는 한 줄의 위아래 단어가 30px 이상 차이 날 수 있다. 다시 한다면 단어 박스 높이에 비례한 기준을 쓰겠다. 번역 검증은 단순한 정규식 검사지만 효과가 컸다. 다만 지금은 문자 체계만 보기 때문에 영어에서 프랑스어로 번역할 때 영어가 남는 경우는 잡지 못한다. 같은 문자 체계 사이의 번역까지 다루려면 언어 감지 라이브러리를 붙이는 쪽이 맞다고 본다. 또 문장마다 API를 한 번씩 부르는 구조라 문장이 많으면 느리다. 한 이미지의 문장을 묶어 번호를 붙여 한 번에 보내고 번호로 다시 나누는 방식을 시험해 보고 싶다. 기간: 2025.10 ~ 2025.12 기술: Python, Google Cloud Vision, OpenAI GPT-4o, 정규식 기반 번역 검증, Tkinter 관련 프로젝트: 이미지 파일 자동 번역 프로그램