由于原生的python不能做出漂亮的界面,发现很多人都选择使用qt.
参考资料:
pyqt 教程 http://code.py40.com/pyqt5/16.html
pyqt进阶教程 https://blog.csdn.net/la_vie_est_belle/category_9279128.html
网络案例 https://blog.csdn.net/DerrickRose25/article/details/86744787
发现一个好用的查询 api的工具 代码中编辑help(xxx)
通过网络案例,我们得到一个初始框架.同时知道了opencv读出的帧如何绘制在pyqt控件上.
# 核心代码-绘制
def show_camera(self):
flag, self.image = self.cap.read()
show = cv2.resize(self.image, (640, 480))
show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
self.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage))
# 核心代码-更新 (利用定时器重复绘制)
self.timer_camera = QtCore.QTimer() # 初始化定时器
self.timer_camera.timeout.connect(self.show_camera)
由于界面不是重点,相关功能简单实现,没有单页跳转,没有动画.
第一个界面,左边是4个小的视频通道预览,右边是检测出的人脸列表,列表可以上下滚动
点击其中一个视频通道,进入第二个界面,左边是1个大的视频预览,右边是检测出的人脸列表,列表可以上下滚动
问题1:让界面适配屏幕
#获取屏幕宽高,screenGeometry(0)就是主屏幕,1的话是分屏1,以此类推
self._screen_width = int(QApplication.desktop().screenGeometry(0).width())
self._screen_height = int(QApplication.desktop().screenGeometry(0).height())
#设置window
# move()方法是移动窗口在屏幕上的位置
self.resize(box_s[0], box_s[1])
self.move(self._screen_offset, self._screen_offset)
#设置layout
#设置widget
label_show_camera = QtWidgets.QLabel()
label_show_camera.setFixedSize(page1_preview_s[0], page1_preview_s[1])
label_show_camera.setAutoFillBackground(False)
pycharm + Qt Designer
参考:https://blog.csdn.net/crazy696/article/details/88721688
问题1:如何调整gridLayout
https://blog.csdn.net/yxy244/article/details/96278255
先打破,然后摆放,然后再组合
问题2: listview如何添加item
1)简单文本列表
item_list = ['item %s' % i for i in range(660)]
model = QtCore.QStringListModel(self)
model.setStringList(item_list)
self.listView.setModel(model)
2)复杂布局列表
实现方法大概有这么三种:
1)如果使用QListWidget的话,我们直接调用 setItemWidget 即可。
void setItemWidget(QListWidgetItem *item, QWidget *widget)
2)QListWidget和QListView都可以通过调用 setIndexWidget 实现。但是该方法只适合做静态数据的显示,不适合做一些插入、更新、删除操作的数据显示。
void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget)
3)我们针对QListView实现自己的ItemDelegate。重写ItemDelegate的paint函数。
首先了解下view/model 参考:https://www.cnblogs.com/hangxin1940/archive/2012/04/23/2806444.html
知道什么是Delegate后,参考这位大哥的c++写法
https://blog.csdn.net/a844651990/article/details/84324560
改了一个python版本
# 重新定义数据结构
class ListData(object):
def __init__(self, pic, name, info):
self.pic = pic
self.name = name
self.info = info
class MyDelegate(QStyledItemDelegate):
"""
自定义的委托
用来在Model获取后,view显示前,再将数据渲染一次
"""
def paint(self, painter, option, index):
if index.isValid():
painter.save()
# 首先,从索引获取数据,这里获取当前索引角色为DisplayRole的数据
item_var = index.data(Qt.DisplayRole) # [QVariant]
if not item_var:
return
# item区域
rect = QRectF()
rect.setCoords(option.rect.x(),option.rect.y(),option.rect.x()+option.rect.width(),option.rect.y()+option.rect.height())
# 图像区域
imageRect = QRect(rect.x()+10,rect.y()+10, 80, 80)
# 绘制图像
show = cv2.cvtColor(item_var.image, cv2.COLOR_BGR2RGB)
showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)
painter.drawImage(imageRect,showImage)
# 文本区域
nameRect = QRect(imageRect.right()+10,imageRect.top(), rect.width()-10-imageRect.width(), 40)
painter.setPen(QtGui.QPen(Qt.black))
painter.setFont(QFont("Microsoft Yahei",12))
painter.drawText(nameRect, 0, "视频源:%s" % item_var.vid)
painter.restore()
# 需要指定item宽高
def sizeHint(self, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtCore.QSize:
return QSize(option.rect.width(), 100);
最后再外部调用,这里好像并不用实现model,用QStandardItemModel就好了
# 初始化列表数据
self.list_model = QtGui.QStandardItemModel()
self.listView.setModel(self.list_model)
# 新建一个委托(Delagate)
delegate = MyDelegate()
# 设置view的delegate
self.listView.setItemDelegate(delegate)
当数据更新,需要刷新界面时
for d in list_data:
item = QtGui.QStandardItem()
item.setData(d, QtCore.Qt.DisplayRole)
self.list_model.appendRow(item)
self.listView.update()
self.listView.scrollToBottom()
这样,一个简单的界面就差不多了
需要配合QThread, 内置信号,通过信号让主线程更新界面.否则会出现界面不更新的奇景,这bug我调了大半天,一直以为是视频解码出错,结果…(泪奔)
# 1)定义线程
class decodeThread(QtCore.QThread):
# 通过类成员对象定义信号对象,传递要刷新的图像
signal = QtCore.pyqtSignal(QtGui.QPixmap)
def __init__(self):
super(decodeThread, self).__init__()
def run(self):
...
self.signal.emit(pixmap)
# 2)调用线程
self.thread = decodeThread(target=self._init_camera)
#指定收到信号后要执行的方法
self.thread.signal.connect(self._refresh_pic)
self.thread.start()
ps:这里注意thread不能是临时变量,否则会出现另一个神bug
pyqt5的QtMultimedia模块找不到,为了不影响系统,这里用pygame
安装pip install pygame
import pygame
class MyPlayer(object):
def __init__(self):
pygame.mixer.init() # 初始化
def play(self, filename):
if not pygame.mixer.music.get_busy():
pygame.mixer.music.load(filename) # 加载音乐
pygame.mixer.music.play() # 播放
audioPlayer = MyPlayer()
ps: 结果我的程序跑到半路挂掉了.折腾了1小时发现pygame.mixer.music.load这个方法只能放在主线程中调用,放到多线程中调用会报错:PyEval_SaveThread: NULL tstate
另外,该方法同一时刻仅能播放一个音频
点击按钮弹出窗口,怎么写.
1)先用qt designer 新建dialog . 一样往里面写控件,然后一样的ui转py,生成Ui_Dialog类
2)实现dialog子类继承该Ui_Dialog类
class childWindow(QDialog,ui_dialog_mask.Ui_Dialog):
def __init__(self):
QDialog.__init__(self)
self.set_ui()
def set_ui(self):
self.setupUi(self)
3)主窗口调用
App = QApplication(sys.argv)
win = Ui_MainWindow()
child = childWindow()
# 通过toolButton将两个窗体关联
btn = win.pushButton
btn.clicked.connect(child.show)
win.show()
sys.exit(App.exec_())
1)Qt Designer制作界面生成.ui文件
2)QtUIC将.ui文件转换为.py文件
3)继承该.py文件
class Ui_MainWindow(QtWidgets.QMainWindow, Ui_Page1Window):
则可以通过self访问其中的控件
这一套下来有点像当年开发android了.确实美观便捷
一些问题:
1)pyqt使用png图片时有时会失真,改用jpg