简介:Umi-OCR for linux 是一套面向 Linux 系统的光学字符识别工具包,基于深度学习模型,支持多语言文字识别,适合需要在服务器或嵌入式环境里批量提取图片文字、开展文档数字化的开发者与运维人员,也可作为后台服务嵌入现有应用。压缩包为 7z 格式,共 56 个文件,约 308MB;内容以主程序与 PaddleOCR-json 模型配置、Python 启动脚本、Dockerfile、shell 部署脚本、JSON 输出配置、说明文档、许可证为主,同时包含 26 个 sample 样本和 Git 仓库元数据(如 head、packed-refs、index 等),目录结构清晰,便于按需裁剪和二次开发。目前已有 303 人学习下载。资源内附 Linux 可嵌入版本、配套模型及运行时组件,并提供 Docker 化部署方案,读者可据此快速搭建 OCR 识别服务,理解 Linux 端 OCR 项目的完整组织方式;结合示例样本与配置文件,还能调整识别模型和输出格式,适用于多语言文档处理、票据识别等实际场景。
1. Umi-OCR for linux 到底在解决什么问题:离线 OCR 放进 Linux 工作流的第一个反直觉结论
我最早接触 Umi-OCR 是在 Windows 上处理扫描版 PDF,后来要把整套识别能力迁到 Linux 服务器上做定时归档,才发现它的 Linux 版本并不只是“换个安装包”那么简单。Umi-OCR for linux 本质上是一个把 PaddleOCR 推理链条(检测、方向分类、文字识别)封装成本地服务的方案,解决的是批量的、离线的、隐私敏感的文字抽取。它的反直觉之处在于:真正卡住你的往往不是识别精度,不是模型大小,而是 Linux 环境里的 Python 依赖、动态库缺失和模型下载超时。适合三类人:需要在无图形界面服务器上处理单据与截图的运维,做私有化文档检索的研发,以及想绕开云端 OCR 限制的自动化流程搭建者。下面这些步骤都是我实际跑过的路径,照着做能少走不少弯路。
2. 在 Linux 上安装 Umi-OCR:Python 虚拟环境、系统依赖与两种落地方式
2.1 前置准备:Python 版本、Linux 镜像源与 libGL 等系统依赖
不管你是用 Debian、Ubuntu 还是 Rocky Linux,第一步都不是下载安装包,而是把系统环境补齐。Umi-OCR for linux 的核心识别依赖是 PaddleOCR,而 PaddleOCR 的 Python 包会连带安装 OpenCV、numpy、shapely、pyclipper 等一堆库,这些库对系统动态库很敏感。我见过太多人在虚拟机里装 Linux 系统后直接 pip install,结果 import 阶段就哭爹喊娘。
先确认 Python 版本。PaddleOCR 对新版本 Python 的支持有滞后,我建议用 Python 3.8 到 3.11 之间,太高容易碰到 wheel 不匹配的坑。然后安装必要的系统包:
# Debian / Ubuntu 系 sudo apt update sudo apt install -y python3 python3-venv python3-pip unzip wget \ libgl1 libglib2.0-0 libsm6 libxext6 libxrender-dev # Rocky / CentOS 系用 dnf:包名略有差异 # sudo dnf install -y python39 python39-devel unzip wget \ # libGL mesa-libGL-devel glib2-devel这段命令的核心是libgl1和libglib2.0-0。如果你之前用的是opencv-python-headless而不是完整版,可能不需要它们,但 PaddleOCR 依赖链里会引入opencv-python,缺了这两个库就会在启动时报libGL.so.1: cannot open shared object file。如果你用的是纯内网 Linux 镜像环境,装完系统依赖后记得把 pip 源指向国内镜像,这样后续安装 Python 包才不会等在下载上。
接下来创建虚拟环境。我一般会在/opt/ocr这类固定目录做,方便后面做服务脚本和软链接:
mkdir -p /opt/ocr/app cd /opt/ocr python3 -m venv ocr_env source ocr_env/bin/activate pip install --upgrade pip pip install paddlepaddle paddleocr这里有几个参数说明。/opt/ocr/ocr_env是虚拟环境目录,激活后所有包都装在这里,不会污染系统 Python;后续如果要给多个用户使用,直接让它们 source 同一个 venv 即可。paddlepaddle是 CPU 版推理后端,如果后面想用 GPU 再换成paddlepaddle-gpu,但安装时机建议放在最后,因为 GPU 版对 CUDA 版本有硬匹配。最后可以顺手看一眼版本:
python -c "import paddle, paddleocr; print(paddle.__version__, paddleocr.__version__)"这一步能确认核心依赖是否完整。如果这里已经抛错,先回头查缺了哪个动态库,不要急着继续。
2.2 两种落地方式:保留图形界面调用与纯命令行接口
在 Linux 上跑 Umi-OCR 其实有两条路,取决于你手里是不是有一台带桌面的机器。如果你只是在一台普通 Linux 工作站上想手动识别一两张图,保留 Umi-OCR 的图形界面是最省事的。常见做法是拉取源码后安装依赖再启动,注意它依赖 Qt 环境,启动方式大致是:
cd /opt/ocr/Umi-OCR source ../ocr_env/bin/activate pip install -r requirements.txt python main.py这种方式适合有显示器的本地机器。但如果你的目标是服务器定时识别、把结果丢进数据库,那就不要碰图形界面。更可靠的落地方式是直接用 PaddleOCR 的 Python API,把 Umi-OCR 当成一个模型和参数的组织者,自己写命令行入口。两种方式本质是同一个识别后端,只是调用形态不同。
我自己的经验是:先在图形界面里调通一遍,识别出满意效果后,再写一份纯命令行脚本。这样既能利用 UI 快速验证模型质量,又能把核心识别能力沉淀成可复用的脚本。你不需要两个都长期维护,但前期用 UI 排查识别问题比每次改脚本快得多。
3. 跑通第一次本地 OCR:det/rec/cls 模型下载与识别参数的验证
3.1 最小识别脚本与三个模型的作用:det、rec、angle cls
第一次跑 PaddleOCR 时,很多人以为它只有一个模型。实际上默认的ch模型由三段组成:det负责检测文本行位置,rec负责把检测到的文本行切成文字,cls负责判断文字方向(比如旋转 180 度的版面)。这三者独立下载、独立加载,任何一个环节出问题,输出都是黑匣子。
我给第一次跑通准备的最小脚本如下:
# test_ocr.py from paddleocr import PaddleOCR ocr = PaddleOCR( use_angle_cls=True, # 启用方向分类 lang='ch', # 中英文识别 det_limit_side_len=960, rec_batch_num=16, cpu_threads=4, ) result = ocr.ocr('sample.png', cls=True) for page in result: if not page: continue for box, (text, score) in page: print(f"{score:.3f}\t{text}")这里几个初始化参数值得说清楚。use_angle_cls=True在遇到竖排或旋转 180 度的文本时非常重要,关闭它会导致整行文字变成乱码,这一点后面踩坑部分还会细说。det_limit_side_len控制送入检测模型的图像最大边长,默认 960,对 4K 截图可以调到 1280 或 1536,换来的是更慢的速度和更高的召回。rec_batch_num表示每批识别多少文本行,16 是 CPU 上的好选择,行数太多会显著拉高内存。cpu_threads决定推理线程数,在 8 核机器上设 4 到 6 比较均衡,设成和核数一样反而可能因为调度开销变慢。
ocr.ocr('sample.png', cls=True)第二遍加cls=True是运行时再次执行方向分类,对歪斜严重的图片有效,但会让总耗时增加 20% 左右。如果你的输入是统一方向的扫描件,可以关掉它换速度。
3.2 中英文混排识别、置信度阈值与输出格式验证
跑通之后,下一步是理解输出。result是一个三层嵌套结构:最外层对应图片列表,第二层是检测到的文本行,第三层是每个文本行的坐标框、文字和置信度。我刚拿到这个结构时花了点时间才理顺,所以这里直接给一个规范化的输出函数:
# format_ocr.py from paddleocr import PaddleOCR def recognize(path: str) -> list[dict]: ocr = PaddleOCR(use_angle_cls=True, lang='ch', cpu_threads=4) result = ocr.ocr(path, cls=True) items = [] for page in result: if not page: continue for box, (text, score) in page: items.append({ "text": text, "score": round(float(score), 4), "box": [[round(x, 1) for x in point] for point in box], }) return items if __name__ == "__main__": for it in recognize("invoice.png"): print(it)这段代码把坐标框和置信度都结构化了,方便后续接 JSON 落盘。这里有一个做批处理时的关键指标:置信度。中英文混排场景下,score低于 0.6 的文本行大概率包含识别错误,尤其是标点符号和数字容易混。我的经验是 0.7 以上基本可信,0.4 到 0.7 之间的需要看上下文,0.4 以下直接放弃或交给人工复核。你可以把低置信度文本单独输出一份人工校对清单,这样在批量归档时不会把错误带进检索库。
首张图的识别验证建议用一张包含中文、英文、数字的混合截图。自己本地造一张最简单的方式是:在 Linux 下用 ImageMagick 生成测试图。这里给一个纯命令行做法:
convert -size 800x300 xc:white -font DejaVu-Sans -pointsize 28 \ -annotate +50+60 "订单号:ORD-20240618,金额 128.5 元" \ -annotate +50+140 "Umi-OCR for linux offline test" \ test_sample.png python format_ocr.py如果输出和图中的内容一致,说明你的环境已经调通。此时再谈批量和参数调优才有意义,否则后面全在错误基础上叠加。
4. 把 OCR 接进日常 Linux 流程:批量脚本、PDF 预处理与结果落盘
4.1 批量识别目录图片:find 遍历、文件名带空格与 JSON 输出
单一图片识别跑通后,放在实际工作中远远不够。Linux 环境最大的优势是可以用 shell 把所有东西串起来。我常用的一套流程是:find遍历目录,把图片路径逐行喂给 Python 脚本,输出 JSON 文件,再统一收进一个结果目录。
先写一个接收单张图片路径并输出 JSON 的 Python 脚本:
# ocr_one.py import json, sys from format_ocr import recognize path = sys.argv[1] out_path = sys.argv[2] items = recognize(path) with open(out_path, "w", encoding="utf-8") as f: json.dump({"source": path, "items": items}, f, ensure_ascii=False, indent=2) print(f"ok: {path} -> {out_path} ({len(items)} lines)")然后在 bash 里做遍历。这里最大的坑是文件名里的空格。很多截图工具生成的图片名是截图 2024-06-18 10-30.png这种带空格的文件名,如果直接用for f in $(ls *.png)会把一个文件拆成两半。正确做法是用find -print0配while read -d '':
#!/bin/bash # batch_ocr.sh INPUT_DIR=/data/images OUTPUT_DIR=/data/ocr_result mkdir -p "$OUTPUT_DIR" find "$INPUT_DIR" -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.bmp" \) -print0 | \ while IFS= read -r -d '' img; do base_name=$(basename "$img" | sed 's/\.[^.]*$//') out_file="$OUTPUT_DIR/${base_name}.json" /opt/ocr/ocr_env/bin/python /opt/ocr/ocr_one.py "$img" "$out_file" done这段脚本有几个值得注意的地方。find ... -print0配合read -d ''是为了正确处理文件名中的空格和换行,这是 Linux 文本处理里最容易翻车的细节之一。sed 's/\.[^.]*$//'用于去掉扩展名,只保留主文件名作为输出名。/opt/ocr/ocr_env/bin/python直接调用虚拟环境内的 Python,不依赖激活状态,这样写进 crontab 也不会因为环境变量没加载而失败。
如果你不希望每个文件单独打开一次模型,更高效的做法是改成一进程处理多图。可以把上面 Python 脚本改成读取文件列表参数,在进程内复用同一个 PaddleOCR 实例。模型加载本身要吃掉几百 MB 内存和 1 到 3 秒时间,每张图都重新加载会浪费大量时间,批量场景下复用是必须的。
4.2 PDF 与扫描件的预处理:dpi、灰度化与旋转校正
很多需要 OCR 的文档是 PDF 扫描件,PaddleOCR 本身不直接吃 PDF,必须先转成图片。我一般在 Linux 下用pdftoppm做转换,它是poppler-utils自带的命令:
sudo apt install -y poppler-utils mkdir -p /data/pdf_pages pdftoppm -r 300 -png -jpegopt quality=90 input.pdf /data/pdf_pages/page这里-r 300指定渲染分辨率为 300 DPI。这个参数对识别效果影响极大,150 DPI 下小字号中文经常断笔画,300 DPI 能覆盖绝大多数扫描件,超过 400 DPI 只会增加图片尺寸和推理时间,识别率并不线性提升。
转换后得到的图片命名是page-01.png、page-02.png,直接交给上面的批量脚本即可。如果扫描件本身是歪的,可以先做旋转校正。简单的场景用 ImageMagick:
# 检测图片是否倾斜超过 2 度,然后做灰度化与轻微锐化 convert page-01.png -colorspace Gray -sharpen 0x1 -rotate 1.5 page-01-prep.png需要强调的是:旋转角度不能拍脑袋。我一般先用page-01.png跑一次 OCR,在多行文本坐标框中发现明显的上下行水平偏移时,再按偏移量反推旋转角度。自动化场景下可以用投影轮廓法算倾斜角,但初期不值得为这个写单独脚本。灰度化和锐化带来的收益在光照不均的扫描件上非常明显,彩色但脏的背景还会干扰文本行检测,所以我会默认在预处理阶段转灰度,如果图片本身是复杂背景(彩色海报、产品包装)则跳过这一步,保留彩色对det反而更好。
5. Linux 部署 Umi-OCR 的常见问题与排查:依赖崩溃、模型下载与内存翻车
5.1 libGL 缺失与 OpenCV 崩溃:换了系统镜像就报错的典型场景
现象:import paddleocr后直接抛ImportError: libGL.so.1: cannot open shared object file,或者更早一点,在import cv2时就崩溃。
原因:PaddleOCR 依赖链中opencv-python需要 libGL 动态库,但最小化的 Linux 系统镜像往往不装图形库。这不是环境坏了,是缺包。
解决:按系统类别补装即可,Debian/Ubuntu 执行sudo apt install -y libgl1 libglib2.0-0 libsm6 libxext6 libxrender-dev,Rocky/CentOS 执行sudo dnf install -y libGL mesa-libGL-devel glib2-devel。装完不要急着重新跑,先执行ldconfig刷新动态库缓存。如果是在 Docker 里使用,要在 Dockerfile 里带上这些包,并且不要用项目自带的python:3.10-slim这类精简镜像,选python:3.10-slim-bullseye后同样要装上述依赖。
另一个隐蔽变体是:系统里同时存在多个 OpenCV 副本,导致cv2依赖的错误动态库,表现是 import 时不报错,一调用就段错误。解决方式是固定opencv-python-headless,并删除多余版本:
pip uninstall -y opencv-python opencv-contrib-python pip install opencv-python-headless==4.10.0.84Headless 版本不依赖 libGL,在服务器上是最安全的。由于 PaddleOCR 内部会自己管理 OpenCV 依赖,如果它重新拉回完整版,可以在 requirements 里手动固定。
5.2 模型下载失败与“黑匣子”识别输出:如何确认模型被正确加载
现象:第一次运行PaddleOCR()时卡在下载模型,进度条不动或中途失败;还有一种是模型成功下载了,但识别结果完全是乱码或空列表。
原因:模型文件默认从 Paddle 官方对象存储下载,文件总量较大(det、rec、cls 合计上百 MB),在部分网络环境下容易中断。而“下载了但识别乱码”的情况,通常不是模型损坏,而是没有启用use_angle_cls=True导致文字方向判断错误,或者det阈值设置过低把背景纹理当成了文本。
解决:模型下载这块的常见做法是手动下载模型文件并解压到指定缓存目录。PaddleOCR 默认从~/.paddleocr/whl/读取,你只要把模型目录按特定层级放好即可。手动放置的目录结构大致如下:
~/.paddleocr/whl/ └── det/ch/ch_PP-OCRv3_det_infer/ ├── inference.pdmodel ├── inference.pdiparams └── inference.pdiparams.info └── rec/ch/ch_PP-OCRv3_rec_infer/ └── cls/ch/ch_ppocr_mobile_v2.0_cls_infer/放置好后在执行ocr.ocr()时不会再有下载动作。如果你只想验证模型是否完整,直接看.pdmodel文件大小是否超过 1MB 即可,几十 KB 的基本是下载失败的残留。对应识别参数上,把use_angle_cls=True打开,det_db_thresh从默认 0.3 调整到 0.5,能滤掉大部分模糊背景干扰。
5.3 CPU 内存占用与并发:多进程跑 OCR 的内存翻车与线程参数
现象:在 8 核 16GB 的 Linux 机器上用脚本同时开 6 个进程跑 OCR,每一批图片识别都很快,但跑了十几分钟后系统内存被吃满,开始频繁 swap,识别速度反而掉到原来的三分之一。
原因:每个 Python 进程都会独立加载 PaddleOCR 的三个模型,6 个进程就是 6 套模型驻留内存。这还没算 OpenCV、numpy 等基础库的常驻开销。多进程只有在 CPU 核数充足且内存宽松时才有意义,盲目开多进程属于常见翻车操作。
解决:优先改成单进程内多线程批处理。给 PaddleOCR 设置cpu_threads=4,rec_batch_num=16,然后让脚本循环处理多张图片,这是最稳妥的 CPU 利用方式。如果确实要并发处理海量文件,限制并发数为 CPU 核数的一半左右,并且每处理完一定数量就清理引用:
from concurrent.futures import ThreadPoolExecutor from format_ocr import recognize image_paths = ["/data/images/1.png", "/data/images/2.png", "/data/images/3.png"] def work(p): return p, recognize(p) with ThreadPoolExecutor(max_workers=2) as pool: for path, items in pool.map(work, image_paths): print(f"{path}: {len(items)} lines")注意这里是 Python 的线程池,PaddleOCR 底层推理本身会占用多线程,所以外层不需要开很多 worker,2 到 3 个足够。如果用了 GPU 版,则不需要开多个进程,GPU 显存上的模型只有一份,多进程反而会把显存成倍占用。
6. 进阶验证:离线模型目录整理、GPU 加速与性能基线
当你已经有一份能稳定出结果的批量脚本后,接下来最值得做的是把整套流程固化、标准化。我现在的习惯是:不再让每个环境各自去下载模型,而是把所有模型文件放到统一目录,比如/opt/ocr/models/,然后通过软链接指过去。这样新机器部署时只需要拷贝一个目录,全部模型立即可用,也能避免每次初始化都去网络拉取。具体做法:
mkdir -p /opt/ocr/models/det /opt/ocr/models/rec /opt/ocr/models/cls # 把下载好的模型按 det/rec/cls 目录解压 ln -s /opt/ocr/models/det ~/.paddleocr/whl/det ln -s /opt/ocr/models/rec ~/.paddleocr/whl/rec ln -s /opt/ocr/models/cls ~/.paddleocr/whl/cls另一个值得验证的方向是 GPU。如果你的 Linux 机器有 NVIDIA 显卡,把paddlepaddle换成paddlepaddle-gpu,并且保证 CUDA 版本匹配。运行前用nvidia-smi确认驱动正常,再执行python -c "import paddle; print(paddle.is_compiled_with_cuda())",输出True即生效。GPU 加速对长文本和大批量识别提升非常明显,一个 300 DPI 的 A4 扫描页从 CPU 的 8 秒缩短到 2 秒以内。
最后,我强烈建议留一份性能基线记录。固定一批 50 张测试图,打印每批总耗时、单图平均耗时、内存峰值,记录每次修改参数对速度的影响。我自己的经验是:det_limit_side_len从 960 调到 1280 会让时间增加 40%,但识别召回率只提升 2% 到 5%,所以除非文字真的很小,否则不值当。rec_batch_num从 16 提到 32,内存占用会净增约 1GB,速度却没有同比例提升。
这套方案我前前后后迭代过好几版,最大的教训是:不要一上来就追求并发和调参,先把模型固定到本地、确认三类模型都正确加载,再用一批真实图片量出基线,最后才谈优化。参数可以复现,踩过的坑也可以避开,希望帮到你。
本文还有配套的精品资源,点击获取