포스팅 개요
본 포스팅은 Chandra라는 파이썬 OCR 라이브러리를 소개하는 포스팅입니다. 최근 RAG(Retrieval Augmented Generation)과 AI Agent에 대한 연구와 개발 수요가 증가하면서 문서 디지털화와 OCR(Optical Character Recognition) 기술에 대한 수요가 급증하고 있습니다.
기존의 OCR 솔루션들은 텍스트만 추출할 뿐, 문서의 레이아웃 구조를 보존하지 못하는 한계가 있었죠.
Chandra는 이러한 문제를 해결하기 위해 등장한 Vision-Language Model(VLM) 기반의 문서 OCR 시스템입니다.
단순히 텍스트만 추출하는 것이 아니라, 문서의 레이아웃 정보까지 보존하면서 HTML, Markdown, JSON 형식으로 변환해주는 강력한 기능을 제공합니다.
개인적으로 2025년에 써본 오픈소스 OCR 라이브러리 중 가장 좋은 성능을 보여주었습니다.
Chandra는 Qwen3 Vision-Language 모델 아키텍처를 활용하며, 40개 이상의 언어를 지원하고 표, 수학 공식, 체크박스가 포함된 양식, 손글씨 등 복잡한 문서 레이아웃을 처리할 수 있습니다. 본 포스팅에서는 Chandra의 기술적 구조부터 실제 사용법까지 상세히 다뤄보도록 하겠습니다.

