python程序打包成win的exe应用(以OCR应用为例)

前言

python脚本适合部署在服务器端,开发和维护都很方便,特别是在人工智能方面的模块非常丰富,许多客户的需求都可以落地。但也有需要制作成window客户端的场景,此时是不适合在用户的电脑上运行python脚本的,这时就需要打包成exe应用进行部署。

以下我将会提供3种打包的OCR应用:不带参数的控制台exe、不带参数的图像界面exe、带参数的控制台exe。涉及的代码分享在资源中,因为exe程序太大,上传不上去,需要的留言。

另外,阅读本文时,一定要先看我分享的上一篇文章,有些关联知识我这里不再重复。python 部署可离线使用的中文识别OCR(window)https://blog.csdn.net/liangyuna8787/article/details/153336448

效果图

只有一个不带参数的图像界面exe可以看到界面,如下图

打包场景介绍

一、不带参数的控制台exe

先用一个简单的图片识别程序来走通打包流程。

python中的打包专属工具是pyinstaller,在上篇文章中搭建的虚拟环境(cnocr_env)中安装

因为部署好cnocr的python环境之后,训练好的模型ch_PP-OCRv5_det_infer.onnx是在线自动下载(下载到C:\Users\xxxx\AppData\Roaming目录下),而客户的电脑没有部署Python环境,或者是在内网,所以我们需要把模型文件目录一起打包。

本次打包,把模型文件涉及的cnocr和cnstd目录放在与待打包python文件同级目录的models目录下,结构如下:

├── ocr_test.py

└── models/

├── cnocr/

└── cnstd/

ocr_test.py是简单的测试例子,加入了指定models目录下的训练模式作为文字识别模型(python部署环境默认是C:\Users\xxxx\AppData\Roaming,所以必须明确指定到models目录),代码如下:

复制代码
# ocr_test.py
import os
import sys
from cnocr import CnOcr

def setup_environment():
    """设置运行环境"""
    # 获取当前脚本所在目录的绝对路径
    if getattr(sys, 'frozen', False):
        # 如果是打包后的exe,使用exe所在目录
        script_dir = os.path.dirname(sys.executable)
    else:
        # 开发环境,使用脚本所在目录
        script_dir = os.path.dirname(os.path.abspath(__file__))
    
    # 设置模型根目录(绝对路径)
    models_dir = os.path.join(script_dir, "models")
    cnocr_dir = os.path.join(models_dir, "cnocr")
    cnstd_dir = os.path.join(models_dir, "cnstd")
    
    # 设置环境变量
    os.environ["CNOCR_HOME"] = cnocr_dir
    os.environ["CNSTD_HOME"] = cnstd_dir
    
    return script_dir, models_dir, cnocr_dir, cnstd_dir

# 设置环境
script_dir, models_dir, cnocr_dir, cnstd_dir = setup_environment()

# 检查模型目录是否存在
if not os.path.exists(models_dir):
    print(f"错误: 模型目录不存在: {models_dir}")
    print("请确保models目录与程序在同一目录下")
    input("按任意键退出...")
    sys.exit(1)

# 初始化OCR实例,使用绝对路径指定模型文件
try:
    ocr = CnOcr(
        det_model_name='ch_PP-OCRv5_det',
        rec_model_name='densenet_lite_136-gru',
        det_model_fp=os.path.join(cnstd_dir, "1.2/ppocr/ch_PP-OCRv5_det/ch_PP-OCRv5_det_infer.onnx"),
        rec_model_fp=os.path.join(cnocr_dir, "2.3/densenet_lite_136-gru/cnocr-v2.3-densenet_lite_136-gru-epoch=004-ft-model.onnx")
    )
    print("OCR初始化成功!")
except Exception as e:
    print(f"OCR初始化失败: {e}")
    print("请检查模型文件是否完整")
    input("按任意键退出...")
    sys.exit(1)

