系统:Win10
python版本:python 3.7.4
tensorflow-gpu: 14.0
cuda版本:10.0
cuDNN版本:7.4.0
目标检测模型:yolo v3
功能说明:在开始检测之前,需要手动选择视频进行检测,当点击“开始检测”的时候,会使用子线程进行yolov3目标检测,然后在主线程中更新界面(注意,一定要在子线程中处理耗时的操作,在主线程中进行界面的更新),主线程和子线程是采用signal和slot的通信方式。
2.1 在子线程中进行检测,在主线程中更新界面
最初的时候,我是重写python中的Thread类来开辟一个子线程进行处理目标检测耗时操作,需要循环读取视频,也就是需要在子线程中循环发送signal。但是在实验的时候,slot无法接收到signal发送到的信息,具体原因不明来。在网上查找相关资料之后,使用PySide2中自带的QThread来开辟一个子线程进行处理。
在使用QThread进行检测和更新的时候,有两种方式可以实现(这边以循环发送signal为例)
第一种方式:将Signal和QThread进行绑定
# 创建Signal的子类
class DemoSignal(QObject):
msg = Signal(str)
def __init__(self):
super().__init__()
def run(self):
for i in range(100):
self.msg.emit('这是信号{}'.format(i))
# 创建QThread的子类
class DemoQThread(QThread):
def __init__(self):
super().__init__()
class Status(QWidget):
def __init__(self):
super().__init__()
# 创建Signal和QThread的实例对象
self.signal = DemoSignal()
self.qThread = DemoQThread()
# 设置signal的slot
self.signal.msg.connect(self.receiveSignal)
# 将signal放到子线程中
self.signal.moveToThread(self.qThread)
# 将signal和qthread进行绑定
self.qThread.started.connect(self.signal.run)
self.qThread.start()
第二种方式:将Signal作为QThread的一个属性
# 创建QThread的子类
class DemoQThread(QThread):
msg = Signal(str)
def __init__(self):
super().__init__()
def run(self):
for i in range(1000):
self.msg.emit('这是信号{}'.format(i))
# 创建一个显示界面
class Status(QWidget):
def __init__(self):
super().__init__()
# 实例化QThread
# 注意一定要有self
self.qthread = DemoQThread()
self.qthread.msg.connect(self.receiveSignal)
self.qthread.start()
参考资料地址:https://www.bilibili.com/video/BV154411n79k?p=88
2.2 使用pyinstaller进行封装成exe文件的时候,无法正常显示图片资源
在做界面的时候,我用到以一些图片来美化界面的显示,但是运行exe文件之后,无法正常显示,这是因为pyintaller在封装成exe文件的时候,并不会将你的资源文件也进行封装,也就是说你的png,jpg等资源文件并会封装在dist中,exe文件是找不到相关图片或者相关资源进行显示的。所以为了解决这个问题,我们可以将资源文件保存在py文件中,然后导入相关的py文件就可以了。
但是,在下面进行封装的时候pyinstaller无法识别自定义的模块,需要进行额外的处理,就把这个步骤通过另外一种方式给覆盖覆盖掉了,也就是说,如果你进行了2.3就没有必要进行2.2。当然,如果你的项目中不包含自定义的模块,仅仅包含图片资源的话,只需要进行这一步就行了。但是相比于2.3而言,2.2就显得复杂一些。
具体思路:(将.png图片转换成.py文件,使用的使用,从.py文件中读取数据并以.png的格式进行保存)
(1)将.png或者.jpg格式的图片转换成.py文件(在.py文件中,图片是以base64编码格式进行存储的)
# -*- coding: utf-8 -*-
# @Time : 2018/6/6 18:29
# @Author : Octan3
# @Email : [email protected]
# @File : Pic2py.py
# @Software: PyCharm
import base64
def pic2py(picture_name):
"""
将图像文件转换为py文件
:param picture_name:
:return:
"""
open_pic = open("%s" % picture_name, 'rb')
b64str = base64.b64encode(open_pic.read())
open_pic.close()
# 注意这边b64str一定要加上.decode()
write_data = 'img = "%s"' % b64str.decode()
f = open('%s.py' % picture_name.replace('.png', '_png'), 'w+')
f.write(write_data)
f.close()
if __name__ == '__main__':
# 其中pics列表中保存的图片的地址,你可以使用相对地址或者绝地地址
pics = ["one.png", "two.png", "com.png", "socket.png", "win.png"]
for i in pics:
pic2py(i)
print("ok")
运行之后,你会发现在当前的图片所在的文件夹下会新增几个_png.py文件。如果你想使用图片,你只需要导入这个模块就行
(2)在程序中引用
from one_png import img as one #引入img变量,赋别名为one
...
# 将.py文件中的数据读取出来并以.png的格式进行保存
tmp = open('one.png', 'wb') #创建临时的文件
tmp.write(base64.b64decode(one)) ##把这个one图片解码出来,写入文件中去。
tmp.close()
#现在就能用了,用完(加载到程序里之后)删了就好
#xxxxxx #这里one.png 就已经拿出来了,可以用来。下面就可以对此进行你想要的操作了。
do_something_with(one.png) #做任何你想做的
#xxxxxx
os.remove('one.png') #用完可以删除这个临时图片
参考资料:https://blog.csdn.net/Monster_li57/article/details/80601050
2.3 封装的exe文件无法使用自定义的模块
为了方便维护,我将yolo检测模型单独放置在一个package下了,但是封装之后的exe程序并不能够加载自定义的模块,所有才会导致运行exe文件的时候,无法进行目标检测。所以我们需要做的就是告诉我们自定义的模块在哪个位置。
(1) 先生成.spec文件
语法:使用 pyi-makespec yourScript.py 得到yourScript.spect文件
# 在我这边是这样的(注意.dll后面是有;.的)
pyi-makespec -F -w --add-binary C:\Users\cumt\AppData\Local\Programs\Python\Python37\Lib\site-packages\cv2\opencv_videoio_ffmpeg412_64.dll;. login.py
其中:-F表示只显示一个文件夹;-w表示运行程序的时候不显示命令行;--add-binary表示要加载的动态链接库(因为我需要调用opencv读取视频,因此需要添加这个动态链接库。这个动态链接库的地址是(注意):
(2)对.spec文件进行修改
主要有两个方面:一方面要将所有的.py文件加载到第一个列表中;另一个方面将你用到的所有的资源文件都加载到datas中(这个方法和2.3的重复了)
(2)然后运行.spec文件生成.exe文件,注意这边即使使用参数也没有效果(需要在第一步中使用参数)
pyinstaller yourScript.spec
参考资料:
[1] https://blog.csdn.net/djshichaoren/article/details/79801531
[2] https://www.cnblogs.com/guyuyun/p/11074424.html
2.4 使用pyinstaller封装的exe文件无法调用opencv进行视频的读取
这是因为运行exe文件的时候,我们缺少opencv读取视频的动态链接库,我们要做的就是在封装的时候,需要将这个动态链接库复制到exe所在的文件夹下就可以了。
添加动态链接库有两种方法:
(1)第一种方法:在2.3的第二部分a列表的binaries列表中添加相关元组。我的动态链接库的地址是"C:\Users\cumt\AppData\
Local\Programs\Python\Python37\Lib\site-packages\cv2\opencv_videoio_ffmpeg412_64.dll"
因此,你需要在binaries列表中添加动态链接库的地址,这时候就变成了binaries = [("C:\\Users\\cumt\\AppData\\
Local\\Programs\\Python\\Python37\\Lib\\site-packages\\cv2\\opencv_videoio_ffmpeg412_64.dll",'.')]
(2) 第二种方式:参考2.3的第一部分,使用--add-binary 来进行添加
参考资料:https://stackoverflow.com/questions/44415424/videocapture-opencv-python-pyinstaller-not-opening
3 封装tensorflow-gpu的时候出错
在使用pyinstaller封装tensorflow-gpu 14.0的时候,报错:No such file or directory: ‘C:\Users\qhcsu\AppData\Local\Temp\_MEI106802\xxxxx\yyyy错误,我是参考了这位大哥的博客。具体做法就是:
(1)在封装目录下新建一个hook文件夹
(2)在hook文件夹中创建一个hook-xxxx的.py文件,然后在hook-xxxx.py文件中写入如下内容:
from PyInstaller.utils.hooks import collect_data_files
datas = collect_data_files("astor")
(3)因为我首先是生成了.spec文件,然后需要在.spec文件的Analysis的datas变量中加上下面这句话,一定是绝对路径
(4)然后修改Analysis下hookspath的内容,将其改成下面内容:
hookspath=['hook']