feat(ocr): PP-OCRv5 ONNX Rust 네이티브 OCR 엔진 #206
Reference in New Issue
Block a user
Delete Branch "feat/rust-native-ocr"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
이미지 OCR 에 두 번째 백엔드
paddle-onnx를 추가한다. 기존ollama-vision(원격 vision LM, 이미지당 ~50초)을 default 로 유지하고,
[image.ocr] engine = "paddle-onnx"로 PP-OCRv5(검출 DBNet + 인식 CTC) ONNX 모델을
ort(=2.0.0-rc.9) 로 in-process 실행한다 —Python 런타임/원격 호출 없이 큰 페이지 CPU <4초, 완전 오프라인.
crates/kebab-parse-image/src/paddle_onnx.rs):OcrEngine두 번째 구현OnnxPaddleOcr. det/recort::Session1회 로드(Mutex<Session>), 검출 후처리(min-area rect = rotating calipers, unclip = polygon offset)는 clipper2/OpenCV 없이
pure-Rust. CTC greedy decode(blank=0 / dict / space) + per-region 실제 confidence.
[image.ocr]에det_model/rec_model/dict(override) +score_thresh/unclip_ratio/max_boxesserde-default +KEBAB_IMAGE_OCR_*env.기존 config 무수정 로드(forward-compat).
kebab-app::build_image_ocr_engine/build_pdf_ocr_engine가 engine문자열로
Box<dyn OcrEngine>생성. 파이프라인 4 site 를&dyn OcrEngine로 전환.ingest_config_signature의 image/pdf 브랜치를|ocr:1:{engine}:{engine_version}로 → engine 전환(ollama↔paddle)/모델 변경 시 영향 자산자동 재색인. paddle engine_version(blake3 3-asset)은 per-process 1회만 계산(memo).
0.26.2 → 0.27.0(minor — 신규 engine 값 + config 키).설계 문서:
docs/superpowers/specs/2026-06-04-rust-native-ocr-spec.md,docs/superpowers/plans/2026-06-04-rust-native-ocr-plan.md.deviation 로그:
tasks/HOTFIXES.md(2026-06-04 PP-OCRv5 ONNX).검증
cargo clippy --workspace --all-targets -j 8 -- -D warnings→ 0 경고.cargo test -p kebab-config -p kebab-parse-image -p kebab-app -j 8전부 통과(T7 from_config, T9 서명 (a)(b)(c), T10 dict-mismatch/decode-failure 포함).
tests/paddle_e2e.rs, synthetic 한/영 fixture): mean gate CER0.0049 ≤ 0.05 (clean_paragraph/korean_heavy/numbers_table/tech_terms = 0.0). PoC 0.024
baseline 보다 우수. 큰 페이지 3.9초 < 5초.
unclip_rect가 corner 를 centroid 방사 확장 → wide/short텍스트 박스 높이가 안 커져 글자 윗부분 잘림(ㄷ→ㄴ,
다→나, 첫 측정 CER 0.26). PaddleOCRpyclipper 처럼 edge 별 polygon offset 으로 재작성 → CER 0.005.
engine = "paddle-onnx"+provider = "none"(lexical-only) 로 이미지 2장 ingest(0 errors).
clean_paragraphOCR 결과가 ground-truth 와글자 단위 일치, per-region confidence 0.99/0.96/0.95(상수 1.0 아님), stored
parser_version에|ocr:1:paddle-onnx:ppocrv5-mobile-kor-1b55f062d055폴딩 확인.kebab search --mode lexical가 한국어("검색")·영어("embedding") 모두 FTS5 hit.자동 머지 금지 — 사용자 리뷰 후 머지.
Assisted-by: Claude Code
T7: OcrCfg gains det_model/rec_model/dict overrides + score_thresh/ unclip_ratio/max_boxes (serde default, KEBAB_IMAGE_OCR_* env). OnnxPaddleOcr::new threads them via ModelPaths::from_config. T8: build_image_ocr_engine / build_pdf_ocr_engine factories return Box<dyn OcrEngine>; match on engine string (ollama-vision|paddle-onnx|err). ImagePipeline.ocr_engine + pdf_ocr_engine signatures switched to &dyn OcrEngine. OcrEngine gains model() for the progress label. T9: ingest_config_signature image/pdf branches emit |ocr:1:{engine}:{engine_version} (memoized blake3 per asset-triple, m3-safe). Unit tests (a)(b)(c) added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>회차 1 — 핵심 로직(CTC decode / unclip edge-offset / min-area rect / 서명 cascade / 팩토리 4-site /
--configfacade 스레딩)은 정확하고 회귀 위험 낮음. 머지 가능 수준이나 아래 actionable 보강 후 진행 권장.[MEDIUM] 골든 회귀 가드 부재:
tests/golden/{ctc_rec_golden,det_boxes_clean_paragraph}.json이 어떤 테스트에서도 소비되지 않음(주석 paddle_onnx.rs:15,550 의 "golden 핀"은 실제 핀 아님). e2e CER 게이트(paddle_e2e.rs)는/build/dogfoodfixture 부재 시 skip 이라 클린 CI 에서 CTC 매핑·unclip 수학의 회귀 가드가 사실상 0. → 골든 JSON 을 deserialize 해ctc_greedy_decode/unclip_rect/box_score를 검증하는 CI 상주 단위테스트 추가(모델/ONNX 불요).[MEDIUM] PDF paddle 튜닝 비대칭:
build_pdf_ocr_engine의 paddle 경로(kebab-app/src/lib.rs:880 부근)가pdf.ocr.*대신image.ocr.*튜닝(max_pixels/score_thresh/…)을 사용. ollama 경로는pdf.ocr.*사용 → 사용자가[pdf.ocr]설정 후 paddle 전환 시 무경고로 image 설정 적용(footgun). → 문서화 또는pdf.ocr배선.[MEDIUM] DBNet threshold 명료화: 이진화 threshold 매직넘버
0.3(paddle_onnx.rs:564) 을 명명 const 로 추출 +score_thresh기본값 0.3(PaddleOCRdet_db_box_thresh기본 0.6 대비 느슨)의 의도 주석.[LOW] Mutex poison → ingest abort 위험:
self.det.lock().expect("…poisoned")(paddle_onnx.rs:346,383) 는 한 자산의 내부 panic 이 전체 ingest 를 abort 시킴(계약은 "자산 skip"). →.unwrap_or_else(|e| e.into_inner())로 복구하거나 에러로 매핑.[LOW] dead field:
DetBox.score(#[allow(dead_code)]) 미사용 → region confidence 에 결합하거나 제거.리더 독립검증: clippy(
--workspace --all-targets -D warnings) 0, 전체 테스트 green(e2e CER 평균 0.0049, 게이트 0.05).- [MEDIUM] 골든 CI 단위테스트 2건 추가: ctc_greedy_decode_golden (argmax_idx one-hot → decoded 문자열 검증), det_box_score_golden (box_score/unclip_rect golden corner 검증). 모델/ONNX 불요, CI 상주. ctc_greedy_decode를 자유 함수(ctc_greedy_decode_with_dict)로 추출하여 테스트 가능하게 함. - [MEDIUM] PDF paddle 튜닝 비대칭 문서화: build_pdf_ocr_engine에 paddle-onnx가 image.ocr.* 사용(pdf.ocr.* 아님) 이유 명시 + PdfOcrCfg.engine 필드 doc 갱신. - [MEDIUM] DBNet 이진화 매직넘버 0.3 → DET_BIN_THRESH const 추출 + score_thresh 기본값 느슨한 이유 1줄 주석. - [LOW] Mutex poison 복구: det/rec .expect("poisoned") → .unwrap_or_else(PoisonError::into_inner). 자산 panic이 ingest abort 안 되도록. - [LOW] DetBox.score dead field 제거 (box_score 결과는 필터에만 사용, 저장 불요). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>회차 2 — 회차 1 actionable 5건 모두 반영 확인(
f3a7222):① 골든 소비 CI 단위테스트 2건(
ctc_greedy_decode_golden/det_box_score_golden, ONNX/모델 불요라 클린 CI 상주) — CTC 매핑·box_score/unclip 회귀 가드 확보.② PDF paddle 튜닝 비대칭 문서화(
build_pdf_ocr_engine+PdfOcrCfg.enginedoc).③
DET_BIN_THRESHconst 추출 +score_thresh기본값 의도 주석.④ det/rec Mutex poison →
into_inner복구(자산 panic 이 ingest abort 안 함).⑤
DetBox.scoredead field + 미사용class_to_str제거.변경 3파일(+142/-46), 스코프 정합(기능 추가 없음).
리더 독립 재검증:
cargo clippy --workspace --all-targets -j 8 -- -D warnings0, 전체 워크스페이스 테스트 green(신규 골든 테스트 + e2e CER 게이트 평균 0.0049 포함). 회귀·blocker 없음. 머지에 동의합니다.