반응형
지난 글에서 markitdown 기본 사용법을 다뤘어요.
근데 이런 파일이 오면 기본 markitdown이 손을 들어요.
기본 markitdown이 못 하는 것들:
❌ 스캔해서 만든 PDF (이미지로만 구성)
❌ PDF 안에 박힌 차트/표 이미지
❌ PPT 슬라이드 안의 스크린샷
❌ Word 문서에 붙여넣은 이미지
❌ 손으로 쓴 필기 문서
이런 파일을 기본 markitdown으로 변환하면:
# 보고서
[이미지]
[이미지]
## 결론
이미지가 전부 [이미지] 플레이스홀더로 대체돼요. 안에 뭐가 있는지 모름.
markitdown-ocr 플러그인이 이 문제를 해결해요. LLM 비전 모델로 이미지 속 텍스트를 실제로 읽어냅니다.
설치
# markitdown 기본 설치
pip install 'markitdown[all]'
# OCR 플러그인 추가
pip install markitdown-ocr
# LLM 클라이언트 (둘 중 하나)
pip install openai # GPT-4o 사용 시
pip install anthropic # Claude 사용 시
작동 원리
기본 markitdown:
PDF 페이지 → 텍스트 레이어 추출 → Markdown
(이미지는 무시)
markitdown-ocr 플러그인:
PDF 페이지 → 텍스트 레이어 추출
↓
이미지 발견 → LLM 비전 API로 전송
↓
"이 이미지에서 텍스트 추출해줘"
↓
추출된 텍스트를 원래 위치에 삽입
↓
Markdown (이미지 내용 포함)
스캔 PDF (텍스트 레이어 아예 없는 경우):
페이지 전체를 이미지로 렌더링 → LLM에 통째로 전송
→ 페이지 전체 텍스트 추출
기본 사용법
GPT-4o로 OCR
from markitdown import MarkItDown
from openai import OpenAI
client = OpenAI() # OPENAI_API_KEY 환경변수 필요
md = MarkItDown(
enable_plugins=True, # 플러그인 활성화 (필수)
llm_client=client,
llm_model="gpt-4o", # 비전 지원 모델
)
# 스캔 PDF
result = md.convert("scanned_report.pdf")
print(result.text_content)
Claude Opus 4.7으로 OCR
from markitdown import MarkItDown
from anthropic import Anthropic
# Anthropic은 OpenAI 호환 클라이언트로 래핑 필요
import anthropic
from openai import OpenAI
# Anthropic API를 OpenAI 호환 엔드포인트로 사용
client = OpenAI(
api_key=anthropic.api_key,
base_url="https://api.anthropic.com/v1/"
)
md = MarkItDown(
enable_plugins=True,
llm_client=client,
llm_model="claude-opus-4-7",
)
result = md.convert("document_with_images.pdf")
print(result.text_content)
프롬프트 커스터마이징
md = MarkItDown(
enable_plugins=True,
llm_client=client,
llm_model="gpt-4o",
# 기본: "Extract all text from this image"
# 커스텀 프롬프트로 특화 가능
llm_prompt="이 이미지에서 모든 텍스트를 추출해줘. 표는 Markdown 표 형식으로, 수식은 LaTeX로 표현해줘.",
)
실전 케이스별 코드
케이스 1 — 스캔된 계약서/보고서
from markitdown import MarkItDown
from openai import OpenAI
def ocr_scanned_pdf(filepath: str, output_path: str = None) -> str:
"""스캔 PDF 전체 OCR"""
client = OpenAI()
md = MarkItDown(
enable_plugins=True,
llm_client=client,
llm_model="gpt-4o",
llm_prompt="""
이 문서 이미지에서 모든 텍스트를 추출해줘.
- 제목과 소제목은 # ## 헤딩으로
- 표는 Markdown 표 형식으로
- 목록은 - 또는 1. 형식으로
- 원본 레이아웃 구조 최대한 유지
"""
)
result = md.convert(filepath)
if output_path:
with open(output_path, 'w', encoding='utf-8') as f:
f.write(result.text_content)
print(f"저장 완료: {output_path}")
return result.text_content
# 사용
text = ocr_scanned_pdf("contract_scan.pdf", "contract.md")
케이스 2 — 차트/표 이미지가 많은 PPT
def ocr_presentation(pptx_path: str) -> str:
"""PPT 슬라이드 이미지까지 OCR"""
client = OpenAI()
md = MarkItDown(
enable_plugins=True,
llm_client=client,
llm_model="gpt-4o",
llm_prompt="""
슬라이드 이미지에서 텍스트를 추출해줘.
차트나 그래프가 있으면 수치와 레이블을 텍스트로 설명해줘.
예: "매출 차트: 1월 1200만, 2월 1500만, 3월 1800만"
"""
)
result = md.convert(pptx_path)
return result.text_content
text = ocr_presentation("q1_review.pptx")
케이스 3 — 이미지 파일 단독 OCR
def ocr_image(image_path: str) -> str:
"""이미지 파일에서 텍스트 추출"""
client = OpenAI()
md = MarkItDown(
enable_plugins=True,
llm_client=client,
llm_model="gpt-4o",
)
# JPG, PNG 등 이미지 파일도 지원
result = md.convert(image_path)
return result.text_content
# 영수증 이미지
text = ocr_image("receipt.jpg")
# 화이트보드 사진
text = ocr_image("whiteboard_photo.png")
# 명함 사진
text = ocr_image("business_card.jpg")
케이스 4 — 일괄 처리 + 비용 추적
from markitdown import MarkItDown
from openai import OpenAI
from pathlib import Path
import time
def batch_ocr_with_cost(
input_dir: str,
model: str = "gpt-4o-mini" # 비용 절약 버전
) -> dict:
"""일괄 OCR + 비용 추적"""
client = OpenAI()
md = MarkItDown(
enable_plugins=True,
llm_client=client,
llm_model=model,
)
results = []
total_cost = 0
# 이미지가 포함된 파일들
targets = list(Path(input_dir).glob("*.pdf")) + \
list(Path(input_dir).glob("*.pptx")) + \
list(Path(input_dir).glob("*.png")) + \
list(Path(input_dir).glob("*.jpg"))
for path in targets:
start = time.time()
try:
result = md.convert(str(path))
duration = time.time() - start
# 토큰 사용량으로 비용 추정 (GPT-4o 기준)
# 이미지 1장 약 1,000 토큰 = $0.01
char_count = len(result.text_content)
results.append({
"file": path.name,
"status": "success",
"chars": char_count,
"duration": f"{duration:.1f}s",
})
print(f"✅ {path.name} ({duration:.1f}s, {char_count:,}자)")
except Exception as e:
results.append({
"file": path.name,
"status": "error",
"error": str(e)
})
print(f"❌ {path.name}: {e}")
success = sum(1 for r in results if r["status"] == "success")
print(f"\n완료: {success}/{len(targets)}개 처리")
return results
results = batch_ocr_with_cost("./scanned_docs", model="gpt-4o-mini")
모델별 품질 vs 비용 비교
GPT-4o:
정확도: 최고
비용: 이미지 1장당 약 $0.01~0.03
속도: 보통
추천: 계약서, 법률 문서 등 정확도 중요한 경우
GPT-4o-mini:
정확도: GPT-4o 대비 약 80~85% 수준
비용: GPT-4o의 1/10 수준
속도: 빠름
추천: 대량 처리, 내부 문서 등 비용 민감한 경우
Claude Opus 4.7:
정확도: GPT-4o와 동급 (비전 3.75MP 지원)
비용: 입력 $5/MTok + 이미지 토큰
속도: 보통
추천: 이미 Anthropic 스택 사용 중인 경우
Gemini 3.1 Flash (API):
정확도: 중상
비용: 매우 저렴 (무료 티어 있음)
속도: 빠름
추천: 대량 스캔 문서 배치 처리
실제 비용 계산:
A4 1페이지 스캔 PDF 기준:
GPT-4o: 약 $0.02~0.05/페이지
GPT-4o-mini: 약 $0.002~0.005/페이지
100페이지 문서:
GPT-4o: 약 $2~5
GPT-4o-mini: 약 $0.2~0.5
llm_client 없이 쓰면?
# llm_client 없이 enable_plugins=True만 해도 설치는 됨
md = MarkItDown(enable_plugins=True)
result = md.convert("scanned.pdf")
# → OCR 조용히 스킵
# → 기본 markitdown처럼 동작
# → 이미지는 [이미지] 플레이스홀더
# 플러그인이 없는 것처럼 fallback 동작
OCR이 실패해도 전체 변환이 중단되지 않고 경고만 출력하고 계속 진행해요. 프로덕션에서 안전하게 쓸 수 있어요.
주의사항
API 비용 발생:
→ 이미지 1장마다 LLM API 호출
→ 페이지 많은 스캔 PDF는 비용 커질 수 있음
→ 먼저 gpt-4o-mini로 테스트 후 필요하면 업그레이드
속도:
→ 이미지 1장당 1~3초 소요
→ 100페이지 스캔 = 2~5분
→ 병렬 처리로 단축 가능
한계:
→ 복잡한 수식 (LaTeX 프롬프트 지정 시 개선)
→ 극도로 흐린 스캔
→ 비표준 폰트
반응형
'AI Development' 카테고리의 다른 글
| LLM 추상화 레이어 — 48시간마다 새 모델이 나오는 시대에 살아남는 법 (1) | 2026.04.23 |
|---|---|
| GitHub Copilot Agent Mode 실전 가이드 — VS Code에서 자율 코딩 에이전트 쓰는 법 (0) | 2026.04.21 |
| markitdown 완전 가이드 — PDF, Word, PPT를 LLM이 읽는 형식으로 자동 변환 (0) | 2026.04.21 |
| Gemini CLI 가이드 — Claude Code 대신 $0에 쓰는 법 (1) | 2026.04.21 |
| AI 에이전트 트래픽 7,851% 폭증 — 바뀌어야 하는 서버 설계, 방어 전략 (0) | 2026.04.20 |