← 목록으로

OCR API를 호출하면 안에서 무슨 일이 일어날까 - 백엔드 개발자를 위한 OCR 원리

들어가며

계약서를 OCR로 읽어야 하는 일이 생겼다고 해보자. 계약서에는 주민등록번호나 사업자등록번호 같은 민감정보가 들어 있어서 이 값들은 가리고 나머지 내용만 읽어야 한다.

선택지는 두 가지다. 외부 OCR API를 호출하거나, 로컬에 OCR 모델을 띄우는 것이다. 그런데 막상 고르려고 하면 어느 쪽도 쉽지 않다. API 문서에서는 이미지를 보내면 JSON이 돌아온다는 것까지는 알 수 있지만, 그 JSON에 담긴 좌표와 신뢰도가 어디서 나온 값인지는 알 수 없다. 로컬 모델은 더 막막하다. 사전학습 모델을 내 문서에 맞게 학습시킬 수 있다고 하는데, 무엇을 학습시킨다는 건지 감이 오지 않는다.

OCR이 무엇인지부터 알아야 한다. 이해해야 할 것은 세 가지다.

  1. OCR은 이미지에서 글자를 어떻게 찾아 읽는가.
  2. OCR API를 호출하면 안에서 무슨 일이 일어나는가.
  3. 이미지에서 글자를 읽는 방법은 OCR밖에 없는가.

고전 OCR에서 딥러닝 OCR까지 원리를 차례로 따라가 보자. 그다음 CLOVA OCR로 가상의 계약서에서 민감정보를 가리고, 가린 이미지를 읽는 VLM이 OCR과 무엇이 다른지 살펴본다. 로컬 모델을 파인튜닝하는 과정은 다음 편에서 이어간다.

이 글에 나오는 계약서는 설명을 위해 직접 만든 가상의 문서다. 등장하는 회사명, 이름, 주민등록번호, 사업자등록번호는 모두 임의로 만든 값이며 실존하는 사람이나 회사와 관계가 없다.

글자를 찾는 일과 읽는 일

사람이 계약서 한 장을 읽는 과정을 떠올려 보자. 종이를 받아 들면 먼저 글자가 어디에 있는지 훑는다. 제목은 맨 위에, 당사자 정보는 표 안에, 서명란은 맨 아래에 있다. 위치를 잡은 다음에야 한 줄씩 읽어 내려간다.

사람은 이 두 일을 구분하지 않고 한 번에 해낸다. 하지만 기계에게는 둘이 전혀 다른 문제라서 OCR은 이 과정을 두 단계로 나눈다.

  1. 검출(detection) — 이미지에서 글자가 있는 영역을 찾는다. 결과는 좌표다.
  2. 인식(recognition) — 찾은 영역을 잘라 그 안의 글자를 읽는다. 결과는 문자열과 신뢰도다.

아래 그림은 가상의 계약서 한 장이 두 단계를 거치며 어떤 결과로 바뀌는지 보여준다.

① 검출결과: 상자의 좌표용역 계약서성명홍길동주민등록번호000000-0000000사업자등록번호000-00-00000갑과 을은 다음과 같이 계약을 체결한다.(x₁, y₁)(x₂, y₂)상자만큼 잘라 넘긴다② 인식결과: 문자열 + 신뢰도용역 계약서"용역 계약서"0.99주민등록번호"주민등록번호"0.99000000-0000000"000000-0000000"0.97

그림 속 계약서와 번호는 모두 임의로 만든 가상의 값이다.

검출은 “여기에 글자가 있다”까지만 말한다. 그 글자가 “갑”인지 “을”인지는 모른다. 인식은 반대다. 잘라서 건네받은 조각은 읽을 수 있지만 그 조각이 원래 문서의 어디에 있었는지는 모른다. 두 결과를 합쳐야 “계약서의 표 안에 ‘주민등록번호’라는 글자가 있다”는 정보가 된다.

서문에서 막혔던 JSON도 이렇게 나눠 보면 읽힌다. 좌표는 검출 단계에서, 문자열과 신뢰도는 인식 단계에서 나온 값이다.