def process_image(image_path):
    """处理图片并返回识别结果"""
    if not os.path.exists(image_path):
        return f"错误: 图片文件不存在: {image_path}"
    
    try:
        results = ocr.ocr(image_path)
        texts = [res['text'] for res in results]
        return texts
    except Exception as e:
        return f"识别过程中出错: {e}"

def save_results(results, output_path):
    """保存识别结果到文件"""
    try:
        with open(output_path, 'w', encoding='utf-8') as f:
            if isinstance(results, list):
                for text in results:
                    f.write(text + '\n')
            else:
                f.write(results)
        return f"结果已保存到: {output_path}"
    except Exception as e:
        return f"保存结果时出错: {e}"

if __name__ == '__main__':
    # 使用示例
    image_file = '123.png'
    
    if os.path.exists(image_file):
        print(f"正在处理图片: {image_file}")
        results = process_image(image_file)
        
        if isinstance(results, list):
            print("识别结果:")
            for i, text in enumerate(results, 1):
                print(f"{i}: {text}")
        else:
            print(results)
        
        # 保存结果
        output_path = os.path.join(script_dir, 'ocr_result.txt')
        save_status = save_results(results, output_path)
        print(save_status)
    else:
        print(f"图片文件不存在: {image_file}")
        print("请将待识别的图片放在程序同目录下,并命名为'123.png'")
    
    input("按任意键退出...")

如果不需要调用模型的话,使用命令pyinstaller ---onefile ocr_test.py就可以完成打包了,以下提供详细配置(.spec配置)方式进行打包,配置代码如下:

复制代码
# ocr_test.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

# 获取虚拟环境路径
venv_path = r"D:\python\cnocr_env"  #这是虚拟环境的实际目录
site_packages = os.path.join(venv_path, "Lib", "site-packages")

def get_package_data(package_name):
    """获取包的数据文件"""
    package_dir = os.path.join(site_packages, package_name)
    if not os.path.exists(package_dir):
        return []
    
    data_files = []
    for root, dirs, files in os.walk(package_dir):
        for file in files:
            if file.endswith('.pyc') or '__pycache__' in root:
                continue
            src_path = os.path.join(root, file)
            rel_path = os.path.relpath(root, site_packages)
            dest_path = os.path.join(rel_path) if rel_path != '.' else ''
            data_files.append((src_path, dest_path))
    
    return data_files

# 收集所有必要的数据文件--必须包含
datas = []
for package in ['rapidocr', 'omegaconf', 'yaml', 'cnocr', 'cnstd']:
    datas.extend(get_package_data(package))

