1. 软件开发
- 业务要求:制作多任务RTSP视频流播放以及区域画线的界面,并完善界面程序相应的基本功能。
- 整体思路:由于使用的语言是Python,且该软件制作只是用作后面务中算法调用的工具,并未采用专业的软开制作程序以及界面美化等手段,只需要达到基本的功能即可,恰好在Python中有制作程序界面最好的库:PyQt5, 实现较为简单。为了更好地调整软件界面相关部件的布局,采用了Qt Designer设计了界面相关部件的布局,使用PyUIC将设计好的界面导出为python 基本程序,在此基础上,在界面程序中嵌入实现相应的功能,开发框架见图1.

1.1 多任务RTSP视频流播放与画线UI设计
1.1.1 Pycharm(或VsCode)配置PyQt5及其工具
环境配置:pyqt5、pyqt5-tools、opencv,为了避免因为环境包之间的冲突,安装包主要在anaconda单独配置的环境中,本文为anaconda下的pytorch环境。若出现版本不一致的问题。建议降低版本再操作,特别是opencv与pyqt5要保持版本匹配,否则在后续RTSP视频流播放嵌入会出现问题。经过测试,在Windows上能够正常运行,在Ubuntu系统上会出现版本匹配不一致,无法播放视频流的问题,需要重新编译opencv才行。
pip install pyqt5
pip install pyqt5-tools
pip install opencv-contrib-python
-
进入Pycharm 项目界面,点击”File“,选择”Settings“,弹出设置界面。在设置界面中选择”Tools“ →\rightarrow→ ”External Tools“,操作见图2。

图2 操作流程1 -
Qt Designer配置:点击”+“按钮新建工具,弹出工具配置界面,“Name”一栏填写”Qt Designer“;在”Group“一栏填写”Qt“;”Program“一栏填designe.exe的路径,本文安装在anaconda下pytorch环境里,位置为:E:\anaconda\envs\pytorch\Lib\sitepackages\qt5_applications\Qt\bin\designer.exe”(根据实际情况填写);在”Working directory“一栏填写”$FileDir“。最后点击OK,如图3所示。

图3 操作流程2 -
PyUIC配置:同样地点击”+“按钮新建工具,弹出工具配置界面;“Name”一栏填写”Qt Designer“,在”Group“一栏填写”Qt“;”Program“一栏填pyuic5.exe的路径,一般在对应环境的”Scripts“目录下,本文路径为”E:\anaconda\envs\pytorch\Scripts\pyuic5.exe“;在”Working directory“一栏填写”$FileDir“。在”Arguments“中填写如下内容,完成填写后,点击“OK”,步骤如图4
$FileName$
-o
$FileNameWithoutExtension.py$

1.1.2 Qt Designer设计基本布局
按照业务要求,分两步进行。
- 主窗口布局设计:将整个窗口主要划分为3部分,具体来说分为菜单栏、主体部分、提交部分。
菜单栏主要在子窗口进行新增任务,通过新增任务按钮实现;以及实现RTSP视频流画线的保存、撤销、清空、撤销等基本功能;主体部分呈现任务列表、RTSP视频流播放以及参数配置;提交部分在完成任务人进行保存区域画线提交并退出所有操作。其界面如下

- 子窗口布局设计:子窗口主要是用来填写新增任务的信息,包括任务名称以及rtsp视频流地址。其布局见图6。