이 구분은 민감정보를 가릴 때 특히 중요하다. 주민등록번호를 가리려면 그 번호가 무엇인지와 어디에 있는지를 둘 다 알아야 하기 때문이다. 인식만 하면 번호를 찾아도 이미지의 어디를 덮어야 할지 모르고, 검출만 하면 어느 상자가 주민등록번호인지 모른다.

결과가 틀렸을 때도 마찬가지다. 원인은 글자를 아예 못 찾았거나, 찾았는데 잘못 읽은 것 둘 중 하나다. 어느 단계에서 틀렸는지에 따라 고칠 곳도 달라진다. 다음 편에서 검출 모델과 인식 모델을 따로 파인튜닝하는 것도 이 때문이다.

실제 OCR 엔진은 앞뒤로 단계를 더 붙인다. 검출 전에는 기울어진 스캔본을 바로 세우고, 인식 후에는 조각난 단어를 줄로 묶어 읽는 순서를 정한다. 그래도 뼈대는 언제나 찾고 읽는 두 단계다. 이제 기계가 이 두 단계를 어떻게 해내 왔는지 시대순으로 따라가 보자.

고전 OCR — 글자를 한 자씩 맞히던 시절

딥러닝 이전의 OCR은 말 그대로 글자를 한 자씩 맞혔다. 계약서 스캔본 한 장이 들어왔다고 해보자. 고전 OCR은 이 이미지를 다음 순서로 처리한다.

  1. 이진화 — 회색조 이미지를 검은 글자와 흰 배경, 두 값으로 나눈다.
  2. 기울기 보정 — 스캔하면서 기울어진 문서를 바로 세운다.
  3. 줄과 글자 분할 — 검은 픽셀이 모인 곳을 찾아 줄을 나누고, 줄 안에서 다시 글자를 하나씩 잘라낸다.
  4. 글자 분류 — 잘라낸 글자 이미지를 미리 알고 있는 글자 모양과 비교해 가장 비슷한 글자를 고른다.

1과 2는 준비 작업이고, 앞 절의 두 단계에 맞춰 보면 3이 검출, 4가 인식에 해당한다.

글자를 찾는 방법은 생각보다 단순하다. 이진화한 이미지에서 가로줄마다 검은 픽셀 수를 세어 보자. 글자가 지나가는 가로줄은 값이 크고 줄 사이 여백은 0에 가깝다. 이 값이 0으로 떨어지는 곳을 경계로 자르면 줄이 나뉜다. 같은 계산을 줄 안에서 세로로 하면 글자 사이의 여백이 드러난다. 이 방법을 투영 프로파일(projection profile)이라고 부른다. 서로 이어진 검은 픽셀 덩어리를 글자 후보 하나로 보는 연결 요소(connected component) 방식도 함께 쓰인다.

아래는 “계약을 체결한다” 한 줄을 실제로 이진화한 뒤 세로 한 줄마다 검은 픽셀을 센 결과다.

① 이진화한 한 줄검은 칸 = 글자 픽셀② 세로 한 줄마다 센 검은 칸 수띠 = 0인 곳, 여기서 자른다계약을체결한다글자 7개 → 조각 10개한 글자가 두 조각으로

글자를 맞히는 가장 단순한 방법은 템플릿 매칭이다. 글자마다 기준 이미지를 저장해 두고, 잘라낸 글자를 하나씩 겹쳐 보며 픽셀이 가장 많이 일치하는 글자를 고른다. 그런데 글꼴이 바뀌면 같은 “가”라도 모양이 달라져 맞지 않는다. 그래서 픽셀을 통째로 비교하는 대신 획의 방향이나 끝점과 교차점의 개수 같은 특징을 뽑아 비교하는 방식이 나왔다. 그래도 어떤 특징을 볼지는 여전히 사람이 정했다. 1980년대 HP에서 시작해 지금은 오픈소스로 공개된 Tesseract도 3.x 버전까지는 이 계열이었다.

