PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件

1. 界面设计

就随便用Qt Designer弄了下,尽量用简单的布局弄得整齐些。

PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件_第1张图片
用到的控件基本上都是 QLabel,QLineEditQPushButtonQFormLayout
其中粉色的那个QLabel的样式,是通过QSS实现的,具体:可以在Property属性编辑器
在这里插入图片描述
具体的值:

QLabel{
     background:rgb(255,204,204);color:black;font-size:18px}

2. 代码

2.1 使用线程不断刷新摄像头图片显示在QLabel中

主要参考了:

  • stackoverflow——PyQt showing video stream from opencv
  • Github——How to display opencv video in pyqt apps

注意,这个例子里采用的是 继承Qt里的QThread,而不是python中自带的Thread库

import cv2
import sys
from PyQt5.QtWidgets import  QWidget, QLabel, QApplication
from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap

# 定义类
class Thread(QThread):
	# 定义信号
    changPixmap=pyqtSignal(QImage)
    def run(self):
        cap = cv2.VideoCapture(0)
        # 或者"rtsp://admin:admin@IP:Port/11"
        print(cap.isOpened(),"读取视频流正常")
        while True:
            ret, frame = cap.read()
            if ret:
                rgbImage = frame
                # 这个读进来不用进行BGR2RGB的转换。。。
                # rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rgbImage.shape
                bytesPerLine = ch * w
                convert2QtFormat = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_BGR888)
                p = convert2QtFormat.scaled(640, 360, Qt.KeepAspectRatio)
                self.changPixmap.emit(p)
            else:
                continue

# 使用
class WinForm(QMainWindow,Ui_MainWindow):
    def __init__(self, parent=None):
        super(WinForm, self).__init__(parent)
        self.setupUi(self)
   self.thread = Thread()
   # 绑定信号和槽
   self.thread.changPixmap.connect(self.setImage)
   # 启动子线程
   self.thread.start()
   
   # 定义槽函数
   @pyqtSlot(QImage)
   def setImage(self, image):
       self.label.setPixmap(QPixmap.fromImage(image))
       # 这里的label就是显示摄像头画面的QLabel的 objectName

2.2 重写QLabel的鼠标事件来绘制矩形

在网上搜了很多地方,基本都是同一个代码,让人诧异的是,谷歌上面搜到的英文博客结果竟然是抄袭国人的,也算是一个好消息,哈哈哈。

主要参考:脚本之家——PyQt5 在label显示的图片中绘制矩形的方法:其余基本都是这个的变形。
主要是把继承了一下QLabel,然后重写了QLabel对象的鼠标点击和释放事件,可以获取鼠标点击和释放的起始位置,就可以画矩形了。
如果想画多个,而不是画了一个,之前的那个就消失,可以参考
PyQt5 在 QLabel 使用 QPainter 绘制矩形

# 类定义
class MyLabel(QLabel):
    x0 = 0
    y0 = 0
    x1 = 0
    y1 = 0
    flag = False

    # 鼠标点击事件
    def mousePressEvent(self, event):
        self.flag = True
        self.x0 = event.x()
        self.y0 = event.y()

    # 鼠标释放事件
    def mouseReleaseEvent(self, event):
        self.flag = False

    # 鼠标移动事件
    def mouseMoveEvent(self, event):
        if self.flag:
            self.x1 = event.x()
            self.y1 = event.y()
            self.update()

    # 绘制事件
    def paintEvent(self, event):
        super().paintEvent(event)
        rect = QRect(self.x0, self.y0, abs(self.x1 - self.x0), abs(self.y1 - self.y0))
        painter = QPainter(self)
        painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
        painter.drawRect(rect)
# 使用
self.label = MyLabel(self.centralwidget)
# 找到自己.ui文件中 实现显示视频流的那个QLabel 也就是要在其上画框的QLabel,把类名从QWidget.QLabel改成MyLabel 然后其他地方就不用改了,使用label来进行对这个对象的其他调用即可