a = Analysis(
    ['ocr_test.py'],
    pathex=[],
    binaries=[],
    datas=datas,
    hiddenimports=[
        'cnocr',
        'PyQt5',
        'cnstd', 
        'rapidocr',
        'omegaconf',
        'yaml',
        'rapidocr.cal_rec_boxes',
        'rapidocr.ch_ppocr_rec', 
        'rapidocr.inference_engine',
        'cnstd.ppocr',
        'cnstd.ppocr.rapid_detector',
        'PIL',
        'PIL.Image',
        'numpy',
        'torch',
        'torchvision',
        'cnocr.utils',
        'cnocr.recognizer',
    ],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    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,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='ocr_test',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,  # 控制台打印print输出的信息
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

此时待打包目录的文件结构如下:

├── ocr_test.py、ocr_test.spec

└── models/

├── cnocr/

└── cnstd/

进入此目录,执行以下命令,即可完成打包,打包耗时较长(3~5分钟),需要耐心等待

复制代码
pyinstaller ocr_test.spec

打包完成之后,会生成两个目录:build和dist。ocr_test.exe文件在dist目录下,把models目录拷贝到dist目录下,一起打包到新的window客户端安装。

因为代码中检测的是"123.png",可以进入到exe程序目录下运行,运行结果如下图所示:

图中标红部分为"G:\python\core\cncor\dist_test\models",说明已经是在读取exe同级目录下的models目录了,至此可以部署在其他window系统中了。

二、不带参数的图像界面exe

python中也有简易的图形界面库pyqt5,与手搓方式的C++ QT很相似,下载方式如下:

复制代码
pip install PyQt5

本应用打包与以上的一样,结构图如下:

├── ocr_tool.py、ocr_tool.spec

└── models/

├── cnocr/

└── cnstd/

如效果图中显示,本界面应用,提供在界面选择图片,可预览图片,然后自动进行中文识别,把结果显示出来,并且提供结果导出功能,方便用于其他用途,ocr_tool.py代码如下:

复制代码
import sys
import os
import traceback
from PyQt5.QtWidgets import QApplication, QMessageBox

def setup_environment():
    """设置运行环境"""
    # 获取当前脚本所在目录的绝对路径
    if getattr(sys, 'frozen', False):
        # 如果是打包后的exe,使用exe所在目录
        script_dir = os.path.dirname(sys.executable)
    else:
        # 开发环境,使用脚本所在目录
        script_dir = os.path.dirname(os.path.abspath(__file__))
    
    # 设置模型根目录(绝对路径)
    models_dir = os.path.join(script_dir, "models")
    cnocr_dir = os.path.join(models_dir, "cnocr")
    cnstd_dir = os.path.join(models_dir, "cnstd")
    
    # 设置环境变量
    os.environ["CNOCR_HOME"] = cnocr_dir
    os.environ["CNSTD_HOME"] = cnstd_dir
    
    return script_dir, models_dir, cnocr_dir, cnstd_dir

# 设置环境
script_dir, models_dir, cnocr_dir, cnstd_dir = setup_environment()

# 检查模型目录是否存在
if not os.path.exists(models_dir):
    print(f"错误: 模型目录不存在: {models_dir}")
    print("请确保models目录与程序在同一目录下")
    input("按任意键退出...")
    sys.exit(1)
    

# 继续原有的导入
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton,
    QTextEdit, QVBoxLayout, QWidget, QFileDialog,
    QHBoxLayout, QLabel, QLineEdit
)
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
from cnocr import CnOcr

def init_ocr():
    """初始化OCR引擎,处理打包环境的路径问题"""
    try:
        ocr = CnOcr(
            det_model_name='ch_PP-OCRv5_det',
            rec_model_name='densenet_lite_136-gru',
            det_model_fp=os.path.join(cnstd_dir, "1.2/ppocr/ch_PP-OCRv5_det/ch_PP-OCRv5_det_infer.onnx"),
            rec_model_fp=os.path.join(cnocr_dir, "2.3/densenet_lite_136-gru/cnocr-v2.3-densenet_lite_136-gru-epoch=004-ft-model.onnx")
        )
        print("OCR初始化成功!")
    except Exception as e:
        print(f"OCR初始化失败: {e}")
        print("请检查模型文件是否完整")
        input("按任意键退出...")
        sys.exit(1)
    return ocr

