首先你要明白我这篇文章的目的是啥,是将Windows中编译通过生成快捷键的exe软件程序进一步在Linux上编译,是为了使UI界面程序尽可能的去适配国产系统例如银河麒麟V10等操作系统。
大概讲一下我的思路先在Windows上验证软件,并且生成外部链接库,不和exe打包到一起。
首先在pycharm上运行大概是这样子的。

为了尽可能的使exe小,我将依赖库全部放到libs中了,有torch,ultralytics,numpy,pandas等等

安装教程:
cd D:\PyCharm\UI
pyinstaller MainUI.spec
python
# -*- MainUI.spec -*-
import sys
import os
import pkgutil
from PyInstaller.utils.hooks import collect_submodules
# ============ 配置 ============
project_path = os.path.abspath(SPECPATH)
libs_path = os.path.join(project_path, 'libs')
if os.path.isdir(libs_path) and libs_path not in sys.path:
sys.path.insert(0, libs_path)
block_cipher = None
# ============ 收集完整 stdlib(含所有子模块) ============
def get_stdlib_top_names():
"""获取所有标准库顶层模块名"""
if hasattr(sys, 'stdlib_module_names'):
names = set(sys.stdlib_module_names)
else:
stdlib_path = os.path.dirname(os.__file__)
names = set()
for m in pkgutil.iter_modules([stdlib_path]):
names.add(m.name)
for entry in os.listdir(stdlib_path):
full = os.path.join(stdlib_path, entry)
if os.path.isdir(full) and os.path.isfile(os.path.join(full, '__init__.py')):
names.add(entry)
# 排除不需要的
exclude = {'antigravity', 'this', 'idlelib', 'turtledemo', 'tkinter', 'test', 'tests', '__phello__', '_pyio'}
return [n for n in names if not n.startswith('_') and n not in exclude]
# 顶层模块名
STDLIB_TOP = get_stdlib_top_names()
# 递归收集所有子模块(关键!解决 unittest.mock 这类问题)
STDLIB_ALL = []
for name in STDLIB_TOP:
try:
STDLIB_ALL.extend(collect_submodules(name))
except Exception:
STDLIB_ALL.append(name)
print(f'[spec] 收集到 {len(STDLIB_ALL)} 个标准库模块')
# ============ 要排除的第三方库(不打包进 exe,运行时从 libs 外部加载) ============
EXCLUDE_LIBS = [
'torch', 'torchvision', 'torchaudio', 'torchmetrics', 'torchsummary',
'ultralytics', 'ultralytics_thop', 'numpy', 'pandas', 'scipy', 'sklearn', 'scikit-learn',
'matplotlib', 'matplotlib_inline', 'mpl_toolkits', 'seaborn',
'PIL', 'Pillow', 'yaml', 'tqdm', 'requests', 'urllib3', 'certifi', 'charset_normalizer',
'idna', 'psutil', 'six', 'pytz', 'dateutil', 'python_dateutil',
'packaging', 'pyparsing', 'cycler', 'kiwisolver', 'fonttools', 'contourpy', 'joblib', 'threadpoolctl',
'IPython', 'ipykernel', 'notebook', 'jupyter',
]
# ============ 强制打包进 exe 的库(即使它们在 libs 中也不排除) ============
FORCE_BUNDLE = [
'cv2', # OpenCV --- DLL 依赖复杂,打包进来更稳
'PyQt5', # Qt GUI --- 避免平台插件缺失
'six', # dateutil 的依赖,libs 中残缺
]
# ============ 自动检测 libs 下的所有包并加入排除列表 ============
def get_all_libs_packages(libs_dir):
"""扫描 libs 目录,返回所有可导入的包名"""
pkgs = set()
if not os.path.isdir(libs_dir):
return pkgs
for item in os.listdir(libs_dir):
item_path = os.path.join(libs_dir, item)
# 目录且不是 __pycache__、不是 .dist-info
if os.path.isdir(item_path) and not item.startswith('__') and not item.endswith('.dist-info') and not item.endswith('.libs'):
# 排除一些非包的目录
if item in ('docs', 'tests', 'third_party', 'bin', 'Scripts'):
continue
pkgs.add(item)
# 合并手动列表
pkgs.update(EXCLUDE_LIBS)
# 剔除强制打包的
pkgs.difference_update(FORCE_BUNDLE)
return sorted(pkgs)
ALL_EXCLUDE = get_all_libs_packages(libs_path)
print(f'[spec] 自动排除 {len(ALL_EXCLUDE)} 个包')
print(f'[spec] 强制打包: {FORCE_BUNDLE}')
a = Analysis(
['MainUI.py'],
pathex=[project_path, libs_path],
binaries=[],
datas=[
(os.path.join(project_path, 'config', 'locations.json'), 'config'),
(os.path.join(project_path, 'best.pt'), '.'),
(os.path.join(project_path, 'ui', '*.ui'), 'ui'),
(os.path.join(project_path, 'ui', '*.qrc'), 'ui'),
(os.path.join(project_path, 'ui', 'icon_rc.py'), 'ui'),
],
hiddenimports=STDLIB_ALL + [
# 你项目自己的模块
'historical_analysis',
'historical_analysis.data_processor',
'historical_analysis.main',
'historical_analysis.map_visualizer',
'historical_analysis.stats_calculator',
'historical_analysis.time_analyzer',
'ui.detect_ui_v2',
'ui.huwai',
'ui.huwai_dialog',
'ui.video_choose',
'ui.video_choose_dialog',
'ui.icon_rc',
'utils.capnums',
'utils.main_utils',
'utils.message',
],
hookspath=[],
hooksconfig={},
runtime_hooks=['runtime_hook.py'],
excludes=ALL_EXCLUDE,
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,
[],
exclude_binaries=True,
name='MainUI',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='MainUI',
)
装好了。现在重新运行 exe 试试:
终端运行可以查看错误
D:\PyCharm\UI\dist\MainUI\MainUI.exe
尽量在终端中运行MainUI.exe,你会发现你有很多报错的。一般是没有包没找到库地址,你让AI改改就好了。