效果类似:
先在上面这个显示视频的地方画个框
PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件_第2张图片
然后点击获取坐标,就可以显示新的坐标
在这里插入图片描述


2.3 使用信号和槽获取配置信息

这里是参考了书上的一部分代码《PyQt5快速开发与实战》:PyQt5/Chapter07/CallMainWinSignalSlog02.py 这个例子就是点一个按钮,传递打印信息;按另一个按钮,传递预览信息

参考上面的,我给出自己写的一个示例,其实很简单:

# 定义信号
configInfo=pyqtSignal(dict) # 这个信号传递的信息是dict类型的
# 绑定信号和槽
self.productConfigBtn.clicked.connect(self.emitConfigInfor)
# productConfigBtn 这个是一个QPushButton按钮的Obeject Name,按钮点击事件本来也是一种信号
self.configInfo.connect(self.productConfig)
# productConfig这是自己写的一个函数
def emitConfigInfor(self):
    configValue={
     }
    configValue.update({
     "camera_ip": self.cameraIPLE.text()})
    configValue.update({
     "device_name":self.deviceNameLE.text()})
    configValue.update({
     "device_type": self.deviceTypeLE.text()})
    .............
    self.configInfo.emit(configValue)
    # 函数中一定要有一个emit()函数  之前定义的configInfo信号 使用emit()发射需要传递给槽的信息
    # (connect函数决定了信息的发送方向)这里就是 点击了上面的productConfigBtn按钮后,使用这个emitConfigInfor函数来获取信息,并把收集到的configValue信息发送给productConfig

def productConfig(self,valueDict):
# 接受传递的json格式的信息,也就是上面的configValue,然后写入json文件
      self.configDict.update(valueDict)
      for k,v in self.configDict.items():
          print(k,v)
      jsonDir=os.path.join(self.savedPath,'configFile')
      if not os.path.exists(jsonDir):
          os.mkdir(jsonDir)
      jsonPath=os.path.join(jsonDir,self.configDict["device_id"]+str(".json"))
      print("文件保存路径:",jsonPath)
       with open(jsonPath, 'w') as fp:
           json.dump(self.configDict, fp,indent=4)
       for i in range(5):
           if os.path.exists(jsonPath):
               self.statusbar.showMessage("配置文件保存成功",2000)
               break

所以逻辑就是,界面上的那些需要手动输入的信息,当我按下一个生成配置文件的按钮时,传递一个消息/信号,生成配置信息的dict,然后发送到生成配置文件的那个函数,其实可以一步完成,哈哈哈。

效果类似
PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件_第3张图片
然后就会在指定目录生成一个json格式的配置文件


2.4 摄像头说明

第一码流和第二码流,
参考监控里的主码流和子码流是什么意思:

  • 主码流:是指视频文bai件在单位时du间内使用的主要数据流量。
  • 子码流:是指视zhi频文件在单位时间内dao使用的次要数据流量。
  • 主码流:主码流用于本地存储。子码流:又称次码流,次码流用于网传。硬件逻辑单元在启动一次后同时产生2路码流,即一路主码流和一路次码流。主码流和次码流可以为不同的编码协议。次码流不能单独存在。
  • 主码流较之次码流,码流大,压缩比小,图像质量高。

我拿到的摄像头的主码流(第一码流)是 1920*1080,子码流(第二码流)是640*352左右。。

2.2 小问题

2.2.1. h264错误

这个错误不是很有所谓,偶尔会出现,偶尔不出现。
[h264@0000000046ba40]error while decoding MB 105 15,bytestream -14
参考CSDN博客——h264 @ 000001d641122200 error while decoding MB 36 106, bytestream -7
PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件_第4张图片
目前没有什么特别有效的解决方案,就是重新初始化一遍摄像头,哈哈哈。

2.3 dict导出json文件

把保存配置的dict变量导出写入一个json文件中,
参考How to save a dictionary in a json file with python ?:其实很简单,直接采用json的dump方法就可以

import json

dict = {
     "member #002":{
     "first name": "John", "last name": "Doe", "age": 34},
        "member #003":{
     "first name": "Elijah", "last name": "Baley", "age": 27},
        "member #001":{
     "first name": "Jane", "last name": "Doe", "age": 42}}

