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

news2024/11/18 11:26:31

欢迎关注『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:实时拍摄视频
    • 1. 用OpenCV获取视频流
      • 1.1 OpenCV的VideoCapture类
      • 1.2 OpenCV拍摄视频例程
    • 2. 用PyQt5显示OpenCV拍摄的视频流
      • 2.1 用 QtDesigner 开发 PyQt5 图形界面
      • 2.2 用 QTime 定时器实现视频播放
      • 2.3 摄像头的打开与关闭控制
      • 2.4 摄像画面的移动控制
    • 3. 项目实战:PyQt 视频拍摄与摄像头控制
      • 3.1 UI 程序 uiDemo12.py
      • 3.2 主程序:视频拍摄与控制
      • 3.3 程序说明和运行结果

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.toolBar.setMinimumSize(QtCore.QSize(0, 30))
        font = QtGui.QFont()
        font.setPointSize(10)
        self.toolBar.setFont(font)
        self.toolBar.setObjectName("toolBar")
        MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
        self.actionOpen = QtWidgets.QAction(MainWindow)
        self.actionOpen.setEnabled(True)
        self.actionOpen.setIconVisibleInMenu(False)
        self.actionOpen.setObjectName("actionOpen")
        self.actionSave = QtWidgets.QAction(MainWindow)
        self.actionSave.setIconVisibleInMenu(False)
        self.actionSave.setObjectName("actionSave")
        self.actionClose = QtWidgets.QAction(MainWindow)
        self.actionClose.setIconVisibleInMenu(False)
        self.actionClose.setObjectName("actionClose")
        self.actionQuit = QtWidgets.QAction(MainWindow)
        self.actionQuit.setVisible(True)
        self.actionQuit.setIconVisibleInMenu(False)
        self.actionQuit.setObjectName("actionQuit")
        self.actionSetup = QtWidgets.QAction(MainWindow)
        self.actionSetup.setObjectName("actionSetup")
        self.actionHelp = QtWidgets.QAction(MainWindow)
        self.actionHelp.setObjectName("actionHelp")
        self.menuFile.addAction(self.actionOpen)
        self.menuFile.addAction(self.actionSave)
        self.menuFile.addAction(self.actionClose)
        self.menuQuit.addAction(self.actionQuit)
        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuQuit.menuAction())
        self.toolBar.addAction(self.actionOpen)
        self.toolBar.addAction(self.actionClose)
        self.toolBar.addAction(self.actionSave)
        self.toolBar.addAction(self.actionSetup)
        self.toolBar.addAction(self.actionHelp)
        self.toolBar.addAction(self.actionQuit)

        self.retranslateUi(MainWindow)
        self.actionQuit.triggered.connect(MainWindow.close)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "OpenCV-PyQt"))
        self.controlButton.setText(_translate("MainWindow", "开 启"))
        self.returnButton.setText(_translate("MainWindow", "返 回"))
        self.menuFile.setTitle(_translate("MainWindow", "文件"))
        self.menuQuit.setTitle(_translate("MainWindow", "退出"))
        self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
        self.actionOpen.setText(_translate("MainWindow", "打开"))
        self.actionSave.setText(_translate("MainWindow", "保存"))
        self.actionClose.setText(_translate("MainWindow", "关闭"))
        self.actionQuit.setText(_translate("MainWindow", "退出"))
        self.actionSetup.setText(_translate("MainWindow", "设置"))
        self.actionHelp.setText(_translate("MainWindow", "帮助"))


3.2 主程序:视频拍摄与控制

# OpenCVPyqt12.py
# Demo05 of GUI by PyQt5
# Copyright 2023 Youcans, XUPT
# Crated:2023-02-24

import sys
import cv2 as cv
import numpy as np
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from uiDemo12 import Ui_MainWindow  # 导入 uiDemo12.py 中的 Ui_MainWindow 界面类