이 방식에는 분할이 먼저 성공해야 분류도 성공한다는 약점이 있다. 앞 단계의 실수를 뒤 단계가 되돌릴 방법이 없기 때문이다. 계약서에서는 이 약점이 자주 드러난다.

  • 도장 — 서명란의 도장이 글자 위에 찍히면 글자와 도장이 한 덩어리로 붙는다.
  • 표 선 — 당사자 정보 표의 테두리에 글자가 닿으면 선까지 글자로 잘린다.
  • 한글의 구조 — 한글은 자모를 모아 한 글자를 만들어서, 자모 사이에 빈 세로줄이 생기는 글자는 두 조각으로 쪼개진다. 위 그림에서도 일곱 글자가 열 조각으로 잘렸다. “다”는 자음과 모음 사이에서, “계”와 “체”는 모음 ㅖ와 ㅔ의 두 세로획 사이에서 잘렸다. 현대 한글로 만들 수 있는 글자만 11,172자라서 글자마다 기준 모양을 준비하기도 어렵다.

결국 문제는 글자를 한 자씩 떼어 내야 한다는 전제에 있었다. 글자의 경계와 비교할 특징을 사람이 정하는 한, 문서가 조금만 지저분해져도 결과가 무너졌다. 딥러닝 OCR은 이 전제를 버리는 데서 출발한다.

딥러닝 OCR — 한 줄을 통째로 읽는다

딥러닝 OCR은 검출과 인식이라는 두 단계는 그대로 두되, 단계마다 사람이 정하던 규칙을 모델이 데이터에서 배우게 한다.

픽셀마다 글자일 확률을 매긴다

딥러닝 검출 모델은 이미지를 받아 픽셀마다 “이 픽셀이 글자 영역일 확률”을 계산한다. 결과는 원본과 같은 크기의 흑백 지도다. 글자가 있는 곳은 밝고 배경은 어둡다. 이 지도에서 밝은 영역을 묶어 테두리를 따면 검출 상자가 된다. 테두리가 꼭 직사각형일 필요는 없어서 OCR API는 좌표를 꼭짓점 목록으로 돌려주기도 한다.

검출하는 단위도 바뀌었다. 글자 하나가 아니라 단어나 줄을 찾는다. 글자 경계를 정하는 일은 다음 단계인 인식 모델에 맡긴다.

대표적인 모델로는 CRAFT와 DBNet이 있다. CRAFT는 2019년 NAVER Clova 연구진이 발표한 모델로, 글자 하나하나의 중심과 글자 사이의 연결을 함께 예측한다. 다만 CLOVA OCR 서비스가 내부에서 어떤 모델을 쓰는지는 공개되어 있지 않다. PaddleOCR의 PP-OCRv5 검출기는 DB 알고리즘을 바탕으로 한다. DBNet의 DB는 미분 가능한 이진화(Differentiable Binarization)의 줄임말이다. 고전 OCR에서 사람이 정하던 이진화 기준값을 이 모델은 픽셀마다 따로 배운다.

한 줄을 잘게 나눠 읽는다

인식 모델은 검출 상자만큼 잘라낸 줄 이미지를 받는다. 대표적인 구조는 CRNN으로, 이름 그대로 CNN과 RNN을 이어 붙였다.

  1. CNN — 줄 이미지를 왼쪽부터 좁은 세로 칸으로 나눠, 칸마다 모양의 특징을 뽑는다.
  2. RNN — 칸을 차례로 읽으며 앞뒤 칸의 맥락을 함께 본다.
  3. CTC — 칸마다 나온 글자 후보를 하나의 문자열로 정리한다.

여기서 핵심은 CTC다. 칸의 폭은 글자 폭과 맞지 않는다. “계” 한 글자에 칸 여러 개가 걸리기도 하고 글자 사이 여백에 칸이 걸리기도 한다. 그래서 모델은 칸마다 글자 하나 또는 빈칸을 뜻하는 -를 고르고, CTC가 그 결과를 두 규칙으로 정리한다.

  1. 같은 글자가 연달아 나오면 하나로 합친다.
  2. 빈칸을 지운다.

칸마다 계 계 - 약 약 - 을 을이 나왔다고 해보자. 같은 글자를 합치면 계 - 약 - 을이 되고, 빈칸을 지우면 계약을이 된다.

아래 데모는 앞 그림과 같은 픽셀에서 “계약을”만 잘라 이 과정을 차례로 보여준다.

주민등록번호처럼 같은 숫자가 정말로 이어질 때는 사이에 빈칸이 끼어야 한다. 0 0 - 0은 00으로 읽히고, 000이 되려면 0 - 0 - 0이 나와야 한다.

