项目大致情况
- 一个利用
YOLO8
进行内容检测的项目,项目中除了通过YOLO8
实现的内容检测部分,还有通过PyQt5
实现的 GUI - 项目使用
Pyinstaller
进行打包 - 所使用的环境:
Windows
10
PyCharm
Anaconda
Python
3.10
Ultralytics
8.1.29
PyQt5
5.15.10
Pyinstaller
5.13.2
所遇到的问题与解决方案
阶段一:报错找不到模块或资源
初次尝试使用 Pyinstaller
进行打包:
pyinstaler -w main.py
此处 main.py
是进行打包的文件名,尝试运行打包出来的 exe,报错找不到文件之类的,解决办法:
- 模块未被打包: 由于
Ultralytics
包比较新,Pyinstaller
还没有包含自动将这个模块打包进去的指令,所以需要到你的这个虚拟环境目录下找到Ultralytics
文件夹,将其复制到你的python
项目目录下,然后使用pyinstaller
打包指令时添加上额外的数据打包指令,即使用:
进行打包pyinstaller --collect-data ultralytics -w main.py
- 额外资源未被打包: 由于使用了自己的
.bt
文件模型以及其他许多图片资源素材,这些内容也需要被打包进去,不然 exe 运行时找不到这些内容就会报错,因此可以在打包时先使用:
生成pyi-makespec --collect-data ultralytics -w main.py
.spec
文件,在这个文件中的
第一个# -*- mode: python ; coding: utf-8 -*- from PyInstaller.utils.hooks import collect_data_files datas = [('res', '.')] datas += collect_data_files('ultralytics')
datas
这里进行修改,默认生成的是datas = []
, 你需要在里面添加上你的资源所在的文件夹,便于打包,例如我在项目目录下添加了res
这个文件夹,并且把所有的资源都放进去,所以最终就是datas = [('res', '.')]
,这样你的这些额外资源就也会被打包进行,理论上你的 exe 就能正常工作了,但是!总是会有意外发生!这就是阶段二的内容了
阶段二:exe 运行奇怪报错
正常来说,你把所有这些 python
模块和资源文件都打包好了,就能正常运行了,但是这边由于用了 -w
这个不显示控制台的打包方法,如果你的程序代码里包含了 print()
或者 LOGGER
之类的打印信息,那么这里就会由于找不到控制台报一些奇怪的错误
如果是 print()
这个很好处理,你自己写的都注释掉就行,但是这边用了 Ultralytics
包 YOLO
模块,它 __init__
了 LOGGER
这个东西,在好几个文件里都有涉及,用来输出一些 YOLO
的过程信息,所以你需要找到 Ultralytics
文件夹里 utils
文件夹下的 __init__.py
在大概 270 行左右:
# Set logger
# LOGGER = set_logging(LOGGING_NAME, verbose=VERBOSE) # define globally (used in train.py, val.py, predict.py, etc.)
LOGGER = None
把 LOGGER
设为 None
,这样之后就不会输出东西了,然后也要找到调用了 LOGGER
的几个文件,基本上是 Ultralytics
文件夹里 engine
文件夹下的几个文件,可以搜索一下然后把所有 LOGGER
都注释了,如果在单行的条件语句里记得补充上 pass
不然会报错
这样把所有的涉及输出到控制台的东西都处理好了,再次重新打包,你的 exe 应该就可以正确运行了,但是!还是会有问题,不然就不会有这篇记录了!
阶段三:启动涉及 YOLO预测功能时,控制台会闪烁出现,同时生成了额外的GUI界面
这部分问题是最摸不着头脑的,想到了估计会是多线程的问题,但是不知道具体问题在哪里。最后在 Ultralytics
以及 Pyinstaller
的 GitHub 问题讨论区留下问题,得到了大佬的解答才得以解决:
-
添加控制多线程的内容: 需要在主程序运行自己的
PyQt
界面的代码执行前,先添加额外的一个:if __name__ == '__main__': import multiprocessing multiprocessing.freeze_support() app = QApplication(sys.argv) window = Window() window.show() sys.exit(app.exec_())
应该是通过
multiprocessing.freeze_support()
避免多线程的一些问题,这样就能避免生成额外的 GUI 界面 -
修改YOLO中存在线程问题的代码: 找到
Ultralytics
文件夹里utils
文件夹下的torch_utils.py
文件,里面的 73 行左右,这边在获取机器的cpu
信息的时候应该是有启动了多线程之类的东西:def get_cpu_info(): # """Return a string with system CPU information, i.e. 'Apple M2'.""" # import cpuinfo # pip install py-cpuinfo # # k = "brand_raw", "hardware_raw", "arch_string_raw" # info keys sorted by preference (not all keys always available) # info = cpuinfo.get_cpu_info() # info dict # string = info.get(k[0] if k[0] in info else k[1] if k[1] in info else k[2], "unknown") import wmi c = wmi.WMI() string = c.Win32_Processor()[0].Name return string.replace("(R)", "").replace("CPU ", "").replace("@ ", "")
问题应该出在
cpuinfo
这个模块估计会启动其他线程造成一些冲突之类,导致了这个控制台在启动YOLO
的检测时会突然出现闪烁的问题,所以我修改成通过wmi
模块简单拿到cpu
型号信息这样一个简单方法去规避了问题。如果你是按我这样在修改完 73 行附近这个函数,那应该基本所有问题就都解决了,如果你这边的
cpu
信息获取方法调整的和我不一样,注意 164 行左右的调用也需要调整,或者你也可以直接在那边写死cpu
信息,不过这样程序运行平台就很局限了
总结
把以上三个阶段的内容都修改完,你的 YOLO
以及 PyQt5
项目应该就能被 Pyinstaller
完美打包好,并且正常的运行了!如果你还希望修改一下 exe 的 icon,记得先在 pyqt
的窗口类里面加上 icon的信息,同时在打包的时候指定上 icon文件:
from PyQt5.QtGui import QIcon
self.setWindowIcon(QIcon('res/your_icon.ico'))
pyi-makespec --collect-data ultralytics -F -w -i your.ico main.py
pyinstaller main.spec
这样最终可以打包出来一个单独的 exe 文件,并且是你的icon,最后附上一个 png
转 ico
文件的代码:
from PIL import Image
def make_icon():
# 读取PNG图像
png_image = Image.open('icon.png')
# 定义要生成的ICO文件中的各个尺寸
sizes = [(16, 16), (32, 32), (48, 48), (64, 64), (256, 256)]
# 创建ICO文件
ico_image = Image.new('RGBA', (max(sizes)), (255, 255, 255, 0))
# 将PNG图像按照不同尺寸复制到ICO文件中
for size in sizes:
resized_image = png_image.resize(size, Image.ANTIALIAS)
ico_image.paste(resized_image, (0, 0))
# 保存ICO文件
ico_image.save('output.ico')
if __name__ == '__main__':
make_icon()
写在最后
第一次做 YOLO
以及 PyQt5
并且最后用 Pyinstaller
来打包,遇到这些找了很多内容,最后在 GitHub 大佬帮助下终于完美解决,记录一下,希望能帮助到同样遇到类似问题的朋友!