class OCRApp(QMainWindow):
    def __init__(self):
        super().__init__()
        # 修正:确保方法名一致
        self.initUI()
        self.ocr = init_ocr()

    def initUI(self):
        self.setWindowTitle('CnOCR文字识别工具')
        self.setGeometry(100, 100, 800, 600)

        # 主组件
        self.path_edit = QLineEdit()
        self.path_edit.setPlaceholderText("图片路径将显示在这里...")
        self.path_edit.setReadOnly(True)

        self.image_label = QLabel()
        self.image_label.setAlignment(Qt.AlignCenter)
        self.image_label.setStyleSheet("background-color: #f0f0f0;")
        self.image_label.setText("图片预览区域")
        self.image_label.setFixedHeight(300)

        self.result_text = QTextEdit()
        self.result_text.setPlaceholderText("识别结果将显示在这里...")
        self.result_text.setReadOnly(True)

        # 按钮
        self.select_btn = QPushButton("选择图片")
        self.select_btn.clicked.connect(self.select_image)
        self.export_btn = QPushButton("导出结果")
        self.export_btn.clicked.connect(self.export_result)
        self.export_btn.setEnabled(False)

        # 按钮样式
        btn_style = """
        QPushButton {
            padding: 8px; 
            min-width: 100px;
            background-color: #4CAF50; 
            color: white;
            border-radius: 4px;
        }
        QPushButton:hover {background-color: #45a049;}
        QPushButton:disabled {background-color: #cccccc;}
        """
        self.select_btn.setStyleSheet(btn_style)
        self.export_btn.setStyleSheet(btn_style)

        # 按钮布局
        btn_layout = QHBoxLayout()
        btn_layout.addWidget(self.select_btn)
        btn_layout.addWidget(self.export_btn)
        btn_layout.addStretch()

        # 主布局
        main_layout = QVBoxLayout()
        main_layout.addWidget(QLabel("图片路径:"))
        main_layout.addWidget(self.path_edit)
        main_layout.addLayout(btn_layout)
        main_layout.addWidget(QLabel("图片预览:"))
        main_layout.addWidget(self.image_label)
        main_layout.addWidget(QLabel("识别结果:"))
        main_layout.addWidget(self.result_text)

        container = QWidget()
        container.setLayout(main_layout)
        self.setCentralWidget(container)

    def select_image(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp)"
        )

        if file_path:
            self.path_edit.setText(file_path)
            pixmap = QPixmap(file_path)
            self.image_label.setPixmap(
                pixmap.scaled(
                    self.image_label.width(),
                    self.image_label.height(),
                    Qt.KeepAspectRatio,
                    Qt.SmoothTransformation
                )
            )

            results = self.ocr.ocr(file_path)
            text_lines = [res['text'] for res in results]
            self.result_text.setPlainText('\n'.join(text_lines))
            self.export_btn.setEnabled(True)

    def export_result(self):
        if not self.result_text.toPlainText():
            QMessageBox.warning(self, "警告", "没有可导出的识别结果!")
            return

        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        export_path = os.path.join(desktop_path, "ocr_result.txt")

        try:
            with open(export_path, 'w', encoding='utf-8') as f:
                f.write(self.result_text.toPlainText())
            
            QMessageBox.information(
                self, 
                "导出成功", 
                f"结果已成功导出到桌面:\n{export_path}"
            )
        except Exception as e:
            QMessageBox.critical(
                self, 
                "导出失败", 
                f"导出过程中发生错误:\n{str(e)}"
            )

if __name__ == '__main__':
    try:
        app = QApplication(sys.argv)
        app.setStyle('Fusion')
        ex = OCRApp()
        ex.show()
        sys.exit(app.exec_())
    except Exception as e:
        error_msg = traceback.format_exc()
        QMessageBox.critical(None, "启动错误", f"程序启动失败:\n\n{error_msg}")
        sys.exit(1)

以下提供详细配置(.spec配置)方式进行打包,配置代码如下:

复制代码
# ocr_tool.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

# 获取虚拟环境路径
venv_path = r"D:\python\cnocr_env"
site_packages = os.path.join(venv_path, "Lib", "site-packages")

def get_package_data(package_name):
    """获取包的数据文件"""
    package_dir = os.path.join(site_packages, package_name)
    if not os.path.exists(package_dir):
        return []
    
    data_files = []
    for root, dirs, files in os.walk(package_dir):
        for file in files:
            if file.endswith('.pyc') or '__pycache__' in root:
                continue
            src_path = os.path.join(root, file)
            rel_path = os.path.relpath(root, site_packages)
            dest_path = os.path.join(rel_path) if rel_path != '.' else ''
            data_files.append((src_path, dest_path))
    
    return data_files

# 收集所有必要的数据文件
datas = []
for package in ['rapidocr', 'omegaconf', 'yaml', 'cnocr', 'cnstd']:
    datas.extend(get_package_data(package))

