PyQt5使用小结

本文所用的 Python 版本为 python-3.6.2,PyQt5 版本为 pyqt5-5.9.1,OpenCV 版本为 opencv-python-3.3.0.10 和 opencv-contrib-python-3.3.0.10,TensorFlow 版本为 tensorflow-1.4.0,编程语言为 python3,系统环境为 Windows 10。

前言

  本文是上一篇(TensorFlow Object Detection API使用小结)的后续,因为那个 project 还需要一个界面,所以 Shaun 使用 PyQt 做了这么个界面,其中借用 OpenCV 的图像数据显示。

准备篇

  首先使用 pip 安装所需库,由于上一篇已经安装了 tensorflow,所以本文其实只需要安装 pyqt5 和 opencv-python 就可以了,安装 pyqt5 指令为:pip install pyqt5,相关依赖关系解决办法在上一篇中已提到,这里不再赘述,然后再使用指令 pip install opencv-python 安装 opencv,这里 Shaun 发现在 python 中配置 OpenCV 简直不要太轻爽 O(∩_∩)O~~,就一个 pip 就解决了,哪有 C++ 那么麻烦,以后可能还会继续使用 python 版的 opencv,所以就顺便把它 python 版的扩展包也顺便一起装上,安装指令为:pip install opencv-contrib-python。至此所需环境库安装完毕。

  ※注:相对于上文中使用 pip 在线安装的方式,还有另一种使用 pip 进行离线安装的方式,在 Unofficial Windows Binaries for Python Extension Packages上下载离线包,即 XXXXXX.whl 文件,文件名一般包含库名称和对应版本、python 版本以及是 64 位还是 32 位的等信息,这里以离线包 numpy-1.13+mkl 为例,首先下载适合自己的库版本,适合 Shaun 的当然是 numpy-1.13.3+mkl-cp36-cp36m-win_amd64.whl(这适合 64 位的 python3.6 安装),将命令行目录切换至 numpy-1.13.3+mkl-cp36-cp36m-win_amd64.whl 文件所在目录,输入指令 pip install numpy-1.13.3+mkl-cp36-cp36m-win_amd64.whl 即可离线安装 numpy-1.13+mkl 库。相比在线安装,这种离线安装更加灵活,而且能够安装一些在线安装无法安装的库,像上文中的 numpy-1.13+mkl 库只能采取离线安装的方式,在线安装只能安装不带 mkl 的 numpy 库。采用离线安装方式也可以直接安装带扩展包的 opencv-python库:opencv_python‑3.3.1+contrib‑cp36‑cp36m‑win_amd64.whl ,不需要像在线安装那样装两个库。

实践篇

  以前有用过 Qt 的基础,所以这次使用 PyQt5 感觉上手很快,毕竟这里面的语法有很多是相通的,再加上网上的资料也有很多,所以很快就做了个简陋的界面。不过直接用代码控制界面的布局确实很麻烦,每改下布局都要重新运行一下看看,而且启动时间还有点长 ╮(╯﹏╰)╭。网上有种说法是:

可以先通过QtDesigner设计UI,然后通过Qt提供的命令行工具pyuic5将.ui文件转换成python代码,具体用法是:若ui文件名称为firstPyQt5.ui,则在命令行界面中输入指令:pyuic5 -o firstPyQt5.py firstPyQt5.ui,即可将firstPyQt5.ui文件转换成python代码文件firstPyQt5.py

不过 Shaun 这里由于界面比较简陋,没有几个控件,所以就直接将其用 python 代码控制了,没去尝试这个命令行工具 pyuic5 了,下次有机会再尝试吧 ↖(^ω^)↗。

  Shaun 做的这个小界面实现的功能是:1、可以选择已经训练好的模型来检测选定图片中的目标;2、可以播放选定的视频;3、还有打开摄像头,显示摄像头拍摄的视频。

其中由于 Shaun 电脑无法实时检测目标,所以在视频和摄像头拍摄中就没有添加检测的代码,只有选择图片时才会执行检测功能,有需要的童靴可以自行添加(•̀ᴗ•́)。

