543 lines
14 KiB
Markdown
543 lines
14 KiB
Markdown
# 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/
|
||
│ └── <相对目录>/<PDF名>/
|
||
│ ├── 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/<PDF名>/`。这些旧结果不会自动删除;新任务统一写入 `outputs/pdfs/<PDF名>/`,确认不再需要后可手动迁移或清理。
|
||
|
||
目录任务再次运行时:
|
||
|
||
- 已存在的 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/<PDF名>/
|
||
├── 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/<运行时间>/<case-id>/
|
||
```
|
||
|
||
### 预设矩阵
|
||
|
||
```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 实机推理:尚未验证
|