Unlimited-OCR이 일반 OCR과 뭐가 다른가요?
기존 OCR은 보통 "글자 인식 → 레이아웃 분석 → 표 추출" 같은 단계를 거치며, 단계마다 별도 모델을 붙여야 했습니다. 문서가 길어질수록 단계 사이 오류가 누적되고 처리 속도도 느려집니다.
Unlimited-OCR은 Baidu가 2026년에 공개한 30억 파라미터 비전-언어 모델로, 이미지와 텍스트를 동시에 이해하는 멀티모달 AI입니다. 한 번의 디코딩으로 페이지 전체의 글자, 문단, 표, 페이지 간 참조까지 구조화된 텍스트로 뽑아내기 때문에 별도의 레이아웃 분석기를 둘 필요가 없습니다.
사전 준비: GPU 환경과 라이브러리
이 튜토리얼은 Google Colab 기준이지만, 로컬 GPU(VRAM 12GB 이상)에서도 동일하게 동작합니다. 필요한 라이브러리는 아래 9개입니다.
- transformers 4.57.1 — Hugging Face 모델 로더
- PyMuPDF — PDF를 이미지로 변환 (코드에서는
fitz로 import) - Pillow, matplotlib — 이미지 생성과 미리보기
- accelerate — GPU 연산 가속
- einops, addict, easydict, psutil — 모델 내부 의존성
모델 파일은 약 6GB(BF16 30억 파라미터)라서 첫 실행 시 다운로드가 조금 걸리지만, 한 번 받아 두면 이후에는 캐시에서 즉시 로드됩니다.
런타임 → 런타임 유형 변경 → GPU로 먼저 바꿔 주세요. 모델이 GPU 없이는 사실상 동작하지 않습니다.
STEP 1. 모델 불러오기
먼저 필요한 패키지를 설치하고, GPU가 감지되면 자동으로 bfloat16 또는 float16 중 하나를 선택합니다. 그다음 Hugging Face에서 baidu/Unlimited-OCR 모델을 받아 GPU로 올립니다.
import subprocess, sys
def pip_install(*pkgs):
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *pkgs])
print(">> Installing dependencies (1-2 min)...")
pip_install(
"transformers==4.57.1",
"Pillow", "matplotlib", "einops",
"addict", "easydict", "pymupdf", "psutil", "accelerate",
)
print(">> Done.")
import os, torch
from transformers import AutoModel, AutoTokenizer
assert torch.cuda.is_available(), "No GPU detected! In Colab: Runtime -> Change runtime type -> GPU."
gpu_name = torch.cuda.get_device_name(0)
print(f">> GPU: {gpu_name}")
use_bf16 = torch.cuda.is_bf16_supported()
DTYPE = torch.bfloat16 if use_bf16 else torch.float16
print(f">> Using dtype: {DTYPE}")
MODEL_NAME = "baidu/Unlimited-OCR"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
model = AutoModel.from_pretrained(
MODEL_NAME, trust_remote_code=True,
use_safetensors=True, torch_dtype=DTYPE,
).eval().cuda()
print(">> Model loaded and moved to GPU.")
이 한 블록으로 (1) 의존성 설치 → (2) GPU 확인 → (3) 데이터 타입 자동 선택 → (4) 모델 로드까지 한 번에 끝납니다.
STEP 2. 샘플 문서 만들기
테스트용 이미지를 미리 준비하지 않아도, Pillow로 제목·본문·표·각주가 들어간 샘플 페이지를 자동 생성할 수 있습니다. 이렇게 만든 3장을 묶어 PDF로도 변환합니다.
from PIL import Image, ImageDraw, ImageFont
import textwrap, os, matplotlib.pyplot as plt
os.makedirs("inputs", exist_ok=True)
os.makedirs("outputs/single_gundam", exist_ok=True)
os.makedirs("outputs/single_base", exist_ok=True)
os.makedirs("outputs/multi_page", exist_ok=True)
def load_font(size):
for path in ["/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"]:
if os.path.exists(path):
return ImageFont.truetype(path, size)
return ImageFont.load_default()
def make_sample_page(path, page_no):
W, H = 1240, 1754
img = Image.new("RGB", (W, H), "white"); d = ImageDraw.Draw(img)
title_f, head_f, body_f = load_font(48), load_font(34), load_font(26)
d.text((80, 70), f"Quarterly Operations Report - Page {page_no}", fill="black", font=title_f)
d.line([(80, 145), (W-80, 145)], fill="black", width=3)
body = ("This document demonstrates Unlimited-OCR's one-shot long-horizon "
"parsing. The model reads an entire page - headings, paragraphs, "
"and tables - and emits structured text in a single decoding pass.")
y = 190
for line in textwrap.wrap(body, width=72):
d.text((80, y), line, fill="black", font=body_f); y += 40
y += 30
d.text((80, y), f"Table {page_no}: Regional Revenue (USD, millions)", fill="black", font=head_f)
y += 60
rows = [["Region","Q1","Q2","Q3"],["North","12.4","13.1","15.0"],
["South","9.8","10.2","11.7"],["East","14.3","13.9","16.2"],
["West","11.1","12.5","12.9"]]
col_w, row_h, x0 = 260, 56, 80
for r,row in enumerate(rows):
for c,cell in enumerate(row):
x = x0 + c*col_w
d.rectangle([x,y,x+col_w,y+row_h], outline="black", width=2)
d.text((x+14,y+12), cell, fill="black", font=body_f)
y += row_h
img.save(path); return path
IMAGE_PATH = make_sample_page("inputs/sample_page_1.png", 1)
PAGE_2 = make_sample_page("inputs/sample_page_2.png", 2)
PAGE_3 = make_sample_page("inputs/sample_page_3.png", 3)
print(f">> Sample pages written: {IMAGE_PATH}, {PAGE_2}, {PAGE_3}")
plt.figure(figsize=(6,8)); plt.imshow(Image.open(IMAGE_PATH))
plt.axis("off"); plt.title("Input document (page 1)"); plt.show()
STEP 3. Gundam 모드로 단일 페이지 OCR
문서 안에 작은 글자나 빽빽한 표가 있다면 Gundam 모드가 정답입니다. 이미지를 잘게 타일로 잘라 부분적으로도 보기 때문에 작은 글자도 놓치지 않습니다.
print("\n" + "="*76)
print("STEP 4: Single image - GUNDAM mode (tiled, high detail)")
print("="*76)
model.infer(
tokenizer,
prompt="<image>document parsing.",
image_file=IMAGE_PATH,
output_path="outputs/single_gundam",
base_size=1024, image_size=640, crop_mode=True,
max_length=32768,
no_repeat_ngram_size=35, ngram_window=128,
save_results=True,
)
no_repeat_ngram_size는 똑같은 n-gram이 다시 나오는 걸 막아 줍니다. 긴 글자 출력에서 같은 문장이 계속 반복되는 "퇴화 현상"을 줄여 줘요.
STEP 4. Base 모드로 단일 페이지 OCR
깨끗하게 인쇄된 한 페이지라면 타일 자르기 없이 한 번에 보는 Base 모드가 더 빠릅니다. 결과 정확도와 속도 trade-off를 직접 비교해 보기 위해 같은 페이지를 한 번 더 돌립니다.
print("\n" + "="*76)
print("STEP 5: Single image - BASE mode (single view, faster)")
print("="*76)
model.infer(
tokenizer,
prompt="<image>document parsing.",
image_file=IMAGE_PATH,
output_path="outputs/single_base",
base_size=1024, image_size=1024, crop_mode=False,
max_length=32768,
no_repeat_ngram_size=35, ngram_window=128,
save_results=True,
)
STEP 5. 여러 페이지 PDF 파싱
3장의 샘플 이미지를 PDF로 묶고, PyMuPDF로 각 페이지를 고해상도 PNG로 래스터화한 뒤 infer_multi()에 통째로 넣습니다. 모델이 페이지 사이 맥락까지 한 번에 이어서 읽습니다.
import fitz로 사용합니다.
print("\n" + "="*76)
print("STEP 6: Multi-page / PDF parsing")
print("="*76)
import tempfile, fitz
def pdf_to_images(pdf_path, dpi=300):
doc = fitz.open(pdf_path)
tmp_dir = tempfile.mkdtemp(prefix="pdf_ocr_")
mat = fitz.Matrix(dpi/72, dpi/72); paths = []
for i, page in enumerate(doc):
out = os.path.join(tmp_dir, f"page_{i+1:04d}.png")
page.get_pixmap(matrix=mat).save(out); paths.append(out)
doc.close(); return paths
SAMPLE_PDF = "inputs/sample_doc.pdf"
pdf = fitz.open()
for p in [IMAGE_PATH, PAGE_2, PAGE_3]:
img_doc = fitz.open(p); rect = img_doc[0].rect
pdf_bytes = img_doc.convert_to_pdf()
img_pdf = fitz.open("pdf", pdf_bytes)
page = pdf.new_page(width=rect.width, height=rect.height)
page.show_pdf_page(rect, img_pdf, 0)
pdf.save(SAMPLE_PDF); pdf.close()
page_images = pdf_to_images(SAMPLE_PDF, dpi=300)
print(f">> Rasterized {len(page_images)} pages")
model.infer_multi(
tokenizer,
prompt="<image>Multi page parsing.",
image_files=page_images,
output_path="outputs/multi_page",
image_size=1024,
max_length=32768,
no_repeat_ngram_size=35, ngram_window=1024,
save_results=True,
)
ngram_window를 1024로 키워서 페이지가 넘어가도 같은 문장이 반복되지 않게 해야 안정적인 결과가 나옵니다.
STEP 6. 결과 확인 & 치트시트
출력 폴더에는 .txt, .md, .mmd, .json 등 다양한 형식의 결과가 자동으로 저장됩니다. 마지막으로 어떤 모드를 골라야 하는지 한눈에 보는 요약표를 정리했습니다.
for out_dir in ["outputs/single_gundam", "outputs/single_base", "outputs/multi_page"]:
for dirpath, _, files in os.walk(out_dir):
for fn in sorted(files):
fp = os.path.join(dirpath, fn)
print(f" {fp} ({os.path.getsize(fp):,} bytes)")
📌 상황별 모드 치트시트
- 단일 이미지, 글자가 작거나 빽빽할 때 →
infer()+gundam (640 + crop_mode=True) - 단일 이미지, 깔끔한 인쇄본 →
infer()+base (1024, crop_mode=False) - 여러 페이지 PDF →
infer_multi()+image_size=1024, ngram_window=1024 - 긴 문서 →
max_length=32768과no_repeat_ngram설정을 그대로 유지 - 내 파일로 테스트 → Colab 사이드바에서 업로드 후
image_file경로만 변경
자주 묻는 질문(FAQ)
Q1. GPU가 없으면 못 쓰나요?
기본적으로는 GPU가 필요합니다. 다만 torch_dtype을 torch.float32로 바꾸고 모델 크기를 양자화하면 16GB 정도의 VRAM이 있는 GPU에서 겨우 돌릴 수 있습니다. CPU만 있다면 처리 시간이 수십 배 길어져 실용적이지 않습니다.
Q2. Gundam 모드와 Base 모드 중 뭘 먼저 써야 하나요?
먼저 Base 모드로 한 번 돌려 보고, 결과에서 글자 누락이나 표 깨짐이 보이면 Gundam 모드로 다시 돌리는 게 가장 효율적입니다. Gundam 모드는 처리 시간이 평균 2~3배 더 걸립니다.
Q3. 한 번에 몇 페이지까지 한 번에 넣을 수 있나요?
공식 안내와 Colab 메모리 한도 기준, 16GB VRAM에서 3~5페이지가 안정적입니다. 더 긴 문서는 PDF를 5페이지 단위로 잘라 infer_multi()를 여러 번 호출하고, 마지막에 텍스트 파일을 이어 붙이는 방식을 추천합니다.
Q4. 결과 파일은 어떤 포맷으로 나오나요?
기본적으로 .txt(순수 텍스트), .md(마크다운), .mmd(멀티모달 마크다운), .json(구조화 데이터) 4종이 같은 폴더에 함께 생성됩니다. 표와 헤딩은 마크다운이나 JSON에서 더 잘 보존됩니다.
Q5. 한국어 문서도 잘 읽나요?
Unlimited-OCR은 영어 학습 비중이 가장 크지만, 한글·일본어·중국어도 학습 데이터에 포함되어 있어 표와 본문 정도는 안정적으로 인식합니다. 다만 띄어쓰기가 없는 구형 한글 인쇄본은 Base 모드보다 Gundam 모드가 더 유리합니다.
Q6. 손글씨나 영수증처럼 비정형 문서도 가능한가요?
손글씨는 정자체 기준으로는 가능하지만, 흘려 쓴 노트나 접히고 구겨진 영수증은 인식률이 떨어집니다. 이런 경우엔 모델을 호출하기 전에 대비·노이즈 제거를 먼저 적용하면 정확도가 크게 올라갑니다.
infer_multi()는 페이지 연결성에 강점이 있으니, 문서 성격에 맞춰 골라 쓰면 됩니다.