class MyMainWindow(QMainWindow, Ui_MainWindow):
    returnSignal = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(MyMainWindow, self).__init__(parent)  # 初始化父类
        self.setupUi(self)  # 继承 Ui_MainWindow 界面类

        # 初始化摄像头,创建捕获对象
        # self.cam = cv.VideoCapture(0)  # 实例化 VideoCapture 类,0 为笔记本摄像头
        self.cam = cv.VideoCapture(0, cv.CAP_DSHOW)  # 修改 API 设置为视频输入 DirectShow
        self.timerCamera = QtCore.QTimer()  # 初始化定时器
        self.frameNum = 0

        # 初始化信号与槽的连接
        self.initSolt()  # 槽函数初始化
        self.timerCamera.timeout.connect(self.refreshFrame)  # 初始化计时器

    def initSolt(self):
        # 菜单栏的动作连接
        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.upFrame)  # 向上
        self.pushButton_2.clicked.connect(self.downFrame)  # 向下
        self.pushButton_3.clicked.connect(self.leftFrame)  # 向左
        self.pushButton_4.clicked.connect(self.rightFrame)  # 向右
        self.timerCamera.timeout.connect(self.refreshFrame)  # 计时器结束时调用槽函数刷新当前帧
        # # 信号和槽连接
        self.returnButton.clicked.connect(self.returnSignal)  # 返回 按键
        self.controlButton.clicked.connect(self.controlCamera)  # 开关摄像头 按键

    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("开启")

    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

    def returnSignal(self):
        if self.cam.isOpened()==True:
            print("关闭视频捕获对象")
            self.cam.release()  # 关闭视频捕获对象
            # self.label_1.clear()  # 清空显示图像

    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

    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 openVideo(self):  # 读取视频文件,点击 pushButton_1 触发
        self.videoPath, _ = QFileDialog.getOpenFileName(self, "Open Video", "../images/", "*.mp4 *.avi *.mov")
        print("Open Video: ", self.videoPath)
        return

    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)“暂停/继续”按钮用于暂停/继续播放视频文件。按钮初始显示为“暂停”,按下“暂停”按钮后暂停播放,按钮显示切换为“继续”;再次按下“继续”按钮后继续播放,按钮显示切换为“暂停”。

(4)在播放过程中,按下键盘回车键,则抓拍视频当前帧并显示在GUI 右侧窗口控件中。

运行结果:

启动程序,控制按键显示为“开启”。

在这里插入图片描述


开始拍摄,控制按键显示变为“停止”。

在这里插入图片描述


操作控制按钮”向上“,显示画面向上移动,可以显示沙发顶部。

在这里插入图片描述


操作控制按钮”向左“,显示画面向左移动,大黄狗躲起来了。

在这里插入图片描述


【本节完】


版权声明:

Copyright 2023 youcans, XUPT

Crated:2023-2-27


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/388928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(中)

系列文章目录 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(上) 【大数据实时数据同步】超级详细的生产环境OGG(GoldenGate)12.2实时异构同步Oracle数据部署方案(中) 文章目录系列文章目录前言安装OGG12C软件一、Linux本地GUI…

配置本地 python GEE、geemap环境

1.安装anconda 百度搜索anconda清华镜像,从清华镜像中选择最新的anconda安装包,国内镜像网站下载速度较快,如果从国外官网下载速度相当慢,详细安装教程请参考: anconda安装教程https://blog.csdn.net/lwbCUMT/article…

这些Python计算机视觉工具,帮你coding事半功倍

作为开发人员喜爱的语言之一,Python以其丰富的社区可用工具和库而闻名。我们列出了开发人员可以用于计算机视觉10个流行流行的Python库或平台,以帮助开发人员自动化开发任务,其中包括检测和可视化。1 | fastaifastai是一个深度学习库&#xf…

HBase读取流程详解

读流程从头到尾可以分为如下4个步骤:Client-Server读取交互逻辑,Server端Scan框架体系,过滤淘汰不符合查询条件的HFile,从HFile中读取待查找Key。其中Client-Server交互逻辑主要介绍HBase客户端在整个scan请求的过程中是如何与服务…

重构·改善既有代码的设计.01

前言近期在看Martin Fowler著作的《重构.改善既有代码的设计》这本书,这是一本经典著作。书本封面誉为软件开发的不朽经典。书中从一个简单的案例揭示了重构的过程以及最佳实践。同时给出了重构原则,何时重构,以及重构的手法。用来改善既有代…

Vue2.0开发之——购物车案例-Goods组件封装-商品名称和图片(46)

一 概述 循环渲染Goods组件为Goods组件封装title属性为Goods组件封装pic属性 二 循环渲染Goods组件 2.1 App.vue中导入Goods组件 import Goods from /components/Goods/Goods.vue2.2 App.vue中注册Goods组件 components: {Header,Goods}2.3 循环渲染每一个商品的信息 <…

记录--在Vue3这样子写页面更快更高效

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 在开发管理后台过程中&#xff0c;一定会遇到不少了增删改查页面&#xff0c;而这些页面的逻辑大多都是相同的&#xff0c;如获取列表数据&#xff0c;分页&#xff0c;筛选功能这些基本功能。而…

windows下neo4j安装及配置,并绘制人物关系图谱

