pyqt制作简单的摄像头监控界面

文章目录

  • 前言
  • 正文
    • 1.界面设计
    • 2.界面开发进阶
    • 3.在线程中不能直接更新界面
    • 4.pyqt播放音频
    • 5.dialog弹窗
  • 小结

前言

由于原生的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)

正文

1.界面设计

通过网络案例,我们得到一个初始框架.同时知道了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)

2.界面开发进阶

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()

这样,一个简单的界面就差不多了

3.在线程中不能直接更新界面

需要配合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

4.pyqt播放音频

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
另外,该方法同一时刻仅能播放一个音频

5.dialog弹窗

点击按钮弹出窗口,怎么写.
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

你可能感兴趣的:(python)