앞의 투영 프로파일 그림과 비교해 보자. 고전 OCR에서 “계”는 0인 열을 사이에 두고 두 조각으로 잘렸다. CRNN에게는 그 0인 열도 칸 하나일 뿐이다. 앞뒤 칸을 함께 보고 “계”를 고르면 되니 글자 경계를 미리 알 필요가 없다. Tesseract도 2018년 4.0 버전부터 인식기를 LSTM 기반으로 바꿔, 한 자씩 맞히던 방식에서 한 줄을 통째로 읽는 방식으로 옮겨 갔다.

신뢰도도 여기서 나온다. 모델은 칸마다 글자 후보별 확률을 내놓는다. 최종 문자열에 남은 글자의 확률을 모으면 모델이 그 문자열을 얼마나 확신하는지 알 수 있다. 예를 들어 PaddleOCR은 같은 글자 합치기와 빈칸 지우기를 거치고 남은 칸의 확률을 평균 내서 점수로 돌려준다. 서문에서 물었던 JSON의 신뢰도가 이런 값이다. CLOVA OCR이 이 값을 정확히 어떻게 계산하는지는 공개되어 있지 않다.

사람이 정하던 규칙을 데이터에서 배운다

딥러닝 OCR이 고전 OCR보다 나은 이유는 모델 구조보다 학습 방식에 있다. 도장이 찍힌 글자나 표 선에 닿은 글자도 학습 데이터에 충분히 들어 있으면, 모델이 알아서 구분하는 법을 배운다. 사람이 예외 규칙을 하나하나 추가할 필요가 없다.

학습 데이터의 모양도 단순하다. 검출 모델에는 이미지와 글자 영역의 꼭짓점 좌표를 주고, 인식 모델에는 줄 이미지와 그 안의 정답 문자열을 주면 된다. CTC 덕분에 글자마다 위치를 표시할 필요도 없다. 서문에서 “무엇을 학습시킨다는 건지 감이 오지 않는다”고 했던 물음의 답이 이것이다. 다음 편에서 계약서에 맞춰 파인튜닝할 때 준비할 것도 결국 이 두 가지다.

이제 이 원리를 바탕으로 실제 OCR API를 써 보자.

CLOVA OCR로 민감정보 가리기

민감정보를 가리는 단계에는 CLOVA OCR을 쓴다고 해보자. 이유는 두 가지다.

  1. 한국 리전에서 쓸 수 있다 — CLOVA OCR은 네이버 클라우드 한국 리전의 KR-1, KR-2 존에서 제공된다.
  2. 보낸 문서를 저장하지 않는다 — CLOVA OCR FAQ는 “사용자가 전송한 이미지나 문서는 클라우드 상에 저장되지 않으며, 엔진 성능 개선이나 데이터를 활용한 패턴 분석의 목적으로 활용하지 않습니다”라고 밝힌다. 인식 결과도 로그에 남기지 않고 과금 확인을 위한 호출 이력만 보관한다.

단서도 하나 있다. 사용 준비 문서에 따르면, 인식 결과를 더 자세히 분석하길 원하는 경우 사용자 동의를 받아 데이터를 내부적으로 분석하는 절차가 있다. 동의 없이는 이뤄지지 않는다는 뜻이다.

호출은 단순하다. 콘솔에서 도메인을 만들고 API Gateway를 연동하면 호출 주소와 Secret Key를 받는다. 이 주소의 /general로 이미지를 보내면 된다.

curl -X POST '{호출 주소}/general' \
  -H 'Content-Type: application/json' \
  -H 'X-OCR-SECRET: {Secret Key}' \
  -d '{
    "version": "V2",
    "requestId": "1234",
    "timestamp": 1722225600000,
    "lang": "ko",
    "images": [{ "format": "png", "name": "contract", "data": "{Base64로 인코딩한 이미지}" }]
  }'

기억할 것은 두 가지다. 좌표를 받으려면 version을 V2로 둬야 하고, 계약서처럼 민감한 문서는 공개 URL을 넘기는 url 대신 Base64로 직접 싣는 data로 보낸다. 나머지 옵션은 API 문서에 정리되어 있다.