之后会在dist中生成exe,正常情况下是可以执行跳转的。
接下来就是在Linux有GPU的条件下进行测试了。可以看看这篇文章我主要是参考的这篇文章。
https://blog.csdn.net/m0_62627216/article/details/135589831?fromshare=blogdetail&sharetype=blogdetail&sharerId=135589831&sharerefer=PC&sharesource=weixin_52531699&sharefrom=from_link
实例开启自定义服务

终端中输入:
c
cd ~
curl -OL https://download.gpushare.com/download/platform/install_desktop/install_desktop

c
chmod +x ./install_desktop
./install_desktop
等待安装完成,网速快的话几分钟就可以下载好
下载好之后,会出现如下界面,密码可以自定义,但是一定要记住,这个是用于连接图形化界面的:
2.启动桌面
输入manage_vnc可以查看启动和停止命令:

然后输入manage_vnc start

图形化界面就开始启动了,然后回到控制台,找到自定义服务:


三、必做设置
1.熄屏设置
如果你一两分钟不碰它,他会自己断开连接,只能重启VNC服务才行,所以要设置永不熄屏,以防止数据丢失。


完成以上设置后,安装目标检测库之后。
pip install PyQt5 ultralytics pandas seaborn
pip install pyinstaller opencv-python numpy pandas matplotlib
seaborn PyQt5 PyYAML tqdm requests psutil python-dateutil
packaging joblib scipy scikit-learn Pillow ultralytics
首先在ubuntu中测试UI程序。


