欢迎关注『OpenCV-PyQT项目实战 @ Youcans』系列,持续更新中
OpenCV-PyQT项目实战(1)安装与环境配置
OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门
OpenCV-PyQT项目实战(3)信号与槽机制
OpenCV-PyQT项目实战(4)OpenCV 与PyQt的图像转换
OpenCV-PyQT项目实战(5)项目案例01:图像模糊
OpenCV-PyQT项目实战(6)项目案例02:滚动条应用
OpenCV-PyQT项目实战(7)项目案例03:鼠标框选
OpenCV-PyQT项目实战(8)项目案例04:鼠标定位
OpenCV-PyQT项目实战(9)项目案例04:视频播放
文章目录
OpenCV-PyQT项目实战(9)项目案例05:视频播放
本节介绍OpenCV和PyQt 实现视频播放。
PyQt 不支持视频解码。本例使用 OpenCV对视频文件进行解码获得图像帧,然后用 QTime 定时器和 QThread 的方式来控制 QLabel 中的图像更新。
1. 通过OpenCV 读取视频文件,在 PyQt 控件中播放
1.1 OpenCV 读取视频文件
视频文件是由一系列图像组成的,视频的每一帧都是一幅图像。
OpenCV提供了VideoCapture类和VideoWriter类处理视频流,既可以处理视频文件,也可以处理摄像头设备。
函数原型:
cv.VideoCapture( index[, apiPreference] ) → <VideoCapture object>
cv.VideoCapture(filename[, apiPreference]) → <VideoCapture object>
cv.VideoWriter([filename, fourcc, fps, frameSize[, isColor]]) → <VideoWriter object>
VideoCapture类用于读取视频文件、视频流或从摄像机捕获视频,VideoWriter类用于视频文件的写入和保存。
构造函数cv.VideoCapture和cv.VideoWrite用于实现类的初始化。
参数说明:
● index:摄像头的 ID 编号,0 表示默认后端打开默认摄像机
● filename:读取或保存的视频文件的路径,包括扩展名
● apiPreference:读取视频流的属性设置
● fourcc:用于压缩帧的编码器/解码器的字符代码,
● fps:视频流的帧速率
● frameSize:元组 (w, h),视频帧的宽度和高度
● isColor:是否彩色图像
成员函数:
- cv.VideoCapture.isOpened(),检查视频捕获是否初始化成功
- cv.VideoCapture.read(),捕获视频文件、视频流或捕获的视频设备
- cv.VideoCapture.release(),关闭视频文件或设备,释放对象
- cv.VideoWriter.release(),关闭视频写入,释放对象
注意问题:
⒈读取视频文件、视频流中读取时,通过 filename 传递视频文件、视频流的路径。使用摄像头时,通过 index 传递摄像头的 ID 号。
2视频处理过程较为复杂,一些程序设置与具体系统环境有关,本文只介绍基本的成员函数,通用的处理方法。更多内容详见:[https://docs.opencv.org/]。
1.2 例程:OpenCV 播放视频文件
视频读取的基本步骤为:
(1)创建视频读取/捕获对象;
(2)获取视频的一帧图像;
(3)检查视频获取是否成功;
(4)释放视频读取/捕获对象。
import cv2 as cv
if __name__ == '__main__':
# 创建视频读取/捕获对象
vedioRead = "../images/nasa_m420p.mov" # 读取视频文件的路径
capRead = cv.VideoCapture(vedioRead) # 实例化 VideoCapture 类
# 读取视频文件
frameNum = 0 # 视频帧数初值
while capRead.isOpened(): # 检查视频捕获是否成功
ret, frame = capRead.read() # 读取下一帧视频图像
if ret is True:
cv.imshow(vedioRead, frame) # 播放视频图像
if cv.waitKey(1) & 0xFF == ord('q'): # 按 'q' 退出
break
else:
print("Can't receive frame at frameNum {}".format(frameNum))
break
capRead.release() # 关闭读取视频文件
capWrite.release() # 关闭视频写入对象
cv.destroyAllWindows() # 关闭显示窗口
2. PyQt 中的 QTimer定时器
使用 OpenCV 对视频文件进行解码获得图像帧以后,可以使用 QTime 定时器来控制 QLabel 控件中的图像更新,实现视频播放。
2.1 PyQt 中的QTimer类
PyQt5 中的 QTimer类提供了重复的和单次的定时器,为计时器提供了高级编程接口。
要使用定时器,需要先创建一个QTimer实例,将定时器的timeout信号连接到相应的槽函数,并调用start(),定时器就会以设定的间隔发出timeout信号。
QTimer类中的常用方法:
- start(milliseconds):启动或重新启动定时器,时间间隔为毫秒。如果定时器已经运行,它将被停止并重新启动。如果singleShot信号为真,定时器将仅被激活一次。
- Stop():停止定时器
QTimer类中的常用信号:
- singleShot:在给定的时间间隔后调用一个槽函数时发射此信号。
- timeout:当定时器超时时发射此信号。
注意:可以设置槽函数的执行次数,默认为定时器开启后周期性调用槽函数。如果设置了setSingleShot(True),则槽函数仅执行一次。
2.2 例程:QTimer定时器的使用
from PyQt5.QtCore import QTimer
self.timer = QTimer(self) #初始化一个定时器
self.timer.timeout.connect(self.operate) #计时结束则调用槽函数operate()
self.timer.start(1000) #设置计时间隔 1000ms
def operate(self): # 自定义的槽函数
# 具体操作,以下示例
time = QDateTime.currentDateTime()
# 时间显示格式
str = time.toString("yyyy-mm-dd hh:mm:ss");
self.lcdNumber.display(str);
2.3 例程:使用 QTimer 在PyQt 控件中播放解码的图像帧
self.timerCam = QtCore.QTimer() # 定时器,毫秒
self.timerCam.timeout.connect(self.refreshFrame) # 计时器结束时调用槽函数刷新当前帧
def refreshFrame(self): # 刷新视频图像
ret, self.frame = self.cap.read() # 读取下一帧视频图像
qImg = self.cvToQImage(self.frame) # OpenCV 转为 PyQt 图像格式
self.label_1.setPixmap((QPixmap.fromImage(qImg))) # 加载 PyQt 图像
return
3. 项目实战:PyQt 视频播放器
本项目基于 PyQt5 GUI 实现一个视频播放器。具体方法使用 OpenCV对视频文件进行解码获得图像帧,然后用 QTime 定时器和 QThread 的方式来控制 QLabel 中的图像更新。
本文介绍的方法是在同一个线程中实现视频的解码和播放。也可以通过多线程操作,将视频的解码和播放分为两个线程。
3.1 使用 QtDesigner 开发 PyQt5 图形界面
本例的 UI 继承自 uiDemo4.ui ,并进行修改如下:
完成了本项目的图形界面设计,将其保存为 uiDemo10.ui文件。
在 PyCharm中,使用 PyUIC 将选中的 uiDemo10.ui 文件转换为 .py 文件,就得到了 uiDemo10.py 文件。
3.2. 完整例程:视频播放程序 OpenCVPyqt10.py
# OpenCVPyqt10.py
# Demo05 of GUI by PyQt5
# Copyright 2023 Youcans, XUPT
# Crated:2023-02-20
import sys
import cv2 as cv
import numpy as np
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from uiDemo10 import Ui_MainWindow # 导入 uiDemo10.py 中的 Ui_MainWindow 界面类
class MyMainWindow(QMainWindow, Ui_MainWindow): # 继承 QMainWindow 类和 Ui_MainWindow 界面类
def __init__(self, parent=None):
super(MyMainWindow, self).__init__(parent) # 初始化父类
self.setupUi(self) # 继承 Ui_MainWindow 界面类
self.timerCam = QtCore.QTimer() # 定时器,毫秒
self.cap = None #
self.frameNum = 1 # 视频帧数初值
# 菜单栏
self.actionOpen.triggered.connect(self.openVideo) # 连接并执行 openSlot 子程序
self.actionHelp.triggered.connect(self.trigger_actHelp) # 连接并执行 trigger_actHelp 子程序
self.actionQuit.triggered.connect(self.close) # 连接并执行 trigger_actHelp 子程序
# 通过 connect 建立信号/槽连接,点击按钮事件发射 triggered 信号,执行相应的子程序 click_pushButton
self.pushButton_1.clicked.connect(self.openVideo) # 打开视频文件
self.pushButton_2.clicked.connect(self.playVideo) # 播放视频文件
self.pushButton_3.clicked.connect(self.pauseVideo) # 停止视频播放
self.pushButton_4.clicked.connect(self.trigger_actHelp) # 按钮触发
self.pushButton_5.clicked.connect(self.close) # 点击 # 按钮触发:关闭
self.timerCam.timeout.connect(self.refreshFrame) # 计时器结束时调用槽函数刷新当前帧
# 初始化
return
def openVideo(self): # 读取视频文件,点击 pushButton_1 触发
self.videoPath, _ = QFileDialog.getOpenFileName(self, "Open Video", "../images/", "*.mp4 *.avi *.flv")
print("Open Video: ", self.videoPath)
return
def playVideo(self): # 播放视频文件,点击 pushButton_2 触发
if self.timerCam.isActive()==False:
self.cap = cv.VideoCapture(self.videoPath) # 实例化 VideoCapture 类
if self.cap.isOpened(): # 检查视频捕获是否成功
self.timerCam.start(20) # 设置计时间隔并启动,定时结束将触发刷新当前帧
else: #
self.timerCam.stop() # 停止定时器
self.cap.release() # 关闭读取视频文件
self.label_1.clear() # 清除显示内容
def pauseVideo(self):
self.timerCam.blockSignals(False) # 取消信号阻塞,恢复定时器
if self.timerCam.isActive() and self.frameNum%2==1:
self.timerCam.blockSignals(True) # 信号阻塞,暂停定时器
self.pushButton_3.setText("3 继续") # 点击"继续",恢复播放
print("信号阻塞,暂停播放。", self.frameNum)
else:
self.pushButton_3.setText("3 暂停") # 点击"暂停",暂停播放
print("取消阻塞,恢复播放。", self.frameNum)
self.frameNum = self.frameNum + 1
def closeEvent(self, event):
if self.timerCam.isActive():
self.timerCam.stop()
def refreshFrame(self): # 刷新视频图像
ret, self.frame = self.cap.read() # 读取下一帧视频图像
qImg = self.cvToQImage(self.frame) # OpenCV 转为 PyQt 图像格式
self.label_1.setPixmap((QPixmap.fromImage(qImg))) # 加载 PyQt 图像
return
def cvToQImage(self, image):
# 8-bits unsigned, NO. OF CHANNELS=1
if image.dtype == np.uint8:
channels = 1 if len(image.shape) == 2 else image.shape[2]
if channels == 3: # CV_8UC3
# Create QImage with same dimensions as input Mat
qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_RGB888)
return qImg.rgbSwapped()
elif channels == 1:
# Create QImage with same dimensions as input Mat
qImg = QImage(image, image.shape[1], image.shape[0], image.strides[0], QImage.Format_Indexed8)
return qImg
else:
QtCore.qDebug("ERROR: numpy.ndarray could not be converted to QImage. Channels = %d" % image.shape[2])
return QImage()
def trigger_actHelp(self): # 动作 actHelp 触发
QMessageBox.about(self, "About",
"""数字图像处理工具箱 v1.0\nCopyright YouCans, XUPT 2023""")
return
if __name__ == '__main__':
app = QApplication(sys.argv) # 在 QApplication 方法中使用,创建应用程序对象
myWin = MyMainWindow() # 实例化 MyMainWindow 类,创建主窗口
myWin.show() # 在桌面显示控件 myWin
sys.exit(app.exec_()) # 结束进程,退出程序
3.3 程序说明
(1)“打开”按钮用于从文件夹选择播放的视频文件。
(2)“播放”按钮用于播放打开的视频文件,播放结束后自动关闭。
(3)“暂停/继续”按钮用于暂停/继续播放视频文件。按钮初始显示为“暂停”,按下“暂停”按钮后暂停播放,按钮显示切换为“继续”;再次按下“继续”按钮后继续播放,按钮显示切换为“暂停”。
运行结果:
【本节完】
版权声明:
Copyright 2023 youcans, XUPT
Crated:2023-2-20