포스팅 본문
포스팅 개요에서도 언급하였듯, Chandra는 Vision-Language Model을 활용하여 문서 이미지를 구조화된 텍스트로 변환하는 OCR 시스템입니다.
본 포스팅에서는 개인적으로 조사해본 Chandra의 시스템 아키텍처, 모델 구조, 실제 사용법, 그리고 직접 테스트한 결과까지 살펴보도록 하겠습니다.
[1]. Chandra OCR 소개
[1-1]. Chandra OCR의 방법
문서 디지털화 및 OCR은 컴퓨터 비전과 자연어 처리 분야의 근본적인 과제였습니다. 기존의 OCR 시스템은 문서의 구조적 레이아웃을 보존하지 않고 주로 텍스트 추출에만 초점을 맞췄죠. 그러나 현대 문서에는 다단 텍스트, 표, 수학 공식, 양식, 내장 이미지 등 단순한 문자 인식을 넘어서는 정교한 이해가 필요한 복잡한 레이아웃이 포함되어 있습니다. 그리고 RAG와 AI Agent 시스템을 설계함에 있어서 이런 복잡한 문서를 처리하는 기술이 필요하죠.
기존 OCR 솔루션이 직면한 한계점들은 다음과 같습니다.
1) 텍스트 추출 시 문서 구조 손실: 단순히 텍스트만 추출하면서 원본 문서의 레이아웃 정보가 사라지는 문제가 있습니다.
2) 표 및 양식의 부적절한 처리: 복잡한 테이블 구조나 양식 필드를 제대로 인식하지 못합니다.
3) 수학적 표기법에 대한 제한적 지원: 수학 공식이나 특수 기호를 정확하게 인식하지 못합니다.
4) 부적절한 손글씨 인식: 필기체나 손글씨 인식에 한계가 있습니다.
5) 내장 이미지 추출 불가: 문서 내에 포함된 이미지를 공간적 맥락과 함께 추출하기 어렵습니다.
Chandra는 문서 이해를 이미지-구조화된 텍스트 생성 작업으로 처리하는 Vision-Language Model(VLM) 접근 방식을 사용하여 이러한 과제를 해결합니다. 시스템은 다음과 같은 과정을 수행합니다.
1. PDF 문서 및 이미지를 입력으로 수용
2. 문서 이해를 위해 훈련된 VLM을 통해 페이지 처리
3. 바운딩 박스 주석이 포함된 구조화된 HTML 출력 생성
4. 출력을 다양한 형식(Markdown, HTML, JSON 청크)으로 변환
5. 감지된 레이아웃 영역을 기반으로 내장 이미지 추출
[1-2]. Chandra의 주요 기능
Chandra가 제공하는 핵심 기능들을 정리하면 다음과 같습니다.
| 기능 | 설명 |
| 다중 형식 출력 | Markdown, HTML, 레이아웃 블록이 포함된 구조화된 JSON 지원 |
| 레이아웃 보존 | 각 콘텐츠 블록에 대한 바운딩 박스 좌표로 문서 구조 보존 |
| 레이아웃 블록 | Text, Table, Image, Figure, Caption, Equation-Block 등 다양한 블록 유형 지원 |
| 손글씨 인식 | 필기체 및 손글씨 텍스트 인식 기능 |
| 양식 필드 감지 | 체크박스 및 라디오 버튼을 포함한 양식 요소 감지 |
| 수학 공식 렌더링 | 인라인 및 블록 수학 공식을 LaTeX로 변환 |
| 표 구조 보존 | colspan/rowspan을 지원하는 정확한 테이블 구조 인식 |
| 이미지 추출 | 자동 네이밍으로 문서 내 이미지 및 그림 추출 |
| 다국어 지원 | 40개 이상의 언어 지원 (한국어 포함) |
| 이중 추론 백엔드 | 로컬 HuggingFace와 원격 vLLM 서버 두 가지 추론 방식 제공 |
[2]. 시스템 요구사항 및 의존성
[2-1]. 런타임 요구사항
Chandra를 사용하기 위한 기본적인 시스템 요구사항은 다음과 같습니다.
- Python >= 3.10
- CUDA 지원 GPU (로컬 추론에 권장)
- Docker (vLLM 서버 배포에 필요)
[2-2]. 핵심 의존성 패키지
시스템이 의존하는 주요 Python 패키지들을 정리하면 다음과 같습니다.
| 패키지 | 버전 | 용도 |
| torch | >= 2.8.0 | 딥러닝 프레임워크 |
| torchvision | >= 0.23.0 | PyTorch용 비전 유틸리티 |
| transformers | >= 4.57.1 | HuggingFace 모델 로딩 및 추론 |
| accelerate | >= 1.11.0 | 분산 추론 가속화 |
| qwen-vl-utils | >= 0.0.14 | Qwen VL 모델 유틸리티 |
| openai | >= 2.2.0 | vLLM용 OpenAI 호환 API 클라이언트 |
| pillow | >= 10.2.0 | 이미지 처리 |
| pypdfium2 | >= 4.30.0 | PDF 렌더링 |
| beautifulsoup4 | >= 4.14.2 | HTML 파싱 |
| markdownify | == 1.1.0 | HTML에서 Markdown으로 변환 |
| pydantic | >= 2.12.0 | 데이터 검증 및 스키마 |
| streamlit | >= 1.50.0 | 웹 애플리케이션 프레임워크 |
추가로, 더 빠른 HuggingFace 추론을 위해 flash-attention을 선택적으로 설치할 수 있습니다.
[3]. Chandra OCR 시스템 아키텍처
[3-1]. 아키텍처(큰 흐름도)
Chandra 시스템은 명확한 관심사 분리를 갖춘 모듈형 아키텍처를 따릅니다.
전체적인 구조를 다이어그램으로 표현하면 다음과 같습니다.
+------------------+ +------------------+ +------------------+
| 진입점 | --> | 추론 계층 | --> | 출력 처리 |
+------------------+ +------------------+ +------------------+
| - CLI (cli.py) | | - InferenceManager| | - parse_html() |
| - Streamlit App | | - HF 백엔드 | | - parse_markdown()|
| - Flask API | | - vLLM 백엔드 | | - parse_chunks() |
| - vLLM 런처 | +------------------+ | - extract_images()|
+------------------+ +------------------+
시스템은 크게 진입점(Entry Point), 추론 계층(Inference Layer), 출력 처리(Output Processing)의 세 가지 주요 컴포넌트로 구성됩니다.
추론의 경우에는 허깅페이스(HuggingFace, HF)를 사용하거나 vLLM을 사용할 수 있습니다.
그리고 출력은 html, markdown과 같은 형태로 출력할 수 있으며, 이미지의 경우 images 형태로 제공됩니다.
[3-2]. 모듈 구조
Chandra의 디렉토리 구조를 살펴보면 다음과 같습니다.
chandra/
|-- __init__.py # 패키지 초기화
|-- input.py # 입력 파일 처리
|-- output.py # 출력 파싱 및 포매팅
|-- prompts.py # 프롬프트 템플릿
|-- settings.py # 설정 관리
|-- util.py # 일반 유틸리티
|-- model/
| |-- __init__.py # InferenceManager 클래스
| |-- hf.py # HuggingFace 추론 백엔드
| |-- vllm.py # vLLM 추론 백엔드
| |-- schema.py # 데이터 구조 정의
| |-- util.py # 모델 전용 유틸리티
|-- scripts/
|-- cli.py # 메인 CLI 애플리케이션
|-- vllm.py # vLLM Docker 런처
|-- app.py # Streamlit 웹 인터페이스
|-- run_app.py # Streamlit 런처 래퍼
Chandra가 지원하는 입력 파일 형식은 다음과 같습니다.
| 형식 | 확장자 | 처리기 | 비고 |
| pypdfium2 | 다중 페이지, 양식 지원 | ||
| PNG | .png | PIL | 무손실 |
| JPEG | .jpg, .jpeg | PIL | 손실 압축 |
| GIF | .gif | PIL | 첫 번째 프레임만 |
| WebP | .webp | PIL | 현대 형식 |
| TIFF | .tiff | PIL | 다중 페이지 지원 |
| BMP | .bmp | PIL | 비압축 |
[4]. 모델 아키텍처 및 추론
본 모델 아키텍처와 추론은 제가 코드를 분석해보고 정리한 자료입니다.
이에 100% 정확하지 않을 수 있으니 참고로 봐주시면 감사하겠습니다.
[4-1]. 기본 모델
Chandra는 백본으로 Qwen3 Vision-Language 모델(Qwen3VL)을 사용합니다.
이 모델은 문서 OCR 작업을 위해 파인튜닝되었으며, datalab-to/chandra 식별자로 HuggingFace에 호스팅됩니다.

