
前阵子接手了一个很现实的活儿某单位的档案数字化项目服务器环境是银河麒麟V10业务网是纯隔离内网连软件源都是内部私有的。业务方要求把一批历史纸质档案扫描后自动识别成结构化文本还要本地离线跑数据不能出网。我第一反应就是上 PaddleOCR——飞桨生态里的中文OCR标配识别效果好、开源免费、离线可部署几乎没有更顺手的替代方案。但真正动起手来才发现从环境搭建到模型优化每一步都有坑Python 版本对不上、底层图形库缺失、PaddlePaddle 的 wheel 包装不上、模型文件没法在线下载、推理速度还慢得离谱。这篇就把完整的离线部署过程写出来目标读者是同样在麒麟系统、内网环境或者国产化适配场景下做 OCR 落地的朋友。你不需要是算法专家只要会基本 Linux 操作按这条路线走基本能跑通从环境搭建到模型优化的全流程。文章里所有操作我都在银河麒麟V10 x86_64 上实测过其他架构ARM、MIPS部分兼容我会在对应位置特别说明。1. 为什么是麒麟系统PaddleOCR一套绕不开的组合1.1 这个组合在解决什么真实问题先说场景。这几年大量政企项目往国产操作系统迁移麒麟系统是出现频率最高的一个。系统换了业务应用也得跟着换其中文档识别、票据录入、证件信息提取这类 OCR 需求几乎是刚性存在。以前在 CentOS 上直接pip install paddleocr就完事了换到麒麟系统后问题开始变得具体系统自带的 Python 版本可能太老、OpenCV 依赖的底层图形库没有、内网环境拉不到包、模型权重文件还需要手动搬运。PaddleOCR 之所以在这个场景里成为首选有几个客观原因。第一它是百度飞桨生态里的组件对中文识别优化做得足够好PP-OCRv4 系列的中文识别能力在开源方案里是头部水平。第二它是纯本地推理模型文件下载后完全离线运行不依赖任何在线 API正好满足数据不出内网的合规要求。第三检测、方向分类、识别三段式模型结构清晰每个环节都可以单独替换、单独调优这在生产环境里非常实用。第四它提供了一整套官方训练和推理工具。不管你是直接拿预训练模型用还是想在自己的业务数据上微调都有完整的链路。所以麒麟系统PaddleOCR不是一种偶然的选择而是在国产化服务器上做 OCR 落地时大概率会遇到的组合。这篇博文要解决的核心问题就是在纯离线、无外网的麒麟机器上怎么把 PaddleOCR 跑起来并且跑得又快又准。1.2 离线部署的整体路径联网准备、内网安装、调优闭环离线部署听起来简单就是先下载再安装实际操作起来远没那么轻松。核心难点在于依赖关系是树状的PaddleOCR 依赖 PaddlePaddlePaddlePaddle 依赖 numpy、protobufOpenCV 依赖系统的 libGL 等动态库而 Python 解释器本身也可能缺失。一个环节没考虑到安装过程就会卡住而且内网机器上没有任何现成的源可以救急。我的整体路径分三步。第一步在一台能上网的机器上把整个依赖树完整地下载下来包括 Python wheel 包、系统级 rpm 包、PaddleOCR 推理模型文件。第二步把这些资源打包拷进内网在麒麟目标机上按顺序安装跑通一个最小 demo。第三步针对实际业务图片做推理速度和精度的调优把检测阈值、识别批量、模型版本这些参数定下来。这三步是一个完整的闭环其中第一步的准备充分程度直接决定了第二步会在坑里待多久。后面我就按这个顺序展开。2. 离线安装包准备联网机器上的依赖树整理2.1 先把版本钉死Python、PaddlePaddle、PaddleOCR 的对应关系离线部署最怕装到最后才发现版本不兼容。与其到时候抓狂不如一开始就把版本组合锁死。我在这次项目中用的是下面这一套组合实测稳定组件推荐版本说明麒麟系统银河麒麟V10x86_64内核对齐 CentOS 系用 yum/dnf 管理软件包Python3.9.13PaddleOCR 支持 3.6~3.12但 3.8/3.9 适配最成熟PaddlePaddle2.5.2CPU/ 2.5.2GPUCUDA 11.72.5 系列对麒麟系统兼容性好改动最少PaddleOCR2.7.3比 2.6 新支持新版 predict API模型选择也多protobuf3.20.3必须锁死Paddle 跟新版 protobuf 有兼容问题numpy1.24.4numpy 2.0 对旧版 Paddle 不友好锁死为什么不用最新版这个我吃过亏。Paddle 最新的版本往往对 CPU 指令集要求更高麒麟服务器如果用的是老款处理器某些情况会出现非法指令错误。另外PaddleOCR 每隔几个版本就会调整模型结构和推理接口模型文件需要跟代码版本匹配。在生产环境里新不如稳版本组合一旦验证通过就不要再动。2.2 pip download 的正确用法一次性拉全依赖树在联网机器上我建议创建一个干净的 Python 虚拟环境然后按下面的方式操作python3 -m venv /tmp/ocr_env source /tmp/ocr_env/bin/activate pip install --upgrade pip # 先安装目标包让 pip 解析出完整依赖 pip install paddlepaddle2.5.2 pip install paddleocr2.7.3 # 冻结当前环境的完整依赖列表 pip freeze requirements.lock.txt # 把所有依赖的 wheel 包下载到目录 pip download -r requirements.lock.txt -d ./ocr_pkgs # 同时把 paddle 主包也单独下载一份带过去 pip download paddlepaddle2.5.2 -d ./ocr_pkgs --no-deps这里有几个关键点。第一pip freeze requirements.lock.txt生成的列表包含所有间接依赖拿到离线机器后直接pip install --no-index --find-links./ocr_pkgs -r requirements.lock.txt就能还原整个环境版本完全一致。第二PaddleOCR 的setup.py里没有把paddlepaddle声明为强制依赖因为 GPU 和 CPU 版本差别太大所以必须先手动装 PaddlePaddle。第三如果你不确定下载是否完整可以再跑一遍pip install --no-index --find-links./ocr_pkgs -r requirements.lock.txt它会把缺失的包名报出来。如果目标机和下载机不是同一个 CPU 架构比如目标机是 ARM 的麒麟而联网机是 x86那就要用--platform参数指定目标平台比如pip download paddlepaddle2.5.2 -d ./aarch64_pkgs \ --platform manylinux2014_aarch64 --only-binary:all: --no-deps这种跨架构下载只对纯 Python 包和带 manylinux 标签的 wheel 有效有些包必须源码编译那就得先在目标机上准备好编译链这个后面会提到。另外pip download默认还会下载一些源码 tar.gz 包如果目标机器上没有编译环境安装时会被卡住。我的建议是尽量选择带manylinux标签的 wheel避免源码编译。2.3 系统级依赖不止是 Python 包PaddleOCR 里的 OpenCV 是 Python 包但 OpenCV 底层链接的是系统的 C 库。在麒麟服务器最小化安装的情况下import cv2十有八九会报libGL.so.1: cannot open shared object file。这种问题 pip 解决不了必须在系统层面补齐。先确认麒麟系统的软件源类型。银河麒麟 V10 server 版大多基于 CentOS/RHEL 体系使用yum桌面版或者优麒麟则偏向 Debian 体系使用apt。确认方式很简单cat /etc/os-release我这台是 RPM 体系的所以在联网机器上准备好对应的 rpm 包yum install -y yum-utils yumdownloader --resolve libglvnd-libGL glib2 libSM libXext libXrender把这些 rpm 拷到目标机器上后执行rpm -Uvh *.rpm。如果是 Debian 系就用apt download libgl1 libglib2.0-0 libsm6 libxext6 libxrender1然后dpkg -i *.deb。说实话这一步你完全可以在目标机器上先跑一次推理脚本让系统把缺的库名报出来再回联网机器下载这样更有针对性。但提前把常见的图形库备好能省一轮内网现场来回折腾。3. 麒麟系统上的环境搭建与模型落地3.1 从零配置 Python 运行环境推荐 Miniconda 方案麒麟系统自带的 Python 版本不一定够用我见过很多台是 Python 3.6甚至有的最小安装连 Python3 都没有。有两个方案一是下载 Python 源码在目标机编译这个比较折腾需要 gcc、make、zlib-devel 等一系列依赖二是直接搬一个现成的 Python 环境过去更省事。我推荐用 Miniconda conda-pack 的组合。在联网机器上# 下载并安装 Miniconda wget https://repo.anaconda.com/miniconda/Miniconda3-py39_23.11.0-2-Linux-x86_64.sh bash Miniconda3-py39_23.11.0-2-Linux-x86_64.sh -b -p /opt/miniconda3 # 创建干净环境并安装依赖 /opt/miniconda3/bin/conda create -n ocr python3.9 -y source /opt/miniconda3/bin/activate ocr pip install paddlepaddle2.5.2 pip install paddleocr2.7.3 pip freeze requirements.lock.txt # 用 conda-pack 打包整个环境 pip install conda-pack conda pack -n ocr -o ocr_env.tar.gz把ocr_env.tar.gz拷到麒麟目标机后mkdir -p /opt/ocr_env tar -xzf ocr_env.tar.gz -C /opt/ocr_env source /opt/ocr_env/bin/activate这个方案的好处是环境是完整搬过去的Python 版本、pip、各类依赖全部一致不存在离线装 Python这个老大难问题。另一个替代方案是直接把联网机器的 venv 目录拷过去但 venv 里记录了绝对路径换机器后经常出问题conda-pack 内部做了路径重新绑定靠谱得多。对于 ARM 架构的麒麟机器你在联网机器上也要选对应架构的 Miniconda 版本过程一样。3.2 离线安装 PaddlePaddle 和 PaddleOCR环境就绪后离线安装就变成一条命令的事pip install --no-index --find-links./ocr_pkgs paddlepaddle2.5.2 pip install --no-index --find-links./ocr_pkgs paddleocr2.7.3 pip install --no-index --find-links./ocr_pkgs -r requirements.lock.txt--no-index表示完全不访问 PyPI--find-links指向本地包目录。装完后先做个基本验证python -c import paddle; paddle.utils.run_check() python -c from paddleocr import PaddleOCR; print(ocr import ok)如果import paddle成功但import paddleocr报缺某个 Python 包说明离线下载时漏包了回到联网机器把缺的包补下再拷过来。如果报缺libGL.so.1等系统库就按 2.3 节的方法补齐。这里提醒一句PaddleOCR 的依赖里有一些包比如 lanms-neo用于表格结构的后处理需要本地编译如果目标机没有 gcc 编译链安装会失败。如果业务里用不到表格识别可以直接跳过这些编译型依赖不必死磕。3.3 模型文件的下载与目录组织离线资源也是模型的一部分环境装好只是第一步模型文件才是真正干活的东西。PaddleOCR 默认会在推理时从网上下载模型离线环境必须提前把模型文件准备好并指定本地路径。在联网机器上下载 PP-OCRv4 中文模型wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_infer.tar wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_infer.tar wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar解压后把模型整理成这样的目录结构/opt/ocr/models/ ├── det/ │ ├── inference.pdmodel │ └── inference.pdiparams ├── rec/ │ ├── inference.pdmodel │ └── inference.pdiparams └── cls/ ├── inference.pdmodel └── inference.pdiparams然后在初始化时明确指定模型路径from paddleocr import PaddleOCR ocr PaddleOCR( langch, det_model_dir/opt/ocr/models/det, rec_model_dir/opt/ocr/models/rec, cls_model_dir/opt/ocr/models/cls, use_angle_clsTrue, use_gpuFalse, show_logFalse )这里最容易踩的坑是模型和 PaddleOCR 版本不匹配。PP-OCRv3 的模型用到 2.6 之后的 PaddleOCR 没问题但如果你想用 PP-OCRv4 的模型PaddleOCR 版本不能低于 2.6。另一个坑是模型解压后文件名不同版本可能是model.pdmodel也可能是inference.pdmodelPaddleOCR 会按文件夹里的实际文件加载所以确认目录里至少有两个模型文件别只解压了一半。4. 推理链路拆解文本检测、方向分类、识别三段式4.1 图片到文本的完整处理流程PaddleOCR 的默认推理是典型的流水线结构一张图进去分三步出结果。文本检测模型Detection负责找出图片里所有文字所在的区域输出是一组坐标框对应每个文本框的四个顶点方向分类模型Classification判断每个文本框里的文字是否旋转了 180 度解决扫描件上下颠倒的问题文本识别模型Recognition负责把每个文本框的图像内容转换成字符串。三个模型串起来最终输出的是一行行文本和对应的置信度。result ocr.ocr(invoice.jpg, clsTrue) # 老版本 API 返回结构 # [[ [box], (text, score) ], ...] for line in result[0]: box, (text, score) line print(box, text, score)如果你的 PaddleOCR 是 2.7 及以上还有新 APIocr.predict(invoice.jpg)返回结构不太一样更适合批量处理。我的经验是老 API 稳定、资料多业务脚本如果已经用老 API 写好升级时保持ocr.ocr()调用不变能少改不少代码。三个模型各自独立加载所以首次调用会比较慢通常在 2~5 秒不等。部署成服务时最好启动时预加载一次 PaddleOCR 对象不要每次请求都重新创建。亲测这样能把单次请求延迟里模型加载那部分省掉对实时性要求高的场景特别重要。4.2 CPU 场景下的参数调优不换显卡也能翻倍大多数麒麟服务器没有独立显卡CPU 推理是常态。不花一分钱通过参数调整就能看到明显的速度提升。参数默认值我的推荐作用enable_mkldnnFalseTrue启用 Intel/AMD CPU 的底层加速算子cpu_threads4物理核数控制线程数过高反而争抢内存带宽rec_batch_num66~16识别阶段一次处理的文本框数量det_limit_side_len960640~1280限制检测时图像最长边影响缩放use_angle_clsTrue根据情况方向固定可直接关闭省一整段推理ocr PaddleOCR( langch, det_model_dir/opt/ocr/models/det, rec_model_dir/opt/ocr/models/rec, cls_model_dir/opt/ocr/models/cls, use_angle_clsFalse, # 方向固定时关掉 use_gpuFalse, enable_mkldnnTrue, # CPU 加速 cpu_threads8, # 按你的核数调 rec_batch_num16, # 批量识别 det_limit_side_len960, # 控制检测输入尺寸 show_logFalse )实测下来开启enable_mkldnn后单张 A4 扫描件的推理时间能从 2.4 秒降到 1.2 秒左右CPU 线程从 4 调到 8 又能再快 20%。det_limit_side_len这个参数影响也很大它决定检测阶段图片最长边能到多少像素如果业务图片都是手机拍的截图或白底文档960 就够了如果是 A4 扫描件文字比较小建议提到 1280漏检会明显减少但耗时会增加 30% 左右。还有一个小技巧大批量处理时在脚本开头设置paddle.set_num_threads(8)效果和cpu_threads参数一致但全局生效更彻底。另外注意服务器内存别低于 2GB模型加载后占用通常在 500MB 到 1GB 之间再加上图片解码和中间结果内存吃紧会导致频繁 swap速度不升反降。4.3 GPU 和 MLU 加速什么时候值得上如果你手里的麒麟机器恰好有 NVIDIA 显卡那 GPU 推理的提升是飞跃式的。选择paddlepaddle-gpu版本时最关键的是 CUDA 版本对应PaddlePaddle 2.5.2 分别提供 CUDA 11.2/11.6/11.7/12.0 的 wheel 包选错版本会直接报CUDA driver version is insufficient。在联网机器上提前用nvidia-smi查看驱动支持的最高 CUDA 版本再下载与之匹配的paddlepaddle-gpu包。GPU 部署还有一个容易忽的细节Paddle 默认会预先申请大量显存。如果服务器上还有其他任务建议在启动脚本里加两行import os os.environ[FLAGS_allocator_strategy] auto_growth这样显存会按需增长而不是一上来就占满。另一个加速卡是寒武纪 MLU飞桨官方有针对 MLU 的定制安装包推理流程和 GPU 完全一致但需要你在下载阶段就确认好对应的版本普通 CPU 代码不用改。我的建议是每天处理量低于几千张的归档场景CPU 配合mkldnn完全够用不必上显卡如果到了批量补录、流式识别的阶段几万张往上GPU 或 MLU 才是值得投入的方向。毕竟在信创服务器上加一张 AI 加速卡流程审批和预算都不会太快先靠 CPU 调优顶上去是更现实的方案。5. 模型优化的实测速度与精度的取舍5.1 轻量模型与量化模型的差距PaddleOCR 的 PP-OCRv4 模型分为 mobile移动端轻量和 server服务端高精度两个系列。离线部署时模型选择直接决定了硬件的压力和响应速度。我这次在测试环境里两种模型都试了模型体积大约单张耗时CPU 8线程感受ch_PP-OCRv4_mobile_det mobile_rec约 15MB0.8s日常清晰文档完全够用ch_PP-OCRv4_server_det server_rec约 180MB2.3s对低清晰度扫描件更友好如果你的业务图片质量不错我建议直接上 mobile 系列部署体积小推理速度快精度损失在清晰场景下基本感知不到。如果图片比较恶劣——比如旧档案扫描件、有褶皱、有背景干扰——server 模型更稳妥漏检率低一截。两个系列的模型文件都在官方模型库里能找到对应的_infer.tar。另外值得尝试的是官方量化模型这类模型是经过量化训练后导出的体积更小、速度更快。在 CPU 上量化模型的推理时间往往能再降 30%~40%精度损失通常可以接受。具体优化空间有多大建议在内网拿一批真实业务图跑个对照实验毕竟精度这东西不能拍脑袋说了算。5.2 检测与识别后处理参数的调优模型换完之后下一步就是调后处理参数。检测阶段最常用的几个参数如下参数默认值调优方向det_db_thresh0.3漏检多就调低0.2误检多就调高det_db_box_thresh0.5漏检多就调低0.4背景干扰大就调高det_unclip_ratio1.6文本框向外扩展比例调高能框住更大范围我遇到过一个很典型的例子。一批 90 年代档案扫描件底色是米黄色文字墨迹有扩散默认参数下大量文本框被漏掉识别结果断断续续。把det_db_thresh从 0.3 调到 0.2det_db_box_thresh从 0.5 调到 0.4召回率立马上来了。副作用是会产生一些多余的小框但业务侧用一个简单规则——过滤面积过小或者置信度低于 0.6 的框——就能清掉整体识别覆盖率反而提升很多。识别阶段rec_batch_num决定一次喂给识别模型多少个文本框。默认 6调到 16 在 CPU 上通常能提高吞吐因为批量推理比循环单张推理省掉了很多调度开销。但也不要无脑调大文本框数量少的图batch 设再大也没意义还可能增加首字延迟。另外rec_image_shape默认是[3, 48, 320]这是识别模型的输入尺寸如果你的文本行特别长可以适当改宽度但改完必须跟模型训练时的尺寸匹配否则精度会下降。5.3 乱码与漏检字体缺失和其他坑部署到麒麟系统后你可能还会遇到另一个诡异的问题识别结果在终端里打出来是乱码或者用 PaddleOCR 自带的可视化功能画出来的图片中文全是方块。这个问题的根源大多数情况下不是 OCR 模型的问题而是系统里缺少中文字体。麒麟系统默认安装的字体很少尤其是服务器版几乎只有英文和基本符号。解决办法很简单装一套开源中文字体yum install -y wqy-zenhei-fonts wqy-microhei-fonts fc-cache -fv如果是 Debian 系的麒麟换成apt install -y fonts-noto-cjk。装完字体后再做可视化或者生成 PDF中文就正常了。还有一种乱码是输出编码问题比如你把 Python 脚本的 stdout 重定向到了文件然后用 Windows 记事本打开发现全是乱码那是因为终端或文件的编码不一致。部署时统一用 UTF-8设置一下环境变量export LANGen_US.UTF-8 export PYTHONIOENCODINGutf-8识别结果里如果出现锟斤拷这种经典乱码通常说明字符串在 GBK 和 UTF-8 之间被反复转了码检查一下前后端的编码是否一致。如果识别结果里形近字错误多比如己和已、未和末这属于模型精度局限建议在后处理阶段针对业务词汇表做一次字典纠错比重新训练模型成本低得多。6. 部署过程中绕不开的故障与排查链路6.1 libGL.so.1 not found最经典的缺失这是我在麒麟机器上遇到的第一个报错完整信息是ImportError: libGL.so.1: cannot open shared object file: No such file or directory。原因前面已经提到OpenCV 依赖系统的 libGL 动态库而最小化安装的麒麟服务器没有装图形相关的库。排查思路很简单先确认到底是哪个库缺失ldd /opt/ocr_env/lib/python3.9/site-packages/cv2/cv2.abi3.so | grep not found这样能列出所有缺失的库文件。RPM 体系下libGL 对应的包是libglvnd-libGL老一点的是mesa-libGLlibgthread 对应glib2。补齐后重新ldd直到没有not found为止。这个经验可以推广到其他系统库缺失问题不要凭记忆装包先ldd再对症下药。6.2 protobuf 和 numpy 版本冲突第二个高频故障是 protobuf 版本冲突。启动 PaddleOCR 时直接报TypeError: Descriptors cannot not be created directly。这个报错在 PaddlePaddle 2.3~2.5 之间非常普遍原因是新版 protobuf4.x改了描述符的创建方式旧版 Paddle 没有跟上。解决办法是把 protobuf 锁到 3.20.xpip install protobuf3.20.3类似的还有 numpy 版本。如果安装了 numpy 2.0 以上Paddle 加载模型时可能报numpy.dtype size changed之类的错误锁回numpy1.24.4就好。这个教训告诉我在离线环境的 requirements 里越冷门的包越要锁版本因为现场没法临时去 PyPI 拉包你只有一次机会。6.3 结果全空或者乱码的排查顺序当你的推理脚本跑通、不报错但识别结果全部为空或者全部乱码的时候排查顺序很重要。我一般按下面的顺序来第一先跑 PaddleOCR 官方 demo 图片一张印刷体截图确认基本流程没问题如果官方图正常、业务图全空问题在图片本身或者参数。第二检查图片格式PaddleOCR 内部走的是 OpenCV 的imreadRGBA 四通道图在识别时会有问题灰度图也可能导致误检统一转成 RGB。第三检查det_db_thresh、det_db_box_thresh这两个阈值是不是被调得过高导致所有候选框都被过滤掉了先调到 0.2/0.4 再试。第四确认模型目录里真的有模型文件并且和 PaddleOCR 版本匹配用 PP-OCRv4 的模型却配了 PaddleOCR 2.5大概率会出怪问题。第五看看是全部为空还是一部分为空如果只有个别图片为空大概率是图片本身太暗或者文字太小可以考虑在前处理里做一次自适应增强。乱码类的排查则简单直接先确认是不是系统字体缺失再确认输出编码最后才怀疑模型问题。因为模型识别错乱通常是部分的、局部的而系统编码错乱则是全军覆没区分度很高。按这个顺序多数问题能在十分钟内定位。最后说点个人的体会。麒麟系统下跑 PaddleOCR真正难的不是算法而是环境离线依赖、系统库、模型版本任何一个环节没对齐都会让你在内网现场多耗一天。我建议第一次做的时候一定先把版本组合钉死在联网机器上把能下载的东西全部准备好多花一小时准备能省内网现场的一整天。另一个心得是模型到手先别急着调算法参数先确认整条推理链路是通的、输出是正常的再去做速度优化和精度优化。按这个顺序来麒麟系统下的 PaddleOCR 离线部署并没有想象中那么吓人。