OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频

Posted YouCans

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频相关的知识,希望对你有一定的参考价值。

欢迎关注『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项目实战(10)项目案例06:键盘事件与视频抓拍
OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频
OpenCV-PyQT项目实战(12)项目案例08:多线程视频处理

文章目录

OpenCV-PyQT项目实战(11)项目案例07:实时拍摄视频

在上一个案例中我们介绍了OpenCV和PyQt 实现视频播放,本节介绍摄像头操作与拍摄实时视频。

本例使用 OpenCV处理摄像头设备进行解码获得图像帧,然后用 QTime 定时器控制 QLabel 中的图像更新,使用按钮控制摄像画面的移动。


1. 用OpenCV获取视频流

1.1 OpenCV的VideoCapture类

OpenCV提供了VideoCapture类和VideoWriter类处理视频流,既可以处理视频文件,也可以处理摄像头设备。

函数原型:

cv.VideoCapture( index[, apiPreference] ) →
cv.VideoWriter([filename, fourcc, fps, frameSize[, isColor]]) →

VideoCapture类用于读取视频文件、视频流或从摄像机捕获视频,VideoWriter类用于视频文件的写入和保存。

构造函数cv.VideoCapture和cv.VideoWrite用于实现类的初始化。

参数说明:

  • index:摄像头的 ID 编号,0 表示默认后端打开默认摄像机
  • filename:读取或保存的视频文件的路径,包括扩展名
  • apiPreference:读取视频流的属性设置
  • fourcc:用于压缩帧的编码器/解码器的字符代码,
    • CV_FOURCC(‘I’,‘4’,‘2’,‘0’),未压缩的YUV编码格式,扩展名为 .avi
    • CV_FOURCC(‘P’,‘I’,‘M’,‘1’),MPEG-1 编码格式,扩展名为 .avi
    • CV_FOURCC( ‘X’,‘V’,‘I’,‘D’),MPEG-4 编码格式,扩展名为 .avi
    • CV_FOURCC( ‘F’,‘L’,‘V’,‘I’),Flash 编码格式,件扩展名为 .flv
  • fps:视频流的帧速率
  • frameSize:元组 (w, h),视频帧的宽度和高度
  • isColor:是否彩色图像

成员函数:

  • cv.VideoCapture.isOpened(),检查视频捕获是否初始化成功
  • cv.VideoCapture.read(),捕获视频文件、视频流或捕获的视频设备
  • cv.VideoCapture.release(),关闭视频文件或设备,释放对象
  • cv.VideoCapture.get(propId) ,获取 VideoCapture 类对象的属性
  • cv.VideoCapture.set(propId, value),设置 VideoCapture 类对象的属性
  • cv.VideoWriter.fourcc(c1, c2, c3, c4[, ]),构造编码器/解码器的fourcc代码
  • cv.VideoWriter.write(image[, ]),写入下一帧视频
  • cv.VideoWriter.release(),关闭视频写入,释放对象

注意问题:

⒈读取视频文件、视频流中读取时,通过 filename 传递视频文件、视频流的路径。使用摄像头时,通过 index 传递摄像头的 ID 号。
⒉使用摄像头时,index=0 表示默认后端打开默认摄像机,例如笔记本内置摄像头。可以使用计算机的内置或外接的摄像头,也支持本地网络或公共网络的 IP 摄像机。
⒊视频写入类VideoWriter的参数frameSize是元组 (w, h),即视频帧的宽度和高度,而OpenCV图像的形状是 (h, w),注意二者的顺序是反的。
⒋视频处理过程较为复杂,一些程序设置与具体系统环境有关,本文只介绍基本的成员函数,通用的处理方法。更多内容详见:[https://docs.opencv.org/]。
⒌视频处理中的很多问题涉及摄像机和计算机的硬件设备,需要结合具体系统环境来分析。


1.2 OpenCV拍摄视频例程

OpenCV视频拍摄的基本步骤为:

(1)创建视频读取/捕获对象;
(2)获取视频的一帧图像;
(3)检查视频获取是否成功;
(4)释放视频读取/捕获对象。

【例程0107】调用摄像头拍照和录制视频

本例程示例调用笔记本内置摄像头抓拍图片和录制视频。根据计算机和摄像头的配置和接口的不同,可能需要修改API的设置。

#  OpenCV 调用摄像头拍照和录制视频
import cv2 as cv

if __name__ == '__main__':
    # 创建视频捕获对象,调用笔记本摄像头
    # cam = cv.VideoCapture(0)  # 创建捕获对象,0 为笔记本摄像头
    cam = cv.VideoCapture(0, cv.CAP_DSHOW)  # 修改 API 设置为视频输入 DirectShow

    # 设置写入视频图像的高,宽,帧速率和总帧数
    fps = 20  # 设置帧速率
    width = int(cam.get(cv.CAP_PROP_FRAME_WIDTH))  # 640
    height = int(cam.get(cv.CAP_PROP_FRAME_HEIGHT))  # 480
    fourcc = cv.VideoWriter_fourcc(*'XVID')  # 编码器设置 XVID
    # 创建写入视频对象
    vedioPath = "../images/camera.avi"  # 写入视频文件的路径
    capWrite = cv.VideoWriter(vedioPath, fourcc, fps, (width, height))
    print(fourcc, fps, (width, height))

    sn = 0  # 抓拍图像编号
    while cam.isOpened():  # 检查视频捕获是否成功
        success, frame = cam.read()  # 读取下一帧视频图像
        if success is True:
            cv.imshow('vedio', frame)  # 播放视频图像
            capWrite.write(frame)  # 将当前帧写入视频文件
            key = cv.waitKey(1) & 0xFF  # 接收键盘输入
            if key == ord('c'):  # 按 'c' 键抓拍当前帧
                filePath = "../images/photo:d.png".format(sn)  # 保存文件名
                cv.imwrite(filePath, frame)  # 将当前帧保存为图片
                sn += 1  # 更新写入图像编号
                print(filePath)
            elif key == ord('q'):  # 按 'q' 键结束录制视频
                break
        else:
            print("Can't receive frame.")
            break

    cam.release()  # 关闭视频捕获对象
    capWrite.release()  # 关闭视频写入对象
    cv.destroyAllWindows()  # 关闭显示窗口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmDfexHk-1677464268030)(D:\\OpenCVPyQt\\images\\OpenCV_35.png)]


2. 用PyQt5显示OpenCV拍摄的视频流

2.1 用 QtDesigner 开发 PyQt5 图形界面

(1)打开QtDesigner,新建窗体。在 “新建窗体” 窗口的左侧菜单选择 “MainWindow” 新建一个图形窗口。

(2)创建一个显示框控件QLabel,用于显示视频流。

  • 从左侧控件栏的 DisplayWidgets 中选择 Label,拖动到新建图形窗口,生成了一个 QLabel 对象。
  • 鼠标选中 QLabel 对象,在右侧的 “属性编辑器” 内可以对对象的属性进行编辑和修改。

(3)创建按钮控件:

  • 从左侧控件栏的 Button 中选择 PushButton 按钮,拖动到新建图形窗口,生成 PushButton 按钮对象。
  • 鼠标左键点击图形窗口中的这个 PushButton 按钮对象,拖动按钮可以调整控件的位置,对于其它控件也可以通过鼠标拖动来调整位置。
  • 鼠标选中 PushButton 按钮对象,控件周围的边界位置上就出现 8个蓝色的点,表示控件被选中,这时可以在右侧的 “属性编辑器” 内对对象的属性进行编辑和修改,例如:
    • 将 PushButton 对象的高度修改为 120,宽度修改为 40;
    • 将 PushButton 对象的 “QAbstractButton->text” 修改为 “开启”。

根据设计方案,用 QtDesigner 完成一个基本的图形界面。

(4) 将设计的图形界面保存为 .ui文件
文件默认保存在添加 QtDesigner 工具时 “Working directory” 所设置的路径,当然也可以另存到其它路径。
如果 PyChrm 或 QtDesigner 设置的文件保存路径不同,要注意导入图形界面文件时设置和使用正确的路径。

于是,我们就完成了本项目的图形界面设计,将其保存为 uiDemo9.ui文件。

在 PyCharm中,使用 PyUIC 将选中的 uiDemo9.ui 文件转换为 .py 文件,就得到了 uiDemo9.py 文件。


2.2 用 QTime 定时器实现视频播放

使用 OpenCV 对视频文件进行解码获得图像帧以后,可以使用 QTime 定时器来控制 QLabel 控件中的图像更新,实现视频播放。

PyQt5 中的 QTimer类提供了重复的和单次的定时器,为计时器提供了高级编程接口。要使用定时器,需要先创建一个QTimer实例,将定时器的timeout信号连接到相应的槽函数,并调用start(),定时器就会以设定的间隔发出timeout信号。

例程:使用 QTimer 在PyQt 控件中播放解码的图像帧

    self.timerCam = QtCore.QTimer()  # 定时器,毫秒
    self.timerCam.timeout.connect(self.refreshFrame)  # 计时器结束时调用槽函数刷新当前帧
   
    def refreshFrame(self):  # 刷新视频图像
        success, self.frame = self.cam.read()  # 读取下一帧视频图像
        image = self.frame[self.top:self.bottom, self.left:self.right].copy()
        qImg = self.cvToQImage(image)  # OpenCV 转为 PyQt 图像格式
        # qImg = self.cvToQImage(self.frame)  # OpenCV 转为 PyQt 图像格式
        self.label_1.setPixmap((QPixmap.fromImage(qImg)))  # 加载 PyQt 图像
        return


2.3 摄像头的打开与关闭控制

通过开启/关闭按键controlButton控制摄像头的打开与关闭。

(1)摄像头当前状态为打开时,按键显示的文本为“停止”,表示触发该按键时停止摄像;摄像头当前状态为关闭时,按钮显示的文本为“打开”,表示触发该按键时开始摄像。

(2)如果摄像头尚未初始化,则要初始化定义一个视频输入对象 self.cam。

    def controlCamera(self):  # 控制摄像头打开与关闭
        if self.timerCamera.isActive()==False:  # 摄像头关闭状态,打开动作
            print("正在打开摄像头")
            if self.cam.isOpened()==True:  # 检查视频捕获是否成功
                success, self.frame = self.cam.read()  # 读取下一帧视频图像
                self.height, self.width = self.frame.shape[:2]
                self.top, self.bottom = int(0.1*self.height), int(0.9*self.height)
                self.left, self.right = int(0.1*self.width), int(0.9*self.width)
                print(self.left, self.right, self.top, self.bottom)
                self.timerCamera.start(30)
            else:
                msg = QMessageBox.warning(self, "Warning", "开启摄像头失败,重试一次")
                self.cam = cv.VideoCapture(0, cv.CAP_DSHOW)  # 修改 API 设置为视频输入 DirectSho
            self.controlButton.setText("停止")
        else:  # 摄像头开启状态,关闭动作
            print("正在关闭摄像头")
            self.timerCamera.stop()  # 关闭定时器
            self.controlButton.setText("开启")

2.4 摄像画面的移动控制

本例程使用按钮控制摄像画面的移动.

由于摄像头移动控制比较复杂,而且通常涉及硬件接口,本文不做详细介绍。为了便于测试,本例从拍摄图像中裁剪不同区域进行显示,在显示窗口实现拍摄画面的移动,来模拟摄像头的移动控制。

默认显示拍摄图像的中间的 0.1~0.9 高度×宽度区域。操作向上按键,则显示 0.0~0.8 高度区域;操作向下按键,则显示 0.2~1.0 高度区域;操作向左按键,则显示 0.0~0.8 宽度区域;操作向右按键,则显示 0.2~1.0 宽度区域。

这样处理,一方面可以达到模拟移动摄像头的显示效果,另一方面简化程序便于读者理解整个程序的结构。

    # 通过 connect 建立信号/槽连接,点击按钮事件发射 triggered 信号,执行相应的子程序 click_pushButton
    self.pushButton_1.clicked.connect(self.upFrame)  # 向上
    self.pushButton_2.clicked.connect(self.downFrame)  # 向下
    self.pushButton_3.clicked.connect(self.leftFrame)  # 向左
    self.pushButton_4.clicked.connect(self.rightFrame)  # 向右
    
    self.height, self.width = self.frame.shape[:2]
    self.top, self.bottom = int(0.1*self.height), int(0.9*self.height)
    self.left, self.right = int(0.1*self.width), int(0.9*self.width)
    print(self.left, self.right, self.top, self.bottom)    
    
    def upFrame(self):
        print("视频 图像上移")
        self.top, self.bottom = 0, int(0.8*self.height)

    def downFrame(self):
        print("视频 图像下移")
        self.top, self.bottom = int(0.2*self.height), self.height

    def leftFrame(self):
        print("视频 图像左移")
        self.left, self.right = 0, int(0.8*self.width)

    def rightFrame(self):
        print("视频 图像右移")
        self.left, self.right = int(0.2*self.width), self.width

3. 项目实战:PyQt 视频拍摄与摄像头控制

本节介绍摄像头操作与拍摄实时视频。本例使用 OpenCV处理摄像头设备进行解码获得图像帧,然后用 QTime 定时器控制 QLabel 中的图像更新,使用按钮控制摄像画面的移动。


3.1 UI 程序 uiDemo12.py

为了便于测试,本节给出uiDemo12.py供读者参考,这是由uiDemo12.ui生成的。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'uiDemo12.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(721, 493)
        MainWindow.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("../images/youcansSmallLogo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label_1 = QtWidgets.QLabel(self.centralwidget)
        self.label_1.setGeometry(QtCore.QRect(0, 0, 480, 400))
        self.label_1.setText("")
        self.label_1.setPixmap(QtGui.QPixmap("../images/youcansSmallLogo.png"))
        self.label_1.setAlignment(QtCore.Qt.AlignCenter)
        self.label_1.setObjectName("label_1")
        self.pushButton_1 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_1.setGeometry(QtCore.QRect(560, 60, 60, 60))
        self.pushButton_1.setText("")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("../images/iconUp.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_1.setIcon(icon1)
        self.pushButton_1.setIconSize(QtCore.QSize(60, 60))
        self.pushButton_1.setObjectName("pushButton_1")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(560, 180, 60, 60))
        self.pushButton_2.setText("")
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("../images/iconDown.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_2.setIcon(icon2)
        self.pushButton_2.setIconSize(QtCore.QSize(60, 60))
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(500, 120, 60, 60))
        self.pushButton_3.setText("")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("../images/iconLeft.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_3.setIcon(icon3)
        self.pushButton_3.setIconSize(QtCore.QSize(60, 60))
        self.pushButton_3.setObjectName("pushButton_3")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(620, 120, 60, 60))
        self.pushButton_4.setText("")
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap("../images/iconRight.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton_4.setIcon(icon4)
        self.pushButton_4.setIconSize(QtCore.QSize(60, 60))
        self.pushButton_4.setObjectName("pushButton_4")
        self.controlButton = QtWidgets.QPushButton(self.centralwidget)
        self.controlButton.setGeometry(QtCore.QRect(500, 300, 80, 55))
        font = QtGui.QFont()
        font.setFamily("微软雅黑")
        font.setPointSize(12)
        self.controlButton.setFont(font)
        self.controlButton.setIconSize(QtCore.QSize(60, 60))
        self.controlButton.setObjectName("controlButton")
        self.returnButton = QtWidgets.QPushButton(self.centralwidget)
        self.returnButton.setGeometry(QtCore.QRect(600, 300, 80, 55))
        font = QtGui.QFont()
        font.setFamily("微软雅黑")
        font.setPointSize(12)
        self.returnButton.setFont(font)
        self.returnButton.setIconSize(QtCore.QSize(60, 60))
        self.returnButton.setObjectName("returnButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 721, 25))
        self.menubar.setBaseSize(QtCore.QSize(9, 0))
        font = QtGui.QFont()
        font.setPointSize(10)
        self.menubar.setFont(font)
        self.menubar.setObjectName("menubar")
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        self.menuQuit = QtWidgets.QMenu(self.menubar)
        self.menuQuit.setObjectName("menuQuit")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.toolBar = QtWidgets.QToolBar(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.toolBar.sizePolicy().hasHeightForWidth())
        self.toolBar.setSizePolicy(sizePolicy)
        self以上是关于OpenCV-PyQT项目实战(11)项目案例07:摄像头操作与拍摄视频的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV-PyQT项目实战项目案例04:视频播放

OpenCV-PyQT项目实战项目案例03:鼠标定位

OpenCV-PyQT项目实战项目案例01:图像模糊

OpenCV-PyQT项目实战项目案例04:视频播放

OpenCV-PyQT项目实战项目案例01图像模糊

OpenCV-PyQT项目实战项目案例02滚动条应用