모델 특성
- 아키텍처: Qwen3VLForConditionalGeneration
- 정밀도: bfloat16 (16비트 브레인 부동소수점)
- 최대 컨텍스트 길이: 32,768 토큰
- 최대 출력 토큰: 12,384 (설정 가능)
[4-2]. InferenceManager 클래스
InferenceManager 클래스는 두 가지 추론 백엔드(HuggingFace, vLLM)에 대한 통합 인터페이스를 제공합니다.
class InferenceManager:
def __init__(self, method: str = "vllm"):
assert method in ("vllm", "hf")
self.method = method
if method == "hf":
self.model = load_model()
else:
self.model = None
def generate(self, batch: List[BatchInputItem], **kwargs) -> List[BatchOutputItem]:
# 적절한 백엔드로 라우팅
# 출력 파싱 수행
# 통합된 BatchOutputItem 객체 반환
- 지연 로딩(Lazy Loading): HuggingFace 모델은 method="hf"일 때만 로드됩니다.
- 통합 출력: 두 백엔드 모두 동일한 BatchOutputItem 구조를 생성합니다.
- 설정 추출: 백엔드별 매개변수가 kwargs에서 추출됩니다.
[4-3]. HuggingFace 백엔드
HuggingFace 백엔드는 로컬에서 직접 모델을 로드하여 추론을 수행합니다.
배치 처리 흐름은 다음과 같습니다.
messages = [process_batch_element(item, processor, bbox_scale) for item in batch]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
image_inputs, _ = process_vision_info(messages)
inputs = processor(text=text, images=image_inputs, padding=True, return_tensors="pt")
generated_ids = model.generate(**inputs, max_new_tokens=max_output_tokens)
주요 설정 옵션은 아래와 같습니다.
- TORCH_DEVICE: 명시적 장치 할당 (예: "cuda:0")
- TORCH_ATTN: 어텐션 구현 (예: "flash_attention_2")
[4-4]. vLLM 백엔드
vLLM 백엔드는 OpenAI 호환 API를 통해 별도의 vLLM 추론 서버와 통신합니다.
동시 API 요청을 위해 ThreadPoolExecutor를 사용하죠.
def generate_vllm(batch, max_workers=None, ...):
if max_workers is None:
max_workers = min(64, len(batch))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(executor.map(process_item, batch, ...))
return results
여기에 병렬화 전략이 있는데요. 다음과 같은 특징을 가지고 있습니다.
- 배치 크기에 따른 동적 워커 스케일링
- 최대 64개 동시 워커
- I/O 바운드 API 호출에 적합한 스레드 기반 병렬 처리
[4-5]. 이미지 전처리: scale_to_fit()
추론 전에 모든 이미지는 scale_to_fit() 함수를 통과합니다. 스케일링 로직은 다음과 같습니다.
- max_size (6,291,456 픽셀)를 초과하는 이미지는 다운스케일
- min_size (784 픽셀) 미만의 이미지는 업스케일
- 제곱근 스케일링을 사용하여 종횡비 보존
\( scale\_factor = \sqrt{\frac{target\_pixels}{current\_pixels}} \)
- 품질을 위한 LANCZOS 리샘플링
[5]. 프롬프트 엔지니어링
[5-1]. 프롬프트 구조
시스템은 VLM이 구조화된 출력을 생성하도록 안내하기 위해 신중하게 설계된 프롬프트를 사용합니다.
허용된 HTML 태그는 37개이며, 대표적인 것들은 다음과 같습니다.
ALLOWED_TAGS = [
"math", "br", "i", "b", "u", "del", "sup", "sub",
"table", "tr", "td", "p", "th", "div", "pre",
"h1", "h2", "h3", "h4", "h5",
"ul", "ol", "li", "input", "a", "span", "img", "hr",
"tbody", "small", "caption", "strong", "thead", "big", "code"
]
[5-2]. 지원되는 레이아웃 라벨
Chandra가 인식하는 15가지 레이아웃 블록 유형은 다음과 같습니다.
| 라벨 | 설명 |
| Text | 표준 본문 텍스트 |
| Section-Header | 제목 및 타이틀 |
| Table | 테이블 데이터 |
| List-Group | 글머리 기호 또는 번호 목록 |
| Image | 내장 이미지 |
| Figure | 캡션이 있는 그림 |
| Caption | 이미지 또는 그림 캡션 |
| Equation-Block | 수학 공식 |
| Code-Block | 소스 코드 |
| Form | 양식 요소 |
| Footnote | 각주 |
| Page-Header | 반복되는 페이지 헤더 |
| Page-Footer | 반복되는 페이지 푸터 |
| Table-Of-Contents | 목차 |
| Complex-Block | 복잡한 혼합 콘텐츠 |
[5-3]. 프롬프트 가이드라인
OCR 레이아웃 프롬프트에서 모델에게 제공하는 주요 가이드라인은 다음과 같습니다.

