简介:这是一份借助PaddleOCR构建的离线文字识别工具包,面向在无Python环境中需要完成图片文字识别的开发者,解决批量OCR与结果保存的实际需求。压缩包共两千个文件,含Python源码与pyc缓存、pyd/dll动态库、msg/tcl等运行依赖,以及txt说明文档和配置文件,整体约279MB,可支撑离线打包环境的还原与再构建。已有3784人学习下载,适合熟悉打包但希望获得完整现成依赖目录的开发者。包内除PaddleOCR推理所需的原生扩展外,还包含模型配置、参数调整文件及运行支撑组件,可避免额外下载;通过查看脚本与目录结构,能理解工具封装层次、图像读取到文字识别并输出结果的完整链路,便于批量识别、接口集成或界面化等二次开发。资源目录结构清晰,便于定位模型、配置与运行库,适合作为离线OCR项目的基线模板。
1. 把 PaddleOCR 打包成“双击就能用”的离线工具,比你想的更值得做
生产环境里最常听到的一句话是:“我知道 PaddleOCR 效果好,可我现场机器没有 Python,也没网。”拿我经手的一个设备检修项目举例:客户把训练好的 PaddleOCR 模型部署到一台老 Windows 工控机上,模型服务也在服务器上跑得好好的,结果拷到现场双击就报“ModuleNotFoundError”,这才意识到这台现场机器连 Python 解释器都没有,而厂区 IT 策略又禁止随意安装运行时。PaddleOCR 打包 exe,就是把 OCR 识别能力从“需要环境”变成“自带环境”,模型、依赖、推理库全部塞进一个可执行文件或一个目录,双击就能用。这个“离线工具”的刚需程度远比很多人想的严重,我自己前后踩了快二十个坑才稳定下来。这篇就按“原理 → 实操 → 避坑 → 进阶”的顺序,把这条路完整讲清楚。
2. PaddleOCR 打包前的“全家桶”:依赖、模型和离线环境的三角关系
2.1 OCR 识别链路里哪些东西最容易在打包时被漏掉
PaddleOCR 不是一个简单的单文件程序,它是一条从图像输入到文本输出的完整推理链路。这条链路上至少有五个环节会参与打包:图像预处理、文本检测、方向分类、文本识别和结果后处理。这里面的每一个环节都可能带独立的 Python 依赖和原生动态库,PyInstaller 只在少数情况下能帮你自动识别,更多时候要靠手动补。
以我在 Windows 下常用的 PaddleOCR 3.x 版本举例,打包时最常被漏掉的是这几类东西:
| 依赖对象 | 出现的环节 | 漏掉后的典型报错 |
|---|---|---|
paddle动态库和paddle.libs下的若干 DLL | 推理引擎启动 | Illegal instruction或加载失败 |
onnxruntime的 DLL(如果走 OpenVINO/ONNX 后端) | 检测/识别推理 | DLL load failed while importing onnxruntime_pybind3_state |
shapely、pyclipper的原生库 | 文本检测的后处理 | AttributeError: module shapely.geometry has no attribute Polygon |
OpenCV 的cv2及其 FFmpeg 相关 DLL | 图像预处理/结果画框 | FileNotFoundError: ffmpeg.dll |
PaddleOCR 的插件目录(.paddleocr下的插件) | 3.x 版本新增插件机制 | PluginNotFoundError |
这种“全家桶”结构导致一个很现实的问题:你在开发环境跑得通,不代表 PyInstaller 打包后跑得通。开发环境里所有依赖都装好了,Python 解释器会按sys.path去找包,而打包后的 exe 是按 PyInstaller 的依赖分析结果来找,分析结果不全,程序就会在运行时才暴露出缺失。
我自己习惯在打包前先做一次静态盘点,用一个脚本把当前环境里所有和 PaddleOCR 相关的包路径打印出来,比对着 PyInstaller 生成的 warn 文件逐项排查。这个习惯能少走至少一半弯路。
2.2 为什么离线场景比在线 API 更适合打包 exe
OCR 业务的接入方式大致有三条路:在线 API、本地 Python 服务、打包 exe。在线 API 的好处是零部署,但现实里很多场景根本不允许。比如产线工位机、医院病历录入终端、政务内网电脑,这些机器要么不能连通外网,要么数据敏感不能上传图片。本地 Python 服务也不够省事,它要求现场有一台跑着 Python 环境的机器,还要常驻进程,出问题排查起来动静很大。
离线 exe 工具在这类场景里的价值是“部署即用”。把识别端做成 exe 之后,现场安装就变成拷贝文件、双击、打开命令窗口,不需要进 Python 环境,不需要装 CUDA,也不需要配PADDLE_PDX_DIR之类的环境变量。对一线上门服务的工程师来说,这直接决定了今天能不能收工回家。
但离线 exe 有一个隐含前提:模型文件必须随程序一起分发。这就带来一个取舍——模型是内置进 exe,还是外置成模板目录。我倾向于外置,原因很直白:内置会让 exe 体积膨胀,而且一旦模型更新就要重新打包整包;外置的话,模型和可执行文件各管各的,升级模型只需要替换文件。工控机上模型版本更新很频繁,外置才是能长期维护的形态。
2.3 PyInstaller 还是 Nuitka:我的选型理由
PaddleOCR 打包 exe 的技术方案里,最多人用的是 PyInstaller,也有少数人尝试 Nuitka 和 cx_Freeze。三个方案都试过之后,我的结论是:
PyInstaller 是最省心的默认选择。它的--collect-all、--hidden-import、--add-data一套组合拳,配合注解式代码就能把绝大多数依赖收进来。PaddleOCR 的社区里大多数人用的也是它,遇到问题能搜到的解决方案最多。缺点是打出来的产物偏大,启动时的解压在单文件模式下会慢一点。
Nuitka 走的是“把 Python 编译成 C 再链接”的路线,启动速度通常更快,因为不需要在运行时解压全部文件。但 Nuitka 对 Paddle 这种重度依赖原生 DLL 的项目不够友好,编译时间动辄二十分钟起步,而且一旦某个包有动态导入,排查复杂度比 PyInstaller 高。除非你对启动速度有硬指标,否则我建议先用 PyInstaller。
cx_Freeze 的脚本配置风格更像setuptools,对习惯了setup.py的开发者友好一点,但它在处理 Paddle 这种带大量可见依赖的包时,自动收集能力明显弱于 PyInstaller,还需要手写很多include_files规则。
所以后面所有实操步骤,我统一按 PyInstaller 的路径写。如果你已经在用 Nuitka 且项目能跑通,不需要推翻重来;如果你是刚要开始,直接 PyInstaller。
3. 把 PaddleOCR 变成离线 exe:从环境准备到产物验证的完整路径
3.1 搭建干净的三方依赖环境
打包的第一步不是写 spec 文件,而是把 Python 环境收拾干净。我强烈建议你建一个全新的虚拟环境,不要用 Anaconda 主环境,也不要用已经装了其他项目的全局环境。因为 PyInstaller 会把虚拟环境里能看到的包都扫一遍,环境越乱,产物体积越大,漏依赖的概率也越高。
这里有一个并行考虑:你的目标机器是 Windows,那就在 Windows 上打包,而不是在 Linux 上交叉打包。Paddle 的 DLL、OpenBLAS、OpenCV 的 DLL 都是平台强相关的,Linux 上打不出能跑在 Windows 的 exe。最稳的做法是在一台干净的 Windows 机器上,装一个全新的虚拟环境,装完依赖再打包。
# 创建一个干净的 Python 3.9 虚拟环境,建议固定 3.9/3.10 python -m venv paddle_venv paddle_venv\Scripts\activate # 升级构建工具链 python -m pip install --upgrade pip setuptools wheel # 安装 CPU 版 PaddlePaddle,注意不要装 GPU 版,目标机大概率没有 N 卡 python -m pip install paddlepaddle==2.6.1 -i https://mirror.baidu.com/pypi/simple # 安装 PaddleOCR 本体 python -m pip install paddleocr==2.7.3 -i https://mirror.baidu.com/pypi/simple # 安装 PyInstaller,版本不需要追新,用当前稳定版即可 python -m pip install pyinstaller==6.6.0这段命令里我特意把版本号写死了。为什么?paddleocr和paddlepaddle的版本组合不是越新越好,3.x 的 PaddleOCR 用了新的插件机制,对 PyInstaller 的隐藏导入识别更麻烦;2.7.x 虽然老一点,但社区资料多,踩坑预案也最全。PaddleOCR 2.7.3 配 paddlepaddle 2.6.1 是一套经过大量项目验证过的组合,你先跑通这套再决定要不要升级。
提示:如果目标机器是离线内网,记得先把 wheel 包下载到本地,现场再
pip install --no-index --find-links安装。这一步是很多项目组最后翻车的地方,想着能打包就没准备离线安装包,结果到了现场连第三方库都没法装。
装完依赖之后,先不急着打包,在虚拟环境里跑一次最简单的 OCR 推理,确认当前环境下代码能正常识别一张图。这一步是为了排除环境本身的问题,否则后面打包出了 bug,你会分不清是环境问题还是 PyInstaller 问题。
3.2 模型外置方案:把模型和 exe 放在同一目录下
我坚持模型外置的原因前面提过,这里给出具体实现。PaddleOCR 支持通过ocr接口指定det_model_dir、rec_model_dir和cls_model_dir,我们可以把这些路径指向 exe 同级的models目录。这样程序无论在 U 盘、桌面还是 D 盘根目录,都能稳定找到模型。
import os import sys from paddleocr import PaddleOCR def get_base_dir(): # PyInstaller 打包后 sys.executable 是 exe 路径; # 开发环境下 sys.executable 是 python.exe 路径,也能用 return os.path.dirname(os.path.abspath(sys.executable)) def create_ocr(): base_dir = get_base_dir() models_dir = os.path.join(base_dir, "models") ocr = PaddleOCR( # 使用中文模型,识别中英文混排 lang="ch", use_angle_cls=True, # 外置模型路径,不随 exe 内置 det_model_dir=os.path.join(models_dir, "det"), rec_model_dir=os.path.join(models_dir, "rec"), cls_model_dir=os.path.join(models_dir, "cls"), # 关闭 GPU,离线目标机基本没有 CUDA 环境 use_gpu=False, show_log=False, ) return ocr这段代码的核心逻辑是get_base_dir()函数。很多人第一次写的时候会用os.getcwd()来定位当前目录,这在开发环境没问题,但双击 exe 时工作目录并不一定等于 exe 所在目录。双击方式启动 exe 时,工作目录有可能是系统盘用户目录,os.getcwd()会指向一个你完全想不到的地方。用sys.executable取 exe 的绝对路径,再取自身目录,最保险。
注意:如果之后你改用 PyInstaller 的
--onefile模式,sys.executable指向的是临时解压目录,模型外置 + sys.executable这套方案依然成立,因为这些模型路径是外置的,不影响运行时。但如果模型是内置的,就绝对不能用sys.executable定位,要用sys._MEIPASS,这个坑后面专门讲。
模型目录的文件结构按 PaddleOCR 的默认布局即可。det目录里放检测模型,rec目录里放识别模型,cls目录里放方向分类器。这些目录里有inference.pdmodel、inference.pdiparams等文件,直接拷贝过去就行,不用特殊处理。
3.3 打包命令与 spec 文件:一次把依赖收集完整
环境准备好了,模型路径设计好了,接下来就到了最核心的打包环节。我建议不要直接用pyinstaller命令行带一堆参数,而是把配置写进.spec文件。spec 文件的好处是配置可重复,你改一个参数重新打包,不用把长达十几行的命令重新敲一遍。
先给出我第一次跑通项目的完整 spec 文件:
# paddle_ocr_tool.spec # 这个 spec 用于打包 PaddleOCR 离线识别工具 # 运行方式:pyinstaller paddle_ocr_tool.spec # -*- mode: python ; coding: utf-8 -*- import os block_cipher = None project_root = os.path.abspath(".") models_path = os.path.join(project_root, "models") a = Analysis( ["ocr_tool.py"], # 程序入口脚本 pathex=[project_root], binaries=[], datas=[ # 当前模型选择外置,所以 datas 里不用放模型 ], hiddenimports=[ # PaddleOCR 动态导入点,让 PyInstaller 强制收集 "paddleocr", "paddle", "paddle.nn", "paddle.tensor", "paddle.utils", "shapely", "pyclipper", "skimage", "imghdr", "paddleocr.plugin", ], hookspath=[], runtime_hooks=[], excludes=[ "matplotlib", # 推理不需要画图,排除可明显瘦身 "tkinter", "PIL.ImageTk", "paddle.audio", ], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name="paddle_ocr_tool", debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True, # 保留命令行窗口,方便看日志 disable_windowed_traceback=False, ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name="paddle_ocr_tool_dist", )这个 spec 文件有几个值得展开的参数:
hiddenimports里列的paddleocr.plugin是 3.x 版本必须要加的,不加会在运行时报PluginNotFoundError。shapely、pyclipper是文本检测后处理链路上的原生库,PyInstaller 经常漏掉它们,尤其是在它们被paddleocr内部以from shapely.geometry import Polygon这种形式调用时。
excludes里排除了matplotlib和tkinter。很多 PaddleOCR 示例代码里有plt.imshow之类的可视化逻辑,但真正的离线服务不需要这些,排除之后产物体积能小几十 MB。注意,如果你在调用代码里确实用了matplotlib画图,就别排,否则运行时报ModuleNotFoundError。
console=True是我个人的强烈建议。离线工具最容易遇到的问题就是双击没反应,如果做成console=False(窗口模式),报错信息会被吞掉,连程序启动到哪一步崩了都看不到。带命令行窗口,用户至少能把 traceback 截图发给你。等真正稳定了,想做成无窗口工具再改成False不迟。
提示:
upx=True能有效压缩 DLL 体积,但 UPX 对某些 DLL 解压后可能出现运行时崩溃。如果打包后运行报错指向特定 DLL,先把upx=True改成False再测一次,这是排查顺序里很常见的一步。
有了 spec 文件,打包命令非常简单:
# 在虚拟环境里执行 pyinstaller paddle_ocr_tool.spec --clean --noconfirm--clean清除上次打包的缓存,--noconfirm覆盖输出目录而不询问。打包完成后,dist\paddle_ocr_tool_dist目录就是完整的离线工具目录。你把这个目录整体拷到目标机器,再把模型目录放进同一级,双击paddle_ocr_tool.exe就能跑。
3.4 对体积敏感的场景:裁剪 Paddle 推理库的三个手段
PaddleOCR 打包后的 exe 或目录体积动辄 800MB 到 1.2GB,这对很多项目是难以接受的,尤其是要通过 U 盘分发或者部署到空间紧张的工控机。体积压缩有三个手段,按见效从高到低排列。
手段一:使用 Tiny 系列模型代替标准模型。PaddleOCR 的 PP-OCRv4 tiny 版本在识别精度损失有限的条件下,模型文件大小能降到标准版的十分之一。检测模型从 4.5MB 降到 1.3MB,识别模型从 11MB 降到 2.8MB,方向分类器从 0.8MB 降到 0.5MB。对大多数单据识别、工牌识别场景,tiny 模型的精度完全够用。而且识别速度和 tiny 模型的组合在近期的社区讨论里口碑不错,速度提升明显,这也是我为什么推荐 2.7.x 配 2.6.1,因为新版 PaddleOCR 对 tiny 模型的支持更完善。
手段二:裁剪paddlepaddle的音频和视觉子模块。paddlepaddle本身包含了语音、图像、文本等多个领域的预置层,OCR 推理只用到其中一部分。在 spec 文件的excludes里把paddle.audio、paddle.vision排除,再排除paddle.dataset和相关数据集加载模块,能省出上百 MB。
手段三:合理控制--onefile和--onedir的选择。很多人冲着 “一个 exe 文件便于分发” 去用--onefile,但单文件模式会把所有依赖解压到临时目录,启动慢,且体积并不比 onedir 小。如果你能接受“一个文件夹”,--onedir是更好的选择——启动快、升级方便、扩展灵活。把整个文件夹压成一个 zip 分发,效果类似。
3.5 首次启动自检函数:让 exe 自己报出问题
打包工具最容易让人崩溃的地方在于:开发机跑得好好的,拷到现场双击没反应,而且没有任何报错。唯一能挽回一点局面的手段,是写一个启动自检函数。在ocr = create_ocr()之前,先检查模型路径是否存在、关键 DLL 是否可加载、当前工作目录是什么,并把检查结果打印到控制台。
def self_check(): """打印程序运行的基本环境信息,帮助定位部署问题""" import platform import ctypes print("Python 版本: ", platform.python_version()) print("exe 路径: ", sys.executable) print("当前工作目录: ", os.getcwd()) base_dir = get_base_dir() models_dir = os.path.join(base_dir, "models") det_pdmodel = os.path.join(models_dir, "det", "inference.pdmodel") rec_pdmodel = os.path.join(models_dir, "rec", "inference.pdmodel") cls_pdmodel = os.path.join(models_dir, "cls", "inference.pdmodel") if os.path.exists(det_pdmodel): print("[OK] 检测模型存在: ", det_pdmodel) else: print("[ERR] 检测模型缺失: ", det_pdmodel) if os.path.exists(rec_pdmodel): print("[OK] 识别模型存在: ", rec_pdmodel) else: print("[ERR] 识别模型缺失: ", rec_pdmodel) if os.path.exists(cls_pdmodel): print("[OK] 方向分类模型存在: ", cls_pdmodel) else: print("[ERR] 方向分类模型缺失: ", cls_pdmodel) # 上面这行是 ctypes 的哨兵检查,不实际调用库, # 只验证 Windows 下能否正常加载系统 DLL,排除系统环境问题 try: ctypes.windll.user32.GetMessageW(0, 0, 0, 0) print("[OK] 系统动态库访问正常") except Exception as e: print("[ERR] 系统动态库访问异常: ", e)自检函数的核心价值是把“程序启动到哪一步挂掉”变成可见的过程。模型缺失会打印[ERR] 检测模型缺失,只要这一行出现在控制台,就可以直接指导现场人员去检查模型目录。这个函数不用做得很复杂,十几行代码能省掉大量来回传 log 的沟通成本。
这个过程做下来,exe 已经能正常识别图片了,但从“能跑”到“稳定地上线到每台机器都能跑”,中间还有不少反复。这块内容放到下一章集中讲。
4. 离线打包避坑实录:5 个让 exe 反复翻车的现场
4.1 单文件模式的临时目录黑洞
现象:用--onefile打包后,程序在本机运行正常,换一台机器运行报FileNotFoundError: model file not found。开发环境里明明模型就在 exe 同级目录,为什么识别不到?
原因:PyInstaller 的--onefile模式启动时会把自身解压到系统临时目录(通常是C:\Users\xxx\AppData\Local\Temp\_MEIxxxxxx),然后把当前工作目录切换到这个临时目录,再执行程序。如果你的代码用了os.getcwd()去找模型路径,找到的就是临时目录,而你的模型在 exe 所在目录,当然找不到。
解决:把模型定位逻辑从“当前工作目录”改为“exe 所在目录”,也就是我前面讲的sys.executable方案。一句话总结,别信工作目录,信 exe 自身物理路径。如果你的模型想内置进 exe,那就必须用sys._MEIPASS定位;如果外置模型,就用sys.executable定位。这两个路径--onefile模式下指向完全不同。
4.2 第三方动态库缺失导致加载即崩溃
现象:双击 exe 后弹窗DLL load failed while importing pyclipper,或者程序日志里出现ImportError: DLL load failed。开发环境用paddleocr命令行跑完全正常,但 exe 就只有这个错。
原因:PyInstaller 对纯 Python 包自动收集得很干净,但 C 扩展库的动态依赖经常分析不全。具体到 PaddleOCR,pyclipper和shapely的 DLL 经常被漏。漏掉的原因一般是这两个包用了延迟加载,PyInstaller 的静态分析只能看到“import shapely”,看不到 shapely 内部还依赖geos.dll。
解决:在项目根目录下找到build\paddle_ocr_tool\warn-paddle_ocr_tool.txt文件,里面会列出 PyInstaller 没找到的模块和缺少的动态依赖。通用的做法是在hiddenimports里手动加:
"shapely", "pyclipper", "skimage", "imghdr"如果加了 hiddenimports 仍报 DLL 缺失,另一个有效做法是用手工拷贝命令把对应的 DLL 直接拷到 dist 目录:到虚拟环境的Lib\site-packages\shapely\libs下把geos_c.dll复制进paddle_ocr_tool_dist的根目录。反复测试下来,这是最稳妥的兜底方案。
4.3 杀毒软件把 exe 当病毒隔离
现象:打包产物在开发机自测没问题,发给客户后对方说“打不开”,一看是 Windows Defender 直接把 exe 隔离了。PyInstaller 打包的 exe 很容易被误报,有项目的报毒率相当高。
原因:PyInstaller 生成的 exe 是把 Python 解释器打包进了可执行文件里,行为和常见加壳程序很像,杀毒软件会基于行为特征判定为可疑。加上 Paddle 在推理时要加载大量 DLL,这些动态行为更容易触发误报。
解决:三步走。第一,给 exe 加数字签名,有正规签名的 exe 误报率能大幅下降。第二,打包时尽量用--onedir而不是--onefile,目录形态的误报率通常更低。第三,向杀毒软件厂商提交误报申诉,至少对 Windows Defender 可以走一遍提交流程。需要注意的是,如果你的目标客户是政企内网,签发的自签名证书往往不够,最好是正规 CA 的代码签名证书。BTW,这里说一句血泪经验:项目里不要用没授权的方式做签名,也不要打包后手工改 PE 资源伪装签名,这给自己埋雷。正规渠道的成本和难度没想象中高,但一旦客户环境有安全合规要求,这一步就是硬门槛。
4.4 多线程推理在冻结环境下卡死或重复执行
现象:离线工具启动后界面卡住,或者程序重复执行了两次 OCR 逻辑,每次识别结果都一样。某些场景下还会出现An attempt has been made to start a new process before the current process has finished its bootstrapping phase的报错。
原因:PyInstaller 打包后的程序在 Windows 多进程场景下有特殊要求。PaddleOCR 内部推理不一定多进程,但如果你在代码里用multiprocessing管理并发识别,就会触发freeze_support()问题。Python 多进程在 Windows 下是通过重新导入主模块实现的,但 exe 里没有正常的模块导入机制,需要标注冻结支持。
解决:在程序的入口if __name__ == '__main__':块内,第一行加上:
from multiprocessing import freeze_support if __name__ == '__main__': freeze_support() ocr = create_ocr() # ... 其余业务逻辑freeze_support()是一个 PyInstaller 和 cx_Freeze 都支持的钩子,它会在当前进程是子进程时提前退出导入逻辑,否则子进程会重新执行主模块,导致你的 OCR 初始化逻辑跑两遍。记住这句话:只要代码里用了multiprocessing,打包时就必须在入口加freeze_support()。但如果你只是单线程跑 OCR,一直没加也正常,不算 bug。
4.5 后台任务路径依赖导致换目录就挂
现象:打包好的工具在桌面运行正常,但把它移动到D:\tools\ocr下就报错,提示找不到config或models目录。而开发机测试时明明已经验证过模型路径是外置的。
原因:多半是程序里有“隐含的绝对路径依赖”。最常见的是读配置文件时用了open("config.yaml"),这个相对路径会依赖当前工作目录,而不是 exe 所在目录。另一个常见来源是sys.path.insert写死了某个开发路径,打包时 spec 文件里pathex也沿用了这个路径,导致运行时在目标机器上找不到。
解决:通读代码,把所有open(...)、os.path.join(...)改成基于 exe 物理路径的绝对路径,统一走get_base_dir()。特别是模型目录、配置目录、日志文件输出目录,一定要显式拼路径,不要依赖相对路径。我这里有个自查技巧:在self_check()里打印出所有关键路径,然后手动把 exe 移到一个深层级目录里再跑一次,看输出的路径是否还是指向正确位置。这种“换目录测试”做一轮,隐性路径依赖基本能清干净。
5. 进阶一步:给 exe 加命令行接口,并验证推理回退
打包成功后,我不建议直接把 exe 设计成带图形界面的产品。更务实的做法是先把它做成一个稳定的命令行工具,方便现场调试和脚本调用。用argparse加几个参数就能实现:
import argparse def parse_args(): parser = argparse.ArgumentParser(description="PaddleOCR 离线识别工具") parser.add_argument("--image", "-i", required=True, help="输入图片路径") parser.add_argument("--output", "-o", default="result.txt", help="识别结果输出文件路径") parser.add_argument("--max-side-len", "-m", type=int, default=960, help="输入图片长边缩放阈值") parser.add_argument("--use-tiny", action="store_true", help="提示使用 tiny 模型,配合加载逻辑使用") return parser.parse_args()这样现场执行paddle_ocr_tool.exe --image E:\scan\20250110\a.jpg --output E:\result.txt就能把识别结果写到指定路径,很适合接入批处理脚本或 PLC 自动化流程。
关于识别速度,我要提一个近期社区里讨论很多的方向:用 ONNX Runtime 替代 Paddle 原生推理。PaddleOCR 的 ONNX 导出工具可以把 PP-OCRv4 tiny 模型转成 ONNX 格式,然后推理时使用 ONNX Runtime 后端。这样做的好处是明显的:ONNX Runtime 的单文件依赖比 PaddlePaddle 整个推理框架轻量得多,打包体积能再砍掉不少;而工程实现上,ONNX Runtime 的推理稳定性更好,不容易出现动态库缺失。网上有人做的对比里,ONNX Runtime 加 tiny 模型在 CPU 上的耗时和 Paddle 原生推理相当,甚至更快,识别精度没有明显下降。如果你的离线工具要部署到很老的双核工控机,这条路值得试一试。代价是模型导出这一步需要额外做转换,而且 PaddleOCR 3.x 里新增的插件机制不支持直接转 ONNX,需要手动梳理插件逻辑。
验证环节一定不能省。我常用的验证方式有两层:第一层是非回归自测,准备一组固定图片(含清晰打印体、模糊手写体、旋转文本),在开发机上用原始 Python 环境跑一遍得到基准结果,再拿打包后的 exe 跑一遍,对比识别文本是否一致。第二层是集成自测,模拟真实使用场景,比如把 50 张图片批量跑一遍,统计识别成功率和平均耗时,确认稳定后再出给客户。
最后一件事,也是我踩过最深的一个坑:不要只测 exe,不测分发形态。你拷贝到 U 盘再拷出来的目录和本地 dist 目录可能差几个文件,尤其是杀毒软件过滤掉 DLL 的情况很常见。我现在的习惯是每次分发前,把整个 dist 目录压缩成一个 zip,计算 MD5,然后在另一台干净的虚拟机上解压并完整跑一遍测试用例,通过后才敢发给现场。这套流程看着笨,但真的救过我很多次。
离线 exe 这件事,本质上就是把一个复杂的技术栈摊平到“部署”这一件简单事上。希望这篇笔记能帮你把 PaddleOCR 打包这条路的坑提前绕过去,也希望你的离线工具第一次拷到现场就能双击出结果。
本文还有配套的精品资源,点击获取