☰
使用PyInstaller将PPOCRLabel打包为Windows exe的完整实践
2026/10/1 20:09:07 网站建设 项目流程

简介:将PPOCRLabel半自动标注工具编译封装为exe程序的资源包,面向需要进行OCR数据标注的开发者、算法工程师及项目团队,免去安装配置PaddlePaddle环境和各种依赖的繁琐步骤,解压后双击即可进入图形化标注界面,适合文档扫描、车牌识别、证件信息提取等场景。压缩包共2000个文件,以py源码、pyd编译扩展、exe主程序、whl依赖包为主,另含部分配置文件、模型参数与示例图片,整体大小约355MB,目录涵盖运行库与依赖,便于用户了解工具构成。工具支持在图像上绘制文字框并录入标注内容,可执行撤销、重做、保存等操作,并能够导出JSON格式的标注数据,与PaddleOCR等主流训练流程兼容;同时借助PaddlePaddle生态具备部分自动标注能力,能明显提高标注效率。目前该资源已有6445人浏览学习,适合从零开始接触OCR训练数据准备的用户,也适合追求高效标注的进阶开发者。

1. 项目概况与打包思路

1.1 为什么要打包PPOCRLabel

先说背景。PPOCRLabel是PaddleOCR生态里非常实用的半自动标注工具,核心价值是把OCR识别结果直接落到标注框里,人工只需要修正错字和漏框,能省下大量从零框选的时间。但问题也很现实:这个工具跑起来依赖Python环境和PaddlePaddle框架,我在实际交付时不止一次遇到以下情况:

  • 标注团队的同事电脑上没装Python,装环境又怕把系统搞乱
  • 项目验收方要求提供独立的可视化工具,而不是一堆源码加命令行
  • 换一台机器就要重新配一遍CUDA、PaddlePaddle、Qt相关依赖,时间成本太高

所以在一次标注任务中,我决定把PPOCRLabel打包成Windows下的exe程序,做到双击即用。这篇文章就把完整过程、坑点和最终方案记录下来。

1.2 打包方案选型对比

在动手之前,我把主流方案过了一遍,这里直接给出横向对比,方便你按自己情况选:

方案优势劣势适不适合PPOCRLabel
PyInstaller文档全、社区案例多、支持hook机制打包体积大、偶尔误报毒首选
cx_Freeze相对轻量对PyQt5和PaddlePaddle等动态库处理不如PyInstaller省心备选
Nuitka编译为C,性能好、不易反编译配置成本高,PaddlePaddle这类大型库编译时容易出幺蛾子不推荐
Docker封装环境隔离彻底不适合Windows桌面端直接交付不适合本场景

综合权衡,我选了PyInstaller + 单目录模式。为什么不选单文件模式?PPOCRLabel依赖PaddleOCR模型文件、Qt相关插件和大量动态库,单文件模式每次启动都要解压到临时目录,启动速度明显变慢,而且在部分企业电脑上更容易被杀毒软件拦截。单目录模式虽然看起来文件多,但胜在稳定可控。

2. 环境准备与依赖处理

2.1 干净环境的重要性

强烈建议在干净的Python虚拟环境中进行打包,不要直接使用系统Python。原因很直接:系统Python里装了太多无关包,PyInstaller打包时会尝试把它们一起收集进去,导致包体膨胀,甚至因为某些包互相冲突导致启动报错。我用的环境如下:

  • Windows 10 专业版 22H2
  • Python 3.9.13(32位和64位都试过,最终用64位,毕竟PaddlePaddle对64位支持更好)
  • PaddlePaddle 2.5.2(CPU版)
  • PaddleOCR 2.7.0
  • PPOCRLabel 2.1.3

创建虚拟环境的命令很简单:

python -m venv ppocrlabel_env ppocrlabel_env\Scripts\activate

这里有个细节要注意:安装PaddlePaddle时不要默认装GPU版,除非你确认目标机器都有NVIDIA显卡且CUDA环境完整。否则打包出来在别人电脑上跑不起来,反而麻烦。纯CPU版本虽然推理慢一些,但标注场景下人工修正占大头,速度完全够用。

2.2 依赖安装顺序有讲究

依赖安装顺序会直接影响最终exe能否正常运行,这绝对是我踩出来的经验。建议按以下顺序来:

pip install paddlepaddle==2.5.2 pip install paddleocr==2.7.0 pip install PPOCRLabel==2.1.3

为什么要按这个顺序?因为PPOCRLabel安装时会自动检测PaddleOCR和PaddlePaddle的版本,如果先装PPOCRLabel,它可能拉取到某些不兼容的版本组合。装完以后执行一下:

PPOCRLabel --lang ch

如果程序能正常启动,说明环境基本OK,再继续打包流程。这一步很有必要,别急着打包,先确认源码能跑,打包后的排查范围才会小。

2.3 补装PyInstaller和必要的辅助工具

pip install pyinstaller==5.13.2 pip install pyinstaller-hooks-contrib

pyinstaller-hooks-contrib一定要装,里面包含了PaddlePaddle、PyQt5等常用库的打包hook,没有它很多动态库不会自动收集。装完以后,最好检查一下hooks是否真的包含paddle相关条目:

python -c "from PyInstaller.utils.hooks import get_hook_config; print('hooks ok')"

这个检测不复杂,但能提前发现hook缺失问题,省得后面打包出来运行报错再回头查。

3. 基于spec文件的精细化打包流程

3.1 编写PPOCRLabel专属spec文件

PyInstaller不推荐直接写一长串命令行参数,更好的做法是编写spec文件,它能把所有打包配置固化下来,后面要调整只需要改spec文件重新执行一次构建,非常方便。我最终使用的spec文件核心内容如下:

# -*- mode: python ; coding: utf-8 -*- from PyInstaller.utils.hooks import collect_data_files, collect_submodules datas = [] binaries = [] hiddenimports = [] # 收集PaddleOCR和PPOCRLabel相关数据文件 datas += collect_data_files('paddleocr') datas += collect_data_files('PPOCRLabel') datas += collect_data_files('paddle') # 收集动态链接库,尤其是paddle相关 binaries += collect_dynamic_libs('paddle') binaries += collect_dynamic_libs('paddleocr') # 隐藏导入 hiddenimports += collect_submodules('paddleocr') hiddenimports += collect_submodules('PPOCRLabel') hiddenimports += collect_submodules('paddle') a = Analysis( ['PPOCRLabel.py'], pathex=[], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=['matplotlib', 'PIL.ImageQt'], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, exclude_binaries=True, name='PPOCRLabel', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True, icon='app.ico', ) coll = COLLECT( exe, a.binaries, a.datas, strip=False, upx=True, name='PPOCRLabel', )

注意几个细节:

  • upx=True默认开启,但如果安装的UPX版本不兼容某些库,反而会损坏二进制文件,建议实测后再决定是否开UPX
  • console=True保留命令行窗口,我在开发阶段保留它,正式交付时改成console=False,避免弹出黑框影响体验
  • icon='app.ico'编译前的图标文件需要256x256以上的ico格式,直接改后缀名没用

3.2 执行打包并查看日志

pyinstaller PPOCRLabel.spec --clean --noconfirm

打包过程大概需要5到15分钟,取决于机器性能。日志出现以下内容说明关键库都被收集到了:

INFO: Processing pre-safe-import-module hook paddleocr INFO: Processing module hooks... INFO: Loading module hook 'hook-paddle.py'... INFO: Loading module hook 'hook-paddleocr.py'...

打包完成后,dist目录下会生成PPOCRLabel文件夹,里面包含exe主程序和大量依赖文件。

但仅仅解压出来能放到别人电脑上用还差得远,接下来才是最关键的验证和补缺阶段。

3.3 首次启动验证与常见启动崩溃修复

双击exe,大概率会遇到问题。我首次打包后的经历比较典型,启动直接报错:

ModuleNotFoundError: No module named 'paddleocr.utils'

这个错误出现的主要原因是PyInstaller在某些情况下没有完整收集paddleocr的内部子模块。解决办法有两个思路:

第一个思路是在spec文件的hiddenimports里强制指定:

hiddenimports += [ 'paddleocr.utils', 'paddleocr.tools', 'paddleocr.parser', 'ppocrlabel.utils', ]

第二个思路是在程序入口处加上显式导入。这个方法百试百灵,因为显式import过的模块,PyInstaller在静态分析阶段就会收入依赖:

import paddleocr.utils import paddleocr.tools import paddleocr.parser import ppocrlabel.utils

两个方法可以一起做,因为实在不想打包完才发现漏了某个子模块,又要重新打包浪费时间。

除了ModuleNotFoundError,还有两类非常常见的启动崩溃:

  • DLL load failed while importing paddle,大概率是VC++运行库缺失,需要在目标机器上安装VC++ Redistributable
  • qt.qpa.plugin: Could not find the Qt platform plugin "windows",这是Qt平台插件没被正确收集,需要在spec里加platform plugin路径

针对Qt插件问题,在spec里显式指定:

from PyInstaller.utils.hooks import collect_data_files qt_plugins = collect_data_files('PyQt5', includes=['Qt/plugins/platforms/*', 'Qt/plugins/imageformats/*']) datas += qt_plugins