응답의 fields에는 단어마다 읽은 문자열인 inferText와 그 단어를 감싼 꼭짓점 네 개인 boundingPoly가 들어 있다. 첫 절에서 나눈 인식과 검출의 결과가 그대로 담긴 셈이다. 민감정보를 가리는 데 필요한 “무엇인지”와 “어디에 있는지”가 바로 이 두 값이다.

이제 그림 속 가상 계약서에서 주민등록번호를 가려 보자. 흐름은 네 단계다.

  1. 형식으로 찾기 — inferText가 주민등록번호 형식 \d{6}-?\d{7}이나 사업자등록번호 형식 \d{3}-?\d{2}-?\d{5}에 맞는 단어를 찾는다.
  2. 라벨로 찾기 — “주민등록번호”라는 단어를 찾고 같은 줄에서 그 오른쪽에 있는 단어를 값으로 본다. 값이 표 안에 있다면 요청에 enableTableDetection을 켜서 같은 행의 다음 칸을 읽는 편이 더 정확하다. 이 옵션은 콘솔의 도메인 설정에서 ‘표 추출 여부’를 먼저 켜 둬야 동작한다.
  3. 덮기 — 찾은 단어의 boundingPoly를 감싸는 사각형을 원본 이미지 위에 검게 칠한다.
  4. 넘기기 — 가린 이미지를 VLM에 보내 계약 내용을 읽게 한다.

첫 절의 가상 계약서에 이 흐름을 적용한 결과는 아래와 같다. 검출 상자가 그대로 덮을 자리가 된다.

① 가리기 전형식과 라벨로 찾은 값용역 계약서성명홍길동주민등록번호000000-0000000사업자등록번호000-00-00000갑과 을은 다음과 같이 계약을 체결한다.boundingPoly만큼 덮는다② 가린 뒤이 이미지를 VLM에 넘긴다용역 계약서성명홍길동주민등록번호사업자등록번호갑과 을은 다음과 같이 계약을 체결한다.

그림 속 계약서와 번호는 모두 임의로 만든 가상의 값이다.

1과 2를 함께 쓰면 서로의 빈틈을 메울 수 있다. 형식만 보면 라벨 없이 본문에 적힌 번호도 잡지만, OCR이 하이픈을 다른 기호로 읽거나 숫자 하나를 잘못 읽으면 놓친다. 라벨만 보면 형식이 깨진 값도 잡지만, 라벨 없이 적힌 번호는 놓친다. 두 방법으로도 못 잡는 값에 대비해, 라벨 근처에서 신뢰도 inferConfidence가 낮은 단어는 형식이 맞지 않아도 덮거나 사람이 확인하게 한다.

가린 이미지를 받은 VLM은 이 문서를 어떻게 읽을까? 여기서부터는 OCR과 읽는 방식이 전혀 다르다.

VLM — 읽는 방식부터 다르다

서문의 세 번째 물음으로 돌아가 보자. 이미지에서 글자를 읽는 방법은 OCR밖에 없을까? 그렇지 않다. GPT, Claude, Gemini 같은 VLM(Vision Language Model)은 이미지를 넣으면 그 안의 글을 읽고 내용까지 설명한다.

VLM은 이미지를 먼저 바둑판처럼 작은 조각(패치)으로 자른다. 조각의 크기는 모델마다 다르지만 가로세로 30픽셀 안팎이다. 비전 인코더가 조각마다 그 모양을 숫자 벡터로 바꾸면, 이 벡터가 LLM에게는 단어 토큰과 같은 입력이 된다. LLM은 이미지 토큰과 질문을 함께 읽고 평소 글을 쓰듯 답을 한 토큰씩 이어 쓴다.

아래 그림은 앞에서 가린 계약서가 VLM 안에서 거치는 순서를 첫 그림과 같은 모양으로 그린 것이다.