附完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import os
import sys

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

import numpy as np
import cv2

import tensorflow as tf
from object_detection.utils import label_map_util
from object_detection.utils import visualization_utils as vis_util

class Detector(object):
def __init__(self):
self.PATH_TO_CKPT = './model/hand_model_faster_rcnn_resnet101.pb' # 选择模型文件
self.PATH_TO_LABELS = r'./model/hands_label_map.pbtxt' # 选择类别标签文件
self.NUM_CLASSES = 1
self.detection_graph = self._load_model() # 加载模型
self.category_index = self._load_label_map()

def _load_model(self):
detection_graph = tf.Graph()
with detection_graph.as_default():
od_graph_def = tf.GraphDef()
with tf.gfile.GFile(self.PATH_TO_CKPT, 'rb') as fid:
serialized_graph = fid.read()
od_graph_def.ParseFromString(serialized_graph)
tf.import_graph_def(od_graph_def, name='')
return detection_graph

def _load_label_map(self):
label_map = label_map_util.load_labelmap(self.PATH_TO_LABELS)
categories = label_map_util.convert_label_map_to_categories(label_map,
max_num_classes=self.NUM_CLASSES,
use_display_name=True)
category_index = label_map_util.create_category_index(categories)
return category_index

def detect(self, image):
with self.detection_graph.as_default():
with tf.Session(graph=self.detection_graph) as sess:
# Expand dimensions since the model expects images to have shape: [1, None, None, 3]
image_np_expanded = np.expand_dims(image, axis=0)
image_tensor = self.detection_graph.get_tensor_by_name('image_tensor:0')
boxes = self.detection_graph.get_tensor_by_name('detection_boxes:0')
scores = self.detection_graph.get_tensor_by_name('detection_scores:0')
classes = self.detection_graph.get_tensor_by_name('detection_classes:0')
num_detections = self.detection_graph.get_tensor_by_name('num_detections:0')
# Actual detection.
(boxes, scores, classes, num_detections) = sess.run(
[boxes, scores, classes, num_detections],
feed_dict={image_tensor: image_np_expanded})
# Visualization of the results of a detection.
vis_util.visualize_boxes_and_labels_on_image_array(
image,
np.squeeze(boxes),
np.squeeze(classes).astype(np.int32),
np.squeeze(scores),
self.category_index,
use_normalized_coordinates=True,
line_thickness=8)

return image



class DetectUI(QWidget):

def __init__(self):
super().__init__()

self.initUI()
self.detector = Detector()
self.cap = cv2.VideoCapture()

def initUI(self):
self.timer = QTimer(self) # 初始化一个定时器
self.timer.timeout.connect(self.showFrame) # 计时结束调用showFrame()方法

self.show_pic_label = QLabel(self)
self.show_pic_label.resize(640, 480)
self.show_pic_label.move(10, 10)
self.show_pic_label.setStyleSheet("border-width: 1px; border-style: solid; border-color: rgb(255, 170, 0);")

self.show_filename_lineEdit = QLineEdit(self)
self.show_filename_lineEdit.resize(200, 22)
self.show_filename_lineEdit.move(10, 500)

self.select_img_btn = QPushButton('Select File', self)
self.select_img_btn.clicked.connect(self.selectImg)
self.select_img_btn.resize(self.select_img_btn.sizeHint())
self.select_img_btn.move(218, 500)

self.open_camera_btn = QPushButton('Open Camera', self)
self.open_camera_btn.clicked.connect(self.openCamera)
self.open_camera_btn.resize(self.open_camera_btn.sizeHint())
self.open_camera_btn.move(292, 500)

self.select_model_btn = QPushButton('Select Model', self)
self.select_model_btn.clicked.connect(self.selectModel)
self.select_model_btn.resize(self.select_model_btn.sizeHint())
self.select_model_btn.move(366, 500)