重新打包后再启动,程序能正常进入主界面,但深坑还在后面:模型文件路径问题。

4. 模型文件路径与运行环境适配

4.1 PaddleOCR模型位置的三种方案

PPOCRLabel启动时,PaddleOCR会自动下载检测模型和识别模型到用户目录的.paddleocr文件夹。但在打包后的exe环境中,程序的工作目录、临时目录和用户目录都跟源码模式不同,所以模型文件经常找不到,要么就是每次启动都在尝试重新下载。

处理这个问题的方案有三种,我按推荐程度排序:

方案一:预下载模型并随包分发

在源码模式下先手动运行一次PPOCRLabel,让模型下载完成,然后找到模型目录,一般是:

C:\Users\<用户名>\.paddleocr\whl\det\ C:\Users\<用户名>\.paddleocr\whl\rec\

把整个.paddleocr目录复制到打包输出目录下,并设置环境变量:

import os os.environ['PADDLE_OCR_HOME'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.paddleocr')

这个方案的好处是离线环境也能用,我给客户的最终交付版本采用的就是这种方案,用户体验最好。

方案二:自动下载模式

如果目标机器能联网,也可以不做任何处理,程序启动时会自动下载模型到当前用户目录。但看起来会卡在下载阶段很久,网络不好的话还可能下载失败,体验很糟糕。

方案三:自定义模型路径

如果你有自己训练或者微调过的模型,可以在PPOCRLabel界面里指定模型路径,或者在启动参数里传--det_model_dir等参数。这个方案灵活性最高,适合算法团队内部使用。

4.2 路径编码问题

打包后的exe如果放在包含中文路径的目录下运行,部分Windows系统会报编码错误,这个坑我必须单独提一下。因为PyInstaller解压临时文件时,如果路径含中文,可能会导致Paddle相关文件加载失败。

建议做法是:

  • 交付时要求exe文件目录路径为纯英文,比如D:\tools\PPOCRLabel
  • 或者在程序入口处加一段代码,强制设置编码方式
import sys import locale sys.stdout.reconfigure(encoding='utf-8')

这一步看似琐碎,但我在现场部署时真的被中文路径问题耽误过半天,最后发现就是路径编码的问题。

4.3 动态库冲突处理

还有一个容易被忽视的问题:目标机器上如果安装过其他Python或者PaddlePaddle版本,系统PATH中的某些DLL可能会被exe优先加载,导致版本冲突。症状是程序启动报一堆奇怪的内存错误,或者识别结果明显异常。

避免办法是在启动脚本文件PPOCRLabel.bat里先清空当前环境变量再启动exe:

@echo off set PATH=C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem start PPOCRLabel.exe

虽然粗暴,但确实管用。实际运行时不会影响识别效果,同时能屏蔽绝大多数系统环境干扰。

5. 打包过程中遇到的问题与排查方法

5.1 常用调试手段

PPOCRLabel打包后的调试思路,不能靠猜,要按日志一层层排查。我用得最多的三个手段:

第一,保留控制台输出。打包后先不要关掉console窗口,看到报错信息再判断是缺模块还是缺DLL。如果是ModuleNotFoundError,说明模块没收集全;如果是OSError: [WinError 126],大概率是DLL缺失;如果是AttributeError,可能是版本不匹配。

第二,在程序入口处加一层日志输出,比如把启动路径、环境变量、关键模块版本都打印出来:

import logging logging.basicConfig(level=logging.DEBUG, filename='debug.log') logging.debug('current dir: %s', os.getcwd()) logging.debug('sys.path: %s', sys.path) import paddle logging.debug('paddle version: %s', paddle.__version__)

第三,用Process Explorer或者Dependency Walker分析exe的DLL依赖,确认哪些系统DLL缺失或多余。

5.2 常见错误速查表

错误现象根本原因解决措施
ModuleNotFoundError: No module named 'paddleocr.utils'PyInstaller漏收集子模块在hiddenimports中显式加入模块名
ImportError: DLL load failedVC++运行库缺失或paddle动态库未收集安装VC++ Redistributable,用collect_dynamic_libs收集
Qt platform plugin not foundPyQt5平台插件缺失在datas中加入plugins/platforms和plugins/imageformats
启动后闪退且无任何提示模型文件路径错误或编码问题检查.paddleocr路径,设置PADDLE_OCR_HOME环境变量
杀毒软件报木马PyInstaller的bootloader特性触发误报更换upx压缩、签名exe、添加白名单说明
启动慢、CPU占用高CPU版Paddle推理,属于正常现象首次启动时模型加载,耐心等待,后续会快一些
图片打开后无法标注Qt插件中qjpeg等图像格式插件缺失收集PyQt5/plugins/imageformats

5.3 杀毒误报问题处理

这个必须单独说。PyInstaller打出来的exe在很多杀毒软件眼中天然带有"危险特征",因为它的bootloader会动态加载Python代码,这个行为跟某些恶意软件的特征很像。我第一次打包完发给同事,他的电脑直接弹出木马警告,当时吓一跳。

处理思路分三步走:

  1. 确认exe确实没有被植入任何恶意代码(整个打包流程都是自己操作,这点可以放心)
  2. 用UPX压缩可能会加剧误报,如果被报毒,建议关闭UPX重新打包
  3. 正式交付时联系目标电脑上的安全软件加白名单,同时建议用代码签名证书对exe进行数字签名

代码签名证书需要购买,价格不便宜,如果不是商业分发可以跳过。但一定要在交付说明文档里写清楚exe的生成方式和校验值,方便客户核对。

6. 最终交付形态与体积优化

6.1 交付目录结构规划

打包完成后,dist里的目录非常杂乱,直接给客户不合适。我整理了一套干净的交付结构:

PPOCRLabel_交付包/ │ ├── PPOCRLabel/ # 打包产物目录 │ ├── PPOCRLabel.exe │ ├── _internal/ # PyInstaller5.x后的依赖目录 │ └── ... │ ├── .paddleocr/ # 预下载的模型文件 │ ├── 标注数据/ # 空目录,让用户直接放图片 │ ├── 输出结果/ # 空目录,标注结果输出位置 │ ├── 使用说明.txt # 一页看懂的操作指引 └── 启动PPOCRLabel.bat # 稳定启动脚本

6.2 体积优化实测数据

我打包出来的初始版本体积是3.6GB,这个体积实在有点大。经过几轮精简后降到1.9GB,说下实际做了哪些操作:

第一,排除无用的库。PaddlePaddle会附带大量开发相关文件,比如paddle.fluid模块其实已经不怎么用了,可以通过spec文件的excludes排除:

excludes=['paddle.fluid', 'paddle.dataset', 'matplotlib', 'PIL.ImageQt']

第二,关闭UPX,虽然体积会略有增大,但稳定性更好,综合考虑后我又把UPX关掉了。

第三,压缩模型。PaddleOCR的检测模型和识别模型原始的精度如果太高,打包体积会比较大,可以在不影响标注效果的前提下换成mobile版模型。实测mobile版模型在标注场景下速度和体积都有优势,精度损失基本可忽略。

第四,PyInstaller版本的选择。5.13.2比4.x版本打出来的包结构更清晰,整体体积也更小,建议优先用新版。

6.3 启动脚本优化

最终交付时,我不建议让用户直接双击exe,因为工作目录不对会引发各种奇怪问题。我在交付包里放了一个启动脚本启动PPOCRLabel.bat,内容如下:

@echo off chcp 65001 >nul cd /d %~dp0 set PATH=C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem set PADDLE_OCR_HOME=%~dp0.paddleocr start "" "%~dp0PPOCRLabel\PPOCRLabel.exe"

这个脚本做了三件事:切换到脚本所在目录、清理系统PATH避免DLL冲突、设置PaddleOCR模型目录。实测下来,无论u盘拷贝到哪台电脑,双击脚本都能稳定启动。

7. 从打包到落地:一份属于自己的复盘

打包PPOCRLabel这个任务,前后花了我一周多时间,第一版被打回,第二版能在部分电脑跑通但报错不断,到第三版才算稳定交付。复盘下来,真正影响成败的核心点其实不在打包命令本身,而在于对依赖链路的理解和对目标环境的充分预判。比如PaddlePaddle这种重型框架,依赖的动态库横跨系统层、Python层、模型层,任何一个环节断了都会在别人的电脑上翻车。

我在测试阶段准备了三类验证环境:一台全新未安装任何开发工具的Windows电脑、一台安装过完整Python环境的电脑、一台安装了安全软件的企业办公电脑。在每台机器上分别做启动测试、图片标注测试、结果导出测试。这个过程很枯燥,但能提前暴露绝大多数现场问题。

最后再分享一个实操心得:打包后的exe如果是在本机跑通,先别急着拷给别人,最好用虚拟机或者另一台物理机做一次"裸机验证"。因为本机环境带着开发时安装的各种依赖,很多问题根本暴露不出来。这个习惯,帮我避免了太多次"我这边明明能跑,客户那边就是不行"的尴尬。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询