- 인라인 수학: <math>...</math> 태그로 수학 표현을 감싸고, KaTeX 호환 LaTeX로 렌더링
- 테이블: colspan과 rowspan 속성을 사용하여 테이블 구조 일치
- 포매팅: 간격, 들여쓰기, 위첨자/아래첨자, 특수 문자 등 원본과 일관된 포맷 유지
- 이미지: img 태그의 alt 속성에 이미지 설명 포함
- 양식: 체크박스와 라디오 버튼을 적절히 표시
- 텍스트: <p>...</p> 태그를 사용하여 문장을 문단으로 결합
[6]. 출력 처리 파이프라인
[6-1]. 기본 모델 출력 형식
모델은 다음과 같은 구조의 HTML을 생성합니다.
<div data-label="Section-Header" data-bbox="[50, 100, 400, 130]">
<h1>Chapter 1: Introduction</h1>
</div>
<div data-label="Text" data-bbox="[50, 150, 400, 250]">
<p>This is the main text content of the document...</p>
</div>
<div data-label="Table" data-bbox="[50, 270, 400, 450]">
<table>
<tr><th>Column 1</th><th>Column 2</th></tr>
<tr><td>Data 1</td><td>Data 2</td></tr>
</table>
</div>
각 div 요소는 data-label 속성으로 블록 유형을, data-bbox 속성으로 바운딩 박스 좌표를 포함합니다.
[6-2]. HTML 파싱
parse_html() 함수는 원시 HTML 출력을 정리하고 처리합니다.
주요 처리 단계는 다음과 같습니다.
1. BeautifulSoup 파싱: HTML을 파싱하여 최상위 div 요소 찾기
2. 라벨 추출: 각 div에서 data-label 속성 읽기
3. 헤더/푸터 필터링: 선택적으로 Page-Header 및 Page-Footer 블록 제외
4. 이미지 필터링: 선택적으로 Image 및 Figure 블록 제외
5. 이미지 소스 할당: img 요소에 파일명 생성 및 할당
6. 텍스트 블록 래핑: 구조를 위해 일반 텍스트를 p 태그로 래핑
7. 콘텐츠 추출: 래퍼 div를 제거하고 내부 HTML 추출
[6-3]. Markdown 변환
parse_markdown() 함수는 사용자 정의 Markdownify 클래스를 사용하여 HTML을 Markdown으로 변환합니다.
주요 기능은 다음과 같습니다.
- 수학 처리: 블록 수학은 $$...$$, 인라인 수학은 $...$ 형식으로 변환
- 테이블 보존: 테이블을 HTML로 유지 (Markdown 테이블로 변환하지 않음)
- 달러 기호 이스케이핑: LaTeX 해석 방지를 위해 $를 \$로 변환
[6-4]. 이미지 추출
extract_images() 함수는 문서 내 이미지를 추출합니다. 처리 단계는 아래와 같습니다.
1. 청크 반복하며 Image/Figure 라벨 감지
2. bbox 좌표를 사용하여 원본 이미지 크롭
3. MD5 해시를 사용하여 결정적 파일명 생성
4. 딕셔너리에 파일명을 키로 크롭된 PIL Image 저장
[7]. Chandra OCR 실행 방법, 사용 방법
Chandra OCR을 실행하는 방법은 크게 2가지 방법이 있습니다.
1. CLI를 통한 실행
2. web(streamlit)을 이용한 실행
두 방법 다 어렵지 않으니 하나씩 살펴보시죠.
그 전에 먼저 설치를 해야겠죠? pip를 통해 간단하게 설치 가능합니다.
pip install chandra-ocr
[7-1]. CLI를 통한 OCR 실행
먼저 CLI를 통해 PDF 파일을 처리했습니다. CLI를 실행할 때 사용하는 옵션과 주요 명령어 포맷은 다음과 같습니다.
# With VLLM
chandra_vllm
chandra input.pdf ./output
# With HuggingFace
chandra input.pdf ./output --method hf
| 옵션 | 기본값 | 설명 |
| --method [hf|vllm] | vllm | 추론 백엔드 선택 |
| --page-range | None | PDF 페이지 범위 (예: "1-5,7,9-12") |
| --max-output-tokens | 12384 | 페이지당 최대 토큰 |
| --max-workers | 64 | 병렬 워커 (vLLM 전용) |
| --include-images | True | 이미지 추출 및 저장 |
| --save-html | True | HTML 출력 생성 |
| --batch-size | 28 (vllm) / 1 (hf) | 배치당 페이지 수 |
제가 직접 실행해본 실행 명령은 다음과 같습니다.
저는 허깅페이스 기반으로 실행해 보았습니다.
chandra 한국AI정책현황및발전방안.pdf output --method hf