self.show_modelname_lineEdit = QLineEdit(self)
self.show_modelname_lineEdit.setText('hand_model_faster_rcnn_resnet101.pb')
self.show_modelname_lineEdit.resize(200, 22)
self.show_modelname_lineEdit.move(450, 500)

self.setGeometry(200, 100, 660, 530)
self.setWindowTitle('Hand Detector')
self.show()


def showImg(self, src_img, qlabel):
src_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2RGB)

# src_img = self.detector.detect(src_img) # 检测目标

height, width, bytesPerComponent = src_img.shape
bytesPerLine = bytesPerComponent * width
# 转为QImage对象
q_image = QImage(src_img.data, width, height, bytesPerLine, QImage.Format_RGB888)
qlabel.setPixmap(QPixmap.fromImage(q_image).scaled(qlabel.width(), qlabel.height()))


def showFrame(self):
if(self.cap.isOpened()):
ret, frame = self.cap.read()
if ret:
self.showImg(frame, self.show_pic_label)
else:
self.cap.release()
self.timer.stop() # 停止计时器


def selectImg(self):
if self.cap.isOpened():
self.cap.release()

file_name, file_type = QFileDialog.getOpenFileName(self,
"选取文件",
"./",
"Image Files (*.jpg *.png *.bmp *.tif);;Video Files (*.avi *.mp4)") #设置文件扩展名过滤,注意用双分号间隔过滤,用空格分隔多个文件
# print(file_name,file_type)

if file_type.find("Image") >= 0:
if file_name:
self.show_filename_lineEdit.setText(os.path.split(file_name)[1])

img = cv2.imread(file_name, cv2.IMREAD_COLOR)
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img)

img = self.detector.detect(img) # 检测目标

height, width, bytesPerComponent = img.shape
bytesPerLine = bytesPerComponent * width
# 转为QImage对象
q_image = QImage(img.data, width, height, bytesPerLine, QImage.Format_RGB888)
self.show_pic_label.setPixmap(QPixmap.fromImage(q_image).scaled(self.show_pic_label.width(), self.show_pic_label.height()))

if file_type.find("Video") >= 0:
if file_name:
self.show_filename_lineEdit.setText(os.path.split(file_name)[1])

self.cap.open(file_name)
self.timer.start(30) # 设置时间隔30ms并启动


def openCamera(self):
self.cap.open(0) # 默认打开0号摄像头
self.timer.start(30) # 设置时间隔30ms并启动


def selectModel(self):
model_name, file_type = QFileDialog.getOpenFileName(self,
"选取文件",
"./",
"model Files (*.pb);;All Files (*)") #设置文件扩展名过滤,注意用双分号间隔过滤,用空格分隔多个文件

if model_name:
self.show_modelname_lineEdit.setText(os.path.split(model_name)[1])
self.detector.PATH_TO_CKPT = model_name
self.detector.detection_graph = self.detector._load_model() # 重新加载模型



if __name__ == '__main__':
app = QApplication(sys.argv)
dtcui = DetectUI()
sys.exit(app.exec_())

后记

  初次使用 Python 做一个小东西,其语法确实简洁,不过对于 Shaun 这种习惯用 C++ 的人来说确实还有点不太习惯 (˘•ω•˘)。

参考资料

[1] 用PyQt5+Caffe+Opencv搭建一个人脸识别登录界面

[2] PyQt5学习笔记09----标准文件打开保存框QFileDialog

[3] PyQt5教程——第一个程序(2)http://www.cnblogs.com/archisama/tag/PyQt5/

[4] PyQt5应用与实践

[5] PyQt5系列教程(二)利用QtDesigner设计UI界面http://www.cnblogs.com/tkinter/tag/pyqt5/

[6] OpenCV 3.2.0/OpenCV-Python Tutorials/Gui Features in OpenCV/Getting Started with Images

[7] OpenCV 3.2.0/OpenCV-Python Tutorials/Gui Features in OpenCV/Getting Started with Videos

[8] python3.3 分割路径与文件名 小例