简介PaddleOCR离线EXE识别工具包面向需在无Python环境下快速完成文字识别的开发者和实施人员将PaddleOCR模型、运行依赖及调用程序封装为可直接执行的exe工具输入图片路径即可输出识别结果适合批量截图转文本、票据信息抽取等场景。压缩包共2000个文件大小约279.22MB包含Python源码与字节码py/pyc、原生动态库pyd/dll、依赖配置文件ini/dll以及运行时所需的时区数据库资源便于离线部署与二次裁剪。已有3789人浏览学习工具不仅覆盖多语言OCR识别流程还保留了完整PyInstaller打包配套结构与依赖清单使用者可对照调整图片路径接口、输出格式或嵌入批处理流程。包内模型与运行文件分类清晰能帮助读者理解PaddleOCR从脚本到可分发exe的完整构建方法降低模型轻量化与打包部署的实操成本。1. 离线部署的刚需PaddleOCR 打包 exe 离线工具到底解决了什么OCR 模型在开发机上跑得风生水起等真要交付给现场用户时麻烦就来了目标机器没装 Python、没有 PaddleOCR 依赖链、甚至不能联网。paddleocr 打包 exe 离线工具就是把你训练好的检测、识别、方向分类模型连同推理库、运行环境全部收进一个目录对方双击 exe 就能跑不用装任何东西。但同时我得先说个反直觉结论打包命令只是最后一步真正的翻车点几乎全在它之前——模型没导成静态图、推理库选了 GPU 版、入口代码里路径写死了随便踩中一个都够你折腾半天。这套思路适合做工具型交付的开发者比如给现场做质检、单据识别、档案数字化的场景。2. 先选对打包对象模型、推理库与运行时的组合策略很多资料一上来就教你敲pyinstaller -F我建议先忍住。打包 exe 本质上是把你开发环境里跑通的那套依赖「搬进」一个独立目录搬哪些、怎么搬直接决定产物能不能用。这一章先把打包对象拆清楚再给选型建议这也是后面所有操作的地基。2.1 离线工具的构成三个模型文件加推理库加运行时一个能离线跑的 OCR 工具拆开看就是三块东西。第一块是模型文件PaddleOCR 默认识别链路由三个模型组成文字检测det负责找出图片里哪些区域有文字方向分类cls把横躺、倒置的图片扳正文字识别rec把区域图像转成字符串。三个模型缺一个链路就断漏了 cls旋转 180 度的图片识别结果基本是乱码漏了 detrec 就没有输入区域。打包前先把这三个模型梳理清楚。第二块是推理库也就是 paddlepaddle 及其依赖。第三块是 Python 运行时和第三方库比如 opencv、shapely、pyclipper 这些。PyInstaller 做的就是把后两块收集进_internal目录再把第一块作为资源文件带过去。以 onedir 模式下典型的产物目录为例ocr_tool/ ├─ ocr_tool.exe ├─ _internal/ │ ├─ paddle/ │ ├─ paddleocr/ │ ├─ opencv/ │ └─ ... 其他依赖 └─ inference/ ├─ det/ ├─ rec/ └─ cls/这里的inference就是静态导出的模型目录注意它必须和 exe 放在同一层级路径逻辑后面第 3 章细说。很多人在这一步就犯了第一个错把动态训练用的权重文件.pdparams直接塞进包结果程序起得来但识别结果全是空。原因很简单PaddleOCR 通过PaddleOCR(det_model_dir...)加载的是推理模型不是训练权重两者文件的组织方式完全不一样。2.2 模型选型server 与 mobile 怎么取舍PaddleOCR 官方在多个版本里都提供 server 和 mobile 两类模型。mobile 系列是为端侧和 CPU 场景设计的模型文件更小推理更快精度上有轻微损失server 系列精度更高但体积和耗时都上去了。对于打包 exe 这种离线交付场景我一般默认选 mobile。对比项mobile 系列server 系列单模型体积通常小几十 MB更大文件明显增多CPU 推理速度快单张图秒级内慢耗时明显上升精度够用边缘场景略弱更好适合密集小字打包后产物体积整体可控容易膨胀适合场景离线工具、普通办公机服务端、性能有冗余的机器如果你的目标机器 CPU 比较老或者用户一跑就是几百张图优先 mobile。把 det、rec、cls 三个模型全配成 mobile 系打包产物体积和速度都更稳。如果现场对精度要求极高比如小字号、弯折票据再考虑 server但要在产物体积和推理时间上做足心理准备。2.3 推理库版本paddlepaddle 的 CPU 版与版本匹配第二个容易踩的坑是选了 GPU 版 paddlepaddle。GPU 版会带上 CUDA 相关运行库打包后体积动辄多出几百 MB而在目标机器没有对应显卡驱动的情况下exe 甚至会直接启动失败。离线分发工具一律用 CPU 版。常见做法是单独建一个干净的 venv然后只装 CPU 版的 paddlepaddle别偷懒复用你平时跑训练的那个环境那个环境里很可能残留 GPU 依赖被 PyInstaller 一起收进包。版本匹配也要提防paddleocr 和 paddlepaddle 的版本是有对应关系的两个都写latest装出来大概率初始化报错。我习惯的做法是先看当前 paddleocr 分支要求的最低 paddlepaddle 版本然后把两者都固定到具体版本号写进 requirements.txt而不是依赖 pip 自动解析。版本号这种玄学问题等到打包阶段再排查成本比现在高得多。还有一个平时不会注意的细节新建的打包环境里opencv 不要装 opencv-python-headless 以外的变体否则会连带拉进 GUI 依赖无谓地增大包体。这一层的选择直接决定第 4 章 spec 文件里要写多少 excludes。3. 动手前先埋好点静态导出与入口代码改造打包失败的最常见原因不是 PyInstaller 不会用而是你喂给它的程序本身就依赖了开发机上的绝对路径和动态导入。这一章讲两个打包前必须完成的工作把动态模型导出成静态推理格式以及把入口代码改成能自适应打包态的路径写法。3.1 静态模型导出把动态图转成 inference 结构PaddleOCR 训练时用的是动态图权重文件是.pdparams部署时更推荐加载静态推理模型也就是.pdmodel加.pdiparams的组合。静态模型少了动态图构建的开销加载和推理都更稳定PyInstaller 打包时也更容易识别文件依赖。导出命令以检测模型为例python tools/export_model.py \ -c configs/det/ch_PP-OCRv4_det.yml \ -o Global.pretrained_model./pretrain/ch_PP-OCRv4_det_train \ Global.save_inference_dir./inference/det这条命令的核心参数就三个-c指定算法配置文件里面写明了模型结构Global.pretrained_model指向训练产出的权重Global.save_inference_dir是静态模型输出目录。识别模型和方向分类模型同理分别用各自对应的 yml 配置导出到inference/rec和inference/cls。导出完成后检查目录inference/ ├─ det/ │ ├─ inference.pdmodel │ └─ inference.pdiparams ├─ rec/ │ ├─ inference.pdmodel │ └─ inference.pdiparams └─ cls/ ├─ inference.pdmodel └─ inference.pdiparams注意一个容易看走眼的地方PaddleOCR 某些版本导出 rec 模型时还会带出一个字典文件ppocr_keys_v1.txt这个文件决定字符映射漏了它识别结果全变成一串乱码或者直接报字符越界。把它放在inference/rec/目录下随包分发并在初始化时通过rec_char_dict_path显式指定。3.2 入口代码改造规避路径与多进程两个坑模型导好之后入口代码不要直接拿训练时的 demo 来打包。训练时ocr PaddleOCR()会走默认路径自动去官方地址下载模型离线环境根本走不通而且打包后当前工作目录会被 PyInstaller 重定向。我一般会把初始化逻辑单独抽成一个模块用一个_base_dir函数统一处理开发态和打包态的路径差异。# ocr_worker.py import sys from pathlib import Path from paddleocr import PaddleOCR def _base_dir() - Path: # 打包后程序在 PyInstaller 的临时解压目录里运行 if getattr(sys, frozen, False): return Path(sys._MEIPASS) # 开发阶段直接定位到项目根目录 return Path(__file__).resolve().parent def init_ocr(): base _base_dir() ocr PaddleOCR( det_model_dirstr(base / inference / det), rec_model_dirstr(base / inference / rec), cls_model_dirstr(base / inference / cls), rec_char_dict_pathstr(base / inference / rec / ppocr_keys_v1.txt), use_angle_clsTrue, # 方向分类旋转图片靠它扳正 langch, # 识别中文 use_gpuFalse, # 离线工具一律 CPU show_logFalse, # 关闭控制台日志避免干扰 ) return ocr这个_base_dir是整个打包成败的关键之一。PyInstaller 的 onedir 模式下程序运行目录可能是_internal里的临时位置onefile 模式更是会把内容解压到系统临时目录你如果写死成./inference在常规运行时几乎必然找不到模型。sys._MEIPASS是 PyInstaller 注入的变量指向资源真正被解压的目录判断条件getattr(sys, frozen, False)用来区分是否处于打包态。参数层面的几个点也值得说清楚use_angle_clsTrue对应 cls 模型如果你的图片全是正向的可以关掉但离线场景用户丢进来的图不可控开着更保险show_logFalse不只是为了干净还会减少大量日志写入临时目录的频率避免拖慢冷启动。还有一个隐藏很深的坑Windows 下 PyInstaller 打包带 PaddleOCR 的程序必须在主入口加多进程保护。PaddleOCR 内部某些依赖比如预处理会走多进程而 Windows 下多进程在打包程序里如果没有保护运行时可能直接崩溃或卡死。主文件里这样写# main.py import multiprocessing if __name__ __main__: multiprocessing.freeze_support() # 然后进入你原来的主逻辑 main()freeze_support()是 Windows 多进程打包的官方要求不加它exe 在用户机器上可能出现随机闪退而这个现象在开发机直接跑 python 脚本时几乎复现不出来。这一点我吃过亏每次打包前都会检查主入口有没有写这一句。4. 打包构建PyInstaller 的 spec 配置与资源收集到了真正打包这一步不要再用pyinstaller -F main.py这种裸命令。PaddleOCR 依赖复杂裸命令大概率漏掉隐式导入或资源文件最终得到一个跑不起来的 exe。正确做法是准备一份 spec 文件把 hidden imports、资源目录、排除项全部写清楚让打包过程可复现改一次配置就能重新出包。4.1 编写 spec 文件hidden imports 与资源目录spec 文件本质是 Python 脚本PyInstaller 执行它来构建产物。我常用的模板如下# ocr_tool.spec # -*- mode: python ; coding: utf-8 -*- from pathlib import Path project_root Path.cwd() inference_dir project_root / inference # 静态模型目录 fonts_dir project_root / fonts # 中文字体防止目标机器缺字 assets_dir project_root / assets # 其他资源如图标、配置文件 a Analysis( [main.py], pathex[str(project_root)], binaries[], datas[ (str(inference_dir), inference), (str(fonts_dir), fonts), (str(assets_dir), assets), ], hiddenimports[ paddle, paddleocr, paddleocr.tools, skimage, imgaug, shapely, pyclipper, ], excludes[ paddle.fluid.core_avx, paddle.dataset, matplotlib.tests, PIL.ImageShow, ], noarchiveFalse, ) pyz PYZ(a.pure) exe EXE( pyz, a.scripts, exclude_binariesTrue, nameocr_tool, consoleTrue, # 调试期保留控制台交付时再关 ) coll COLLECT( exe, a.binaries, a.datas, nameocr_tool, )这份 spec 里最核心的是hiddenimports。PyInstaller 做的是静态分析它扫描 import 语句但 PaddleOCR 和 paddle 内部大量模块是动态导入靠分析根本抓不全。这里的清单是根据实际跑出来的报错不断补出来的属于血泪经验。新版本的环境可能还会报缺其他模块比如lmdb、prefetch_generator看报错缺什么就往这个列表里加什么加到不报为止。datas把模型、字体、资源目录整体拷进包元组左边是源路径右边是目标目录。注意这里的目标路径是相对_internal的程序运行时通过_base_dir()拼出完整路径。excludes是为了压体积。paddle.dataset里带了一堆训练数据下载脚本对推理毫无用处matplotlib.tests、PIL.ImageShow这类都是无关测试资源。每排除一项产物能瘦一圈。第一次打包时体积往往是最大的后续不断加 excludes 会逐步瘦下来。4.2 构建命令与产物校验spec 写好后构建命令很简单pyinstaller --clean --noconfirm ocr_tool.spec--clean清掉上次构建的缓存--noconfirm覆盖输出目录。用 spec 而不是-F的好处是-F生成单文件运行时把全部依赖解压到临时目录冷启动慢杀毒软件误报率高而 spec 里的COLLECT是 onedir 模式产物是一整个目录资源文件直接裸露在外模型路径更直观启动也快。离线交付多一个文件夹不多换取的是稳定性和可排查性这笔账值得。构建完成后先看dist/ocr_tool/目录是否完整exe 在顶层_internal存在inference三个模型子目录都在。然后在开发机上双击 exe 跑一张测试图确认能出结果再考虑后续。第一次跑的时候保留consoleTrue控制台会把 PaddleOCR 的初始化日志和报错原样打出来比黑匣子式的闪退好排查得多。全部调通之后再把它改成consoleFalse重新出包体验上更像一个正规工具。5. 打包避坑最容易翻车的五个现场这一章全是真实翻车记录。每一条我都按「现象 → 原因 → 解决」的结构写你对照自己的环境就能快速定位问题不用把半天时间耗在搜索上。5.1 现象运行报错 No module named paddle打包好的 exe 在开发机上双击就弹错提示找不到paddle模块但直接在命令行跑 python 脚本又是好的。原因基本有两个一是打包用的环境不是干净 venv里面 paddle 相关包残缺二是 PyInstaller 静态分析漏掉了 paddle 的动态导入模块压根没被收进_internal。解决方法是新建干净 venv 重新安装 CPU 版 paddlepaddle 和 paddleocr并把hiddenimports里的paddle、paddleocr显式补全。之后再报缺什么模块就往这个列表里加这个动作会重复几次别嫌烦。5.2 现象exe 秒退或报错找不到模型文件程序启动就直接闪退或者报FileNotFoundError看路径指向的是开发机的C:\Users\xxx\inference。原因很典型训练代码里用了相对路径或硬编码绝对路径开发时能跑是因为路径恰好存在打包后工作目录被 PyInstaller 改了路径就失效了。解决方法是回到第 3 章把入口代码改成_base_dir()统一取路径。这里有个小技巧凡是报错信息里层级很深、带_MEI字样临时目录的十有八九是路径问题检查代码里有没有裸写相对路径。5.3 现象打包产物体积爆炸动辄 800 MB 以上第一次打包出 800 多 MB这是每个初学者都会经历的事。原因大多是打包环境装了 GPU 版 paddle把 CUDA 运行库一起卷进来了或者没写excludes把训练数据、测试资源全收进包。解决方法是卸载 GPU 版、确认 pip list 里是 CPU 版 paddlepaddle然后在 spec 里持续补充 exclude 项。正常一个 mobile 模型的离线 OCR 工具压到 400 MB 上下是合理的太离谱就从这两个方向排查。5.4 现象目标机器上提示缺少 DLL 或运行库exe 在你机器上正常拷到用户机器上启动时报缺少msvcp140.dll之类的错误。原因是用户机器没有安装 VC 运行库Python 打包产物依赖这部分系统组件。解决方法是让用户安装 VC Redistributable或者把缺失的 DLL 放进_internal随包分发。如果你的目标用户是普通办公人员装运行库这一步本身就是阻力我把常用运行库合并进交付包的依赖目录并写进交付说明让现场的人先装再跑。5.5 现象识别中文全乱码或杀毒软件直接隔离 exe这两个问题表面不相干但都出现在交付阶段。中文乱码往往不是模型问题而是目标机器缺少中文字体PaddleOCR 画框和部分预处理依赖系统字体。解决方法是把中文字体放进fonts目录并在datas里声明程序初始化时通过PaddleOCR的字体参数指定。杀毒软件隔离是 PyInstaller 打包产物的通病根源是打包壳特征被误判。解决方法是在测试机上先做白名单验证正式交付时建议提供校验值并和对方说明这是打包产物必要时换 Nuitka 加密打包可以明显降低误报率但构建配置也要重新调一遍。6. 离线验证在干净机器上跑通全流程再交付6.1 验证清单与冒烟脚本打包完成并不代表交付完成。我每次都会用一台没有装 Python 和任何依赖的干净机器做验证步骤如下把dist/ocr_tool/整个目录拷贝过去不执行任何安装断网环境下双击 exe用一批测试图跑识别核对识别文本是否有中文乱码、是否出现闪退记录冷启动耗时和单张图推理耗时反复退出重启三次以上确认多进程保护生效冒烟脚本可以做成一个简单的文件放在工具目录里# smoke_test.py from pathlib import Path from ocr_worker import init_ocr ocr init_ocr() result ocr.ocr(str(Path(test_img.png)), clsTrue) assert result and result[0], no text detected text .join(line[1][0] for line in result[0]) assert len(text.strip()) 0, empty text result print(smoke test passed:, text)这段脚本在干净机器的目标目录下直接运行如果它通过了说明模型、字体、资源路径全都没问题。注意冒烟脚本运行完后要删掉避免占用交付目录空间。6.2 版本固化与最终交付物最后一步是把版本信息固化成文件。写一个requirements.txt把 paddleocr、paddlepaddle、pyinstaller 和相关依赖的版本号全部锁定和 spec 文件、导出命令放同一个目录作为下次重建的完整依据。交付物清单建议只保留三样ocr_tool整个目录、smoke_test.py若需要对方自检、一份一页纸的说明写清运行方式和运行库要求即可。从那以后我每次打包完都强制走一遍干净机器冒烟清单哪怕时间再紧也不跳过。路径问题、字体问题、缺失模块这类坑只有在干净环境里才会现出原形。希望帮到你。本文还有配套的精品资源点击获取