切记一定要在Ubuntu中打开,你在服务器的jupterLab中打开会显示缺少 XCB 库的。以上可知测试没问题。
同样我是将库放到了libs中
Linux安装:
将依赖包转移
c
mkdir -p "/hy-tmp/UI(Linux)/libs"
cp -r /usr/local/miniconda3/lib/python3.8/site-packages/* "/hy-tmp/UI(Linux)/libs"
c
pyinstaller MainUI_linux.spec
bash
# -*- MainUI_linux.spec -*-
"""
PyInstaller spec for Linux (Kylin V10 / x86_64) --- onedir 模式
==============================================================
使用方法:
pyinstaller MainUI_linux.spec
产物:
dist/MainUI/ ← onedir 输出目录
├── MainUI ← 可执行文件
├── start.sh ← 启动脚本(手动复制)
└── _internal/ ← 所有依赖和资源
外部依赖策略(与 Windows 版 MainUI.spec 一致):
大型第三方库(torch/numpy/scipy/matplotlib/ultralytics 等)
不打包进 exe,而是放在项目根目录的 libs/ 文件夹中。
运行时由 runtime_hook.py 自动将 libs/ 添加到 sys.path 和
LD_LIBRARY_PATH,实现外部 .so 库的动态加载。
这大幅减小了打包体积(从 ~5GB 降到 ~500MB)。
"""
import os
import sys
import pkgutil
from PyInstaller.utils.hooks import collect_submodules
# ============ 项目路径(自动从 spec 文件位置推导)============
PROJECT_DIR = os.path.abspath(SPECPATH)
LIBS_DIR = os.path.join(PROJECT_DIR, 'libs')
# 将 libs/ 加入搜索路径,便于 PyInstaller 分析时找到模块
if os.path.isdir(LIBS_DIR) and LIBS_DIR not in sys.path:
sys.path.insert(0, LIBS_DIR)
block_cipher = None
# ============ 收集完整标准库(含所有子模块,解决 unittest.mock 等隐式依赖)============
def get_stdlib_top_names():
"""获取所有标准库顶层模块名"""
if hasattr(sys, 'stdlib_module_names'):
names = set(sys.stdlib_module_names)
else:
stdlib_path = os.path.dirname(os.__file__)
names = set()
for m in pkgutil.iter_modules([stdlib_path]):
names.add(m.name)
for entry in os.listdir(stdlib_path):
full = os.path.join(stdlib_path, entry)
if os.path.isdir(full) and os.path.isfile(os.path.join(full, '__init__.py')):
names.add(entry)
exclude = {'antigravity', 'this', 'idlelib', 'turtledemo', 'tkinter', 'test', 'tests', '__phello__', '_pyio'}
return [n for n in names if not n.startswith('_') and n not in exclude]
STDLIB_TOP = get_stdlib_top_names()
STDLIB_ALL = []
for name in STDLIB_TOP:
try:
subs = collect_submodules(name)
STDLIB_ALL.extend(subs)
except Exception:
pass
# 确保顶层模块本身一定被包含(C 扩展模块如 cmath 可能无子模块,collect_submodules 返回空列表)
if name not in STDLIB_ALL:
STDLIB_ALL.append(name)
print(f'[spec] 收集到 {len(STDLIB_ALL)} 个标准库模块')
# ============ C 扩展标准库模块(PyInstaller 钩子可能遗漏,需显式声明)============
STDLIB_C_EXTENSIONS = [
'cmath', # 复数数学
'math', # 数学函数
'_json', # JSON 序列化
'_socket', # 网络 socket
'_ssl', # SSL/TLS
'_hashlib', # 哈希算法
'_ctypes', # C 类型调用
'_io', # IO 基类
'_codecs', # 编解码器
'_collections', # 集合类型
'_functools', # 函数工具
'_operator', # 运算符
'_stat', # 文件状态
'_string', # 字符串工具
'_struct', # 二进制结构
'_weakref', # 弱引用
'_abc', # 抽象基类
'_csv', # CSV 解析
'_datetime', # 日期时间
'_decimal', # 十进制
'_elementtree', # XML 解析
'_heapq', # 堆队列
'_lsprof', # 性能分析
'_md5', # MD5
'_multibytecodec', # 多字节编码
'_opcode', # 操作码
'_pickle', # Pickle
'_posixsubprocess', # 子进程
'_queue', # 队列
'_random', # 随机数
'_sha1', '_sha2', '_sha3', # SHA 系列
'_signal', # 信号处理
'_sqlite3', # SQLite
'_sre', # 正则表达式
'_statistics', # 统计
'_thread', # 线程
'_tokenize', # 词法分析
'_tracemalloc', # 内存追踪
'_typing', # 类型提示
'_uuid', # UUID
'_warnings', # 警告
'_winapi', # Windows API(Linux 上通常不存在,但不影响)
'_zoneinfo', # 时区
'array', # 数组
'binascii', # 二进制 ASCII
'fcntl', # 文件控制
'grp', # 组数据库
'mmap', # 内存映射
'ossaudiodev', # OSS 音频(Linux 特有)
'posix', # POSIX 接口
'pwd', # 密码数据库
'readline', # 行编辑
'resource', # 资源限制
'select', # IO 多路复用
'spwd', # 影子密码
'syslog', # 系统日志
'termios', # 终端控制
'unicodedata', # Unicode 数据库
'zlib', # 压缩
]
# ============ 排除列表:不打包进 exe 的大型第三方库(运行时从 libs/ 外部加载)============
EXCLUDE_LIBS = [
# PyTorch 生态(体积巨大,~3GB)
'torch', 'torchvision', 'torchaudio', 'torchmetrics', 'torchsummary',
# YOLO / Ultralytics
'ultralytics', 'ultralytics_thop',
# 科学计算
'numpy', 'pandas', 'scipy', 'sklearn', 'scikit-learn',
# 可视化
'matplotlib', 'matplotlib_inline', 'mpl_toolkits', 'seaborn',
# 图像处理
'PIL', 'Pillow',
# 基础依赖
'yaml', 'tqdm', 'requests', 'urllib3', 'certifi', 'charset_normalizer',
'idna', 'psutil', 'six', 'pytz', 'dateutil', 'python_dateutil',
'packaging', 'pyparsing', 'cycler', 'kiwisolver', 'fonttools', 'contourpy',
'joblib', 'threadpoolctl',
# 开发工具(本就不需要打包)
'IPython', 'ipykernel', 'jupyter', 'notebook',
]
# ============ 强制打包:即使 libs/ 中存在也打包进 exe(关键依赖)============
FORCE_BUNDLE = [
'cv2', # OpenCV --- .so 依赖链复杂,打包进来更稳定
'PyQt5', # Qt GUI --- 避免平台插件缺失
'six', # dateutil 的依赖,libs 中可能残缺
]
# ============ 自动扫描 libs/ 目录,合并排除列表 ============
def get_all_libs_packages(libs_dir):
"""扫描 libs 目录,返回所有可导入的包名"""
pkgs = set()
if not os.path.isdir(libs_dir):
return pkgs
for item in os.listdir(libs_dir):
item_path = os.path.join(libs_dir, item)
# 只收集合法的 Python 包目录
if os.path.isdir(item_path) and not item.startswith('__') and \
not item.endswith('.dist-info') and not item.endswith('.libs'):
if item in ('docs', 'tests', 'third_party', 'bin', 'Scripts'):
continue
pkgs.add(item)
# 合并手动列表
pkgs.update(EXCLUDE_LIBS)
# 剔除强制打包的
pkgs.difference_update(FORCE_BUNDLE)
return sorted(pkgs)
ALL_EXCLUDE = get_all_libs_packages(LIBS_DIR)
print(f'[spec] 自动排除 {len(ALL_EXCLUDE)} 个包(将从 libs/ 外部加载)')
print(f'[spec] 强制打包: {FORCE_BUNDLE}')
# ============ 项目内模块(确保不被 tree-shaking 丢掉)============
PROJECT_HIDDEN_IMPORTS = [
# 项目主模块
'detect_mainui',
'asr_service',
'target',
'huwai_detect',
'relitu',
'data_processor',
# historical_analysis 子包
'historical_analysis',
'historical_analysis.__init__',
'historical_analysis.data_processor',
'historical_analysis.main',
'historical_analysis.map_visualizer',
'historical_analysis.stats_calculator',
'historical_analysis.time_analyzer',
# ui 子包
'ui',
'ui.__init__',
'ui.detect_ui_v2',
'ui.huwai',
'ui.huwai_dialog',
'ui.video_choose',
'ui.video_choose_dialog',
'ui.icon_rc',
# utils 子包
'utils',
'utils.__init__',
'utils.capnums',
'utils.main_utils',
'utils.message',
]
# ============ 隐式导入(仅包含强制打包的库 + 不被排除的库)============
THIRD_PARTY_HIDDEN_IMPORTS = [
# OpenCV(强制打包)
'cv2', 'cv2.data',
# PyQt5(强制打包)
'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets',
'PyQt5.sip',
]
ALL_HIDDEN_IMPORTS = STDLIB_ALL + STDLIB_C_EXTENSIONS + PROJECT_HIDDEN_IMPORTS + THIRD_PARTY_HIDDEN_IMPORTS
# ============ 数据文件(被打包进 _internal)============
datas = [
# 模型文件
(os.path.join(PROJECT_DIR, 'best.pt'), '.'),
# 配置文件
(os.path.join(PROJECT_DIR, 'config', 'locations.json'), 'config'),
# UI 模板文件
(os.path.join(PROJECT_DIR, 'ui', 'detect_ui_v2.ui'), 'ui'),
(os.path.join(PROJECT_DIR, 'ui', 'huwai.ui'), 'ui'),
(os.path.join(PROJECT_DIR, 'ui', 'video_choose.ui'), 'ui'),
# 图标资源
(os.path.join(PROJECT_DIR, 'ui', 'icon_rc.py'), 'ui'),
]
# ============ Analysis ============
a = Analysis(
['MainUI.py'],
pathex=[PROJECT_DIR, LIBS_DIR],
binaries=[],
datas=datas,
hiddenimports=ALL_HIDDEN_IMPORTS,
hookspath=[],
hooksconfig={},
runtime_hooks=['runtime_hook.py'],
excludes=ALL_EXCLUDE,
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,
[],
exclude_binaries=True,
name='MainUI',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True, # Linux 上建议 console=True,方便看日志
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='MainUI',
)
c
pyinstaller MainUI_linux.spec

编译测试通过。

找到dist中的可执行文件执行程序。
c
./MainUI

可执行程序没问题。
编译成功后,将文件压缩即可部署到其它ubuntu中。
c
zip -r 'UI(Linux)-test1.zip' 'UI(Linux)'