a = Analysis(
    ['ocr_tool.py'],
    pathex=[],
    binaries=[],
    datas=datas,
    hiddenimports=[
        'cnocr',
        'PyQt5',
        'cnstd', 
        'rapidocr',
        'omegaconf',
        'yaml',
        'rapidocr.cal_rec_boxes',
        'rapidocr.ch_ppocr_rec', 
        'rapidocr.inference_engine',
        'cnstd.ppocr',
        'cnstd.ppocr.rapid_detector',
        'PIL',
        'PIL.Image',
        'numpy',
        'torch',
        'torchvision',
        'cnocr.utils',
        'cnocr.recognizer',
    ],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    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,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='ocr_tool',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False, #这里设置了不显示控制台信息
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

进入打包目录,执行以下命令,即可完成打包,打包耗时较长(3~5分钟),需要耐心等待

复制代码
pyinstaller ocr_tool.spec

打包完成之后,会生成两个目录:build和dist。ocr_tool.exe文件在dist目录下,把models目录拷贝到dist目录下,一起打包到新的window客户端安装,然后手动点击ocr_tool.exe即可运行,但需要注意的是,运行的时间稍微有点长(1~2分钟),主要是加载训练模型,应用界面出来之后,图片识别一半是3~5秒给出结果。

三、带参数的控制台exe

如果是控制台程序,带参数可以更灵活的作为第三方工具使用,而pyinstaller打包带参数的程序时,需要特殊处理,所以这里单拎出来讲解。

本应用打包与以上的一样,结构图如下:

├── ocr_png.py、ocr_png.spec

└── models/

├── cnocr/

└── cnstd/

本工具支持两个参数:图片的路径和识别结果的保存路径。ocr_tool.py代码如下:

复制代码
import os
import sys
import argparse
from cnocr import CnOcr

def setup_environment():
    """设置运行环境"""
    # 获取当前脚本所在目录的绝对路径
    if getattr(sys, 'frozen', False):
        # 如果是打包后的exe,使用exe所在目录
        script_dir = os.path.dirname(sys.executable)
        # 设置环境变量,让cnocr能找到数据文件
        if hasattr(sys, '_MEIPASS'):
            base_path = sys._MEIPASS
            # 设置cnocr和cnstd的环境变量
            os.environ['CNOCR_HOME'] = os.path.join(script_dir, "models", "cnocr")
            os.environ['CNSTD_HOME'] = os.path.join(script_dir, "models", "cnstd")
            # 添加包路径到sys.path
            for package in ['cnocr', 'cnstd', 'rapidocr']:
                package_path = os.path.join(base_path, package)
                if os.path.exists(package_path) and package_path not in sys.path:
                    sys.path.append(package_path)
    else:
        # 开发环境,使用脚本所在目录
        script_dir = os.path.dirname(os.path.abspath(__file__))
    
    return script_dir

def init_ocr():
    """初始化OCR引擎"""
    # 设置模型根目录(绝对路径)
    script_dir = setup_environment()
    models_dir = os.path.join(script_dir, "models")
    cnocr_dir = os.path.join(models_dir, "cnocr")
    cnstd_dir = os.path.join(models_dir, "cnstd")
    
    # 检查模型目录是否存在
    if not os.path.exists(models_dir):
        print(f"错误: 模型目录不存在: {models_dir}")
        print("请确保models目录与程序在同一目录下")
        return None
    
    # 检查必要的模型文件是否存在
    required_files = [
        os.path.join(cnstd_dir, "1.2/ppocr/ch_PP-OCRv5_det/ch_PP-OCRv5_det_infer.onnx"),
        os.path.join(cnocr_dir, "2.3/densenet_lite_136-gru/cnocr-v2.3-densenet_lite_136-gru-epoch=004-ft-model.onnx")
    ]
    
    for file_path in required_files:
        if not os.path.exists(file_path):
            print(f"错误: 模型文件不存在: {file_path}")
            return None
    
    # 初始化OCR实例
    try:
        ocr = CnOcr(
            det_model_name='ch_PP-OCRv5_det',
            rec_model_name='densenet_lite_136-gru',
            det_model_fp=required_files[0],
            rec_model_fp=required_files[1]
        )
        print("OCR初始化成功!")
        return ocr
    except Exception as e:
        print(f"OCR初始化失败: {e}")
        print("请检查模型文件是否完整")
        return None

def process_image(ocr, image_path, output_path=None):
    """处理图片并保存识别结果"""
    if not os.path.exists(image_path):
        print(f"错误: 图片文件不存在: {image_path}")
        return False
    
    try:
        print(f"正在处理图片: {image_path}")
        results = ocr.ocr(image_path)
        texts = [res['text'] for res in results]
        
        # 打印识别结果
        print("识别结果:")
        for i, text in enumerate(texts, 1):
            print(f"{i}: {text}")
        
        # 确定输出文件路径
        if output_path is None:
            script_dir = setup_environment()
            output_path = os.path.join(script_dir, 'ocr_result.txt')
        
        # 确保输出目录存在
        output_dir = os.path.dirname(output_path)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir)
        
        # 保存结果
        with open(output_path, 'w', encoding='utf-8') as f:
            for text in texts:
                f.write(text + '\n')
        
        print(f"结果已保存到: {output_path}")
        return True
        
    except Exception as e:
        print(f"处理图片时出错: {e}")
        return False