with open('data.json', 'w') as fp:
    json.dump(dict, fp)

如果想让写入的json格式更好看,可以加入缩进参数,例如

with open('data.json', 'w') as fp:
    json.dump(dict, fp,  indent=4)

如果还想让json对写入的字段进行排序,可以使用排序参数,例如:

with open('data.json', 'w') as fp:
    json.dump(dict, fp, sort_keys=True, indent=4)

3. 打包成exe

使用

pyinstaller -F main.py

打包之后,显示了许多warning信息。
PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件_第5张图片
PyQt——3. 使用opencv读取rtfs流并标记矩形框导出配置文件_第6张图片
运行报错,尴尬。

4. 参考链接

一般参考链接都是前期调研,看大致情况的,所以实际参考的并不一定是这些链接,还是以正文为主,哈哈哈

4.1. 读取rtfs流

百度搜索结果

  • PyQt结合Opencv显示图片以及摄像头
  • PyQt5 实现视频播放器(二) ,详细版本 ,适合新手入门
  • python + pyqt5 视频播放UI界面:设置背景图
  • PyQt5 中嵌入rtsp视频流播放,使用vlc
  • 记录: pyqt5+opencv实现rtsp/rtmp视频流播放
  • pyqt5 opencv 播放rtsp
  • python+opencv+pyqt5控制摄像头在Qlabel上显示

谷歌搜索结果:

  • stackoverflow——PyQt showing video stream from opencv
  • Github——How to display opencv video in pyqt apps
  • Face Detection with OpenCV and PyQt

4.2. 在视频流上绘制矩形框

搜索了一下labelImg的实现,看到代码中使用了canvas,地址链接——Github-canvas

  • 【pyqt5】拖拽绘制矩形框
  1. 脚本之家——PyQt5 在label显示的图片中绘制矩形的方法:全网统一实现,就是这个。
  2. PyQt5 在 QLabel 使用 QPainter 绘制矩形
  • Pyqt5入门学习笔记(四)在QLabel显示的图片上绘制点和矩形

下面的都是单纯绘制矩形,但是这个矩形无法显示在label层上面,其它空白的地方倒是可以看到。

  • stackoverflow——How to draw a rectangle and adjust its shape by drag and drop in PyQt5
  • stackoverflow——PYQT Draw selection rectangle over picture

4.3. 获取实时绘制的QRect对象的坐标显示在状态栏中

获取坐标

  • PyQt5 QRect和QRectF
  • 语雀-PyQt4中文文档:感谢

多线程
由于获取的鼠标一直在移动,获取的坐标也一直在改变,所以需要使用一个单独的线程对坐标进行监控和获取

  • PyQt4多线程定时刷新控件
  • pyqt5-多线程QThread类
  • PYQT5开启多个线程和窗口,多线程与多窗口的交互
  • pyqt5 QThread多线程示例
  • PyQt5之QThread多线程
    鼠标移动事件 状态栏显示

mousemove event statusbar show pyqt

  • Qt论坛-有代码-tracking mouse coordinates
  • Googel Group-How to set StatusBar() message when hover mouse over some widget ?
  • PyQt 键盘事件和鼠标事件
  • pyqt5移动鼠标显示坐标的方法

4.4.PyQt实现数据标注

  • PyQt写的简单图像标注工具
  • PyQt5实现简单数据标注工具

4.5. python多线程队列及pyqt线程

  • python 多线程中子线程和主线程相互通信
  • Python多线程和队列结合demo
  • Python 用队列实现多线程并发
  • python多线程技术 python-线程的暂停, 恢复, 退出
  • python多线程技术 python-线程的开始、停止、暂停总结
  • PyQt5中异步刷新UI和Python中的多线程总结
  • Python多线程与队列

4.6. pyqt+opencv显示摄像头视频

  • 知乎-2.使用PyQt5+OpenCV显示摄像头图像

你可能感兴趣的:(#,PyQt,项目实战)