
프로젝트에서 영수증(매출전표) 이미지를 분석해 상호명, 승인번호, 카드번호, 금액 등을 자동으로 구조화하는 OCR 기능을 검토하게 되었습니다. "비용을 들이지 않는 오픈소스 형태"라는 제약 조건 속에서, 인식률과 속도라는 두 마리 토끼를 잡기 위해 진행했던 최적화 과정을 기록으로 남겨봅니다.
1. 자바 내장 Tesseract의 한계
처음에는 스프링 부트(Springboot) 내부에 Tess4J(Tesseract)를 직접 내장하여 OCR을 시도했습니다. 가장 간단하게 구현할 수 있는 선택지이기도 했고, 외부 서버와의 통신이 없기 때문에 아키텍처가 단순하다는 장점이 있었습니다. 직접 구현하여 테스트를 진행해 보니 매출전표나 영수증을 다운로드한 이미지는 꽤나 잘 인식을 했는데, 사용자가 직접 촬영한 영수증 이미지에서는 굉장히 만족스럽지 못한 결과를 얻었습니다.
문제점: 영어와 정자체 한글은 그런대로 읽었지만, 실제 영수증처럼 약간 기울어지거나 서체가 특이하고 여백이 불규칙한 한글 이미지는 글자가 완전히 깨졌습니다. (롯데마스터승인 -> ㅁㄴㅇㄹ... 수준의 결과)
결론: 상용 수준의 영수증 파싱을 위해서는 한국어 영수증 데이터셋 학습이 잘 되어 있는 현대적인 딥러닝 OCR 엔진이 필수적
2. [1차 개선] Spring Boot + Python FastAPI 하이브리드 설계
한글 영수증 인식률이 좋다는 평가가 많았던 PaddleOCR(PP-OCRv5)로 변경해 보기로 결정했습니다. 하지만 PaddleOCR은 Python 기반의 딥러닝 프레임워크인 PaddlePaddle 위에서 동작하므로 자바 웹 서버에 직접 얹을 수 없었습니다. 그래서 서버를 분리하여 서로 통신하는 하이브리드 아키텍처를 구축했습니다.
다만 테스트 시 2개의 서버를 실행하여 테스트해야 하는 번거로움이 있어서 메인이 되는 Springboot 서버만 실행해도 전체 시스템이 유기적으로 실행될 수 있도록 ServerRunner를 구현하여 Python 서버의 생명주기를 자동으로 관리하도록 설계하여 불편함을 줄였습니다.
3. [1차 개선 문제점] 로컬 CPU 성능의 한계
PaddleOCR은 GPU 환경에서는 빠르게 동작하는 가벼운 모델이지만, 일반 개발 환경인 로컬 CPU에서는 영수증 사진 1장을 스캔하는데, 40~50초가 넘는 엄청난 지연 시간을 보여주었습니다. 추출 결과물은 Tesseract와 비교했을 때 만족스럽게 나왔지만, 40~50초의 시간은 사용자가 기다리기에 너무나도 많은 시간이었습니다.
문제점: PaddlePaddle 프레임워크 자체가 CPU 인프라 최적화 연산에 최적화되어 있지 않아 로컬에서 간단한 테스트를 돌리기조차 어려웠습니다.
4. [2차 개선] ONNX Runtime (RapidOCR) 도입
CPU 최적화 명령어가 포함된 ONNX 런타임으로 경량화하여 추론 과정을 거칠 수 있도록 변경 작업을 진행했습니다. 경량 한글 모델이었던 PP-OCRv3 Mobile 모델을 적용하여 속도는 3초 미만으로 비약적으로 빨라졌지만, 특수기호(*), 숫자 쉼표(,) 등에서 오타가 다수 발생되었습니다. 정확도를 높이기 위해서 PP-OCRv5 ONNX 정밀 모델로 변경작업을 진행한 후 다시 테스트했을 때 12초 정도의 시간이 소요되었고, 추출 정확도는 이용할 수 있는 값들에 대해서는 95% 이상 정확하게 추출되는 것을 확인해 볼 수 있었습니다.
[첫 번째 추출 결과물]
한국어 설정이 누락되어 기본 중국어/영어 모델이 강제 적용되면서 한자로 글자가 심하게 깨진 상태를 얻었습니다.
TID:10****50-[6681]
[LTPayPass]
[号Y同]:青晨
5137-92**-****-3625
22:26/03/2008:15:59
13.6362
早:
1.364
15.000
66028074[EC]
9214239451/107-14-56234
号(TEL:0234390333)
B20克
KI
VANKEY:0079051297591783
J4307649-6681-SM99
[0004]
[두 번째 추출 결과물]
언어 설정으로 한글은 읽기 시작했으나 모바일 경량 모델의 한계로 카드 번호(5127...), 부가세(1니원), 승인번호(Aaagn74...) 등에서 크고 작은 오타와 글자 뭉개짐이 다수 발생했습니다.
로데마스터습인
TID: 10***5O- [6681]
[가맹점용]
[LTPayPass]
5127--92+- ie -SH25
거래일시:26/0312008:15:59
금액
13630원
부가세
1니원
합계
15000원
승인번호
Aaagn74 led]
매입사명[010-DESC]
롯데카드
... 생략 ...
[세 번째 추출 결과물]
카드 번호(5137-92**-****-3625), 부가세 및 합계 쉼표 문자(13,636원, 15,000원), 승인번호(66028074[EC])까지 95% 이상 무결점으로 복원된 상태의 추출 결과물을 얻었습니다.
롯데마스터
승인
TID:10****50-[6681]
[가맹점용]
[LTPayPass]
[Y]
5137-92**-****-3625
거래일시:26/03/20 08:15:59
금 액:
13,636원
부가세:
1 364원
합계:
15,000원
승인번호
66028074[EC]
매입사명[010-DESC ]
롯데카드
... 생략 ...
5. 결론
최종 최적화를 마친 뒤에 선택에 따라서 Option A와 Option B라는 유연한 선택지를 남겨두었습니다.
Option A (초고정밀도 모드):
* PP-OCRv5 모델 사용
* 로컬 CPU 속도: 약 12초 (Native PaddleOCR CPU의 30초 대기 대비 2배 이상 개선)
* 인식률: 95% 이상 (필요한 데이터에 한함)
Option B (고속 모드):
* PP-OCRv3 Mobile 모델 사용
* 로컬 CPU 속도: 약 4초 (속도는 압도적으로 빠름)
* 인식률: 90% 내외 (특수기호, 숫자 쉼표 등에서 오타 발생)
물론 실제 서비스에 붙여서 사용하기 위해서는 4초의 속도도 부족할 수 있습니다. 정확도와 인식 속도 모두 잡아야 하기 때문에 다른 방법을 고려해야 할 수도 있습니다. 다만 이번 테스트 구현은 회사에서 필요할 수도 있는 기능을 비용이 들지 않는 방향에서 검토하다 보니 정확도와 속도 모두 잡는 방법은 더 많은 시간 투자가 필요할 것으로 여겨집니다. 사내에서 해당 서비스 구축에 대해서 비용을 지불할 수 있는 상황이라면 CLOVA OCR과 같은 서비스도 있기 때문에 고려해 보면 좋을 것 같습니다.
'🧑🏻💻 Dev > SpringBoot' 카테고리의 다른 글
| [Spring] @Async 이용하여 비동기 구현하기 (0) | 2024.01.20 |
|---|---|
| JackSon DTO 역직렬화 파헤치기 (JackSon과 Getter) (0) | 2023.09.25 |
| [Spring | LetsEncrypt] Http 서버를 Https로 바꿔보자 (0) | 2023.08.07 |
| [Spring] 스프링에서 캐시를 사용하는 방법 (with. Redis) (0) | 2023.04.10 |
| [Spring] application.yml 설정값 가져오기 (0) | 2023.04.07 |