Go to file
kuuhaku 2988521c41 refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
benchmarks refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
cpu refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
data refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
gpu refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
logs/legacy refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
ocr_app refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
tests refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
.gitignore refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
README.md refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00
ocr.py refactor:调整代码结构,将cpu模式收纳进子目录,统一入口,根据文件后缀和目录自动判断使用路由(pdf、图片和批量) 2026-07-16 16:20:55 +08:00

README.md

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 机器上安装和测试。

目录结构

ocr-VL1.6/
├── ocr.py                      # 唯一用户入口,自动选择 CPU/GPU 子环境
├── ocr_app/                    # CPU/GPU 共享业务代码
│   ├── cli.py                  # image/batch/pdf/verify 命令
│   ├── commands.py             # 命令实现
│   ├── runtime.py              # 设备验证、模型延迟加载
│   ├── pdf.py                  # 混合 PDF、断点续传、导出
│   ├── pdf_text.py             # 文本层提取与质量评估
│   └── 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/.venvgpu/.venv

安装

CPU

uv sync --project cpu

GPU

根据目标机器 CUDA/驱动和 PaddlePaddle 官方兼容表选择 Wheel

python gpu/setup_env.py --cuda cu118 --dry-run
python gpu/setup_env.py --cuda cu118

也支持:

python gpu/setup_env.py --cuda cu126

安装成功后脚本生成 gpu/.gpu-ready。统一入口只会调用已安装的 gpu/.venv,不会从默认 PyPI 误装 GPU 包,也不会回退到 CPU。

简化统一入口

主用法只有一种:

python ocr.py <文件或目录> --device cpu|gpu

程序自动判断输入类型:

输入 自动路由
.png/.jpg/.jpeg/.bmp/.tif/.tiff/.webp 图片 OCR + Benchmark
.pdf PDF 混合文本提取/OCR
目录 发现其中的图片和 PDF逐个调用同一个单文件路由器

不支持的文件后缀会给出明确错误;目录模式会自动忽略不支持的文件。

验证环境

python ocr.py verify --device cpu
python ocr.py verify --device gpu

单张图片

python ocr.py data/images/手写01.png --device cpu

多轮 Benchmark

python ocr.py data/images/手写01.png \
  --device cpu \
  --warmup 1 \
  --rounds 3

图片识别结果和 Benchmark 默认一起写入:

outputs/images/<图片名_扩展名>/

单个 PDF

python ocr.py data/documents/sample.pdf --device cpu

PDF 默认使用混合模式。强制模式:

python ocr.py sample.pdf --pdf-mode text --device cpu
python ocr.py sample.pdf --pdf-mode ocr --device cpu

--mode 仍作为 --pdf-mode 的简写别名保留。

批量目录

# 扫描当前目录层级中的图片和 PDF
python ocr.py data/ --device cpu

# 递归扫描所有子目录
python ocr.py data/ --recursive --device cpu

目录模式的底层就是重复调用同一个单文件路由器,因此:

  • 图片使用与单图片相同的 Benchmark 和日志逻辑
  • PDF 使用与单 PDF 相同的混合路由、断点和导出逻辑
  • 所有文件共享同一个延迟加载模型实例
  • 电子 PDF 不会触发模型加载;首张图片或首个 OCR 页面才加载模型
  • 保持串行处理,避免此前多进程造成卡顿、无响应和黑屏

递归目录的图片和 PDF 输出都会保留相对目录结构,避免不同子目录下同名文件相互覆盖。

统一输出目录

图片和 PDF 现在都会写入 --output 指定目录,默认是 outputs/

outputs/
├── images/
│   └── <相对目录>/<图片名_扩展名>/
│       ├── result.md          # Markdown 结果
│       ├── result.txt         # 纯文本结果
│       ├── result.json        # PaddleOCR 结构化结果
│       ├── benchmark.json     # 模型/推理/导出耗时
│       └── assets/            # 识别结果中的图片资源(如有)
├── pdfs/
│   └── <相对目录>/<PDF名>/
│       ├── manifest.json
│       ├── document.md
│       ├── document.json
│       └── pages/
└── batches/
    └── <目录名>-<时间戳>.json  # 批量任务汇总

例如:

python ocr.py data/images/名片01.jpg --device cpu

会生成:

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.pngsame.jpg 相互覆盖。

重构前生成的 PDF 结果可能仍直接位于 outputs/<PDF名>/。这些旧结果不会自动删除;新任务统一写入 outputs/pdfs/<PDF名>/,确认不再需要后可手动迁移或清理。

目录任务再次运行时:

  • 已存在的 PDF manifest 自动断点续传
  • 新加入的 PDF 自动创建新任务
  • 图片重新识别,并使用原子写入覆盖对应输出文件
  • --overwrite 会强制 PDF 重新处理
  • 每次目录任务都会生成新的 outputs/batches/*.json 汇总

旧命令前缀 imagepdfbatch 暂时兼容,例如 python ocr.py image a.png,但推荐直接传入路径。

PDF 混合模式

默认hybrid

python ocr.py data/documents/sample.pdf --device cpu

每页流程:

读取 PDF 文本层
        ↓
文本质量评估
   ┌────┴────┐
   │         │
有效文本    无效/不足
   │         │
直接保存    渲染页面 → PaddleOCR-VL
   └────┬────┘
        ↓
逐页 Markdown/JSON + 合并文档

如果整份 PDF 都具有有效文本层,模型完全不会加载。实测项目中的电子合同 PDF单页混合处理约 0.06s,且 model_used=false

强制文本模式

python ocr.py data/documents/sample.pdf \
  --device cpu \
  --pdf-mode text

所有页面只提取 PDF 文本层,不执行 OCR。扫描页可能得到空文本。

强制 OCR 模式

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_characterslow_content_ratiohigh_replacement_ratio 自动回退 OCR。

每页的判定结果会写入日志和 manifest

{
  "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 页码与恢复

# 第 15 页、第 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 输出

outputs/pdfs/<PDF名>/
├── manifest.json
├── document.md
├── document.json
├── pages/
│   ├── page-0001.md
│   ├── page-0001.json
│   └── ...
├── assets/
└── rendered/          # 仅 --keep-rendered 时存在

合并 Markdown 标题会标明页面来源:

## Page 1 (text)
## Page 2 (ocr)

Manifest 汇总包括:

{
  "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 常用参数

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
普通打印文字 120144
小字号 150200
手写/低质量扫描件 200250

DPI 只影响需要 OCR 的页面,不影响直接提取文本层的页面。

日志

统一日志格式:

2026-07-16 15:07:45 | INFO | pid=33552 | PAGE_ROUTED page=1 source=text reason=usable_text_layer

默认目录:

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 控制台即使显示乱码,日志文件中的中文仍正常。

测试

uv run --project cpu pytest -q

当前测试覆盖:

  • 页码范围解析
  • 文本标准化
  • 有效文本层判定
  • 空文本/短文本自动回退条件
  • 根入口设备解析

当前结果:

17 passed

已验证状态

  • CPU verify:通过
  • 单图统一入口:假模型端到端通过,原真实模型能力此前已验证
  • 批量统一入口:假模型端到端通过
  • PDF hybrid 电子文本页:真实 PDF 验证通过,未加载模型
  • PDF 扫描页自动回退 OCR假模型端到端通过
  • PDF text / ocr 强制模式:通过
  • GPU 环境路由:无环境时安全提示,不回退 CPU
  • GPU 实机推理:尚未验证