① 이미지를 조각으로 자른다조각 하나 = 토큰 하나용역 계약서성명홍길동주민등록번호사업자등록번호갑과 을은 다음과 같이 계약을 체결한다.조각마다 벡터로 바꾼다② 이미지 토큰과 질문을 함께 읽는다…계약서항목을JSON으로LLM — 다음 토큰을 하나씩 이어 쓴다③ 결과는 글이다상자 좌표도, 단어별 신뢰도도 없다{"title":"용역계약서","name":"홍길동",…

그림 속 계약서와 번호는 모두 임의로 만든 가상의 값이다.

여기에는 검출도 인식도 없다. 글자가 어디 있는지 찾는 단계도, 상자를 잘라 읽는 단계도 따로 두지 않고, 모델이 이미지 전체를 보고 곧바로 답을 쓴다. 그래서 VLM은 OCR이 못 하는 일을 한다. “갑”이 누구인지, 계약 기간이 언제부터 언제까지인지처럼 글자 너머의 의미를 묻는 질문에 바로 답한다.

대신 잃는 것도 있다. 세 공급자의 공식 문서가 직접 밝히는 한계를 모아 보면 이렇다.

  • 좌표 — VLM의 출력은 글이다. 좌표를 돌려주는 모델도 있지만, Claude 문서는 그 값이 근삿값이라고, GPT 문서는 정밀한 위치 파악에 약하다고 적었다. 마스킹처럼 하나라도 빠지면 안 되는 일에 맡기기는 어렵다.
  • 신뢰도 — 단어마다 신뢰도가 붙지 않는다. 흐릿한 숫자 앞에서 모델이 그럴듯한 숫자를 지어낼 수 있다.
  • 해상도 — 큰 이미지는 줄이거나 나눠서 읽기 때문에 작은 글씨가 뭉개질 수 있다.
  • 한국어 — 라틴 문자가 아닌 글자에서는 성능이 떨어질 수 있다고 공급자 문서가 적었다.

벤치마크도 같은 이야기를 한다. 2024년 말 모델로 잰 벤치마크에서 한국어 점수는 모델마다 차이가 컸고, 문서의 핵심 정보를 뽑는 과제는 아직 실무에 쓰기에 부족하다는 평가를 받았다. 모델이 바뀌면 순위도 바뀌니 한국어 계약서라면 내 문서로 직접 재 봐야 한다.

공급자 문서와 벤치마크 자세히 보기
ClaudeGPTGemini
이미지 조각28×28픽셀 조각 하나를 토큰 하나로 센다일부 모델은 32×32픽셀 조각으로 덮는다384픽셀 이하는 258토큰이고, 큰 이미지는 768×768 타일마다 258토큰을 쓴다
큰 이미지긴 변이 1568픽셀, Claude 4.7 이상은 2576픽셀을 넘으면 줄인다GPT-4o, GPT-4.1 계열은 2048픽셀 정사각형에 맞춘 뒤 짧은 변을 768픽셀로 줄인다해상도가 높을수록 작은 글씨를 잘 읽지만 토큰과 지연이 늘어난다
좌표줄인 이미지 기준의 픽셀 좌표를 돌려주며, 문서가 근삿값이라고 밝힌다정밀한 위치 파악이 필요한 작업에 약하다고 적었다[ymin, xmin, ymax, xmax]를 0~1000으로 정규화해 돌려준다
문서가 밝힌 한계화질이 낮거나 회전되거나 200픽셀 미만인 이미지에서 환각을 일으키거나 틀릴 수 있다회전된 글자를 잘못 읽을 수 있고, 한국어·일본어 같은 비라틴 문자에서는 최적이 아닐 수 있다회전을 바로잡고 흐리지 않은 이미지를 쓰라고 권한다
  • CC-OCR의 다국어 OCR 표에서 한국어 점수는 Gemini-1.5-Pro 80.01, GPT-4o 74.2, Claude-3.5-Sonnet 58.72였다. 비교한 버전은 Gemini-1.5-Pro-002, GPT-4o-2024-08-06, Claude-3.5-Sonnet-20241022다. 핵심 정보 추출은 가장 잘한 모델도 F1 71.76%였다.
  • OCRBench v2에서는 대부분의 모델이 100점 만점에 50점 아래였고, 자주 보지 못한 글자, 세밀한 인식, 레이아웃 파악 등 다섯 가지 약점이 꼽혔다.
  • 표의 출처는 Claude, OpenAI, Gemini의 공식 문서이고, 2026년 10월에 확인했다.

이제 앞 절의 설계가 왜 그렇게 나뉘었는지 보인다. 마스킹은 “어디에 있는지”가 정확해야 하니 좌표와 신뢰도를 주는 OCR이 맞다. 계약 내용을 이해하는 일은 의미를 읽는 VLM이 맞다.

보관 정책을 보면 순서가 왜 중요한지도 드러난다. 세 공급자 모두 유료 API의 입력을 모델 학습에 쓰지 않는다고 밝히지만, 일정 기간 보관은 한다. 민감정보를 VLM에 보내기 전에 가리면, 주민등록번호가 공급자 쪽 기록에 남을 일 자체가 사라진다.

공급자별 보관 정책 자세히 보기
공급자학습 사용보관
OpenAI직접 동의하지 않는 한 쓰지 않는다남용 감시 로그를 최대 30일 보관한다. 승인을 받으면 이 로그에서 제외할 수 있다
Anthropic업로드한 이미지를 학습에 쓰지 않는다API 입력과 출력을 30일 안에 지운다. 사용 정책 위반으로 걸린 경우 최대 2년 보관한다
Google 유료쓰지 않는다금지된 사용을 막기 위해 프롬프트와 응답을 제한된 기간 기록한다
Google 무료제품 개선에 쓴다사람이 입력과 출력을 읽고 주석을 달 수 있다

2026년 10월에 확인한 내용이다. 약관은 바뀔 수 있으니 도입 전에 원문을 다시 확인하자.

가린 계약서를 넣을 때는 필요한 항목만 JSON으로 달라고 하면 된다. 읽을 수 없는 값을 지어내지 말라는 지시와 가린 칸을 표시하는 규칙을 함께 적어 두자.

첨부한 계약서 이미지에서 아래 항목을 JSON으로 추출해 줘.
이미지에 없거나 읽을 수 없는 값은 지어내지 말고 null로 둬.
검게 가려진 칸은 "[MASKED]"로 적어 줘.

{ "title": string, "name": string | null, "residentNumber": string | null,
  "businessNumber": string | null, "startDate": string | null, "amount": string | null }

그림 속 가상 계약서라면 아래와 같은 답을 기대할 수 있다. 실제로 호출한 결과가 아니라 형태만 보여주는 예시다.

{
  "title": "용역 계약서",
  "name": "홍길동",
  "residentNumber": "[MASKED]",
  "businessNumber": "[MASKED]",
  "startDate": null,
  "amount": null
}

그래서 무엇을 쓸까

서문의 물음으로 돌아가 보자. 처음에 고민한 것은 외부 OCR API를 쓸지, 로컬 OCR 모델을 띄울지였다. 원리를 따라와 보니 질문을 조금 바꿔야 한다. 고를 것은 도구 하나가 아니라 단계마다 맡길 도구다.

  1. 민감정보 가리기 — CLOVA OCR이 맡는다. 덮을 자리를 정확히 알려면 좌표와 신뢰도가 필요하고, 원본을 저장하지 않는 서비스여야 한다.
  2. 내용 읽기 — VLM이 맡는다. 가린 이미지를 받아 계약 당사자, 기간, 금액 같은 의미를 JSON으로 뽑는다.

그렇다면 로컬 OCR은 언제 필요할까? 로컬 모델이 맞는 때는 이렇다.

  • 사내 규정 때문에 원본을 외부 서비스에 한 장도 보낼 수 없을 때
  • 처리하는 문서가 많아 호출 비용이 부담될 때
  • 우리 문서에만 있는 양식이나 글꼴 때문에 API의 인식률이 떨어질 때

이때 필요한 것이 딥러닝 절에서 본 파인튜닝이다. 검출 모델에는 이미지와 꼭짓점 좌표를, 인식 모델에는 줄 이미지와 정답 문자열을 주면 된다. 다음 편에서는 PaddleOCR을 가상 계약서에 맞춰 파인튜닝하고, 그 결과를 CLOVA OCR과 나란히 놓고 비교해 본다.

원리를 알면 OCR API는 더 이상 블랙박스가 아니다. 좌표는 검출이, 문자열과 신뢰도는 인식이 만든 값이다. VLM은 그 두 단계를 건너뛰고 의미를 읽는 대신 좌표를 잃는다. 이 차이만 기억하면 어느 단계에 무엇을 맡길지 스스로 고를 수 있다.