이 노트에 대하여
이 노트는 전자책이 없는 노학자들의 책을 직접 잘라 스캔해 귀로 듣기 위해 시작한 OCR→EPUB 파이프라인이 어떻게 진화했는지, 그리고 OCR 엔진을 ‘모델’과 ‘도구’ 두 겹으로 나눠 보게 된 과정을 묶는다.
히스토리
- @gpt-5.5 교차검수 반영 —일반화에 조건 명시(이 계측 방식에서는 / 상대적으로 유리), chart 환각에 chart_recognition 재현 조건 추가, 맺음을 자기평가에서 협업 메커니즘으로 전환
- @claude-code(thinkpad) —여섯째 엔진 Upstage 를 재려다 대조 도구의 정규화 비대칭 4종을 발견해 수정. 충돌점 3,125→1,430(따옴표 잡음 1,464→0), 각주 포함 여부로 엔진 순위가 뒤집힘을 확인. 정량 CER 도구를 별도 분리
- @pi(thinkpad) — OCR 여정은 독립 사례로 보존하고, 리포 전체의 공통 구조와 현재 경계는 memex-kb 담당자 문서 20260223T040400으로 연결했다.
- @junghan — 이거 내보내자!
- 생성 — 세 엔진(MinerU/DeepSeek-OCR/PaddleOCR-VL) 비교와 ‘모델 layer vs 도구 layer’ 구분 정리
- 네 번째 엔진 PP-StructureV3(도구) 측정 — ‘도구 대 도구’ 빈칸 채움. MinerU 도구 유일성이 깨지고, 띄어쓰기 붕괴가 한국어 린터(kime)와 만나는 지점 발견
- 다섯째 GLM-OCR 측정 — 공개 벤치마크 1등(SOTA)이 한국어 스캔책에선 꼴찌(한자 환각). ‘점수표 ≠ 내 책’ 확인
- 진짜 답 — PaddleOCR-VL을 ‘도구’로 세우니 모델+도구 동시(본문·띄어쓰기 + asset·수식·구조), 앞선 세 약점(MinerU 환각·PP 띄어쓰기·GLM 한국어) 동시 회피. 같은 모델도 모델/도구 쓰임으로 갈림. 다섯 엔진을 격리 서빙으로 올려 재현·확장 가능하게 정리
관련메타
- † #도서 #전자책 #오디오북
- † #북마크 #책갈피 #바로가기
- † #판독
- † #독서 #리딩 #읽기
- † #교재 #개념서 #기본서 #입문서 #개론서 #텍스트북
- † #노트북 #주피터 #랩톱_#코랩
- † #책꼽문 #인용 #문장수집
- † #독후감 #북리뷰 #책리뷰 #독서노트 #북로그
- † #독립 #인디 - 창작자
- † #문헌정보: #분류체계 #도서 #카테고리
- † #노트테이킹: §노션 §에버노트 §오그롬 §tidybook
관련노트
- §memex-kb 제안서 문서 변환 메타포멧
- @힣: §memex-kb 힣의 범용 #지식베이스 변환 시스템
- @힣: #힣의시작 #명상하는글쓰기 #개개인성 #평균의종말 #오디오북
- @힣: 추천은 위험하다 - 흘려듣기
- @힣: 메타 기록을 조테로에 담는 이유 - 제목 저자 요약 그리고 단어
- PDF 전자책 포멧 변환 방법
- §memex-kb 담당자 — 형식 사이에서 정본을 지키는 문서 작업장
OCR 파이프라인의 여정 — 스캔책을 귀로 듣기까지
왜 책을 잘라서 스캔했나
내가 모으는 책 다섯 권 중 번역서 한 권을 빼면, 모두 내가 존경하는 한국 노학자의 저술이다. 《물질 생명 인간》, 《최무영 교수의 물리학 강의》, 《자연철학 강의》… 평생을 한 분야에 부은 노학자의 책에는 전자책이 없다. 종이로만 남아 있다.
나는 책을 눈으로 읽기보다 귀로 듣는다. 그래서 이 책들을 듣고 싶었다. 방법은 하나뿐이었다 — 제본 업체를 찾아가 책등을 잘라내고, 낱장을 스캔해 PDF로 들여왔다. 거기서부터가 시작이다. 스캔한 PDF를 깨끗한 EPUB으로 바꾸는 일.
막다른 길에서 세 번 갈아엎다
처음엔 tesseract 한국어 OCR을 썼다. 결과는 차마 못 쓸 정도였다. 답답한 마음에 아주 무식한 방법을 택했다 — GPT에게 통째로 부탁했더니 Opus를 여러 개 병렬로 띄워, 페이지 그림을 눈으로 보고 받아 적게 했다. 놀랍게도 품질은 나쁘지 않았다. 그러나 이건 너무 어리석은(비싸고 느린) 방법이었다. 사람 대신 거대한 모델에게 한 글자씩 베껴 쓰게 한 셈이니까.
그래서 제대로 찾아보기 시작했고, 갈아엎기를 거듭했다. 깃 로그가 그 속도를 증언한다.
- 2026-05-31 — Opus vision 전사 파이프라인(scanpdf2org). 동시에 “결국 핵심은 Org→EPUB”이라고 못 박음.
- 2026-06-02 — 하루 사이에 두 번 갈아엎은 분기점. tesseract를 버리고 marker(surya OCR)를 들였다가, 같은 날 marker마저 은퇴시키고 MinerU VLM 에 정착. 이날
scanbook스킬이 태어남. - 2026-06-03 —
DeepSeek-OCR을 둘째 엔진으로. 한 엔진을 믿지 않고 여러 엔진을 같은 잣대로 재는 멀티엔진 평가가 시작됨. - 2026-06-04 — 페이지에서 잘린 문단을 도로 봉합하는 노하우와 구조 복원기를 쌓으며 다섯 권을 본격 진행.
- 2026-06-06 —
PaddleOCR-VL합류. 세 엔진을 한자리에 세움.
변하지 않는 중심: Org와 ox-epub
OCR 엔진을 세 번 갈아엎는 동안에도 파이프라인의 중심은 한 번도 바뀌지 않았다. Org 파일 이다. 엔진이 무엇이든 결국 깨끗한 .org 로 모이고, 거기서 EPUB이 나온다. 그래서 Org→EPUB 변환기인 ox-epub 패키지(개인 포크)도 이 파이프라인에 맞춰 다시 손봤다. EPUB3 네이티브 출력에 epubcheck 무결점(0/0/0).
PDF ─(MinerU)→ md + 구조 ─(mineru2org.py)→ 깨끗한 .org ─(ox-epub)→ EPUB
엔진은 갈아끼우는 부품이고, Org가 골격이다. 이 구분이 다음 깨달음의 씨앗이었다.
세 엔진을 같은 책으로 재다
《물리학 강의》 5강 열일곱 쪽을, 세 엔진에 똑같이 먹였다. 후처리(교정·구조 복원)는 어차피 우리가 결정론적으로 소유하니, 엔진은 우리가 못 고치는 부분 — 날것의 글자·이미지·수식 충실도 로만 줄 세웠다.
| 항목 | MinerU | DeepSeek-OCR | PaddleOCR-VL |
|---|---|---|---|
| 인명 ‘톰슨’ | ✗ 톈슨 + 환각 | ✓ 정확 5/5 | ✗ 네 갈래로 흩어짐 |
| 인명 ‘돌턴’ | ✓ 정확 | ✗ ‘돌탄’ 4/5 | ✓ 정확 |
| ’슈뢰딩거’ | ✓ | ✓ | ✗ ‘슈퇴당거’ |
| 수식(LaTeX) | ✓ | ✓ | ✗ 평문화 |
| 이미지 픽셀 | ✓ 유일(크롭 추출) | ✗ 위치만 | ✗ 없음 |
| 구조 라벨 | ✓ 가장 풍부 | △ 일부 | ✗ 본문에 섞임 |
| 본문 가독성 | △ 환각 잦음 | ✓ 깨끗 | ✓ 깨끗 |
점수표만 보면 “어느 게 1등이냐”를 묻고 싶어진다. 그런데 표를 한참 보다가, 더 중요한 건 등수가 아니라 분류 라는 걸 깨달았다.
깨달음: OCR 엔진은 ‘모델’과 ‘도구’ 두 겹이다
엔진은 한 덩어리가 아니라 두 겹이다.
- 모델 — 페이지 그림을 글자로 바꾸는 인식기. MinerU2.5-VLM, DeepSeek-OCR, PaddleOCR-VL이 여기서 경쟁한다.
- 도구 — 레이아웃을 잡고, 그림을 픽셀째 오려내고, 수식을 LaTeX로 인식하고, 각주·쪽번호·캡션을 구조 라벨로 분리하는 파이프라인.
이 구분으로 보면 오래 품었던 질문 — “MinerU가 의미가 있나?” — 의 답이 갈린다. 모델로서 MinerU는 약하다. 흔한 단어도 깨먹고, 없는 글자(mosaic, 한자 焮)를 지어내는 환각까지 있다. 그런데 도구로서 MinerU는 압도적이다. 그림을 실제 이미지로 오려 주고, 수식을 LaTeX로 주고, 문서 구조를 라벨로 분리해 주는 건 순수 OCR 모델이 원천적으로 못 하는 일이다.
그래서 결론은 “모델 직결이 낫다”가 아니다. 그림과 수식이 많은 책에서는 모델만으로는 픽셀도 수식도 못 얻는다. MinerU를 도구(골격)로 두고, 글자가 더 정확한 모델을 교정용 오라클로 덧대는 조합 이 맞다.
뜻밖의 선물: 엔진 불일치가 곧 오독 탐지기
세 엔진을 나란히 놓으니 묘한 규칙이 보였다. 서로 다른 단어에서 깨진다. 톰슨은 MinerU·PaddleOCR이 틀리고 DeepSeek이 맞혔다. 돌턴은 DeepSeek이 틀리고 둘이 맞혔다. 슈뢰딩거는 PaddleOCR만 틀렸다.
뒤집으면 — 세 엔진이 서로 다르게 읽은 지점이 곧 OCR 오독 의심점 이다. 지금까지 사람이 책을 정독하며 손으로 찾던 인명·고유어 오독을, 세 엔진의 다수결로 기계가 후보를 던질 수 있다는 뜻이다. 한 엔진이 같은 실수를 반복하면 못 잡지만, 엔진 사이의 불일치 는 그 자체로 신호가 된다.
빈칸을 채우다 — 도구는 둘이 되었다
마지막 빈칸은 “도구 대 도구”였다. 앞의 비교는 모델끼리였지, PaddleOCR의 도구 레이어인 PP-StructureV3 는 아직이었다. 같은 날 GPU 노드에 그것을 올려, 같은 열일곱 쪽을 먹였다.
답은 분명했다. PP-StructureV3도 진짜 도구였다. 이미지를 아홉 장 그대로 오려내고(MinerU와 같은 수), 수식을 인라인까지 LaTeX로 잡고, 레이아웃·캡션·쪽번호를 구조 라벨로 분리했다. MinerU 도구의 유일성은 깨졌다 — 이제 골격을 맡길 도구가 둘이다.
다만 공짜가 아니었다. PP-StructureV3의 한국어 글자 모델은 띄어쓰기를 뭉갰다. 공백 비율 0.12(정상은 0.21 안팎)로, 단어가 죄다 붙어 나왔다. 그런데 이 약점이 뜻밖의 자리로 이어졌다. 띄어쓰기를 도로 세우는 일은 형태소 분석의 몫이고, 나는 이미 그걸 하려고 한국어 린터(textlint + 형태소 분석 kime)를 짓고 있었다. 스캔책 파이프라인과 한국어 린터, 따로 가던 두 작업이 여기서 만났다.
마지막으로, 네 엔진이 인명 ‘톰슨’을 저마다 다르게 — 톈슨, 톰슨, 톱슨, 통슨 — 읽었다. 네 개의 서로 다른 오답은 곧 네 배 더 또렷한 신호다. 어느 엔진도 혼자선 못 잡는 오독을, 넷의 불일치가 가리킨다.
SOTA의 역설 — 1등이 내 책에선 꼴찌
하나가 더 남아 있었다. GLM-OCR. 문서 인식 공개 벤치마크에서 다섯 중 1등, 그러니까 세계 최고로 알려진 모델이다. 당연히 기대했다.
그런데 같은 열일곱 쪽을 먹이자, 한국어가 무너졌다. 톰슨은 ‘통속’이, 데카르트는 ‘대칭르트’가, 볼츠만은 ‘불초만’이 되었다. 한국어 글자를 닮은 중국 한자를 지어내기까지 했다(磊, 嚣, 螽). 띄어쓰기만은 멀쩡했지만, 글자가 그 모양이니 소용없다. 다섯 중 꼴찌였다.
교훈은 분명하다. 점수표는 내 책을 모른다. 영어와 표·그림 위주로 매긴 벤치마크의 1등이, 노학자의 한국어 산문 앞에서는 가장 약했다. 그래서 나는 매번 “한 챕터를 직접 재 보고 정한다”는 규칙을 고집한다. 예측은 번번이 틀렸고, 측정만이 맞았다.
진짜 답 — 같은 모델도 ‘모델’로 쓸 때와 ‘도구’로 쓸 때가 다르다
다섯 엔진을 줄 세우고 났을 때 한 가지가 걸렸다. PaddleOCR-VL은 이미 ‘모델’로 재 봤다. 본문은 깨끗했지만 그림도 수식도 주지 않았다. 그런데 바로 그 PaddleOCR-VL을 도구로 — 레이아웃 파이프라인의 글자 엔진으로 — 끼워 다시 세울 수 있었다.
같은 모델, 다른 자리. 결과가 갈렸다. 이번엔 본문을 제대로 읽고 띄어쓰기를 지키면서도, 그림을 픽셀째 오려내고 수식을 LaTeX로, 각주·소제목을 구조로 분리했다. 앞서 본 약점들 — MinerU의 환각, PP-StructureV3의 뭉개진 띄어쓰기, GLM의 무너진 한국어 — 을 한꺼번에 비켜갔다.
그러니까 “어느 엔진이 최고냐”라는 질문 자체가 반쪽이었다. 같은 엔진도 어떻게 세우느냐 가 절반을 결정했다. 오늘의 답은 PaddleOCR-VL을 도구로 세우는 것이다. 느린 게 흠이고, 인명 같은 고유어는 여전히 다른 엔진의 눈을 빌려 고쳐야 한다. 그래도 골격으로 삼을 단 하나를 꼽으라면, 오늘은 이것이다.
그리고 이 ‘오늘의 답’은 박제가 아니다. 다섯 엔진을 각자 격리된 자리에 올려 두어, 언제든 같은 책으로 다시 재고 새 엔진을 더 세울 수 있게 해 두었다. 더 나은 엔진이 나오면 답은 또 바뀔 것이다. 중요한 건 답을 고정하는 게 아니라, 답을 다시 잴 수 있는 자리를 갖추는 일이었다.
맺으며
세 번 갈아엎고 다섯 엔진을 지났다. 도구는 부품이고, Org가 골격이다. 그 골격 위에서 노학자의 목소리를 귀로 듣는 날이 가까워졌다.
관련 노트
memex-kb리포의scanbook스킬과NEXT.md에 측정 원자료와 엔진별 상세가 남아 있다.
여섯째 엔진을 재려다, 자(尺)가 틀린 걸 알았다
여섯 번째 엔진으로 Upstage Document Parse(관리형 API)를 재려던 참이었다. 그런데 숫자를 내려는 자리마다 걸린 것은 숫자가 아니라 자(尺)였다.
첫 번째 헛디딤 — 범위를 잘못 잡았다
산문 표본 열일곱 쪽을 vision 전사본과 맞대려 했다. “이 표본은 저 정답지 범위 안”이라고 적어두고 넘어갔는데, 교차검토를 부탁한 다른 모델이 짚었다. 뒤쪽 다섯 쪽은 그 정답지 밖이었다. 한 쪽은 장 사이 간지라 어느 정답지에도 없고, 나머지 네 쪽은 다른 파일에 있었다.
그대로 실행했으면 엉뚱한 구간을 맞대고 그럴듯한 숫자를 냈을 것이다. 교차검토는 판정이 아니라 메꿈이다.
두 번째 헛디딤 — 자가 한쪽으로 기울어 있었다
범위를 고치고 대조를 돌리니 전권에서 충돌점이 3,125개 나왔다. 그런데 가장 큰 덩어리를 열어보니 이상했다. Upstage 쪽 최대 블록은 라그랑지안과 해밀토니안을 설명하는 각주 132자였는데, 그건 Upstage가 맞게 읽은 문장이었다. 정답지 쪽에서 사라져서 오류가 된 것이다.
우리 대조 도구가 org 각주 정의줄을 통째로 지우고 있었다. 정답지는 org이고 엔진 산출물은 마크다운이다. 한쪽 포맷에서만 사라지는 내용이 있으면 그 차이가 고스란히 엔진의 잘못으로 계상된다. 이 책 정답지에서만 각주 23줄 2,790자, 전체의 2.55%가 한쪽에서만 증발하고 있었다.
즉 적어도 이 계측 방식에서는, 이 자가 각주를 잘 잡는 엔진일수록 손해를 보게 만들어져 있었다. 비대칭은 넷이었다.
- org 각주 정의줄을 줄째로 삭제 → 각주를 담은 엔진이 벌점
- 곡선 따옴표가 정규화 목록에서 빠짐 → 인용부호가 전부 충돌로 계상
- 마크다운 제목은 줄째로 지우면서 org 제목은 기호만 떼어냄 → 제목 글자가 한쪽에만 남음
- 이미지 참조의 해시 파일명과 HTML 표 태그가 본문 글자로 계상
특히 두 번째는 소스를 열어보니 어이없었다. 곡선 따옴표 네 개를 적어둔 자리에 작은따옴표가 세 번 중복돼 있었다. 리터럴로 쓴 문자가 어느 시점에 ASCII로 눌린 것이다. 조용히 빠져서 오래 남았다. 고치면서 이스케이프 표기로 못박아 두었다.
넷을 고치니 충돌점은 3,125개에서 1,430개로 줄었다. 그중 따옴표 잡음만 1,464개가 사라졌다 — 절반 가까이가 내용이 아니라 계측 잡음이었던 셈이다.
재미있는 건 정렬 유사도는 오히려 내려갔다는 점이다. 가짜 일치를 만들어 주던 삭제를 멈췄으니 당연하다. 숫자가 좋아 보이는 것과 정직해지는 것은 다른 방향일 수 있다.
순위가 뒤집힌다
자를 고치고 다시 재니, 두 엔진의 순위가 무엇을 재느냐에 따라 뒤집혔다.
- 본문에 각주를 포함해서 재면: Upstage 4.127%, MinerU 4.280%
- 각주를 빼고 본문만 재면: MinerU 2.869%, Upstage 5.661%
뒤쪽 축은 공정해 보이지만 아니다. 정답지에서 각주를 빼도 엔진 산출물은 각주를 본문에 섞어 담는다. 그래서 각주를 빼고 재는 축은, 엔진 산출물에서 각주를 같은 방식으로 분리해내지 못하는 한, 각주를 놓친 엔진을 상대적으로 유리하게 만든다. 실제로 MinerU가 그랬다. 각주를 빼고 재는 축 자체가 틀린 게 아니라, 양쪽에서 같은 기준으로 빼내지 못한 것이 문제다.
하나의 숫자로 합쳤으면 둘 중 아무 이야기나 만들 수 있었다. 축을 밝히지 않은 순위는 순위가 아니다.
두 엔진은 정반대로 실패한다
가장 큰 어긋남을 열어보니 두 엔진의 실패가 서로 다른 종류였다.
MinerU는 빠뜨린다. 라그랑지안 각주 114자, 칸트의 경험 조건을 나열한 대목 86자, 직관의 공리들을 세는 항목 39자. 상위 세 개가 전부 통째 누락이었다.
Upstage는 지어낸다. 칸트의 도식이 있어야 할 자리에 |0.08|0.28|0.47| 같은 표를 만들어 넣었다. 원문 어디에도 없는 수치다. 차트 인식 기능이 도식을 그래프로 착각한 것이다.
다만 조건을 밝혀 둔다. 이건 chart_recognition 이 켜진 상태에서의 실패다. 그 옵션은 기본으로 켜져 있고, 도식이 실린 문서라면 꺼서 다시 재야 한다. 그러니 이 결함은 글자 인식 전체의 문제가 아니라 차트 인식 경로의 실패 로 적어 둔다.
교정 비용의 관점에서 이 둘은 전혀 다른 문제다. 누락은 견줘 보면 드러나지만, 그럴듯한 숫자로 채워진 환각은 읽는 사람이 의심하지 않으면 그대로 통과한다. 글자를 잘 읽는 엔진이라도 생성형 구조 인식 경로가 켜져 있으면 더 위험한 실패를 만들 수 있다는 뜻이다.
정답지도 정답이 아니다
한 가지 더 남는다. 우리가 정답지라 부르는 것은 예전에 vision 모델로 전사한 결과물이다. 그것도 틀린다. 정답지가 틀린 자리에서는 맞게 읽은 엔진이 벌점을 받는다.
그래서 이건 “절대 정확도”가 아니라 “저 전사본을 기준으로 삼았을 때의 차이”라고 불러야 한다. 순환을 끊으려면 어긋난 지점을 사람이 페이지 이미지로 되짚어 판정하고, 정답지가 틀린 자리는 정답지를 고쳐야 한다. 그렇게 판정을 마친 구간만 정답지라는 이름을 가질 자격이 있다.
덧 — 채널을 올리면 박아둔 경로가 끊긴다
같은 날 하나 더 걸렸다. 원격 파싱 서버가 죽은 줄 알고 되살렸는데, 정작 막고 있던 것은 로컬이었다. 이틀 전 시스템 패키지 채널을 올리면서 파이썬 버전 고정을 풀었고, 옛 파이썬이 정리되면서 가상환경이 가리키던 경로가 끊겨 있었다. 서버를 살려도 파싱은 못 하는 상태였다.
버전을 고정하지 않는 판단은 옳았다. 다만 **고정된 경로를 박아둔 산출물은 채널을 올릴 때 함께 끊긴다**는 것을 계산에 넣지 않았다. 상한선 하나를 풀고 다시 지으니 끝났지만, 같은 방식으로 만들어 둔 다른 환경들도 같은 상태일 것이다.
오늘 남은 것
측정을 하려다 측정기를 고쳤다. 순서가 뒤집힌 것 같지만 아니다. 기울어진 자로 낸 숫자는 많을수록 해롭다. 엔진을 더 세우는 것보다 자를 바로 세우는 편이 먼저였다.
그리고 다시 확인했다. 같은 문제를 옆에서 다시 읽는 눈이 있을 때, 범위 오류도 계측 오류도 훨씬 빨리 드러난다. 오늘은 두 번 다 그랬다.
Comments