Repository navigation
Local OCR
Clippy 有两条 OCR 路径。默认路径是系统 Tesseract 子进程;显式配置后启用结构化增强引擎。没有配置时不自动下载模型、不自动安装 Python。
command.args(["stdin", "stdout", "-l", "eng+chi_sim"]);用 CLI 子进程隔离 Tesseract 的 ABI,避免编译期动态链接依赖;缺失时返回友好错误,不影响应用启动。
- 显式
CLIPPY_TESSERACT_PATH - 应用同目录 / macOS
Resources中的 sidecar - 进程
PATH - 平台常见安装目录
每个候选必须通过 --version 才会被用于识别。 这条规则保证能力探测与真正执行不会指向两份不同的程序——这是本项目反复出现的一条通用原则。
探测的成功与失败都做进程内缓存;应用内安装成功、或已缓存路径启动时报 NotFound 时立即失效。
发布包不含 Tesseract。Linux 提供用户点击触发的 apt 安装,Windows/macOS 设置页只展示各自安装方式——不能调用 Linux 的提权命令。
实现在 src-tauri/src/ocr/enhanced.rs(Rust supervisor)+ src-tauri/ocr-sidecar/(Python 运行时)。
检测(PP-OCRv6 small det)
→ 原图文字框
→ 四表几何/颜色特征
→ 段落关系模型 Run
→ 段落合并
→ 原像素透视 crop
→ 识别(PP-OCRv6 small rec)
→ greedy CTC 解码
→ 阅读顺序排序
三个模型职责不同,不是「三个模型一起识字」:
| 模型 | 职责 | 输入 → 输出 |
|---|---|---|
| det | 找文字位置 |
x[N,3,H,W] → 概率图 [Nout,1,Hout,Wout]
|
| 段落关系 | 给候选框之间的关系打分 | 节点特征 + 边索引 + 两组边特征 → 每边一个 logit |
| rec | 识别字符 |
x[N,3,48,W] → 逐时间步字符概率 [N,T,C]
|
顺序是 det → 段落 → rec。 段落模型的输入里没有识别文本或 token,它只吃几何与颜色统计。它输出的是 logit,不是 0–1 概率,也不直接输出最终段落 ID——分组要靠调用侧做 union-find 与几何约束。
rec 对每个 crop 单独 Run,每次 batch=1,宽度随 crop 变化。不要把 rec 宽度写成固定值,也不要假设时间步 T 等于宽度的某个简单函数——直接读运行时 shape。
两个模型的归一化不同,写错会静默降低准确率:
det: RGB float32 NCHW,(RGB/255 - [0.485,0.456,0.406]) / [0.229,0.224,0.225]
rec: RGB float32 NCHW,RGB/255 - 0.5 ← 没有再除 0.5
其它约定:透视 crop、cubic 插值、BORDER_REPLICATE、高宽比 ≥ 1.5 时逆时针旋转 90°、等比缩放到 H=48。
0 = blank(不输出)
1..N-1 = 字典逐行顺序
最后一类 = ASCII space
两个容易错的细节:
- 字典文件末尾有换行,分割后会保留一个末尾空项,随后把最后一项置为空格。不要在已经含末尾空项的数组上再盲目 append。
- blank 会更新 prev,所以相同字符之间若存在 blank,应保留两次字符。折叠相邻重复类时不能忽略这一点。
行置信度是实际接受字符的最大概率均值,低于阈值清空整行。
段落分组之后的排序用本项目自己的 XY-cut 策略:先找列间隙,跨栏标题先按段切,再行内左到右。方向/尺度特征采用 clippy-edge-features-v1。
大图处理用 960/96 的 detail tile 与单次 1280 的 overview 协调完整行框,按高度、方向、法向中心及源框交集去掉接缝碎片。完整性核对使用扩框前的文字核心行轴——用扩框后的边界会把短词的扩框留白误判成缺字,也容易吞并临近小字与宽列间隙。
结构化引擎是子进程,所有资源都要显式设界。
python -I main.py --manifest PATH --manifest-sha256 SHA
-I 隔离模式,脚本只显式加入自身目录,忽略外部 PYTHONPATH。
stdin 是一行 ≤16 KiB 的 JSON,紧随恰好 N 字节 PNG:
{"version":1,"requestId":"example","pngBytes":12345,"deadlineMs":60000}stdout 只返回 ≤4 MiB 的 {version,requestId,result};stderr ≤64 KiB。
- 全局 1 active + 2 queued;准备/配置校验另限 3 个有界工作
- 相同 clip + 相同配置的请求合并,快照共享
Arc<Vec<u8>> - 按图片 hash + 完整运行身份复用结果
- 取得许可之后才加载数据库 BLOB——先排队再取数据,避免队列里堆满解码后的大图
| 项 | 上限 |
|---|---|
| PNG 字节 | 64 MiB |
| 像素 | 32 Mi |
| 最大边 | 16384 |
| det tile | 最多 128 个 960 tile |
| contour / 行 / 每源边 | 1000 / 512 / 15 |
| rec | H=48,W ≤ 4096,总 crop 像素 ≤ 48×4096×128,T ≤ 2048 |
超过预算明确失败,不静默截断。
统一 60 s 识别期限,覆盖启动、模型加载、I/O、推理,以及剩余预算内的 fallback。超时或触及输出上限后不再启动第二引擎。
子进程 kill/wait 完成后才释放并发许可;运行文件不创建后代进程。
缓存键覆盖协议版本、完整 manifest、options、预期资产 SHA,以及四个运行 Python 文件。命中缓存前核对真实资产 SHA;运行期间配置改变则拒绝写入旧身份的缓存。
显式增强配置会绕过旧的无版本字符串缓存(包括图片翻译路径)。fallback 结果不缓存为增强成功——否则一次临时失败会污染后续所有请求。
lines[]: 原始文字、逐字置信度、accepted
result.text: 只拼接 accepted 的行
pipeline.engine: "enhanced" | "tesseract"
fallbackReason: 回退原因
空白或单行图明确标记 layoutExecuted=false;多行必须有真实的段落模型 logit。几何排序不能标成一次段落模型 Run——引擎实际做了什么必须能从结果里读出来。
Tesseract 路径本身没有这些结构化框,因此它的结果不带 lines。
det / rec / 字典使用官方公开发布物(PaddlePaddle PP-OCRv6,Apache-2.0),由 setup_models.py 在显式执行时下载并完整核对 SHA-256;已有文件不符或 manifest 已存在时拒绝覆盖。
段落关系模型必须由用户提供已有的本地 ONNX 文件,工具只核对并引用,不下载、不提取、不复制、不打包。用户须自行具备使用该模型的权利。
本仓库不包含任何权重。重新分发模型时应随附适用许可证与 NOTICE;本地能加载不等于有再分发许可。
python3 -m venv /absolute/clippy-ocr/venv
/absolute/clippy-ocr/venv/bin/python -m pip install -r src-tauri/ocr-sidecar/requirements.lock
/absolute/clippy-ocr/venv/bin/python src-tauri/ocr-sidecar/setup_models.py \
--runtime /absolute/clippy-ocr/models \
--python /absolute/clippy-ocr/venv/bin/python \
--edge /absolute/existing-local/paragraph-model.onnx \
--edge-sha256 <已核对的 64 位小写 SHA256>
CLIPPY_OCR_MANIFEST=/absolute/clippy-ocr/models/manifest.json cargo tauri dev已实测 Python 3.12 / Linux x64 / CPU。这是可配置的本地运行时,尚未打包为三平台安装器;其它平台需要对应版本的可用 wheel 和绝对 Python 路径。
应用不会从 PATH 猜测运行时位置。删除环境变量即恢复纯 Tesseract。
# Python 纯逻辑回归
venv/bin/python -m unittest discover -s src-tauri/ocr-sidecar -p test_pipeline.py -v
# Rust 侧 supervisor 与故障注入
cargo test --manifest-path src-tauri/Cargo.toml --lib ocr:: -- --test-threads=2
# 真实模型集成(默认 ignored)
CLIPPY_OCR_MANIFEST=... CLIPPY_OCR_TEST_PNG=... \
cargo test --manifest-path src-tauri/Cargo.toml --lib \
configured_real_pipeline_through_rust_supervisor -- --ignored --nocapture故障注入需覆盖:超时、输入不被消费、输出超量、请求合并、进程回收。
公式单元测试不等同于模型准确率。 真实验收要另记原图文字、阅读顺序、真实多行分组、CER、耗时和 RSS,并覆盖亮底/暗底、中英文、旋转、双列、4K/8K、空图及坏模型回退。
写测试时的纪律:模型可能省略中文视觉空格,分组也可能选择 singleton。保留这些真实结果,不要插入期望文本让测试通过。
真实 Tesseract 对一张合成 18 行图,1080p / 4K / 8K 分别约 1.37 / 1.65 / 3.26 s,均正确识别 18 行。
这个数字不能外推到所有真实图片的准确率或耗时。带调试器或带大量诊断输出的运行也不能当作正常耗时基准。