简介:面向无Python环境的文字识别需求,这套PaddleOCR离线打包工具提供了可直接运行的exe及完整依赖文件。使用者只需输入图片路径,程序即可调用OCR完成识别,并将结果保存为txt,适合办公自动化、批量文档抽取等场景,也便于入门者快速落地OCR应用。资源共2000个文件,压缩包约279MB,包含Python脚本、pyc编译文件、pyd动态模块、DLL库以及大量时区数据与文本配置文件,构成一套自带解释器和依赖的离线运行环境。目前已有3788人学习下载。通过解析包内脚本与目录结构,能够还原PyInstaller在Windows下打包PaddleOCR的完整流程,同时学习模型文件、动态链接库和时区数据的部署方式;对需要在无Python机器上分发OCR工具的开发人员来说,这份打包成品既可直接运行验证效果,也可修改输入输出参数或替换模型,作为自制离线工具的基础模板。
1. PaddleOCR打包exe离线工具:先搞清楚你的场景和依赖
手头有个项目要求在不装 Python、不联网的 Windows 机器上做文字识别,老板问能不能把 PaddleOCR 打成 exe 直接扔过去用。我相信很多做 OCR 落地的朋友都卡过这一步:本地跑得好好的脚本,一打包就哪哪都是问题。PaddleOCR 打包 exe 离线工具的核心,是把 PaddleOCR 的推理代码、模型文件、动态库、字体资源全部塞进一个独立可执行程序里,让目标机器不需要任何解释器和联网环境。适合做内部工具、工业巡检、档案录入这类固定场景。今天这篇文章会从环境选型一路讲到打包参数、离线部署和踩坑修复,照着做基本能跑通。
2. 为什么离线版要自己打包:环境、依赖和交付形态
2.1 PaddleOCR 的运行依赖和离线场景的取舍
PaddleOCR 在开发机上是典型的三层依赖:Python 解释器、Paddle 深度学习框架、OCR 特有的模型与配置。只要目标机器装过 Python,理论上跑个pip install也行,但现实往往是客户机器不联网、不给装 Python,甚至拿到的是一台只有 4GB 内存的工控机。这时候,把整套运行环境打成一个 exe 就成了唯一靠谱的交付方式。
离线场景里最大的取舍是“能不能用 GPU”。打包 GPU 版需要带上 CUDA 和 cuDNN 动态库,体积会爆炸,而且目标机器的显卡驱动版本不一定匹配。我的建议是:如果识别速度每秒一两张图可以接受,就老老实实打包 CPU 版;如果业务要求高并发、毫秒级,那应该考虑把 OCR 服务化,而不是强行打包 exe。常见做法是用 CPU 版 paddlepaddle 打包,模型文件选择轻量级的 mobile 系列,这样单个 exe 加模型目录也可以控制在 300MB 以内。
另一个取舍是模型外置还是打进 exe。模型打进 exe 的好处是只有单文件,但每次升级模型都要重新打包;模型外置到 exe 同级的目录,则可以让实施人员直接替换模型文件来升级。我一般倾向模型外置,理由很简单:真实项目里模型迭代频繁,外置模型目录能少走很多次打包流程,也算一张后悔药。
2.2 打包前要先锁定的三件事:Python 版本、Paddle 版本、模型目录
很多翻车案例都始于 Python 版本太新。PaddleOCR 及其底层框架对 Python 的兼容并不是无条件的,常见做法是选 Python 3.8 或 3.9,太新的解释器有可能遇到动态库绑定问题。打包前先建一个干净的虚拟环境,只装和项目直接相关的依赖,不要图省事直接用系统 Python。虚拟环境里跑通整个识别流程,再开始打包,这一步能过滤掉大半“玄学”报错。
Paddle 版本也要锁定。pip install paddlepaddle默认装的是 CPU 版,但不同小版本的模型算子实现有差异,换版本后可能出现识别结果变化。建议在 requirements 里把 paddlepaddle、paddleocr 的版本写成固定版本号,比如paddlepaddle==2.x.x这种格式(具体以你实际验证过的为准)。注意 PaddleOCR 的依赖会拉起 shapely、pyclipper、opencv-contrib-python 等一堆库,这些库里有动态链接文件,是打包时最容易漏的东西。
模型目录结构建议提前规划好。在项目根目录下建inference文件夹,里面放置检测模型、方向分类器、识别模型三个子目录。代码里不写绝对路径,而是基于sys.executable或os.path.dirname(__file__)拼接相对路径。这样打包后无论 exe 被复制到哪个目录,只要模型目录在旁边就能找到。
2.3 两种常见打包路线:PyInstaller 与 Nuitka 怎么选
目前在 Windows 上打包 Python 程序,主流就两条路:PyInstaller 和 Nuitka。PyInstaller 是纯打包工具,把 Python 字节码和依赖的二进制库收集到一起,生成一个 exe;Nuitka 会先将 Python 代码编译成 C 然后链接成二进制,运行效率更高,但打包时间更长,配置也更繁琐。
PaddleOCR 这种带大量动态库和子模块的项目,PyInstaller 的兼容性明显更好,社区里踩坑资料也最多。如果你的目标只是交付离线工具,没有“启动速度必须压缩到一秒”这类硬指标,直接用 PyInstaller。Nuitka 适合对性能敏感、愿意花时间调编译参数的场景,它生成的 exe 更接近原生程序,但处理 Paddle 这种巨型库时,稍微配置不当就会出现链接错误。
还有一种说法是“先 PyInstaller 打包,再对 exe 做压缩壳”。我试过几次,压缩后启动反而变慢,而且容易被杀软误报,得不偿失。打包工具选 PyInstaller,踩坑资料多、可控性强;Nuitka 可以作为后续优化方向,不建议第一次做离线工具的人一上来就挑战它。
3. 用 PyInstaller 把 PaddleOCR 打成 exe:最小命令与参数调优
3.1 先让脚本在本地跑通:最小识别脚本与自检
打包之前,先把识别功能写成一个独立的命令行脚本ocr_tool.py,让它支持传图片路径、输出识别文本。这个脚本会作为打包入口,所以它的兼容性直接决定 exe 好不好用。
# -*- coding: utf-8 -*- import os import sys import argparse import paddleocr def resource_path(relative_path): """兼容 PyInstaller 打包后的临时目录路径""" if hasattr(sys, "_MEIPASS"): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path) def build_ocr(): # 模型目录默认放 exe 同级 inference 目录 model_dir = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "inference") ocr = paddleocr.PaddleOCR( use_angle_cls=True, # 启用方向分类器,处理旋转文本 lang="ch", # 中英文识别 use_gpu=False, det_model_dir=os.path.join(model_dir, "det"), rec_model_dir=os.path.join(model_dir, "rec"), cls_model_dir=os.path.join(model_dir, "cls") ) return ocr def main(): parser = argparse.ArgumentParser(description="PaddleOCR 离线识别工具") parser.add_argument("image", help="图片路径") args = parser.parse_args() ocr = build_ocr() result = ocr.ocr(args.image, cls=True) # 按行打印识别结果和置信度 for line in result[0]: text = line[1][0] score = line[1][1] print(f"{text}\t{score:.4f}") if __name__ == "__main__": main()这段代码的关键点在于:模型路径用的是sys.argv[0]所在目录,也就是 exe 所在目录。这样模型外置到 exe 旁后,无论把 exe 挪到哪里都不会因为当前工作目录变化而找不到模型。build_ocr里的det_model_dir、rec_model_dir、cls_model_dir三个参数直接覆盖默认模型路径,离线场景必须显式指定。
脚本写好后,先在本地跑一遍:python ocr_tool.py test.png,确认能正常输出文本。如果这里就报错,比如找不到模型文件,先排查路径;如果输出乱码,检查语言参数和模型是否匹配。只有本地命令运行稳定,打包才有意义。
3.2 打包命令的完整形态:hidden-import、collect-all、datas
PyInstaller 打包 PaddleOCR 最怕的就是“动态库漏收集”。Paddle 的核心库是编译好的.pyd和.dll,PyInstaller 的静态分析扫描不到它们,所以必须在命令行里显式收集。参考下面的命令:
pyinstaller -F -w \ --collect-all paddleocr \ --collect-all paddle \ --collect-all pyclipper \ --collect-all shapely \ --hidden-import=imghdr \ --hidden-import=skimage \ --add-data "fonts;fonts" \ --exclude-module=matplotlib \ --exclude-module=tkinter \ --clean \ -n PaddleOCR离线工具 \ ocr_tool.py--collect-all会收集对应包的所有子模块、数据文件和动态库,是我用下来解决 Paddle 漏包最有效的手段。--add-data "fonts;fonts"把字体目录复制进 exe 临时目录,Windows 下分隔符是分号,Linux 下是冒号,别记反。--hidden-import声明那些运行时会动态导入的模块,PaddleOCR 会用到imghdr、skimage这类不太显眼的依赖,预先把它们写进去能省很多排查时间。
-F表示单文件模式,把所有东西打成一个 exe;-w表示不显示控制台窗口。如果希望看到识别日志,比如调试阶段,可以把-w去掉。--exclude-module用来剔除不需要的库,比如 matplotlib、tkinter,能有效减小体积。第一次打包建议先不加--exclude-module,保证跑通后再逐项删减。
打包完成后,在dist目录下会得到 exe 文件。执行PaddleOCR离线工具.exe test.png,如果看到报错,不要慌,按第 4 章的排查思路来定位。
3.3 离线运行前的目录结构与模型外置
打包成功只是第一步,离线运行还要把目录结构摆对。我的惯例是在 dist 目录下创建一个发布文件夹,把 exe、inference 模型目录、fonts 目录、测试图片放成下面这样:
发布目录/ PaddleOCR离线工具.exe inference/ det/ # 检测模型 rec/ # 识别模型 cls/ # 方向分类器 fonts/ simfang.ttf 测试图.png这种布局下,exe 启动时通过sys.argv[0]定位自身目录,然后拼接inference子目录加载模型。模型外置的好处是:现场人员如果发现某些文字识别不准,只需要替换rec目录下的模型文件,然后重新运行 exe 即可,不需要我重新打包。这个习惯帮我躲过很多次半夜被叫起来重新发版的经历。
需要特别注意fonts目录的路径处理。PaddleOCR 在画框和识别某些字符时会调用字体文件,如果打包时用--add-data把 fonts 收进了临时目录,代码里就得用resource_path()函数来获取路径;如果像我这样在--add-data里把 fonts 复制到包内,就要确保resource_path("fonts")能取到。最省心的方式是直接把字体文件放到 exe 外部的fonts目录,代码用 exe 同级的绝对路径,免除sys._MEIPASS的干扰。
4. 打包过程的常见坑与排查:黑匣子里的翻车现场
4.1 报错 ModuleNotFoundError: No module named 'paddle'
现象:打包后的 exe 双击运行,弹窗显示ModuleNotFoundError: No module named 'paddle',或者控制台模式下直接退出。
原因:PyInstaller 在分析阶段只扫描了 Python 源码里的import语句,Paddle 框架内部通过动态方式加载子模块,静态分析根本看不到。
解决:打包命令里加上--collect-all paddle和--collect-all paddleocr,让 PyInstaller 无条件复制整个包。如果还不行,再加一个--hidden-import paddleocr。我在一次升级依赖后遇到过这个报错,当时以为是打包命令没加--clean导致缓存冲突,清掉 build 目录重打就好了。所以遇到这类问题第一步先执行pyinstaller --clean,删除build和spec文件,再重新打包。
4.2 GPU 版动态库没带进来,换机器就崩
现象:在开发机上打包运行正常,拷到客户机器上双击没反应,或者提示缺少cudnn64_8.dll、cublas64_11.dll等文件。
原因:开发机装了 GPU 版 paddlepaddle-gpu,PyInstaller 把 GPU 动态库也收了一部分,但目标机器的显卡驱动或 CUDA 版本不匹配。就算能用,也会因为少了某个版本的cudnn而崩溃。
解决:离线工具优先使用 CPU 版 Paddle。在虚拟环境里执行pip uninstall paddlepaddle-gpu,然后安装 CPU 版paddlepaddle。代码里强制use_gpu=False。这样打包出来的 exe 不依赖任何显卡驱动。如果你确实需要 GPU 加速,那就别指望一个 exe 通用所有机器,建议做成服务端,或者单独提供 GPU 专用包。
4.3 模型文件被压缩进 exe 后找不到路径
现象:打包时把模型目录一起--add-data进了 exe,运行时提示[Errno 2] No such file or directory: model/inference/det/inference.pdmodel。
原因:单文件模式下,PyInstaller 会把所有数据解压到临时目录,这个临时目录每次都变,代码里写的相对路径肯定找不到。
解决:不要在代码里写死在开发机的绝对路径,而是用第 3.1 节的resource_path()函数。这个函数判断了sys._MEIPASS,当程序运行在 PyInstaller 生成的临时目录里时,自动指向解压出来的路径。另外,我的习惯是干脆不把模型打进 exe,直接外置到 exe 同级目录,彻底绕开这个问题。
4.4 打包后体积巨大与启动慢
现象:exe 体积动辄 500MB,双击后几秒钟才显示窗口,用户抱怨“是不是死机了”。
原因:Paddle 框架本身就有大量动态库,PyInstaller 默认全量收集,再加上--collect-all会把所有子模块数据都塞进去,体积自然爆炸。
解决:先确认你需要的功能,只收集必要包。--exclude-module可以剔除 matplotlib、scipy 等重库,这些在纯 OCR 场景里用不到。另外,模型外置也能减小 exe 体积。启动慢是因为单文件模式每运行一次都要解压全部内容到临时目录,我这边的实际数据是解压耗时 2 到 5 秒,属正常范围。如果想明显加快启动,改用目录模式打包(去掉-F),让 exe 旁边的_internal文件夹存放依赖,代价是发布时要多复制一个文件夹。
4.5 被杀毒软件误报和处理方式
现象:exe 打包后,在客户机器上下载或复制时被 Windows Defender 或第三方杀软直接隔离,连运行机会都没有。
原因:PyInstaller 生成的 exe 带有自解压逻辑,加上 Paddle 的深度学习算子和动态库特征明显,杀软容易把它识别成未知程序或木马。这属于玄学,但几乎每个打包过深度学习工具的人都碰到过。
解决:首先确认 exe 行为正常,能用杀毒软件在线扫描一遍,确认无病毒感染。然后给 exe 加上数字签名,有签名的程序误报概率大幅降低。没有签名时,至少把发布说明写好,指导客户在杀软内添加排除目录。注意不要为了“防报毒”而加壳压缩,加壳反而更容易触发启发式查杀。我现在的做法是每次发布前,把 exe 丢到两台不同杀软环境里实测,误报就立刻调整打包参数。
5. Nuitka 加速 exe 的另一条路:半步编译与离线部署
5.1 Nuitka 与 PyInstaller 的核心区别
Nuitka 不是一个纯粹的打包器,它更像一个 Python 编译器。它会把 Python 源码翻译成 C 代码,再编译成机器码,最终产出的二进制不依赖 Python 解释器,所以运行效率比 PyInstaller 高,启动也更快。代价是编译时间很长,配置复杂,而且对动态库和隐式导入的处理不如 PyInstaller 那么“宽容”。
对于 PaddleOCR 这个具体项目,Nuitka 的优势在于:Paddle 的动态库加载通常发生在运行时,Nuitka 配合--include-package可以把关键库直接编译进二进制,减少运行时解压动作。但劣势也很明显:Paddle 的部分算子在大规模编译时可能触发链接错误,需要逐个排查依赖。
我的建议是:如果你已经用 PyInstaller 打通了流程,先用它在内部交付一版;如果业务方抱怨启动慢、识别延迟高,再花时间尝试 Nuitka。它可以作为同一个项目的二次优化方案,而不是替代方案。
5.2 Nuitka 打包 PaddleOCR 的命令模板
我在本地虚拟环境里验证过的 Nuitka 打包命令如下,你可以直接参考:
nuitka --standalone \ --output-dir=nuitka_build \ --enable-plugin=tk-inter \ --include-package=paddle \ --include-package=paddleocr \ --include-package=pyclipper \ --include-package=shapely \ --include-package-data=paddle \ --include-package-data=paddleocr \ --include-data-dir=fonts=fonts \ --windows-console-mode=disable \ --follow-imports \ ocr_tool.py--standalone表示生成独立的目录部署包,里面包含 exe 和依赖动态库。--include-package-data会带上包内的非 Python 文件,比如 Paddle 自带的一些配置文件。--windows-console-mode=disable相当于 PyInstaller 的-w。--follow-imports让 Nuitka 尽可能跟随所有导入链,避免漏包。
编译完毕后,产物在nuitka_build/ocr_tool.dist目录下,把整个目录复制到目标机器运行。执行速度通常比 PyInstaller 版快一些,但第一次编译可能需要十几分钟,耐心等待即可。
5.3 打包后的依赖目录处理
Nuitka 不会把数据文件打进单一 exe,它生成的ocr_tool.dist下会有lib、plugins等文件夹。发布时要把整个.dist文件夹完整打包,不能只拿里面的 exe,否则运行时会因为缺少动态库直接崩溃。
离线部署时,把你原有的inference模型目录复制到ocr_tool.dist同级目录,保持 exe 与模型目录的父子关系。因为代码里用的是sys.argv[0]来定位 exe 所在目录,所以无论 Nuitka 还是 PyInstaller,这套逻辑都通用。
Nuitka 的另一个优点是支持--nofollow-import-to排除某些包。比如 PaddleOCR 会隐式引入 matplotlib 用于可视化,但离线识别不需要,你可以把它排除掉,缩小 dist 体积。注意排除前先跑一遍测试,确认不影响识别功能。
6. 离线工具的验证清单与发布习惯:从能跑到真好用
等打包出来的 exe 能跑通,先别急着发出去,我之前就因为少验证一个步骤,现场差点翻车。现在我把验证固定成一张清单,每次发布前照着过一遍。
第一,在一台完全干净的 Windows 机器上测试,不装 Python,不装任何 paddle 相关环境。把 exe 和 inference 目录复制进去,双击运行。注意看是否报缺 dll、缺字体、缺模型。第二,换一个工作目录再跑一遍,比如把 exe 放到带空格的路径下,确认路径拼接没出问题。第三,测试不同图片:横排文本、竖排文本、带角度的图片、大分辨率图片,确认方向分类器和检测器都正常。第四,看内存占用,一张 1080p 图片识别过程中内存不超过 1.5GB 算正常,如果出现明显的内存泄漏,要考虑换PaddleOCR实例的释放策略。第五,检查识别结果是否在控制台正确打印,如果客户需要保存结果,确认输出文件的路径规则。
我现在的习惯是把模型目录外置、字体外置、输出日志全留在 exe 同级,这样现场实施人员不需要任何技术背景也能替换模型。每次发布时,用压缩软件把发布目录压成一个 zip,命名为“工具名_日期_版本号”,并附一份一页纸的使用说明。这套流程是从几次血泪经验里磨出来的,希望帮到你。
本文还有配套的精品资源,点击获取