def main():
    """主函数,处理命令行参数"""
    parser = argparse.ArgumentParser(description='OCR文字识别工具')
    parser.add_argument('image_path', nargs='?', help='要识别的图片路径')
    parser.add_argument('output_path', nargs='?', help='结果输出文件路径(可选)')
    
    # 解析命令行参数
    args = parser.parse_args()
    
    # 初始化OCR
    ocr = init_ocr()
    if ocr is None:
        input("按任意键退出...")
        sys.exit(1)
    
    # 处理图片
    if args.image_path:
        # 使用命令行参数
        success = process_image(ocr, args.image_path, args.output_path)
        if not success:
            input("按任意键退出...")
            sys.exit(1)
    else:
        # 没有参数,使用默认图片
        script_dir = setup_environment()
        default_image = os.path.join(script_dir, '123.png')
        
        if os.path.exists(default_image):
            success = process_image(ocr, default_image)
            if not success:
                input("按任意键退出...")
                sys.exit(1)
        else:
            print("使用方法:")
            print("  ocr_png.exe <图片路径> [输出文件路径]")
            print("")
            print("示例:")
            print("  ocr_png.exe C:\\images\\test.png C:\\results\\output.txt")
            print("  ocr_png.exe test.png  (结果将保存为ocr_result.txt)")
            print("")
            print("如果没有参数,将尝试处理同目录下的123.png文件")
            input("按任意键退出...")

if __name__ == '__main__':
    main()

以下提供详细配置(.spec配置)方式进行打包,配置代码如下:

复制代码
# ocr_png.spec
# -*- mode: python ; coding: utf-8 -*-

import os
import sys

block_cipher = None

# 获取虚拟环境路径
venv_path = r"D:\python\cnocr_env"
site_packages = os.path.join(venv_path, "Lib", "site-packages")

def get_package_data(package_name):
    """获取包的数据文件"""
    package_dir = os.path.join(site_packages, package_name)
    if not os.path.exists(package_dir):
        return []
    
    data_files = []
    for root, dirs, files in os.walk(package_dir):
        for file in files:
            # 排除不必要的文件
            if file.endswith('.pyc') or '__pycache__' in root:
                continue
            src_path = os.path.join(root, file)
            # 计算相对路径
            rel_path = os.path.relpath(root, site_packages)
            dest_path = os.path.join(rel_path) if rel_path != '.' else ''
            data_files.append((src_path, dest_path))
    
    return data_files

# 收集所有必要的数据文件
datas = []
essential_packages = ['rapidocr', 'omegaconf', 'yaml', 'cnocr', 'cnstd']