실행 결과, 5페이지의 PDF가 성공적으로 처리되었고, 각 페이지별로 OCR이 수행되었습니다. 터미널에서 확인할 수 있듯이 "Processing pages 1-1...", "Processing pages 2-2..." 와 같이 순차적으로 처리가 진행됩니다.
처리가 완료된 후 output 폴더에 생성된 파일들입니다.

생성된 파일들은
- 한국AI정책현황및발전방안.md (Markdown 파일)
- 한국AI정책현황및발전방안.html (HTML 파일)
- 한국AI정책현황및발전방안_metadata.json (메타데이터)
- 추출된 차트 이미지들
와 같은 파일들이 생성되었습니다.
생성된 HTML 파일을 브라우저에서 열어보면, 원본 PDF의 레이아웃이 잘 보존된 것을 확인할 수 있습니다.


특히 눈에 띄는 점은 차트 이미지가 정확하게 추출되어 표시되었고 표(Table)의 구조가 정확하게 인식되었습니다.
무엇보다 한국어 텍스트가 정확하게 인식이 되었고, 제목,본문,각주 등의 레이아웃 구분도 잘 되었습니다.
[7-2]. 웹 인터페이스를 통한 테스트
Streamlit 웹 인터페이스를 통해서도 테스트를 진행했습니다.
streamlit으로 실행하려면 아래와 같이 명령어를 입력하면 됩니다.
# Interactive streamlit app
chandra_app

위 화면에서 모델을 선택하고, 파일을 업로드하면 결과를 받아볼 수 있습니다.


마무리
이번 포스팅은 Vision-Language Model 기반의 문서 OCR 시스템인 Chandra에 대해 상세히 살펴보았습니다.
Chandra는 단순한 텍스트 추출을 넘어, 문서의 레이아웃 구조까지 보존하면서 다양한 형식(HTML, Markdown, JSON)으로 출력할 수 있는 강력한 기능을 제공합니다.
실제로 한국어 PDF 문서를 테스트해본 결과, 표, 차트, 텍스트 등이 정확하게 인식되고 레이아웃이 잘 보존되는 것을 확인할 수 있었습니다. 문서 디지털화나 OCR 작업이 필요한 분들께 Chandra를 추천드립니다.
비록 부족한 글이지만, OCR과 문서 처리에 관심 있으신 분들에게 도움이 되시길 바랍니다.
긴 글 읽어주셔서 감사합니다.
혹시라도 저에게 연락을 주시고 싶으시다면,
- Linkedin: https://www.linkedin.com/in/lsjsj92/
- 블로그 댓글 또는 방명록
으로 연락 남겨주시면 됩니다!
'인공지능(AI) > Data processing' 카테고리의 다른 글
| DeepSeek의 새로운 OCR: Contexts Optical Compression 논문 리뷰 (0) | 2025.10.25 |
|---|