简介:这份资源面向需要在无 Python 环境下完成文字识别的开发者与运维人员,围绕 PaddleOCR 构建可离线运行的 exe 识别工具,输入图片路径即可输出识别文本,适合内网部署、嵌入式设备或轻量级批量识别场景。压缩包为 rar 格式,共约 2000 个文件,整体约 279MB,其中 319 个 py 与 319 个 pyc 为程序源码及编译缓存,232 个 pyd、139 个 dll 支撑核心推理与依赖库,另有 tcl、txt、png、qm 等运行时资源与界面素材,目录结构完整,可直接解压使用。资源已有 3790 人学习下载,配套博文讲解了脚本编写、PyInstaller 打包命令及模型选择思路,读者可据此理解离线工具的组织方式,并在此基础上扩展批量识别、错误处理与简单图形界面,降低 OCR 工具落地门槛。
1. 把 PaddleOCR 塞进一个 exe:离线场景下最省心的交付方式
上周有个做产线质检的朋友找我,说车间里有台工控机,网络是物理隔离的,但每天要人工录入几百张物料标签,眼睛都快看花了。他想让我帮忙搞个能双击就跑的 OCR 小工具,不要装 Python、不要配环境、不要联网。这种需求其实很典型——PaddleOCR 本身识别效果不错,但它的依赖链对非技术用户来说就是一场灾难。把 PaddleOCR 打包成 exe 离线工具,核心要解决的就是三件事:模型文件怎么跟着走、动态库怎么不丢、首次启动怎么不卡在下载上。这篇笔记拆的就是这套打包流程,从环境准备到 PyInstaller 配置再到体积和启动速度的取舍,适合需要给内网或离线机器交付 OCR 能力的开发者照着复现。
2. 打包前的环境与依赖梳理:先把“会丢的东西”找齐
动手之前得先搞清楚 PaddleOCR 跑起来到底依赖哪些东西。很多人第一次打包翻车,不是因为 PyInstaller 参数写错,而是根本没意识到 PaddlePaddle 的推理库、OpenCV 的动态链接库、以及模型权重文件在打包后各自会跑到哪里去。这一章先把依赖地图画清楚,后面打包才有据可依。
2.1 为什么不能直接 pip install 完就打包
PaddleOCR 的安装方式看起来很简单,pip install paddleocr paddlepaddle两条命令就完事。但这两条命令背后拉下来的东西远比想象中复杂。PaddlePaddle 的 wheel 包里带着编译好的推理引擎动态库,Windows 下通常是paddle/libs目录下的一堆.dll;PaddleOCR 自身又依赖 OpenCV、Shapely、PyClipper、imgaug 等一串包,其中 OpenCV 的opencv_videoio_ffmpeg*.dll这类文件在 PyInstaller 默认分析时经常被漏掉。
更关键的是模型文件。PaddleOCR 默认行为是首次运行时从远端下载检测模型、识别模型和方向分类模型到用户目录下的.paddleocr文件夹。离线机器上这一步必然失败,程序会直接抛异常退出。所以打包前必须先把模型下载好,再通过参数或代码指定本地路径。
我一般会先建一个干净的虚拟环境,把版本钉死,避免打包时混入全局环境里的杂包:
python -m venv ocr_env ocr_env\Scripts\activate pip install paddlepaddle==2.6.1 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install paddleocr==2.7.3 pip install pyinstaller==6.3.0版本号不是随便写的。PaddlePaddle 2.6.x 和 PaddleOCR 2.7.x 这个组合在 Windows 下的动态库依赖相对稳定,再新的版本有时会引入额外的运行时依赖,打包时容易多出一堆找不到的模块。虚拟环境的作用是让pip list的输出干净可控,后面写 spec 文件时心里有数。
2.2 模型文件的离线化处理
模型离线化有两种做法。一种是在有网的机器上先跑一次 PaddleOCR,让它把模型下载到C:\Users\用户名\.paddleocr\下,然后把整个目录拷出来;另一种是直接从官方模型库手动下载推理模型压缩包解压。前者省事,后者可控。我倾向后者,因为目录结构清晰,打包时路径好写。
下载下来的模型一般包含三个文件夹:ch_PP-OCRv4_det_infer(检测)、ch_PP-OCRv4_rec_infer(识别)、ch_ppocr_mobile_v2.0_cls_infer(方向分类)。把它们放到项目目录下的models/里,代码里这样指定:
from paddleocr import PaddleOCR ocr = PaddleOCR( use_angle_cls=True, lang="ch", det_model_dir="./models/ch_PP-OCRv4_det_infer", rec_model_dir="./models/ch_PP-OCRv4_rec_infer", cls_model_dir="./models/ch_ppocr_mobile_v2.0_cls_infer", use_gpu=False, show_log=False )det_model_dir、rec_model_dir、cls_model_dir三个参数分别指向本地模型目录,这样初始化时就不会触发任何网络请求。use_gpu=False是因为目标机器大概率没有独立显卡,强制走 CPU 推理更稳妥。show_log=False是给最终用户看的,避免控制台刷一堆日志吓到人。
提示:模型目录用相对路径时,打包后要确保运行时的工作目录正确。PyInstaller 单文件模式下,
sys._MEIPASS才是资源解压的临时目录,路径处理要单独写,后面 3.2 节会展开。
2.3 依赖清单的确认方法
打包前建议跑一遍依赖树,把隐式依赖揪出来。PaddleOCR 有些模块是运行时动态导入的,PyInstaller 的静态分析扫不到。可以用pipdeptree看层级,再用一个最小脚本实际跑一次识别,观察有没有警告:
import paddleocr from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=True) result = ocr.ocr("./test.png", cls=True) print(result)跑通之后,把控制台里出现的所有ImportWarning、UserWarning记下来,这些往往就是打包后缺模块的源头。常见的有skimage、scipy、lmdb相关的警告,虽然不影响当前功能,但 PyInstaller 打包时如果完全没包含,运行时可能在某些分支上报ModuleNotFoundError。稳妥做法是在 spec 文件的hiddenimports里补上。
3. PyInstaller 配置实战:spec 文件里的关键参数
环境理清楚之后,进入打包环节。PyInstaller 的命令行参数能覆盖大部分场景,但 PaddleOCR 这种依赖复杂的项目,直接命令行打包十有八九会缺东西。我习惯先pyinstaller --name ocr_tool main.py生成一个初始 spec,再在 spec 里逐项补配置。这一章把 spec 的关键段落拆开讲。
3.1 数据文件与二进制的收集
spec 文件里最核心的是Analysis对象的datas和binaries两个列表。datas放模型文件和非代码资源,binaries放动态库。PaddlePaddle 的 dll 分布在 site-packages 的多个位置,手动列不现实,用collect_all更省事:
from PyInstaller.utils.hooks import collect_all, collect_dynamic_libs datas = [] binaries = [] hiddenimports = [] for pkg in ["paddle", "paddleocr", "cv2", "shapely", "pyclipper"]: tmp_datas, tmp_binaries, tmp_hidden = collect_all(pkg) datas += tmp_datas binaries += tmp_binaries hiddenimports += tmp_hidden # 模型目录整体带入 datas += [("models", "models")]collect_all会把包内的数据文件、动态库、子模块全部收集进来,代价是体积膨胀,但离线工具优先保证能跑。datas += [("models", "models")]这一行把项目下的models文件夹原样复制到打包产物的models目录下,运行时通过sys._MEIPASS拼接路径就能找到。
hiddenimports里collect_all已经补了不少,但 PaddleOCR 有些后处理模块是字符串动态导入的,建议再手动加几个保险:
hiddenimports += [ "paddleocr.tools.infer", "paddleocr.ppocr.data", "paddleocr.ppocr.postprocess", "skimage", "scipy.special", ]这些模块名来自实际报错记录。不同版本可能略有差异,打包后第一次运行如果报ModuleNotFoundError,把缺失的模块名补进这个列表重新打包即可,属于常规迭代。
3.2 单文件与单目录的取舍
PyInstaller 支持--onefile单文件和--onedir单目录两种模式。单文件交付最省心,用户拿到一个 exe 双击就行,但代价是每次启动都要把内部资源解压到临时目录,PaddleOCR 这种带几百兆模型和 dll 的项目,首次启动可能要等十几秒,用户会以为程序卡死了。单目录模式启动快,但交付的是一个文件夹,用户可能误删里面的 dll。
我的选择是单目录加一个启动脚本,或者用单文件但做好启动提示。如果坚持单文件,路径处理必须用sys._MEIPASS:
import sys import os def resource_path(relative_path): if hasattr(sys, "_MEIPASS"): base = sys._MEIPASS else: base = os.path.abspath(".") return os.path.join(base, relative_path) model_root = resource_path("models")sys._MEIPASS是 PyInstaller 运行时注入的属性,指向资源解压的临时目录。开发环境下没有这个属性,回退到当前目录。这样同一份代码在开发和打包后都能正确找到模型。注意单文件模式下临时目录在程序退出后会被清理,如果程序里有缓存写入逻辑,不要往这个目录写。
3.3 控制台窗口与日志输出
打包成 exe 后默认会带一个黑色控制台窗口。对内部工具来说这反而是好事,出错时能看到堆栈。如果要去掉,加--noconsole,但代价是异常信息无处可看,只能靠写日志文件。我一般保留控制台,同时在代码里加一个简单的日志重定向:
import logging logging.basicConfig( filename="ocr_runtime.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s" )日志文件写在 exe 同级目录,方便现场排查。注意如果 exe 放在Program Files这类无写权限的目录,日志会写失败,交付时提醒用户放到普通目录下。
打包命令最终形如:
pyinstaller ocr_tool.spec --clean --noconfirm--clean清掉上次的缓存,避免旧文件干扰;--noconfirm跳过覆盖确认,适合脚本化重复打包。产物在dist/ocr_tool/下,整个文件夹拷给用户即可。
4. 避坑排查:打包后最常见的五类翻车
前面流程走顺了不代表交付就稳。实际打包 PaddleOCR 的过程中,有几类问题几乎每次都会遇到,这里按现象、原因、解决三段式记下来,省得重复踩。
4.1 启动报缺少 DLL
现象:双击 exe 后弹窗提示找不到 paddle/libs/xxx.dll或ImportError: DLL load failed。
原因:PaddlePaddle 的部分 dll 是通过运行时os.add_dll_directory加载的,PyInstaller 的collect_all有时没把它们放进binaries,或者放进了但相对路径不对。
解决:在 spec 里显式用collect_dynamic_libs("paddle")再收一遍,并确认binaries里的目标路径是包内相对路径。打包后到dist目录下用dir /s *.dll核对关键 dll 是否存在。如果还缺,手动把 site-packages 下对应 dll 加进binaries列表。
4.2 首次识别卡住或报网络错误
现象:程序启动正常,点识别后长时间无响应,日志里出现连接超时或下载失败。
原因:模型路径没生效,PaddleOCR 回退到默认的在线下载逻辑。
解决:检查det_model_dir等参数是否指向了实际存在的目录,且目录下有inference.pdmodel和inference.pdiparams两个文件。打包后路径要用resource_path拼接,不能写死相对路径。可以在初始化后打印一次模型路径确认。
4.3 打包体积超过一个 G
现象:dist文件夹体积巨大,拷贝和分发都很痛苦。
原因:collect_all把 PaddlePaddle 的训练相关模块、测试数据、无用 dll 全带进来了。
解决:在 spec 的Analysis里用excludes排除明显用不到的包,比如matplotlib、pandas(如果没用到)、paddle.distributed。模型方面,如果业务只识别中文印刷体,可以只保留ch_PP-OCRv4_rec_infer的精简版模型。体积能压到 300MB 左右,再小就要考虑换轻量推理方案了。
4.4 中文路径下识别失败
现象:图片放在含中文的目录下,识别结果为空或报编码错误。
原因:OpenCV 的imread在 Windows 下对中文路径支持不好,返回 None。
解决:读图不要用cv2.imread,改用numpy.fromfile加cv2.imdecode:
import numpy as np import cv2 def read_image(path): data = np.fromfile(path, dtype=np.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)这个函数对中文路径和普通路径都适用,替换掉 PaddleOCR 内部默认读图逻辑的入口即可。
4.5 换一台机器就报缺少 VC 运行库
现象:在自己机器上跑得好好的 exe,拷到目标机器上提示缺少VCRUNTIME140.dll或MSVCP140.dll。
原因:PaddlePaddle 和 OpenCV 依赖 Visual C++ 运行库,打包机装了,目标机没装。
解决:交付时附带 VC++ 运行库安装包,或者在打包时把相关 dll 一并收进binaries。后者不总是合规,前者更稳妥。交付文档里写清楚这一步,比事后远程排查省事得多。
5. 体积与启动速度的进阶压榨:几个实测有效的技巧
打包能跑只是及格线,交付体验好不好还得看体积和启动速度。我拿一个中文通用识别场景做过对比,原始collect_all全量打包产物约 1.1GB,首次启动约 18 秒;经过下面几步处理后压到 340MB 左右,启动降到 6 秒上下。这些数字随机器和版本浮动,但优化方向是通用的。
第一步是砍模型。PaddleOCR 的识别模型分服务器版和移动版,移动版精度略低但体积小一个量级。如果业务是标签、单据这类规整文本,移动版完全够用。检测模型同理,ch_PP-OCRv4_det_infer已经比较轻,不必再换。方向分类模型如果业务图片都是正向拍摄,可以直接use_angle_cls=False省掉一个模型。
第二步是 spec 里做排除。下面这段是我常用的排除列表,配合collect_all使用:
excludes = [ "matplotlib", "pandas", "IPython", "notebook", "paddle.distributed", "paddle.fluid.tests", "paddle.vision.datasets", "tkinter", ]excludes里的包不会被打进产物。注意排除前确认业务代码没间接引用,比如某些可视化调试代码引了matplotlib,排除后运行到那行会报错,但主流程不受影响。稳妥做法是排除后完整跑一遍识别流程验证。
第三步是启动优化。单文件模式每次解压是硬伤,如果必须单文件,可以在 exe 启动时先弹一个简单的加载提示窗口,用tkinter画个进度条,让用户知道程序在干活。如果接受单目录,直接把dist文件夹打成压缩包交付,启动速度接近原生 Python 脚本。
还有一个容易被忽略的点:PaddleOCR 初始化时会做一次模型加载和预热,这部分耗时固定。可以在程序启动时后台起一个线程提前初始化 OCR 对象,用户点识别时直接复用,感知上快不少。代码结构大致是全局单例加锁,这里不展开,思路就是“把等待藏到用户还没操作的时候”。
最后说个验证方法。打包完成后不要只在自己机器上测,找一台没装过 Python、没装过 VC 运行库的干净机器跑一遍,重点看三件事:能否启动、能否识别一张中文路径下的图片、日志文件是否正常生成。这三项过了,交付基本就稳了。从那以后我每次打完包都强制走一遍干净机验证,宁可多花二十分钟,也不想在现场被一个缺失的 dll 卡住半天。希望这套流程帮到你。
本文还有配套的精品资源,点击获取