for package in essential_packages:
    package_files = get_package_data(package)
    if package_files:
        datas.extend(package_files)
        print(f"已添加 {len(package_files)} 个 {package} 文件")
    else:
        print(f"警告: 未找到 {package} 包的数据文件")

# 手动添加可能缺失的关键文件
def add_critical_files():
    """手动添加关键文件"""
    critical_files = []
    
    # 查找 cnocr 目录中的 label_cn.txt
    cnocr_path = os.path.join(site_packages, 'cnocr')
    if os.path.exists(cnocr_path):
        label_file = os.path.join(cnocr_path, 'label_cn.txt')
        if os.path.exists(label_file):
            critical_files.append((label_file, 'cnocr'))
            print("已添加 label_cn.txt")
    
    # 查找 rapidocr 目录中的 default_models.yaml
    rapidocr_path = os.path.join(site_packages, 'rapidocr')
    if os.path.exists(rapidocr_path):
        default_models = os.path.join(rapidocr_path, 'default_models.yaml')
        if os.path.exists(default_models):
            critical_files.append((default_models, 'rapidocr'))
            print("已添加 default_models.yaml")
    
    return critical_files

# 添加关键文件
critical_files = add_critical_files()
datas.extend(critical_files)

hidden_imports = [
    'cnocr',
    'cnstd', 
    'rapidocr',
    'omegaconf',
    'yaml',
    'argparse',
    'rapidocr.cal_rec_boxes',
    'rapidocr.ch_ppocr_rec', 
    'rapidocr.inference_engine',
    'cnstd.ppocr',
    'cnstd.ppocr.rapid_detector',
    'PIL',
    'PIL.Image',
    'numpy',
    'torch',
    'torchvision',
    'cnocr.utils',
    'cnocr.recognizer',
    'onnxruntime',
]

a = Analysis(
    ['ocr_png.py'],
    pathex=[],
    binaries=[],
    datas=datas,
    hiddenimports=hidden_imports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    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,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='ocr_png',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,  # 使用控制台以便查看输出
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

进入打包目录,执行以下命令,即可完成打包,打包耗时较长(3~5分钟),需要耐心等待

复制代码
pyinstaller ocr_png.spec

打包完成之后,会生成两个目录:build和dist。ocr_png.exe文件在dist目录下,把models目录拷贝到dist目录下,一起打包到新的window客户端安装,可以进入到exe程序目录下运行,运行结果如下图所示:

图中标红部分为"G:\python\core\cncor\dist_test\models",说明已经是在读取exe同级目录下的models目录了,至此可以部署在其他window系统中了。

结尾

以上python打包的exe很大,有两百多M,如果不用虚拟环境会更加大,整体来说控制台程序首次启动后15秒内执行完成,而pyqt的界面需要1~2分钟左右,建议可以考虑用C++ QT调用控制台程序的组合方式来部署。

相关推荐
Psycho_MrZhang3 小时前
自定义层和读写文件
pytorch·python·深度学习
似水流年 光阴已逝4 小时前
从Excel姓名匹配案例学Python:由点及面的系统化学习指南
开发语言·python·excel
这里有鱼汤4 小时前
量化的困局:当所有人都在跑同一个因子时,我们还能赚谁的钱?
后端·python
新子y4 小时前
【小白笔记】strip的含义
笔记·python
Q_Q19632884754 小时前
python+vue的在线租房 房屋租赁系统
开发语言·vue.js·spring boot·python·django·flask·node.js
空影星4 小时前
GridPlayer,一个好用的多视频同步播放器
python·flask·电脑·音视频
草莓熊Lotso5 小时前
《C++ Web 自动化测试实战:常用函数全解析与场景化应用指南》
前端·c++·python·dubbo
叼菠萝5 小时前
AI 应用开发三剑客系列:LangChain 如何撑起 LLM 应用开发基石?
python·langchain
程序员小远5 小时前
软件测试之压力测试详解
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·压力测试