neo4j安装及配置&#xff0c;绘制人物关系图谱 先升级pip&#xff0c;安装py2neo pip install py2neo2021.0.1依赖 jdk1.8&#xff0c; neo4j 3.xx&#xff1b; 或者jdk18&#xff0c;neo4j 4.x&#xff0c;5.x&#xff1b; 官网下载了neo4j4.x,5.x 因为jdk版本原因都不行&am…

段错误排查方法与防御性措施~

什么是段错误 首先我们需要知道什么是段错误&#xff0c;才能对症下药。 段错误是一种在程序运行时发生的错误&#xff0c;通常是由于程序试图访问不在其地址空间范围内的内存引起的。 例如&#xff0c;当一个程序访问空指针或者已经被释放的内存时&#xff0c;就有可能触发…

Mybatis框架的搭建与使用

Mybatis框架的搭建 一.创建新模块 二、在pom.xml导入依赖 <dependencies><!-- Mybatis核心 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependenc…

软件研发管理经验总结 - 技术管理

软件研发管理经验总结 - 技术管理 技术管理主要负责有技术团队建设、管理团队成员技术相关事务、帮助团队成员成长、负责团队成员交付的代码质量、以及负责产品技术方向、以及产品相关前沿技术调研&#xff1b;管理团队成员技术相关事务有代码Review、故障率跟踪、分析及根据分…

算法系列之数值积分的目的

PLC算法里的数字积分器详细介绍请参看下面的文章链接: PLC算法系列之数值积分器(Integrator)_RXXW_Dor的博客-CSDN博客数值积分和微分在工程上的重要意义不用多说,闭环控制的PID控制器就是积分和微分信号的应用。流量累加也会用到。有关积分运算在流量累加上的应用,请参看下…

LeetCode 349. 两个数组的交集和 692. 前K个高频单词

两个数组的交集 难度 简单 题目链接 这道题的难度不大&#xff0c;我们可以把数组里的数据存到set里面。这样就完成了排序和去重&#xff0c;然后我们再把一个set里面的数据和另外一个set数据进行比较。如果相同就插入到数组里。 代码如下&#xff1a; 但是这个算法的时间复…

自学大数据第四天~hadoop集群的搭建

Hadoop集群安装配置 当hadoop采用分布式模式部署和运行时,存储采用分布式文件系统HDFS,此时HDFS名称节点和数据节点位于不同的机器上; 数据就可以分布到多个节点,不同的数据节点上的数据计算可以并行执行了,这时候MR才能发挥其本该有的作用; 没那么多机器怎么办~~~~多几个虚拟…

网络安全之资产及攻击面管理

“摸清家底&#xff0c;认清风险”做好资产管理是安全运营的第一步。那么什么是资产&#xff0c;资产管理的难点痛点是什么&#xff0c;如何做好资产管理&#xff0c;认清风险。带着这些问题我们来认识一下资产及攻击面管理。 一、资产的定义 《GBT 20984-2007信息安全技术信…

论文阅读_善用Midjourney

论文信息 name_en: Grimm in Wonderland: Prompt Engineering with Midjourney to Illustrate Fairytales name_ch: 用Midjourney生成格林童话插图 paper_addr: http://arxiv.org/abs/2302.08961 date_publish: 2023-02-17 author: Martin Ruskov,米兰大学 读后感 针对生成图…

力扣-计算特殊奖金

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1873. 计算特殊奖金二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总…

【C++】stl_list介绍和实现,list和vector区别,list vector string 迭代器失效

本篇博客详细介绍list的实现&细节讲解&#xff0c;并且在文章末对list和vector&#xff0c;string进行区分和复习 list的基本结构就是双向带头循环链表&#xff0c;链表和顺序表的差别我们在前面数据结构的时候早就学过了&#xff0c;不再赘述 在使用stl库里面list时&…

3.1 第一个外设GPIO GPIO输出

GPIO简介•GPIO是通用输入输出口&#xff08;俗称IO口&#xff09;•可配置为8种输入输出模式•引脚电平&#xff1a;0V~3.3V&#xff08;数据0是0V&#xff0c;数据1是3.3V&#xff09;&#xff0c;部分引脚可容忍5V.•输出模式可控制端口输出高低电平&#xff0c;用以驱动LED…

Javascript周学习小结(初识,变量,数据类型)

JS的三大书写方式行内式如图所示&#xff1a;几点说明&#xff1a;JS的行内式写在HTML的标签内部&#xff0c;(常以on开头)&#xff0c;如onclick行内式常常使用单引号括住字符串以区分HTML的双引号可读性差&#xff0c;不建议使用引号易出错&#xff0c;不建议使用特殊情况下使…