# PaddleOCR-VL-1.6 本地 OCR 本项目使用 PaddleOCR-VL-1.6 实现统一的图片 OCR、批量图片 OCR 和 PDF 识别,CPU/GPU 环境完全隔离,并通过根目录唯一入口 `ocr.py` 调用。 PDF 默认使用 **文本提取 + OCR 混合模式**:优先提取 PDF 原始文本层,仅当页面没有有效文本层或文本质量不足时才加载 PaddleOCR-VL 并 OCR。 > 当前开发机器只有集成显卡。CPU 功能已验证;GPU 代码已实现,但必须在 NVIDIA CUDA GPU 机器上安装和测试。 ## 目录结构 ```text ocr-VL1.6/ ├── ocr.py # 唯一用户入口,自动选择 CPU/GPU 子环境 ├── benchmark.py # data 参数矩阵测试与 Markdown 报告 ├── ocr_app/ # CPU/GPU 共享业务代码 │ ├── cli.py # image/batch/pdf/verify 命令 │ ├── commands.py # 命令实现 │ ├── runtime.py # 设备验证、模型延迟加载 │ ├── pdf.py # 混合 PDF、断点续传、导出 │ ├── pdf_text.py # 文本层提取与质量评估 │ ├── output.py # 图片/PDF/批次统一输出路径和保存 │ └── logging_utils.py # UTF-8 结构化日志 ├── cpu/ │ ├── pyproject.toml # CPU 独立依赖 │ ├── uv.lock # CPU 独立锁文件 │ ├── .python-version # Python 3.13 │ └── runner.py # 统一入口的 CPU 执行器 ├── gpu/ │ ├── pyproject.toml # GPU 独立依赖 │ ├── .python-version # Python 3.11 │ ├── setup_env.py # CUDA Wheel 安装脚本 │ └── runner.py # 统一入口的 GPU 执行器 ├── data/ │ ├── images/ # 测试图片 │ └── documents/ # 测试 PDF ├── outputs/ # PDF 输出(git ignored) ├── benchmarks/ # 单图 Benchmark JSON ├── logs/ # UTF-8 运行日志 └── tests/ # 共享逻辑测试 ``` 根目录不再保存 Paddle 虚拟环境或 Python 项目依赖。CPU 与 GPU 分别使用 `cpu/.venv` 和 `gpu/.venv`。 ## 安装 ### CPU ```bash uv sync --project cpu ``` ### GPU 根据目标机器 CUDA/驱动和 PaddlePaddle 官方兼容表选择 Wheel: ```bash python gpu/setup_env.py --cuda cu118 --dry-run python gpu/setup_env.py --cuda cu118 ``` 也支持: ```bash python gpu/setup_env.py --cuda cu126 ``` 安装成功后脚本生成 `gpu/.gpu-ready`。统一入口只会调用已安装的 `gpu/.venv`,不会从默认 PyPI 误装 GPU 包,也不会回退到 CPU。 ## 简化统一入口 主用法只有一种: ```bash python ocr.py <文件或目录> --device cpu|gpu ``` 程序自动判断输入类型: | 输入 | 自动路由 | |------|----------| | `.png/.jpg/.jpeg/.bmp/.tif/.tiff/.webp` | 图片 OCR + Benchmark | | `.pdf` | PDF 混合文本提取/OCR | | 目录 | 发现其中的图片和 PDF,逐个调用同一个单文件路由器 | 不支持的文件后缀会给出明确错误;目录模式会自动忽略不支持的文件。 ### 验证环境 ```bash python ocr.py verify --device cpu python ocr.py verify --device gpu ``` ### 单张图片 ```bash python ocr.py data/images/手写01.png --device cpu ``` 多轮 Benchmark: ```bash python ocr.py data/images/手写01.png \ --device cpu \ --warmup 1 \ --rounds 3 ``` 图片识别结果和 Benchmark 默认一起写入: ```text outputs/images/<图片名_扩展名>/ ``` ### 单个 PDF ```bash python ocr.py data/documents/sample.pdf --device cpu ``` PDF 默认使用混合模式。强制模式: ```bash python ocr.py sample.pdf --pdf-mode text --device cpu python ocr.py sample.pdf --pdf-mode ocr --device cpu ``` `--mode` 仍作为 `--pdf-mode` 的简写别名保留。 ### 批量目录 ```bash # 扫描当前目录层级中的图片和 PDF python ocr.py data/ --device cpu # 递归扫描所有子目录 python ocr.py data/ --recursive --device cpu ``` 目录模式的底层就是重复调用同一个单文件路由器,因此: - 图片使用与单图片相同的 Benchmark 和日志逻辑 - PDF 使用与单 PDF 相同的混合路由、断点和导出逻辑 - 所有文件共享同一个延迟加载模型实例 - 电子 PDF 不会触发模型加载;首张图片或首个 OCR 页面才加载模型 - 保持串行处理,避免此前多进程造成卡顿、无响应和黑屏 递归目录的图片和 PDF 输出都会保留相对目录结构,避免不同子目录下同名文件相互覆盖。 ## 统一输出目录 图片和 PDF 现在都会写入 `--output` 指定目录,默认是 `outputs/`: ```text outputs/ ├── images/ │ └── <相对目录>/<图片名_扩展名>/ │ ├── result.md # Markdown 结果 │ ├── result.txt # 纯文本结果 │ ├── result.json # PaddleOCR 结构化结果 │ ├── benchmark.json # 模型/推理/导出耗时 │ └── assets/ # 识别结果中的图片资源(如有) ├── pdfs/ │ └── <相对目录>// │ ├── manifest.json │ ├── document.md │ ├── document.json │ └── pages/ └── batches/ └── <目录名>-<时间戳>.json # 批量任务汇总 ``` 例如: ```bash python ocr.py data/images/名片01.jpg --device cpu ``` 会生成: ```text outputs/images/名片01_jpg/result.md outputs/images/名片01_jpg/result.txt outputs/images/名片01_jpg/result.json outputs/images/名片01_jpg/benchmark.json ``` 图片目录名包含扩展名,避免 `same.png` 与 `same.jpg` 相互覆盖。 重构前生成的 PDF 结果可能仍直接位于 `outputs//`。这些旧结果不会自动删除;新任务统一写入 `outputs/pdfs//`,确认不再需要后可手动迁移或清理。 目录任务再次运行时: - 已存在的 PDF manifest 自动断点续传 - 新加入的 PDF 自动创建新任务 - 图片重新识别,并使用原子写入覆盖对应输出文件 - `--overwrite` 会强制 PDF 重新处理 - 每次目录任务都会生成新的 `outputs/batches/*.json` 汇总 旧命令前缀 `image`、`pdf`、`batch` 暂时兼容,例如 `python ocr.py image a.png`,但推荐直接传入路径。 ## PDF 混合模式 ### 默认:hybrid ```bash python ocr.py data/documents/sample.pdf --device cpu ``` 每页流程: ```text 读取 PDF 文本层 ↓ 文本质量评估 ┌────┴────┐ │ │ 有效文本 无效/不足 │ │ 直接保存 渲染页面 → PaddleOCR-VL └────┬────┘ ↓ 逐页 Markdown/JSON + 合并文档 ``` 如果整份 PDF 都具有有效文本层,模型完全不会加载。实测项目中的电子合同 PDF,单页混合处理约 `0.06s`,且 `model_used=false`。 ### 强制文本模式 ```bash python ocr.py data/documents/sample.pdf \ --device cpu \ --pdf-mode text ``` 所有页面只提取 PDF 文本层,不执行 OCR。扫描页可能得到空文本。 ### 强制 OCR 模式 ```bash python ocr.py data/documents/sample.pdf \ --device cpu \ --pdf-mode ocr ``` 所有页面都渲染后交给 PaddleOCR-VL,适合模型一致性 Benchmark。 ## 文本层质量判定 混合模式默认要求: | 参数 | 默认值 | 含义 | |------|-------:|------| | `--text-min-chars` | 50 | 非空白字符最小数量 | | `--text-min-printable-ratio` | 0.85 | 可打印字符最低比例 | | `--text-min-content-ratio` | 0.60 | 字母、数字、CJK 字符最低比例 | | `--text-max-replacement-ratio` | 0.02 | Unicode 替换字符最高比例 | | `--text-min-density` | 25 | 页面文本密度最低值 | 例如某些扫描 PDF 只有页码或隐藏乱码层,混合模式会因为 `too_few_characters`、`low_content_ratio` 或 `high_replacement_ratio` 自动回退 OCR。 每页的判定结果会写入日志和 manifest: ```json { "source_type": "ocr", "routing_reason": "too_few_characters", "text_layer": { "usable": false, "non_whitespace_chars": 8, "printable_ratio": 1.0, "content_ratio": 0.75 } } ``` ## PDF 页码与恢复 ```bash # 第 1~5 页、第 8 页、第 10 页到末页 python ocr.py sample.pdf --pages "1-5,8,10-" --device cpu # 中断后继续 python ocr.py sample.pdf --resume --device cpu # 删除旧输出并重做 python ocr.py sample.pdf --overwrite --device cpu ``` `--resume` 会校验: - PDF SHA-256 - PDF 处理模式 - DPI - 文本层判定阈值 - manifest 版本 旧纯 OCR manifest(版本 1)不能直接用于新混合模式,请使用 `--overwrite` 重新生成。 ## PDF 输出 ```text outputs/pdfs// ├── manifest.json ├── document.md ├── document.json ├── pages/ │ ├── page-0001.md │ ├── page-0001.json │ └── ... ├── assets/ └── rendered/ # 仅 --keep-rendered 时存在 ``` 合并 Markdown 标题会标明页面来源: ```markdown ## Page 1 (text) ## Page 2 (ocr) ``` Manifest 汇总包括: ```json { "text_pages": 8, "ocr_pages": 2, "model_used": true, "model_initialized_during_task": true, "timing": { "text_extract_total_seconds": 0.15, "render_total_seconds": 0.8, "ocr_total_seconds": 324.5, "model_init_seconds": 40.0, "task_total_seconds": 366.0 } } ``` ## PDF 常用参数 ```bash python ocr.py sample.pdf \ --device cpu \ --pdf-mode hybrid \ --pages "1-10" \ --dpi 144 \ --threads 18 \ --keep-rendered \ --log-file logs/pdf-sample.log ``` DPI 建议: | 文档 | DPI | |------|----:| | 普通打印文字 | 120~144 | | 小字号 | 150~200 | | 手写/低质量扫描件 | 200~250 | DPI 只影响需要 OCR 的页面,不影响直接提取文本层的页面。 ## 日志 统一日志格式: ```text 2026-07-16 15:07:45 | INFO | pid=33552 | PAGE_ROUTED page=1 source=text reason=usable_text_layer ``` 默认目录: ```text logs/input/ logs/verify/ logs/legacy/ ``` 主要事件: - `RUNTIME_PREPARED` - `MODEL_INITIALIZED` - `FILE_ROUTED` - `PAGE_ROUTED` - `PAGE_FINISHED` - `TASK_COMPLETED` - `IMAGE_COMPLETED` - `DIRECTORY_SUMMARY` - `VERIFY_COMPLETED` 日志使用 UTF-8。Windows 控制台即使显示乱码,日志文件中的中文仍正常。 ## 参数矩阵性能测试 新增 `benchmark.py`,会对 `data/` 中的图片和 PDF 按多组参数运行,统计速度并增量生成 Markdown 报告。 也可通过统一入口调用: ```bash python ocr.py benchmark --profile smoke --device cpu ``` 默认报告: ```text benchmarks/parameter-comparison.md ``` 每个用例的日志、输出和批次 manifest 保存在: ```text benchmarks/runs/<运行时间>// ``` ### 预设矩阵 ```bash # 较快:图片 token=64/128、PDF hybrid/text python ocr.py benchmark --profile smoke --device cpu # 常用对比:增加 CPU 线程、默认 token、PDF 阈值和 OCR DPI python ocr.py benchmark --profile standard --device cpu # 完整矩阵:更多线程、token 和 DPI,耗时最长 python ocr.py benchmark --profile full --device cpu ``` 真实 CPU OCR 很慢,`standard/full` 可能运行数小时。建议先运行 `smoke`,或通过 `--case` 单独运行: ```bash python ocr.py benchmark \ --profile standard \ --case image-threads-8 \ --case image-threads-safe \ --device cpu ``` 查看用例列表而不执行: ```bash python ocr.py benchmark --profile standard --list-cases ``` 仅生成计划和空报告: ```bash python ocr.py benchmark --profile standard --dry-run ``` ### 自定义参数矩阵 示例配置: ```text benchmarks/parameter-matrix.example.json ``` 运行: ```bash python ocr.py benchmark \ --config benchmarks/parameter-matrix.example.json \ --device cpu \ --report benchmarks/custom-comparison.md ``` 每个 case 支持统一入口中的参数,例如: ```json { "id": "image-threads-8", "title": "图片 8 线程", "group": "image-threads", "kind": "image", "params": { "threads": 8, "max_new_tokens": 64, "rounds": 1 } } ``` ### Markdown 报告统计 报告按参数组比较: - 系统、CPU、逻辑核心和内存信息 - 测试文件清单、类型和大小 - Wall time - 模型初始化耗时 - 图片核心推理 / PDF OCR 总耗时 - 平均每文件耗时 - 每分钟处理文件数 - 相对基准加速比 - 输出文本字符数 - 文本 SHA-256 是否一致 - PDF Text/OCR 页数 - PDF 路由原因统计 - 完整命令、日志和输出路径 报告每完成一个 case 就原子更新;按 `Ctrl+C` 中断时,已经完成的结果不会丢失。 其他参数: ```text --data PATH 测试数据目录 --report PATH Markdown 报告位置 --run-root PATH 详细运行数据目录 --timeout SEC 单个用例超时 --case ID 只执行指定用例,可重复使用 ``` ## 自动化测试 ```bash uv run --project cpu pytest -q ``` 当前测试覆盖: - 页码范围解析 - 文本标准化 - 有效文本层判定 - 空文本/短文本自动回退条件 - 根入口设备解析 当前结果: ```text 21 passed ``` ## 已验证状态 - CPU `verify`:通过 - 单图统一入口:假模型端到端通过,原真实模型能力此前已验证 - 批量统一入口:假模型端到端通过 - PDF `hybrid` 电子文本页:真实 PDF 验证通过,未加载模型 - PDF 扫描页自动回退 OCR:假模型端到端通过 - PDF `text` / `ocr` 强制模式:通过 - GPU 环境路由:无环境时安全提示,不回退 CPU - GPU 实机推理:尚未验证