본문 바로가기

AI Development

markitdown-ocr 플러그인 — 스캔 PDF, 이미지 속 텍스트까지 뽑아내는 법

반응형

지난 글에서 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 프롬프트 지정 시 개선)
→ 극도로 흐린 스캔
→ 비표준 폰트

 

반응형