영수증·거래명세서 사진 한 장으로 ERP 전표 자동 입력하는 비정형 OCR 파이프라인
사진 촬영 ➡️ 시각 언어 모델(VLM) 파싱 ➡️ 계정과목 자동 매핑 ➡️ 회계 ERP 자동 분개 전송
이지웍스랩 정산솔루션팀 · 2026-08-17 · B2B 업무 자동화 · 읽는 데 9분
매월 말 경비 정산 시즌마다 임직원들은 영수증 풀칠하느라 시간을 낭비하고, 회계팀은 전표 수기 입력에 시달립니다. 스마트폰 촬영 한 번으로 전표 입력을 끝내는 비정형 OCR 파이프라인을 구축해 드립니다.
1. 비정형 영수증 파싱의 난제 해결
매월 말 경비 정산 시즌마다 임직원은 영수증을 붙이느라 시간을 쓰고, 회계팀은 전표를 수기 입력하며 야근합니다. 문제는 영수증이 표준 양식이 아니라는 점입니다. 감열지 영수증, 카드 전표, PDF 세금계산서, 손글씨 간이영수증이 뒤섞여 들어옵니다.
전통적인 OCR은 글자를 읽을 뿐 '어느 숫자가 공급가액이고 어느 것이 부가세인지' 구분하지 못합니다. 실무에서 필요한 것은 텍스트 추출이 아니라 항목 인식이며, 여기서 시각 언어 모델(VLM)이 결정적인 차이를 만듭니다.
VLM은 영수증의 레이아웃과 문맥을 함께 보기 때문에 '합계' 옆의 숫자와 '부가세' 옆의 숫자를 구분해 구조화합니다. 다만 금액 자릿수 오인식은 여전히 발생하므로, 뒤에서 다룰 검산 규칙이 반드시 함께 있어야 합니다.
| 증빙 유형 | 주요 난점 | 처리 전략 |
|---|---|---|
| 감열지 영수증 | 인쇄 흐림, 시간 경과 시 소실 | 촬영 즉시 업로드, 대비 보정 전처리 |
| 카드 전표 | 가맹점명이 축약·영문 표기 | 카드사 가맹점 코드 매핑 테이블 활용 |
| 전자세금계산서 PDF | 구조는 정형이나 양식 편차 존재 | 텍스트 레이어 우선 파싱, OCR은 예비 경로 |
| 손글씨 간이영수증 | 필체 편차로 금액 오인식 | 신뢰도 임계값 미달 시 사람 검토 강제 |
2. 촬영 품질을 보정하는 전처리 파이프라인
인식률의 절반 이상은 전처리에서 결정됩니다. 구겨진 영수증, 조명 반사, 기울어진 촬영 각도는 모델 성능과 무관하게 오인식을 유발하므로, 파싱 이전에 보정 단계를 반드시 거쳐야 합니다.
실무에서 효과가 큰 순서는 ① 문서 영역 검출 및 원근 보정(deskew), ② 그림자·반사 제거를 위한 적응형 이진화, ③ 해상도 정규화입니다. 이 세 가지만 적용해도 금액 오인식이 눈에 띄게 줄어듭니다.
촬영 단계에서 막는 것이 가장 저렴합니다. 업로드 앱에서 흐림도와 밝기를 즉시 검사해 기준 미달이면 재촬영을 요청하면, 뒤쪽 파이프라인 전체의 부담이 사라집니다.
import cv2
import numpy as np
def preprocess(path: str) -> np.ndarray:
img = cv2.imread(path)
# 1) 문서 영역 검출 후 원근 보정
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(cv2.GaussianBlur(gray, (5, 5), 0), 60, 180)
cnts, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
if cnts:
c = max(cnts, key=cv2.contourArea)
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
img = four_point_transform(img, approx.reshape(4, 2))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2) 그림자/반사 제거 (적응형 이진화)
binary = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, blockSize=35, C=11,
)
# 3) 해상도 정규화 (긴 변 1600px 기준)
h, w = binary.shape
scale = 1600 / max(h, w)
if scale < 1:
binary = cv2.resize(binary, None, fx=scale, fy=scale,
interpolation=cv2.INTER_AREA)
return binary
def is_too_blurry(path: str, threshold: float = 100.0) -> bool:
"""업로드 시점에 흐림 검사 -> 미달이면 재촬영 요청"""
gray = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
return cv2.Laplacian(gray, cv2.CV_64F).var() < threshold
3. 계정과목 자동 매핑과 검산 규칙
추출된 상호명을 계정과목으로 연결하는 단계는 규칙과 학습을 병행해야 합니다. '스타벅스 → 복리후생비', '주유소 → 차량유지비'처럼 반복되는 패턴은 매핑 테이블로 고정하고, 신규 상호만 AI 추천에 맡기는 구조가 안정적입니다.
가장 중요한 것은 금액 검산입니다. 공급가액 + 부가세 = 합계가 성립하지 않으면 무조건 사람 검토로 보내야 합니다. 이 단순한 규칙 하나가 자릿수 오인식으로 인한 전표 오류를 대부분 차단합니다.
추가로 동일 카드·동일 금액·동일 일자 조합이 중복 제출되는 경우를 감지하십시오. 경비 중복 청구는 악의가 아니라 실수로 훨씬 자주 발생하며, 사후 적발보다 입력 시점 차단이 서로에게 편합니다.
4. ERP 전송과 감사 대응 증빙 체계
ERP 전송 단계에서는 멱등성(idempotency)이 핵심입니다. 네트워크 오류로 재시도가 발생했을 때 동일 전표가 두 번 등록되면 장부가 어긋납니다. 영수증 고유 ID를 멱등 키로 전달해 중복 등록을 원천 차단하십시오.
전표마다 원본 이미지 링크를 함께 보관해야 합니다. 세무 조사나 내부 감사에서 요구되는 것은 숫자가 아니라 원본 증빙이며, 링크가 끊긴 전표는 증빙이 없는 것과 같습니다.
보관 기간도 미리 설계하십시오. 법정 증빙 보관 의무 기간 동안 이미지가 유지되도록 스토리지 수명 주기 정책을 설정하고, 만료 전 자동 아카이빙되도록 구성하는 것이 안전합니다.
자주 막히는 지점
금액이 한 자릿수 크거나 작게 입력됩니다.
원인: 감열지 인쇄 흐림으로 쉼표와 숫자가 붙어 인식되거나, 0이 8로 오인식됩니다.
해결: 공급가액 + 부가세 = 합계 검산과 함께, 동일 가맹점의 과거 평균 결제액 대비 10배 이상 벗어나면 자동 보류시키는 이상치 규칙을 추가하십시오.
같은 영수증이 ERP에 두 건으로 등록되었습니다.
원인: 전송 타임아웃 후 재시도 시 멱등 키 없이 신규 전표로 생성되었습니다.
해결: 영수증 ID를 멱등 키로 ERP API에 전달하고, 서버 측에서 동일 키 요청은 기존 전표를 반환하도록 구현하십시오. 이미 발생한 중복은 키 기준으로 일괄 조회해 정리할 수 있습니다.
핵심 요약
- 인식률은 모델보다 전처리(원근 보정·반사 제거·해상도 정규화)에서 더 크게 좌우됩니다.
- 공급가액 + 부가세 = 합계 검산 규칙 하나가 자릿수 오인식 사고를 대부분 차단합니다.
- 계정과목은 AI 추천까지만, 세무 영향이 있는 최종 확정은 담당자가 수행해야 합니다.
- ERP 전송에는 멱등 키를, 전표에는 원본 이미지 링크를 반드시 함께 남기십시오.
자주 묻는 질문
인식률은 실제로 어느 정도 나오나요?
전처리를 적용한 전자세금계산서와 카드 전표는 금액 항목 기준 99% 이상이 현실적인 목표치입니다. 반면 손글씨 간이영수증은 편차가 커서 사람 검토 경로를 반드시 병행해야 합니다.
기존 ERP에 연동하려면 개발이 많이 필요한가요?
전표 등록 API가 제공된다면 연동 자체는 크지 않습니다. 실제 공수는 계정과목 체계와 거래처 코드를 맞추는 매핑 작업에 집중되며, 이 부분은 회계 담당자와 함께 정리해야 합니다.
직원들이 앱 사용을 번거로워하지 않을까요?
촬영 한 번으로 끝나고 영수증 실물 제출이 사라지는 구조라면 오히려 선호도가 높습니다. 다만 재촬영 요청이 잦으면 이탈하므로 흐림 판정 임계값을 지나치게 엄격하게 잡지 마십시오.