1.1.3 PyUIC转python代码
- 主窗口mainWindow.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1675, 854)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_5.setContentsMargins(9, 9, 9, 9)
self.verticalLayout_5.setSpacing(6)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setSpacing(6)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.button_a_1 = QtWidgets.QPushButton(self.centralwidget)
self.button_a_1.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_a_1.setObjectName("button_a_1")
self.horizontalLayout_2.addWidget(self.button_a_1)
spacerItem = QtWidgets.QSpacerItem(148, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.hl_a_1 = QtWidgets.QHBoxLayout()
self.hl_a_1.setSpacing(6)
self.hl_a_1.setObjectName("hl_a_1")
self.button_a_2 = QtWidgets.QPushButton(self.centralwidget)
self.button_a_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_a_2.setObjectName("button_a_2")
self.hl_a_1.addWidget(self.button_a_2)
self.button_a_3 = QtWidgets.QPushButton(self.centralwidget)
self.button_a_3.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_a_3.setObjectName("button_a_3")
self.hl_a_1.addWidget(self.button_a_3)
self.button_a_4 = QtWidgets.QPushButton(self.centralwidget)
self.button_a_4.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_a_4.setObjectName("button_a_4")
self.hl_a_1.addWidget(self.button_a_4)
self.button_a_5 = QtWidgets.QPushButton(self.centralwidget)
self.button_a_5.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_a_5.setObjectName("button_a_5")
self.hl_a_1.addWidget(self.button_a_5)
self.horizontalLayout_2.addLayout(self.hl_a_1)
spacerItem1 = QtWidgets.QSpacerItem(298, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.horizontalLayout_2.setStretch(0, 1)
self.horizontalLayout_2.setStretch(1, 1)
self.horizontalLayout_2.setStretch(2, 4)
self.horizontalLayout_2.setStretch(3, 5)
self.verticalLayout_5.addLayout(self.horizontalLayout_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSpacing(6)
self.horizontalLayout.setObjectName("horizontalLayout")
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setSpacing(6)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.label_b_1 = QtWidgets.QLabel(self.centralwidget)
self.label_b_1.setObjectName("label_b_1")
self.verticalLayout_3.addWidget(self.label_b_1)
self.scrollArea_b_1 = QtWidgets.QScrollArea(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.scrollArea_b_1.sizePolicy().hasHeightForWidth())
self.scrollArea_b_1.setSizePolicy(sizePolicy)
self.scrollArea_b_1.setWidgetResizable(False)
self.scrollArea_b_1.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.scrollArea_b_1.setObjectName("scrollArea_b_1")
self.scrollAreaWidgetContents_b_1 = QtWidgets.QWidget()
self.scrollAreaWidgetContents_b_1.setGeometry(QtCore.QRect(80, 0, 131, 251))
self.scrollAreaWidgetContents_b_1.setObjectName("scrollAreaWidgetContents_b_1")
self.verticalLayout = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_b_1)
self.verticalLayout.setContentsMargins(9, 9, 9, 9)
self.verticalLayout.setSpacing(6)
self.verticalLayout.setObjectName("verticalLayout")
self.button_b_1_1 = QtWidgets.QPushButton(self.scrollAreaWidgetContents_b_1)
self.button_b_1_1.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.button_b_1_1.sizePolicy().hasHeightForWidth())
self.button_b_1_1.setSizePolicy(sizePolicy)
self.button_b_1_1.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_b_1_1.setObjectName("button_b_1_1")
self.verticalLayout.addWidget(self.button_b_1_1)
self.button_b_1_2 = QtWidgets.QPushButton(self.scrollAreaWidgetContents_b_1)
self.button_b_1_2.setEnabled(False)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.button_b_1_2.sizePolicy().hasHeightForWidth())
self.button_b_1_2.setSizePolicy(sizePolicy)
self.button_b_1_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_b_1_2.setObjectName("button_b_1_2")
self.verticalLayout.addWidget(self.button_b_1_2)
self.scrollArea_b_1.setWidget(self.scrollAreaWidgetContents_b_1)
self.verticalLayout_3.addWidget(self.scrollArea_b_1)
self.hline_2 = QtWidgets.QFrame(self.centralwidget)
self.hline_2.setFrameShape(QtWidgets.QFrame.HLine)
self.hline_2.setFrameShadow(QtWidgets.QFrame.Sunken)
self.hline_2.setObjectName("hline_2")
self.verticalLayout_3.addWidget(self.hline_2)
self.verticalLayout_3.setStretch(0, 1)
self.verticalLayout_3.setStretch(1, 5)
self.horizontalLayout.addLayout(self.verticalLayout_3)
self.vline_1 = QtWidgets.QFrame(self.centralwidget)
self.vline_1.setFrameShape(QtWidgets.QFrame.VLine)
self.vline_1.setFrameShadow(QtWidgets.QFrame.Sunken)
self.vline_1.setObjectName("vline_1")
self.horizontalLayout.addWidget(self.vline_1)
self.verticalLayout_4 = QtWidgets.QVBoxLayout()
self.verticalLayout_4.setSpacing(6)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.label = QtWidgets.QLabel(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.label.setSizePolicy(sizePolicy)
self.label.setScaledContents(True)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.verticalLayout_4.addWidget(self.label)
self.horizontalLayout.addLayout(self.verticalLayout_4)
self.vlie_2 = QtWidgets.QFrame(self.centralwidget)
self.vlie_2.setFrameShape(QtWidgets.QFrame.VLine)
self.vlie_2.setFrameShadow(QtWidgets.QFrame.Sunken)
self.vlie_2.setObjectName("vlie_2")
self.horizontalLayout.addWidget(self.vlie_2)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setSpacing(6)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.label_b_3 = QtWidgets.QLabel(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_b_3.sizePolicy().hasHeightForWidth())
self.label_b_3.setSizePolicy(sizePolicy)
self.label_b_3.setObjectName("label_b_3")
self.verticalLayout_2.addWidget(self.label_b_3)
self.hline_4 = QtWidgets.QFrame(self.centralwidget)
self.hline_4.setFrameShape(QtWidgets.QFrame.HLine)
self.hline_4.setFrameShadow(QtWidgets.QFrame.Sunken)
self.hline_4.setObjectName("hline_4")
self.verticalLayout_2.addWidget(self.hline_4)
self.button_b_3_1 = QtWidgets.QPushButton(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.button_b_3_1.sizePolicy().hasHeightForWidth())
self.button_b_3_1.setSizePolicy(sizePolicy)
self.button_b_3_1.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.button_b_3_1.setObjectName("button_b_3_1")
self.verticalLayout_2.addWidget(self.button_b_3_1)
self.splitter_b_31 = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter_b_31.sizePolicy().hasHeightForWidth())
self.splitter_b_31.setSizePolicy(sizePolicy)
self.splitter_b_31.setOrientation(QtCore.Qt.Horizontal)
self.splitter_b_31.setObjectName("splitter_b_31")
self.button_b_3_2 = QtWidgets.QPushButton(self.splitter_b_31)
self.button_b_3_2.setEnabled(True)
self.button_b_3_2.setObjectName("button_b_3_2")
self.lineEdit_b_3_1 = QtWidgets.QLineEdit(self.splitter_b_31)
self.lineEdit_b_3_1.setEnabled(True)
self.lineEdit_b_3_1.setText("")
self.lineEdit_b_3_1.setObjectName("lineEdit_b_3_1")
self.verticalLayout_2.addWidget(self.splitter_b_31)
self.splitter_b_32 = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter_b_32.sizePolicy().hasHeightForWidth())
self.splitter_b_32.setSizePolicy(sizePolicy)
self.splitter_b_32.setOrientation(QtCore.Qt.Horizontal)
self.splitter_b_32.setObjectName("splitter_b_32")
self.button_b_3_3 = QtWidgets.QPushButton(self.splitter_b_32)
self.button_b_3_3.setEnabled(True)
self.button_b_3_3.setObjectName("button_b_3_3")
self.lineEdit_b_3_2 = QtWidgets.QLineEdit(self.splitter_b_32)
self.lineEdit_b_3_2.setEnabled(True)
self.lineEdit_b_3_2.setText("")
self.lineEdit_b_3_2.setObjectName("lineEdit_b_3_2")
self.verticalLayout_2.addWidget(self.splitter_b_32)
self.splitter_b_33 = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter_b_33.sizePolicy().hasHeightForWidth())
self.splitter_b_33.setSizePolicy(sizePolicy)
self.splitter_b_33.setOrientation(QtCore.Qt.Horizontal)
self.splitter_b_33.setObjectName("splitter_b_33")
self.button_b_3_4 = QtWidgets.QPushButton(self.splitter_b_33)
self.button_b_3_4.setEnabled(True)
self.button_b_3_4.setObjectName("button_b_3_4")
self.lineEdit_b_3_4 = QtWidgets.QLineEdit(self.splitter_b_33)
self.lineEdit_b_3_4.setEnabled(True)
self.lineEdit_b_3_4.setText("")
self.lineEdit_b_3_4.setObjectName("lineEdit_b_3_4")
self.verticalLayout_2.addWidget(self.splitter_b_33)
self.splitter_b_34 = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter_b_34.sizePolicy().hasHeightForWidth())
self.splitter_b_34.setSizePolicy(sizePolicy)
self.splitter_b_34.setOrientation(QtCore.Qt.Horizontal)
self.splitter_b_34.setObjectName("splitter_b_34")
self.button_b_3_7 = QtWidgets.QPushButton(self.splitter_b_34)
self.button_b_3_7.setEnabled(True)
self.button_b_3_7.setObjectName("button_b_3_7")
self.lineEdit_b_3_5 = QtWidgets.QLineEdit(self.splitter_b_34)
self.lineEdit_b_3_5.setEnabled(True)
self.lineEdit_b_3_5.setText("")
self.lineEdit_b_3_5.setObjectName("lineEdit_b_3_5")
self.verticalLayout_2.addWidget(self.splitter_b_34)
self.splitter = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth())
self.splitter.setSizePolicy(sizePolicy)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.button_b_3_6 = QtWidgets.QPushButton(self.splitter)
self.button_b_3_6.setObjectName("button_b_3_6")
self.lineEdit_b_3_3 = QtWidgets.QLineEdit(self.splitter)
self.lineEdit_b_3_3.setEnabled(False)
self.lineEdit_b_3_3.setText("")
self.lineEdit_b_3_3.setObjectName("lineEdit_b_3_3")
self.verticalLayout_2.addWidget(self.splitter)
self.hline_5 = QtWidgets.QFrame(self.centralwidget)
self.hline_5.setFrameShape(QtWidgets.QFrame.HLine)
self.hline_5.setFrameShadow(QtWidgets.QFrame.Sunken)
self.hline_5.setObjectName("hline_5")
self.verticalLayout_2.addWidget(self.hline_5)
self.horizontalLayout.addLayout(self.verticalLayout_2)
self.horizontalLayout.setStretch(0, 2)
self.horizontalLayout.setStretch(2, 5)
self.horizontalLayout.setStretch(4, 4)
self.verticalLayout_5.addLayout(self.horizontalLayout)
self.hl_c = QtWidgets.QHBoxLayout()
self.hl_c.setSpacing(6)
self.hl_c.setObjectName("hl_c")
spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.hl_c.addItem(spacerItem2)
self.button_c_1 = QtWidgets.QPushButton(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.button_c_1.sizePolicy().hasHeightForWidth())
self.button_c_1.setSizePolicy(sizePolicy)
self.button_c_1.setObjectName("button_c_1")
self.hl_c.addWidget(self.button_c_1)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.hl_c.addItem(spacerItem3)
self.hl_c.setStretch(0, 5)
self.hl_c.setStretch(1, 1)
self.hl_c.setStretch(2, 5)
self.verticalLayout_5.addLayout(self.hl_c)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1675, 28))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.button_c_1.clicked.connect(MainWindow.close)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "多任务RTSP视频流区域画线"))
self.button_a_1.setText(_translate("MainWindow", "新增任务"))
self.button_a_2.setText(_translate("MainWindow", "保存"))
self.button_a_3.setText(_translate("MainWindow", "撤销"))
self.button_a_4.setText(_translate("MainWindow", "清空"))
self.button_a_5.setText(_translate("MainWindow", "删除"))
self.label_b_1.setText(_translate("MainWindow", "任务列表"))
self.button_b_1_1.setText(_translate("MainWindow", "2207002"))
self.button_b_1_2.setText(_translate("MainWindow", "2207001"))
self.label.setText(_translate("MainWindow", "RTSP视频流"))
self.label_b_3.setText(_translate("MainWindow", "参数配置"))
self.button_b_3_1.setText(_translate("MainWindow", "检测区域"))
self.button_b_3_2.setText(_translate("MainWindow", "区域"))
self.lineEdit_b_3_1.setPlaceholderText(_translate("MainWindow", "请输入区域名称"))
self.button_b_3_3.setText(_translate("MainWindow", "区域编号"))
self.lineEdit_b_3_2.setPlaceholderText(_translate("MainWindow", "请输入区域编号"))
self.button_b_3_4.setText(_translate("MainWindow", "区域类型"))
self.lineEdit_b_3_4.setPlaceholderText(_translate("MainWindow", "请输入区域类型"))
self.button_b_3_7.setText(_translate("MainWindow", "区域偷油dist"))
self.lineEdit_b_3_5.setPlaceholderText(_translate("MainWindow", "请输入区域dist"))
self.button_b_3_6.setText(_translate("MainWindow", "RTSP地址"))
self.button_c_1.setText(_translate("MainWindow", "提交"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = QtWidgets.QMainWindow()
Ui= Ui_MainWindow()
Ui.setupUi(mainWindow)
mainWindow.show()
sys.exit(app.exec_())
- 子窗口Ui_Dialog.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(388, 161)
font = QtGui.QFont()
font.setBold(False)
font.setWeight(50)
Dialog.setFont(font)
self.layoutWidget = QtWidgets.QWidget(Dialog)
self.layoutWidget.setGeometry(QtCore.QRect(0, 110, 372, 26))
self.layoutWidget.setObjectName("layoutWidget")
self.hl_b = QtWidgets.QHBoxLayout(self.layoutWidget)
self.hl_b.setContentsMargins(0, 0, 0, 0)
self.hl_b.setObjectName("hl_b")
spacerItem = QtWidgets.QSpacerItem(208, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.hl_b.addItem(spacerItem)
self.ok_cancel_box_b = QtWidgets.QDialogButtonBox(self.layoutWidget)
self.ok_cancel_box_b.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.ok_cancel_box_b.setObjectName("ok_cancel_box_b")
self.hl_b.addWidget(self.ok_cancel_box_b)
self.layoutWidget1 = QtWidgets.QWidget(Dialog)
self.layoutWidget1.setGeometry(QtCore.QRect(10, 20, 261, 52))
self.layoutWidget1.setObjectName("layoutWidget1")
self.hl_a = QtWidgets.QHBoxLayout(self.layoutWidget1)
self.hl_a.setContentsMargins(0, 0, 0, 0)
self.hl_a.setObjectName("hl_a")
self.vl_a_1 = QtWidgets.QVBoxLayout()
self.vl_a_1.setObjectName("vl_a_1")
self.label_a_1 = QtWidgets.QLabel(self.layoutWidget1)
self.label_a_1.setObjectName("label_a_1")
self.vl_a_1.addWidget(self.label_a_1)
self.label_a_2 = QtWidgets.QLabel(self.layoutWidget1)
self.label_a_2.setObjectName("label_a_2")
self.vl_a_1.addWidget(self.label_a_2)
self.hl_a.addLayout(self.vl_a_1)
self.vl_a_2 = QtWidgets.QVBoxLayout()
self.vl_a_2.setObjectName("vl_a_2")
self.lineEdit_a_1 = QtWidgets.QLineEdit(self.layoutWidget1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lineEdit_a_1.sizePolicy().hasHeightForWidth())
self.lineEdit_a_1.setSizePolicy(sizePolicy)
self.lineEdit_a_1.setText("")
self.lineEdit_a_1.setObjectName("lineEdit_a_1")
self.vl_a_2.addWidget(self.lineEdit_a_1)
self.lineEdit_a_2 = QtWidgets.QLineEdit(self.layoutWidget1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lineEdit_a_2.sizePolicy().hasHeightForWidth())
self.lineEdit_a_2.setSizePolicy(sizePolicy)
self.lineEdit_a_2.setObjectName("lineEdit_a_2")
self.vl_a_2.addWidget(self.lineEdit_a_2)
self.hl_a.addLayout(self.vl_a_2)
self.retranslateUi(Dialog)
self.ok_cancel_box_b.accepted.connect(Dialog.accept)
self.ok_cancel_box_b.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "+新增任务"))
self.label_a_1.setText(_translate("Dialog", "* 任务名称"))
self.label_a_2.setText(_translate("Dialog", "* RTSP地址"))
self.lineEdit_a_1.setPlaceholderText(_translate("Dialog", "请输入任务名称,不能为空"))
self.lineEdit_a_2.setPlaceholderText(_translate("Dialog", "请输入RTSP地址,不能为空"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
Ui = Ui_Dialog()
Ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
1.2 RTSP视频流播放以及区域画线嵌入UI
由于整个项目的代码较多,本文只放核心代码以供展示。
1.2.1 新增任务
点击主窗口的新增任务按钮,弹出子窗口,在子窗口的界面中填写新增任务的名称以及对应的rtsp视频流地址,点击OK键,保存该任务的初始收据并且会在主窗口任务列表下新增该任务的Label。其技术要点涉及到PyQt5最为重要的知识,即信号与槽的绑定。初始数据为字典形式,如下:
{"任务名称": "2208001", "rtsp": "对应的rtsp地址", "区域位置信息": {}}
class AddTask:
'''
click button(“新增任务”): main_ui.button_a_1
fill task_name, rtsp_url in child_ui
show task_name in main_ui
'''
def __init__(self, main_ui, child_ui, child,
click_rtsp_url,click_task_name,click_task_data,
root_path=task_root_path):
self.main_ui = main_ui
self.child_ui = child_ui
# 新增任务按钮绑定子窗口
self.child = child
self.root_path = root_path
self.main_ui.button_a_1.clicked.connect(self.addtask_slot) #新增任务
self.child_ui.ok_cancel_box_b.accepted.connect(self.setTask) #子窗口添加任务
self.add_task_data = {} #往新增列表加任务初始化的data
# 点击新增任务时的,拿到的rtsp,任务名,初始化数据
self.click_rtsp_url = click_rtsp_url
self.click_task_name = click_task_name
self.click_task_data = click_task_data
print('请填写任务名称与rtsp地址')
def addtask_slot(self):
self.child.show()
def fillTask(self):
'''
task_name fill in child_ui.lineEdit_a_1
rtsp_url fill in child_ui.lineEdit_a_2
:return:
'''
# add_task_button = self.main_ui.button_a_1
add_task_name = self.child_ui.lineEdit_a_1.text() # 任务名称
add_rtsp_url = self.child_ui.lineEdit_a_2.text() # rtsp地址
if add_task_name == '' or add_rtsp_url == '':
print('任务名,rtsp地址不能为空,请重新填写')
return add_task_name, add_rtsp_url
# add_task_name, add_rtp_url只针对增加的task_name和rtsp,不关联点击任务名,独立的
def setTask(self):
'''
add_task_name button show in main_ui.scrollAreaWidgetContents_b_1
:return:
'''
# global main_ui
# global task_name
add_task_name, add_rtsp_url = self.fillTask()
self.main_ui.button_add = QtWidgets.QPushButton(self.main_ui.scrollAreaWidgetContents_b_1)
self.main_ui.button_add.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.main_ui.button_add.sizePolicy().hasHeightForWidth())
self.main_ui.button_add.setSizePolicy(sizePolicy)
self.main_ui.button_add.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.main_ui.button_add.setObjectName(add_task_name)
self.main_ui.button_add.setText(add_task_name)
self.main_ui.verticalLayout.addWidget(self.main_ui.button_add)
self.generate_data(add_task_name, add_rtsp_url)
print(self.main_ui.verticalLayout.count())
# sender获取信号发送
def clickTask():
global click_rtsp_url, click_task_name, click_task_data, main_ui
self.click_rtsp_url = add_rtsp_url_item
self.click_task_name = add_task_name_item
self.click_task_data = add_task_data_item
self.main_ui.lineEdit_b_3_3.setText(self.click_rtsp_url)
self.main_ui.lineEdit_b_3_3.setEnabled(True)
click_rtsp_url = self.click_rtsp_url
click_task_name = self.click_task_name
click_task_data = self.click_task_data
main_ui = self.main_ui
# add_display()
def generate_data(self, add_task_name, add_rtsp_url):
if not os.path.isdir(os.path.join(self.root_path, add_task_name)): # 如果没有tasks/task_name文件夹则生成
data_path = os.path.join(self.root_path, add_task_name)
os.mkdir(data_path)
data_path = os.path.join(self.root_path, add_task_name)
self.add_task_data['任务名'] = add_task_name
self.add_task_data['rtsp'] = add_rtsp_url
self.add_task_data['区域位置信息'] = {}
np.save(data_path+'/taskData.npy', self.add_task_data)
1.2.2 RTSP视频流播放
点击任务列表中的任务按钮,会在RTSP视频流播放区域进行播放视频流。此节的技术要点是要将一个视频流放入QLabel中进行播放。其解决思路如下:
- 清空上一个视频流播放,再利用opencv读取当前视频流视频帧
- 将读取到视频帧的图片转化为Qimage对象
- 然后将Qimage对象的图片转为Qlabel能够展示图片的对象QPixmap
- 最后在Qlabel中直接读取QPixmap对象,即可达到对视频帧的读取
关键代码如下:
def display():
main_ui.label.setPixmap(QPixmap.fromImage(QImage(np.zeros((img_height, img_width)),
img_width, img_height, QImage.Format_RGB888)))
cap = cv2.VideoCapture(click_rtsp_url)
while cap.isOpened():
success, frame = cap.read() # frame是RGB
# 宽度
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
#高度
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# print('图片尺寸为:宽度%s,高度%s' % (frame_width, frame_height))
label_with = main_ui.label.width()
label_height = main_ui.label.height()
ratio_width = label_with/frame_width
ratio_height = label_height/frame_height
ratio = resize_width/frame_width
if success:
frame = imutils.resize(frame, height=resize_width)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # 红绿蓝转蓝绿红
img = QImage(frame.data, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
main_ui.label.setPixmap(QPixmap.fromImage(img))
cv2.waitKey(10)
cv2.destroyAllWindows()
1.2.3 RTSP视频流区域画线
在视频流播放后,填写当前任务的区域信息,点击参数配置的检测区域按钮后,启动画线操作。鼠标左键点击视频流上的点,会生成红色点,再次点击会实现当前点与上一个点的连接,并且最后一个点与初始点也会进行连接。这样在点击至少3个不同点的位置后会形成区域框图,完成画线操作后,点击菜单栏上的保存按钮完成当前区域画线的数据保存。

class MyLabel(QLabel):
def __init__(self, parent=None):
super(MyLabel, self).__init__(parent)
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter(self)
if click_org: #画保存了的区域task_areas_org
if len(task_areas_org)>0:
for org_pts_info in list(task_areas_org.values()):
# print(org_pts)
# print(len(org_pts))
org_pts = org_pts_info['areaPos']
for i in range(len(org_pts)):
painter.setPen(QPen(Qt.green, 7))
painter.drawPoint(org_pts[i][0], org_pts[i][1])
if len(org_pts) > 1:
for j in range(0, len(org_pts) - 1):
painter.setPen(QPen(Qt.red, 2))
painter.drawLine(org_pts[j][0], org_pts[j][1], org_pts[j + 1][0], org_pts[j + 1][1])
painter.drawLine(org_pts[0][0], org_pts[0][1], org_pts[-1][0], org_pts[-1][1])
if del_choose:
if del_org_area_idx is not None:
del_org_pts = list(task_areas_org.values())[del_org_area_idx-1]['areaPos'] #获取要删除的区域位置
for i in range(len(del_org_pts)):
painter.setPen(QPen(Qt.blue, 7))
painter.drawPoint(del_org_pts[i][0], del_org_pts[i][1])
if len(pts) > 0 and flag == False:
for i in range(len(pts)): # 对每个点进行绘制,绿色
painter.setPen(QPen(Qt.green, 7))
painter.drawPoint(pts[i][0], pts[i][1])
if len(pts) > 1:
for j in range(0, len(pts) - 1):
painter.setPen(QPen(Qt.blue, 2)) # 线条设置为蓝色
painter.drawLine(pts[j][0], pts[j][1], pts[j + 1][0], pts[j + 1][1])
painter.drawLine(pts[0][0], pts[0][1], pts[-1][0], pts[-1][1])
# 点击了保存按钮,之前保存的区域都显示为红色,后面画的为蓝色
if len(pts) > 0 and flag == True: #分del_choose
for i in range(0, len(pts)):
painter.setPen(QPen(Qt.green, 7))
painter.drawPoint(pts[i][0], pts[i][1])
if len(pts[len_save:]) >= 0: # 表示区域保存完后面有点
for i in range(len(all_len) - 1): # 点了保存前面m每个区域显示为红色,区域个数为len(all_len) - 1, 如all_len=[0,4,8]
# 起始点索引
start = all_len[i]
# 第i个区域的长度
length = all_len[i + 1] - all_len[i]
# 绘制第i个区域
for j in range(start, start + length - 1):
painter.setPen(QPen(Qt.red, 2))
painter.drawLine(pts[j][0], pts[j][1], pts[j + 1][0], pts[j + 1][1])
painter.drawLine(pts[start][0], pts[start][1], pts[start + length - 1][0],
pts[start + length - 1][1])
#绘制len_save索引后面的点:绿色,线:蓝色
for k in range(len_save, len(pts) - 1):
painter.setPen(QPen(Qt.blue, 2))
painter.drawLine(pts[k][0], pts[k][1], pts[k + 1][0], pts[k + 1][1])
painter.drawLine(pts[len_save][0], pts[len_save][1], pts[-1][0], pts[-1][1])
if del_choose:
if del_pts_index_start is not None:
# 表示选中了新增的区域
for i in range(del_pts_index_start, del_pts_index_end):
painter.setPen(QPen(Qt.blue, 7))
painter.drawPoint(pts[i][0], pts[i][1])
else:# 选中了原始区域del_org_area_idx
del_org_pts = task_areas_org.values()[del_org_area_idx-1]['areaPos']
for i in range(len(del_org_pts)):
painter.setPen(QPen(Qt.blue, 7))
painter.drawPoint(del_org_pts[i][0], del_org_pts[i][1])
1.3 总结
- 本项目需要的基础知识:opencv、PyQt5、UI设计以及能够熟练使用python实现业务的要求。
- 技术难点:多个任务的代码耦合处理(此处是最容易出现Bug的地方),RTSP视频流画线的基本功能操作。
- 走的弯路:没有清晰地对项目进行合理分解以及在实现业务要求时没有做到整体把握,导致走一步看一步(过程思维),遇到一个Bug花费太多时间进行处理,甚至因为项目的耦合部分陷入不能解决的地步。过程思维的缺陷,就是不能把握全局,在全局完成后,很容易因为一个错误而陷入较大部分崩盘。
- 解决办法:必须清楚明白的了解项目整体,以及各部分的具体任务以及解决思路,不能有一丝混乱和不清楚的部分,把握全局必须做到没有一丝问题,否则后面修补将会花费大量的时间代价。在动手之前,必须将整体项目拆分为多个独立的对象,不能耦合,即使在后面遇到问题,只需要改独立的块,而不影响其他部分。
- 收获:强化了python代码动手实现业务逻辑的能力。深刻体验到从过程思维转化为面向对象思维的重要性,灵活使用类与函数处理各种业务要求,能够使用UI设计以及PyQt5完成简单的软件开发工作,为接下来的算法实操奠定了基础。
这篇博客总结了算法工程师在实习期间使用Python的PyQt5库开发多任务RTSP视频流播放及画线UI的过程。通过配置PyQt5和OpenCV,使用Qt Designer设计界面,PyUIC转换代码,实现了视频流播放、区域画线等功能。文章还讨论了项目中的技术难点、走过的弯路以及解决问题的方法,强调了面向对象思维在软件开发中的重要性。
3441

被折叠的 条评论
为什么被折叠?



