ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PaddleOCR离线部署实战:麒麟系统下的绿色环境打包方案

PaddleOCR离线部署实战:麒麟系统下的绿色环境打包方案 去年做国产化替代项目时遇到一个特别典型的场景办公网里有一批银河麒麟系统的终端业务上需要批量识别合同扫描件里的文字但整个网络是物理隔离的外网根本不通。装软件得靠U盘拷Python库更别指望pip在线安装。当时我第一反应就是PaddleOCR——中文识别效果好、开源免费、社区资料也多但问题是怎么在一台完全离线的麒麟机器上把环境、模型、依赖一次打包到位让不懂技术的同事解压就能用。这篇文章就围绕我自己跑通的一套“离线绿色部署”方案展开从依赖收集、模型选型、环境打包到常见坑位排查尽量把每个步骤的来龙去脉讲清楚。希望给正被国产系统、内网环境、OCR选型折腾的朋友一些直接可抄的作业。1. 整体设计为什么是离线、为什么是PaddleOCR1.1 项目要解决的三个实际问题先还原一下场景。项目背景是一家单位的档案数字化系统部署在麒麟V10x86_64架构的国产化终端上。需求是识别历史纸质档案扫描件中的文字生成可检索的电子档案。当时摆在桌面上的问题有三个离线环境。这应该是国产化项目里最常见的痛。生产网络与互联网物理隔离没有yum源、没有pip源连基础的系统依赖包都得人工拷进去。目标机器上只有最基础的操作系统环境很多编译工具链都没有。麒麟系统的差异化。麒麟系统虽然基于Linux内核但不同版本之间的库版本差异很大。有的是基于CentOS 7衍生有的是基于Ubuntu衍生glibc版本、系统库路径都不一样稍不留神就会遇到“GLIBC_2.29 not found”之类的报错。使用者的技术水平。系统最终要交给档案室的工作人员用不可能要求他们配Python环境、改代码、跑命令行。部署方式必须足够“傻瓜”——最好就是解压一个文件夹运行一个启动脚本然后通过Web页面或者本地工具来上传图片、拿结果。基于这三个问题我给自己列了一个硬性要求整套OCR能力不能依赖目标机器的任何网络资源不能要求使用者有编程背景同时所有核心文件必须在同一个目录下自包含删掉也不会污染系统。1.2 为什么不用商业OCR也不选Tesseract在确定PaddleOCR之前我其实对比过几条路线。商业OCR服务比如云厂商的通用文字识别接口识别效果是不错但是对数据敏感的项目来说图片出域这件事本身就过不了合规。而且内网环境下调用云端接口根本不现实网络都不同谈何调用。开源方案里最知名的是Tesseract。它的社区很成熟离线也能跑但我做过对比测试在同样一份中文印刷体扫描件、300DPI分辨率下Tesseract的识别准确率大概在85%左右而PaddleOCR的PP-OCRv4模型能做到95%以上。别小看这10个百分点的差距放在几百页的档案扫描场景里意味着要人工校对几千个字符工作量完全不是一个量级。PaddleOCR的另一个优势是部署模型相对轻量。整套推理模型压缩后不到20MB即便跑在纯CPU的麒麟机器上单张A4扫描件也能在2到3秒内出结果这个性能完全能满足档案室批量录入的需求。还有一个很关键的考虑——PaddleOCR的输出结构非常规整检测框坐标、识别文本、置信度都有结构化字段后续接业务系统做数据归档都很省事。1.3 方案选型离线依赖自包含的“绿色”思路所谓“绿色部署”就是让整个Python运行环境跟着应用走而不是让应用去适配系统里已有的Python。传统做法是在麒麟机器上先装Python再一个一个装依赖库。这在有网的环境下没问题但在离线环境下就非常折磨人——一个依赖没装好就报错装了又可能搞乱系统已有的库版本。更糟的是如果目标机器上还有别的业务在跑盲目的pip install可能会破坏人家依赖的libssl、libcurl等动态库。我的方案是三步走在一台相同CPU架构、相同操作系统内核版本的联网机器上准备好所有wheel包和源码包。在该机器上用Python的venv模块创建一个独立虚拟环境把需要的所有库都装进去。把整个venv目录连同模型文件、中文字体、启动脚本一起打包成tar.gzU盘拷到麒麟机器上解压即用。因为venv本质上是把解释器、库文件、可执行文件都放在同一个目录下只要内核版本差距不大整个环境移动过去是可以直接运行的。这就是“绿色”的核心思路——应用自包含不依赖系统的“公共设施”。2. 核心细节解析PaddleOCR离线运行结构拆开看2.1 检测、方向分类、识别三段式推理流程PaddleOCR的推理链路不是单一模型而是三个模型串联这点在部署前必须先心里有数。文本检测Detection负责在图片里找出“哪里有文字”输出的是一个个文本框的坐标。它用的是DBDifferentiable Binarization这类分割模型可以理解成先在图上画一个“哪里可能有文字”的概率图再通过阈值把文字区域抠出来。方向分类Angle Classification负责判断文本框里的文字是不是倒着的。因为扫描件里经常会有旋转90度或者180度的页面如果不把方向纠正过来识别模型直接去认就会输出一堆乱码。这个模型很小只有几百KB推理速度极快但对整体准确率贡献不小。文本识别Recognition负责把裁剪出来的文本框图像转化成字符串。它用的是CRNN或者SVTR这类序列识别模型可以理解成“看图说话”按像素顺序读出每个字符。整个流程用生活化的类比解释就是先找好“字在哪里”的框再把歪的框扶正最后把框里的字读出来。第二步的“方向分类器”很多新人容易忽略但实际项目里碰到扫描件倒置、页面旋转是常有的事。我在测试麒麟系统时专门跑了一批随机旋转的测试图开着方向分类器时准确率是93.2%关掉后掉到81.5%差距非常明显。2.2 训练模型与推理模型的区别PaddleOCR的GitHub仓库里提供了两种模型格式训练模型训练用checkpoint和推理模型inference model。部署时只能使用推理模型。推理模型是把训练好的权重经过静态图导出后固化得到的包含inference.pdmodel网络结构和inference.pdiparams权重参数两个文件配合推理引擎直接加载运行。如果误拿训练模型来部署会碰到eval阶段特有的网络节点轻则报错重则模型加载就失败。搜索资料时经常会搜到“paddleocr推理模型”这个词说的就是这一对新文件。另一个区别体现在模型目录组织上。官方推荐的目录结构是这样inference/ ├── ch_PP-OCRv4_det/ │ ├── inference.pdmodel │ └── inference.pdiparams ├── ch_PP-OCRv4_rec/ │ ├── inference.pdmodel │ └── inference.pdiparams └── ch_ppocr_mobile_v2.0_cls/ ├── inference.pdmodel └── inference.pdiparamsPaddleOCR在初始化PaddleOCR类时默认会到{项目根目录}/inference/下去找带det_model_dir、rec_model_dir、cls_model_dir三个参数对应的模型。所以最简单的做法就是按上述结构放置模型目录然后调用代码里不用写任何路径参数直接指定语言和推理参数即可。2.3 离线环境的三块“自包含”内容真正要做到“解压即用”核心是要让三样东西全部随目录迁移Python解释器、第三方库、系统级依赖。Python解释器venv创建的虚拟环境里有一个bin/python它是软链接指向创建时用过的系统Python。问题来了如果目标机器的/usr/bin/python3指向的版本不一致这个软链接就失效了。所以打包前要确认两件事一是创建的虚拟环境使用的是一个固定路径可用的Python解释器二是目标机器的Python路径下确实存在同版本的解释器。最保险的验证方法就是在联网机器上创建一个“既有venv又有系统Python”的组合把venv里的解释器换成Python的静态编译版本但这样太重了一般项目不需要。实际上只要源机器和目标机器的系统都是基于相同的Linux发行版大版本比如都是CentOS 7衍生/usr/bin/python3路径下解释器版本基本一致venv直接打包是可以跑起来的。第三方库这部分最麻烦。PaddleOCR依赖PaddlePaddle、opencv-python、numpy、shapely、pyclipper、Pillow、PyYAML等一堆库。尤其opencv-python的wheel包非常大动辄七八十MB手动一个个找很容易漏。正确做法是在联网机器上用pip download一次性拉全所有依赖的wheel包。系统级依赖PaddlePaddle的CPU版本依赖libgomp.so.1GNU的OpenMP库、libstdc.so.6opencv-python依赖libGL.so.1、libgthread-2.0.so.0。这些在桌面版麒麟系统上一般默认存在但在精简版服务器上可能缺失。提前用ldd检查一下可以避免现场抓瞎。3. 实操过程与核心环节实现3.1 第一步在联网机器上准备离线安装包我强烈建议准备离线包时找一台和目标机器架构一致的联网Linux机器。架构不一致比如在x86机器上拉出的wheel包拿到aarch64机器上用会在安装时直接报“is not a supported wheel on this platform”。具体命令我分三步执行先把PaddleOCR项目代码clone下来git clone https://github.com/PaddlePaddle/PaddleOCR.git cd PaddleOCR git checkout v2.7.3然后拉取依赖的wheel包。这里有个小技巧不要用pip download paddleocr直接拉因为PaddleOCR的依赖列表在它的requirements.txt里还需要手工补上paddlepaddlepip download paddlepaddle2.5.2 -d ./offline_packages pip download paddleocr2.7.3 -d ./offline_packages这一步会把PaddleOCR、PaddlePaddle以及它们依赖的所有轮子包全部下载到offline_packages目录下。我在实际执行时发现PaddleOCR还有些依赖没被pip自动拉全比如lmdb、scikit-image等需要根据requirements.txt再手动补一次pip download -r requirements.txt -d ./offline_packages最后把整个目录连同模型一起迁移到目标机器后面再继续安装。这里走过的坑提醒一下不要用最新版的PaddleOCR直接配最新版PaddlePaddle。我当时一开始用了paddleocr 2.7.3和paddlepaddle 2.5.2的组合二者兼容性很好。如果混用会导致paddleocr内部调用paddle.fluid模块时提示“module not found”——因为新版本飞桨把旧的fluid接口移除了。3.2 第二步模型获取与目录规划模型不要通过pip自动下载离线环境这一步必然失败。手动从PaddleOCR官方模型库下载推理模型然后在目标机器上搭建如下目录结构ocr_offline/ ├── inference/ │ ├── ch_PP-OCRv4_det/ │ │ ├── inference.pdmodel │ │ └── inference.pdiparams │ ├── ch_PP-OCRv4_rec/ │ │ ├── inference.pdmodel │ │ └── inference.pdiparams │ └── ch_ppocr_mobile_v2.0_cls/ │ ├── inference.pdmodel │ └── inference.pdiparams ├── fonts/ │ └── simhei.ttf ├── offline_packages/ └── venv_ocr/fonts目录下放一个中文字体虽然PaddleOCR识别模型本身不直接用字体文件但在可视化画框时需要用到中文字体把识别结果叠加到图片上。麒麟系统默认字体很有限如果缺少中文字体draw_img保存出来的图会显示一堆方块排查定位比较困难以为识别乱了实际上是显示层字体缺失。模型版本我推荐PP-OCRv4系列的中英文模型。对比过v3v4在印刷体、手写体、复杂版面表格上的鲁棒性明显更好模型体积也没增加多少离线环境内存完全扛得住。3.3 第三步打包虚拟环境迁移到麒麟系统联网机器上创建虚拟环境并安装所有离线依赖包python3 -m venv venv_ocr source venv_ocr/bin/activate pip install --no-index --find-links./offline_packages paddlepaddle2.5.2 pip install --no-index --find-links./offline_packages paddleocr2.7.3--no-index是关键参数强制pip只从本地目录找包防止它试图连网。安装完成后把整个ocr_offline目录打包cd .. tar -czf ocr_offline.tar.gz ocr_offline/U盘拷贝到麒麟机器后解压然后激活venv测试环境tar -xzf ocr_offline.tar.gz cd ocr_offline source venv_ocr/bin/activate python test_ocr.py第一次跑通后就可以把source venv_ocr/bin/activate等环境激活动作写进一个启动脚本里让最终用户直接运行脚本。3.4 第四步调用脚本与性能验证写一个验证用Python脚本test_ocr.pyfrom paddleocr import PaddleOCR import time ocr PaddleOCR( use_angle_clsTrue, langch, show_logFalse, det_model_dirinference/ch_PP-OCRv4_det, rec_model_dirinference/ch_PP-OCRv4_rec, cls_model_dirinference/ch_ppocr_mobile_v2.0_cls, ) start time.time() result ocr.ocr(test_page.png, clsTrue) print(infer time: %.2fs % (time.time() - start)) for res in result[0]: if res is None: continue box, (text, score) res print(ftext: {text}, score: {score:.4f})在麒麟V10 x86_64机器上我用一份300DPI的A4合同扫描件测过模型组合单张耗时CPU占用内存占用PP-OCRv4 mobileCPU2.3s约80%约520MBPP-OCRv4 serverCPU3.1s约90%约780MB绝大多数档案场景下mobile版完全够用。如果机器内存只有2GB建议把rec_batch_num从默认的6调低到2降低批量识别时的峰值内存ocr PaddleOCR( use_angle_clsTrue, langch, show_logFalse, rec_batch_num2, det_limit_side_len960, )det_limit_side_len控制检测阶段图像缩放的最长边。默认960对A4扫描件够用如果原始图片特别大超过960会把图片等比缩小再检测这样会加速但可能漏掉小字。我的建议是先保持默认实测效果不好再调。4. 常见问题与排查技巧实录4.1 常见问题速查表整理一下我在项目里实际碰到的坑先给一个速查表问题现象根因定位方法解决办法GLIBC_2.28 not found系统glibc版本过低ldd --version查看版本换用更低版本PaddlePaddle或升级系统libGL.so.1: cannot open shared object file缺OpenGL系统库ldd venv_ocr/lib/python3.8/site-packages/cv2/*.soyum install libGL或补拷libGL.so.1模型加载路径报错模型目录结构不对打印os.listdir检查文件按官方结构放置inference.pdmodel和inference.pdiparams识别结果全是乱码或为空方向分类器未开启/图像严重旋转用一张正向清晰图片测试设use_angle_clsTrue保存的可视化图片文字变方块系统缺中文字体检查fc-list :langzh把simhei.ttf放到fonts目录并设置环境变量pip安装时提示“not supported wheel”架构不匹配uname -m确认在相同架构机器上重新拉取wheel包启动时Python报ModuleNotFoundError: No module named paddle虚拟环境未激活which python查看路径先执行source venv_ocr/bin/activate4.2 两个必须现场处理的硬核报错第一个是GLIBC版本冲突。麒麟系统的不同版本内核和库差异很大有的精简版系统glibc版本只到2.17这个版本下PaddlePaddle 2.5.2的wheel包根本加载不了。这个问题的本质是飞桨的编译目标基于较新的glibc而老系统的动态链接库没有对应符号。我当时手头没有系统重装的权限是换了一个PaddlePaddle 2.2.2的旧版本才在最低配机器上跑通的。这个版本对glibc的要求低一些代价是部分新模型结构跑不了所以如果目标机器系统版本偏老建议直接用旧版飞桨搭配PP-OCRv2或v3模型。第二个是libssl冲突。麒麟系统自带的OpenSSL是1.1.1但有的业务机器装了别的软件把libcrypto.so.1.1替换成了更新版本导致Python启动时加载不到符号表。这种情况常规排查思路会走偏——因为它不是Python包的问题而是系统级依赖。可以用下面命令检查真实加载路径ldd venv_ocr/bin/python如果发现某一行是“not found”就用find / -name libcrypto.so*找到合适的库文件然后通过LD_LIBRARY_PATH临时指定。4.3 上线后的优化把OCR服务工程化绿色部署版本跑通后后续要接业务系统时最好把OCR能力做成一个本地服务而不是每次在命令行里调Python脚本。我用Flask写了个简单的HTTP接口from flask import Flask, request, jsonify import base64 from paddleocr import PaddleOCR app Flask(__name__) ocr PaddleOCR(use_angle_clsTrue, langch) app.route(/ocr, methods[POST]) def ocr_api(): data request.get_json() img_bytes base64.b64decode(data[image]) with open(/tmp/ocr_input.png, wb) as f: f.write(img_bytes) result ocr.ocr(/tmp/ocr_input.png, clsTrue) lines [{text: r[1][0], score: r[1][1]} for r in result[0] if r] return jsonify({lines: lines}) if __name__ __main__: app.run(host127.0.0.1, port5000)业务系统通过HTTP POST调用图片编码成Base64传过去拿回结果后处理。这个接口在麒麟系统上跑得很稳定内存占用在800MB左右适合作为本地私有化OCR服务。考虑到最终使用者的习惯我在启动脚本里专门做了两个版本一个是命令行批处理工具指定一个文件夹自动递归识别所有图片生产结果保存为JSON另一个是启动Web服务让使用者在浏览器里上传图片查看识别结果。这样同事无需安装任何客户端打开浏览器就能干活。4.4 一个绕不开的认知误区PaddleOCR识别不依赖字体文件有段时间识别结果总是“空”排查好久一度以为是系统缺中文字体导致识别不出来。后来查阅资料和测试才确认识别模型本身完全不依赖系统字体文件它是直接对图像像素做卷积和序列解码的。中文字体只影响PaddleOCR可视化组件在图片上画识别文本时的显示效果跟识别结果无关。如果场景里识别结果确实为空排查优先级应该先看方向分类器、再看图像质量、最后确认模型加载路径。用一张纯白底黑字的测试图来缩小问题范围是最快的定位方式。还有一个容易被忽视的点图片的透明通道Alpha通道。部分扫描软件输出的PNG带透明通道PaddleOCR加载后如果不做处理识别结果可能异常。我在封装接口时统一把PNG转换成了RGB三通道顺手把模式不统一的问题也解决了from PIL import Image img Image.open(/tmp/ocr_input.png).convert(RGB) img.save(/tmp/ocr_input_rgb.png)5. 最后分享两件实际操作中验证过的小技巧第一件打包整个venv目录之前手动删掉venv_ocr/lib/python3.8/site-packages下所有__pycache__和*.pyc缓存文件能明显缩小打包体积。我那个环境从500多MB瘦身到420MB左右U盘拷贝节省一半时间。第二件如果目标机器有大于4GB的大U盘插上认不出来大概率是U盘文件系统格式问题。麒麟系统对exFAT支持度不如FAT32但FAT32单文件不能超过4GB。我的做法是把打包文件分割或者干脆用两块存储保证能顺利把文件拷过去。再补一点纯CPU环境下第一次实例化PaddleOCR()对象时模型加载可能需要10到15秒这是正常的因为推理引擎在做模型初始化。如果业务系统对首次响应时间有要求建议服务启动时预热一次比如启动后立即对空白图片跑一次推理后面的请求就不再有这个冷启动延迟。这个方案做完之后我又在另一批aarch64架构的麒麟机器上复现过一遍流程完全一致只要保证wheel包、模型文件是基于arm64架构的就行。所以无论你的目标环境是哪种CPU基本都可以照着这个思路推。搞国产化系统部署很多时候不是技术有多难而是“没网怎么搞定一切”这个限制让常规经验全部失效。把环境做成自包含的绿色目录提前把所有依赖固化好现场解压即用——这套打法基本可以复用在你接下来遇到的任何一个离线部署需求上。
返回列表