手把手教你从零搭建自己的pyQT5高级质感界面(二)

手把手教你从零搭建自己的pyQT5高级质感界面(二)

本项目的主要功能包括主副界面的按钮、栏目设计,主副界面的相互界面等,使用 pythonpyQT5 进行界面开发,使用 anaconda 构建虚拟环境,并使用 pycharm 进行代码调试和运行。本博客为系列内容中的第2篇,在第一篇的基底上给出视频转图片的界面设计代码和设计思路

1 环境搭建

项目开发对 anacondapycharm 的版本没有限制,按照站内的其他项目安装即可,此外,本项目中不包含任何只能在特定系统上才能顺利运行的库

在进行下面操作时,默认已安装了anaconda和pycharm,本次测试使用的是windows系统,但是ubuntu系统下的对应操作代码与ubuntu下的完全相同,mac系统下的操作代码会在之后更新

如果尚未安装anaconda,可参考往期博客

ubuntu 22.04服务器版本cuda、Anaconda、pytorch环境配置以及安装llama_factory-优快云博客

此外,如果看过第一期的朋友也可以直接跳过anaconda构建环境的步骤,直接安装新的库

下面回到正题

首先,给项目创建虚拟环境,这里使用的是python 3.9.20,建议使用相同的环境以确保不会出现一些奇怪的错误。在windows系统下通过 win + r 并输入 cmd ,ubuntu系统下可通过 ctrl + alt + t 打开终端,现输入下列代码

conda create -n video_split python==3.9 -y

虚拟环境的创建除了网络错误外,基本不会报错,继续进入下一步。

P.S. 如果网络出现错误的话,首先查看是否使用了梯子,如果开启了,就直接关闭,如果没有开启,就换成国内源,基本可解决问题

加载创建出的虚拟环境 video_split

conda activate video_split

下面,将安装所有需要用到的库,即本次的主角, cv2pyqt5

pip install opencv-python PyQt5

这里的库直接默认安装即可,无特殊版本需求。这里安装无报错,就可以关闭 cmd 窗口(windows)或 终端(ubuntu),主要的代码执行和环境加载,我们会在 vscode 中进行。

P.S. vscode 是无法直接编辑 python 文件的,需要下载扩展,笔者用的是 Pylance Python 还有 Python Debugger 这 3 个扩展,使用 vscode 的扩展功能安装即可 😋😋😋

project cover
pycharm创建项目文件夹

接下来在合适的磁盘中,创建文件夹,此处使用的是 D盘 中的 video_split_inter 文件夹,及示例用的视频文件 test.mp4 ,并创建本次需要测试的文件 demo_video_split.py 如下图所示

project cover
vscode 创建项目文件夹

这里的 test.mp4 是从网上随便下载的,由于界面程序的兼容性不错,大家可以使用任何可以在视频播放软件上正常播放的 .mp4 文件、.avi 文件和 .mkv 文件。

在安装完 python 扩展后,找到 vscode的右下角 并点击 选择解释器 (select interpreter) 按钮

project cover
pycharm加载编译器2

讲道理,这比 pycharm 可方便很多了。

接下来将代码复制到对应的文件即可:

demo_video_split.py 对应代码:

import sys, os
import cv2
from pathlib import Path
from PyQt5.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QFileDialog,
    QComboBox, QProgressBar, QHBoxLayout, QStyle, QProxyStyle
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QFont

class VideoProcessor(QThread):
    """
    后台线程类,用于从视频文件中逐帧读取并保存为图像,同时发出进度和图像信号更新UI。
    """
    progress = pyqtSignal(int, int, QImage)
    finished = pyqtSignal()

    def __init__(self, video_path, output_format):
        super().__init__()
        self.video_path = Path(video_path)
        self.output_format = output_format.lower()

    def run(self):
        cap = cv2.VideoCapture(str(self.video_path))
        total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        output_dir = self.video_path.stem
        Path(output_dir).mkdir(exist_ok=True)

        frame_idx = 0
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            h, w, ch = rgb.shape
            qt_img = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)
            filename = f"{frame_idx+1:04d}.{self.output_format}"
            cv2.imwrite(str(Path(output_dir) / filename), frame)
            self.progress.emit(frame_idx + 1, total, qt_img)
            frame_idx += 1

        cap.release()
        self.finished.emit()

class ModernComboStyle(QProxyStyle):
    def drawComplexControl(self, control, option, painter, widget=None):
        # 使用系统默认绘制并添加自定义圆角
        super().drawComplexControl(control, option, painter, widget)

class VideoSplitUI(QWidget):
    """
    主窗口类,负责初始化和管理 UI 元素,以及响应用户交互。
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("🎞️ Smart Video Frame Extractor")
        self.resize(1000, 700)
        self.setStyleSheet("background-color: #f6f8fb;")
        self.video_path = None
        self.init_ui()

    def init_ui(self):
        title = QLabel("Video to Image Converter")
        title.setFont(QFont("Segoe UI", 24, QFont.Bold))
        title.setAlignment(Qt.AlignCenter)

        self.label = QLabel("No video selected")
        self.label.setFont(QFont("Segoe UI", 11))
        self.label.setStyleSheet("color: #333;")

        self.image_label = QLabel("Preview Area")
        self.image_label.setFixedHeight(360)
        self.image_label.setAlignment(Qt.AlignCenter)
        self.image_label.setStyleSheet(
            "border: 2px dashed #aaa; background: #fff;"
        )

        self.select_btn = QPushButton("Select Video")
        self.select_btn.setStyleSheet(self.button_style())

        self.start_btn = QPushButton("Start Conversion")
        self.start_btn.setEnabled(False)
        self.start_btn.setStyleSheet(self.button_style())

        # 高级下拉框:圆角、渐变背景、下拉箭头自定义
        self.format_selector = QComboBox()
        self.format_selector.setStyle(ModernComboStyle())
        self.format_selector.addItems(["jpg", "png", "bmp"])
        self.format_selector.setFixedWidth(120)
        self.format_selector.setStyleSheet(
            "QComboBox {"
            "border: 2px solid #ccc;"
            "border-radius: 8px;"
            "padding: 6px 30px 6px 12px;"
            "background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #ffffff, stop:1 #e6e6e6);"
            "font-size: 14px;"
            "}"
            "QComboBox::drop-down {"
            "subcontrol-origin: padding;"
            "subcontrol-position: top right;"
            "width: 24px;"
            "border-left: none;"
            "image: url(:/icons/arrow_down.svg);"
            "}"
            "QComboBox QAbstractItemView {"
            "border: 1px solid #bbb;"
            "selection-background-color: #3b82f6;"
            "}"
        )

        self.progress_bar = QProgressBar()
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setStyleSheet(
            "QProgressBar { height: 24px; font-weight: bold; "
            "border: 1px solid #bbb; border-radius: 5px; text-align: center; }"
            "QProgressBar::chunk { background-color: #3b82f6; width: 10px; }"
        )

        top_bar = QHBoxLayout()
        top_bar.addWidget(QLabel("Image Format:"))
        top_bar.addWidget(self.format_selector)
        top_bar.addStretch()
        top_bar.addWidget(self.select_btn)
        top_bar.addWidget(self.start_btn)

        layout = QVBoxLayout()
        layout.addWidget(title)
        layout.addSpacing(10)
        layout.addWidget(self.label)
        layout.addSpacing(10)
        layout.addWidget(self.image_label)
        layout.addSpacing(10)
        layout.addLayout(top_bar)
        layout.addSpacing(10)
        layout.addWidget(self.progress_bar)
        self.setLayout(layout)

        self.select_btn.clicked.connect(self.select_video)
        self.start_btn.clicked.connect(self.start_process)

    def button_style(self):
        return (
            "QPushButton {"
            " background-color: #3b82f6; color: white; border-radius: 6px;"
            " padding: 10px 20px; font-size: 14px; }"
            "QPushButton:disabled { background-color: #a0aec0; }"
        )

    def select_video(self):
        file, _ = QFileDialog.getOpenFileName(
            self, "Select Video File", ".", "Video Files (*.mp4 *.avi *.mkv)"
        )
        if file:
            self.video_path = file
            self.label.setText(f"Selected: {Path(file).name}")
            self.start_btn.setEnabled(True)

    def start_process(self):
        fmt = self.format_selector.currentText()
        self.thread = VideoProcessor(self.video_path, fmt)
        self.thread.progress.connect(self.update_progress)
        self.thread.finished.connect(self.finish_process)
        self.thread.start()
        self.start_btn.setEnabled(False)
        self.label.setText("Processing video...")

    def update_progress(self, idx, total, img):
        self.progress_bar.setMaximum(total)
        self.progress_bar.setValue(idx)
        self.label.setText(f"Progress: {idx}/{total}")
        self.image_label.setPixmap(
            QPixmap.fromImage(img).scaled(
                self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
            )
        )

    def finish_process(self):
        self.label.setText("✅ Conversion completed!")
        self.start_btn.setEnabled(True)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = VideoSplitUI()
    win.show()
    sys.exit(app.exec_())

这里代码的逻辑也比较简单:

  1. 主程序进行 UI 界面初始化,并显示主界面

  2. 主界面等待用户操作,如果用户点击 Select video 按钮,打开文件选择交互框,在选择文件后,更新文件路径显示,并启用 Start Conversion 按钮

  3. Start Conversion 按钮启用后,如果点击它,就可以通过 cv2 库创建后台线程,并逐帧将视频转为图片,并在界面中可视化当前进度,包括当前处理的帧,还有处理进度

绘制的流程图如下

用户操作
点击 Select Video
等待用户操作
打开文件选择对话框
更新文件路径显示
启用 Start Conversion 按钮
点击 Start Conversion
创建后台线程
线程逐帧处理视频
更新进度条和预览图
处理完成?
显示完成提示
开始
初始化 UI 界面
显示主窗口
返回等待状态
结束

界面的处理过程为

project cover
界面可视化

转化结果直接保存在界面程序所在文件夹下的与视屏同名的文件夹下,在本例中是 test 子文件夹下,并以最适合的命名方式自动进行统一的命名

project cover
保存结果可视化
## 2 界面程序解读

这里主程序部分、库加载部分,统一进行解释:

# 导入系统模块,用于处理命令行参数和操作系统交互
import sys, os  
# 导入OpenCV库,用于视频处理和图像转换
import cv2  
# 导入Path类,用于处理文件路径(更简洁的路径操作)
from pathlib import Path  
# 导入PyQt5的UI组件类
from PyQt5.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QFileDialog,  # 基础UI组件
    QComboBox, QProgressBar, QHBoxLayout, QStyle, QProxyStyle              # 组合框、进度条、布局、样式相关
)
# 导入PyQt5的核心功能类(线程、信号、枚举值)
from PyQt5.QtCore import Qt, QThread, pyqtSignal  
# 导入PyQt5的图形相关类(图片、字体)
from PyQt5.QtGui import QPixmap, QImage, QFont  


# ---------------------- 主程序入口 ---------------------- #
if __name__ == "__main__":  # 判断当前脚本是否为主程序(避免被其他模块导入时执行)
    # 创建PyQt应用程序对象,sys.argv用于传递命令行参数(此处未使用,但为标准写法)
    app = QApplication(sys.argv)  
    
    # 创建主窗口实例(假设VideoSplitUI是自定义的UI类,需提前定义)
    win = VideoSplitUI()  
    
    # 显示主窗口(窗口默认隐藏,需手动调用show())
    win.show()  
    
    # 进入应用程序事件循环,处理用户交互(如按钮点击、窗口关闭等)
    # app.exec_()会阻塞直到窗口关闭,sys.exit()确保程序干净退出
    sys.exit(app.exec_())  

2.1 VideoSplitUI 类解释

这个类是界面的主要类,用于界面可视化和功能调用。首先是类初始化部分,调用了界面名称设置(self.setWindowTitle(“🎞️ Smart Video Frame Extractor”))、窗口大小调整(self.resize(1000, 700))、界面背景颜色设置(self.setStyleSheet(“background-color: #f6f8fb;”))、以及 UI 初始化 (self.init_ui()) 函数

class VideoSplitUI(QWidget):
    """
    主窗口类,负责初始化和管理 UI 元素,以及响应用户交互。
    """
    def __init__(self):
        super().__init__()  # 调用父类(QWidget)的构造函数,初始化窗口
        self.setWindowTitle("🎞️ Smart Video Frame Extractor")  # 设置窗口标题
        self.resize(1000, 700)  # 设置窗口初始大小(宽1000,高700)
        self.setStyleSheet("background-color: #f6f8fb;")  # 设置窗口背景颜色(浅灰色)
        self.video_path = None  # 用于存储用户选择的视频文件路径
        self.init_ui()  # 调用初始化UI的方法

这里,除了 init_ui 之外,都是 PyQt 自带的函数,下面对咱们自己写的函数进行逐行解析

    def init_ui(self):
        # ---------------------- 标题组件 ---------------------- #
        title = QLabel("Video to Image Converter")  # 创建标题标签
        title.setFont(QFont("Segoe UI", 24, QFont.Bold))  # 设置字体:Segoe UI,大小24,加粗
        title.setAlignment(Qt.AlignCenter)  # 设置文本居中对齐

        # ---------------------- 状态提示标签 ---------------------- #
        self.label = QLabel("No video selected")  # 创建提示标签,默认显示“未选择视频”
        self.label.setFont(QFont("Segoe UI", 11))  # 设置字体:Segoe UI,大小11
        self.label.setStyleSheet("color: #333;")  # 设置文本颜色(深灰色)

        # ---------------------- 图像预览区域 ---------------------- #
        self.image_label = QLabel("Preview Area")  # 创建预览区域标签
        self.image_label.setFixedHeight(360)  # 设置固定高度360像素
        self.image_label.setAlignment(Qt.AlignCenter)  # 设置内容居中对齐
        self.image_label.setStyleSheet(
            "border: 2px dashed #aaa; background: #fff;"  # 设置样式:虚线边框,白色背景
        )

        # ---------------------- “选择视频”按钮 ---------------------- #
        self.select_btn = QPushButton("Select Video")  # 创建按钮
        self.select_btn.setStyleSheet(self.button_style())  # 应用自定义按钮样式

        # ---------------------- “开始转换”按钮 ---------------------- #
        self.start_btn = QPushButton("Start Conversion")  # 创建按钮
        self.start_btn.setEnabled(False)  # 初始状态:按钮禁用(不可点击)
        self.start_btn.setStyleSheet(self.button_style())  # 应用自定义按钮样式

        # ---------------------- 图像格式下拉框 ---------------------- #
        self.format_selector = QComboBox()  # 创建下拉框组件
        self.format_selector.setStyle(ModernComboStyle())  # 设置自定义样式(圆角等)
        self.format_selector.addItems(["jpg", "png", "bmp"])  # 添加可选格式选项
        self.format_selector.setFixedWidth(120)  # 设置固定宽度120像素
        self.format_selector.setStyleSheet("""
            QComboBox {
                border: 2px solid #ccc;          /* 边框:2px 灰色实线 */
                border-radius: 8px;              /* 圆角:8px */
                padding: 6px 30px 6px 12px;      /* 内边距:上下6px,左右12px和30px(预留箭头空间) */
                background: qlineargradient(...); /* 渐变背景:从上到下由白到浅灰 */
                font-size: 14px;                 /* 字体大小:14px */
            }
            QComboBox::drop-down { ... }      /* 下拉箭头样式:自定义图标、位置 */
            QComboBox QAbstractItemView { ... } /* 下拉列表样式:边框、选中颜色 */
        """)

        # ---------------------- 进度条组件 ---------------------- #
        self.progress_bar = QProgressBar()  # 创建进度条
        self.progress_bar.setTextVisible(True)  # 显示进度文本(如“42%”)
        self.progress_bar.setStyleSheet("""
            QProgressBar { ... }             /* 进度条样式:高度、边框、字体 */
            QProgressBar::chunk { ... }       /* 进度块样式:蓝色背景,宽度10px */
        """)

        # ---------------------- 顶部水平布局(格式选择+按钮) ---------------------- #
        top_bar = QHBoxLayout()  # 创建水平布局
        top_bar.addWidget(QLabel("Image Format:"))  # 添加“图像格式”标签
        top_bar.addWidget(self.format_selector)  # 添加格式下拉框
        top_bar.addStretch()  # 添加弹性空间,使右侧按钮右对齐
        top_bar.addWidget(self.select_btn)  # 添加“选择视频”按钮
        top_bar.addWidget(self.start_btn)  # 添加“开始转换”按钮

        # ---------------------- 主垂直布局(整体界面结构) ---------------------- #
        layout = QVBoxLayout()  # 创建垂直布局
        layout.addWidget(title)  # 添加标题
        layout.addSpacing(10)  # 添加10px垂直间距
        layout.addWidget(self.label)  # 添加状态提示标签
        layout.addSpacing(10)  # 添加10px垂直间距
        layout.addWidget(self.image_label)  # 添加预览区域
        layout.addSpacing(10)  # 添加10px垂直间距
        layout.addLayout(top_bar)  # 添加顶部水平布局
        layout.addSpacing(10)  # 添加10px垂直间距
        layout.addWidget(self.progress_bar)  # 添加进度条
        self.setLayout(layout)  # 设置主布局为垂直布局

        # ---------------------- 按钮点击事件绑定 ---------------------- #
        self.select_btn.clicked.connect(self.select_video)  # 点击“选择视频”时调用select_video方法
        self.start_btn.clicked.connect(self.start_process)  # 点击“开始转换”时调用start_process方法

可以看到,这里其实已经定义了所有界面的可视化部分,并给每个按钮都关联了对应的函数。具体为: Select Video 按钮 关联 self.select_video 函数 、Start Conversion 按钮 关联 self.start_process 函数 。这里所有的按钮也都使用同一个样式 button_style 定义为

    def button_style(self):
        return (
            "QPushButton {"
            " background-color: #3b82f6; color: white; border-radius: 6px;"
            " padding: 10px 20px; font-size: 14px; }"
            "QPushButton:disabled { background-color: #a0aec0; }"
        )

这里大家可以使用自己的样式。然后就是关键函数的定义,包括与 Select Video 按钮 关联的 self.select_video 函数

    def select_video(self):
        # 打开文件选择对话框,过滤视频文件类型(mp4/avi/mkv)
        file, _ = QFileDialog.getOpenFileName(
            self, "Select Video File", ".", "Video Files (*.mp4 *.avi *.mkv)"
        )
        if file:  # 如果用户选择了文件
            self.video_path = file  # 保存文件路径
            self.label.setText(f"Selected: {Path(file).name}")  # 更新提示标签显示文件名
            self.start_btn.setEnabled(True)  # 启用“开始转换”按钮

Start Conversion 按钮 关联的 self.start_process 函数

    def start_process(self):
        fmt = self.format_selector.currentText()  # 获取当前选择的图像格式(jpg/png/bmp)
        # 创建后台处理线程,传入视频路径和格式
        self.thread = VideoProcessor(self.video_path, fmt)
        # 绑定线程信号到UI更新方法:
        # - progress信号:传递当前帧索引、总帧数、预览图像
        # - finished信号:处理完成时触发
        self.thread.progress.connect(self.update_progress)
        self.thread.finished.connect(self.finish_process)
        self.thread.start()  # 启动线程
        self.start_btn.setEnabled(False)  # 禁用按钮,避免重复点击
        self.label.setText("Processing video...")  # 更新状态提示

以及负责 更新进度和预览图 功能的 update_progress 函数,以及 处理完成后的回调 的 finish_process 函数

    def update_progress(self, idx, total, img):
        self.progress_bar.setMaximum(total)  # 设置进度条最大值(总帧数)
        self.progress_bar.setValue(idx)  # 更新当前进度值
        self.label.setText(f"Progress: {idx}/{total}")  # 显示进度文本
        # 将OpenCV图像转换为Qt可显示的格式,并适配预览区域大小
        self.image_label.setPixmap(
            QPixmap.fromImage(img).scaled(
                self.image_label.size(), 
                Qt.KeepAspectRatio,  # 保持宽高比
                Qt.SmoothTransformation  # 平滑缩放(抗锯齿)
            )
        )
    
    def finish_process(self):
      self.label.setText("✅ Conversion completed!")  # 显示完成提示
      self.start_btn.setEnabled(True)  # 重新启用“开始转换”按钮

2.2 VideoProcessor 类解释

这个库是在后台进行视频处理的核心类,在 VideoSplitUI.start_process 函数中被调用,其定义为

class VideoProcessor(QThread):
    """
    后台线程类,用于从视频文件中逐帧读取并保存为图像,同时发出进度和图像信号更新UI。
    """
    # 定义自定义信号:
    # - progress:传递当前帧索引、总帧数、QImage格式的预览图像
    # - finished:处理完成时发出的空信号
    progress = pyqtSignal(int, int, QImage)
    finished = pyqtSignal()

    def __init__(self, video_path, output_format):
      super().__init__()  # 调用父类(QThread)的构造函数,初始化线程
      # 将视频路径转换为Path对象,方便文件操作
      self.video_path = Path(video_path)
      # 确保输出格式为小写(如用户输入"JPG"会转为"jpg")
      self.output_format = output_format.lower()

这里可以看到,VideoProcessor 继承自QThread,通过信号(pyqtSignal)与 UI 线程通信,避免直接操作 UI 组件导致线程安全问题。并可接收视频路径和输出格式,自动处理路径解析和格式统一(转为小写)。

其核心处理程序为

    def run(self):
        # ---------------------- 初始化视频读取 ---------------------- #
        # 创建VideoCapture对象,用于读取视频文件
        # str(self.video_path)将Path对象转为字符串路径
        cap = cv2.VideoCapture(str(self.video_path))
        
        # 获取视频总帧数
        total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        
        # 提取视频文件名(不含扩展名)作为输出目录名
        output_dir = self.video_path.stem
        # 创建输出目录(exist_ok=True:若目录存在则不报错)
        Path(output_dir).mkdir(exist_ok=True)

        # ---------------------- 逐帧处理循环 ---------------------- #
        frame_idx = 0  # 当前处理的帧索引(从0开始)
        while True:
            # 读取一帧视频(ret为是否成功,frame为图像数据)
            ret, frame = cap.read()
            if not ret:  # 读取失败或到达视频末尾
                break  # 退出循环

            # ---------------------- 图像格式转换 ---------------------- #
            # OpenCV默认使用BGR通道,转为RGB通道(QImage需要RGB格式)
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            # 获取图像尺寸(高度、宽度、通道数)
            h, w, ch = rgb.shape
            # 将OpenCV的RGB图像数据转换为QImage对象(供Qt显示)
            # 参数说明:
            # - rgb.data:图像数据指针
            # - w, h:宽度、高度
            # - ch * w:每行字节数(RGB三通道,每行w*3字节)
            # - QImage.Format_RGB888:指定RGB格式(每个通道8位)
            qt_img = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)

            # ---------------------- 保存图像文件 ---------------------- #
            # 生成文件名(如"0001.jpg",4位补零确保顺序)
            filename = f"{frame_idx+1:04d}.{self.output_format}"
            # 拼接文件路径(输出目录+文件名)
            file_path = Path(output_dir) / filename
            # 使用OpenCV保存图像(支持jpg/png/bmp等格式)
            cv2.imwrite(str(file_path), frame)  # 保存原始BGR格式图像(非预览用)

            # ---------------------- 发送进度信号 ---------------------- #
            # 发出progress信号,携带当前帧索引(+1后从1开始显示)、总帧数、预览图像
            self.progress.emit(frame_idx + 1, total, qt_img)
            
            frame_idx += 1  # 帧索引递增

        # ---------------------- 资源释放 ---------------------- #
        cap.release()  # 释放视频捕获对象,释放资源
        self.finished.emit()  # 发出处理完成信号

在流程上,这里的逻辑为

  1. 初始化视频读取对象,创建输出目录。使用 OpenCV 的VideoCapture读取视频,CAP_PROP_FRAME_COUNT获取总帧数。逐帧读取视频,通过cvtColor转换颜色通道,生成可供 Qt 显示的QImage对象。
  2. 逐帧读取视频,转换格式并保存为图像文件。输出目录为视频文件名(不含扩展名),自动创建目录(mkdir(exist_ok=True))。文件名使用 4 位补零数字(如0001.jpg),确保文件按顺序排列。
  3. 实时发送进度和预览图像信号。progress信号实时传递处理进度和预览图像,UI 线程通过槽函数(如update_progress)更新界面。finished信号在处理完成后触发,通知 UI 线程恢复交互状态(如重新启用按钮)。
  4. 处理完成后发送完成信号,释放资源。循环结束后调用cap.release()释放视频文件句柄,避免资源泄漏。

2.3 ModernComboStyle 类解释

ModernComboStyle 类继承自 QProxyStyle

class ModernComboStyle(QProxyStyle):
    """
    自定义样式类,用于为QComboBox添加自定义圆角等效果。
    继承自QProxyStyle(代理样式,用于修改现有样式的行为)。
    """
        def drawComplexControl(self, control, option, painter, widget=None):
        """
        重写父类方法,自定义复杂控件(如QComboBox)的绘制逻辑。
        
        参数说明:
        - control: 要绘制的控件类型(QStyle.ComplexControl枚举值,如CC_ComboBox)
        - option: QStyleOptionComplex对象,包含控件的状态和样式信息
        - painter: QPainter对象,用于执行绘制操作
        - widget: 关联的控件对象(可选)
        """
        # ---------------------- 核心逻辑 ---------------------- #
        # 调用父类(系统默认样式)的绘制方法,先绘制原始控件
        super().drawComplexControl(control, option, painter, widget)

3 总结

至此,本期的视频转图像的相关内容就结束了,主要总结了使用 python 结合 PyQt5 来进行视频转图像的功能

往期优质文章包括:

  1. 大模型微调系列之LoRA详解_lora微调需要多少显存-优快云博客
  2. 大模型评价标准_大模型评分标准-优快云博客
  3. 大模型分类介绍之业务分类与提示工程(一)-优快云博客
感谢您的提问!以下是手把手搭建属于自己的PyQt5-YOLOv5目标检测平台的保姆级程: 1. 安装Anaconda 首先,您需要下载并安装Anaconda,Anaconda是一个 Python 数据科学平台,包含了许多常用的数据科学包,如Numpy、Pandas等。 2. 创建一个虚拟环境 在安装了Anaconda之后,您需要创建一个虚拟环境,以便隔离开发环境和系统环境。您可以在命令行中输入以下命令来创建一个名为yolov5的虚拟环境: ``` conda create -n yolov5 python=3.8 ``` 其中,“yolov5”是您的虚拟环境的名称,您可以根据自己的需要进行更改。 3. 激活虚拟环境 创建完虚拟环境之后,您需要激活它,以便在环境中进行开发。在命令行中输入以下命令来激活yolov5环境: ``` conda activate yolov5 ``` 4. 安装PyQt5和YOLOv5 在激活了虚拟环境之后,您需要安装PyQt5和YOLOv5。您可以在命令行中输入以下命令来安装它们: ``` pip install PyQt5 pip install yolov5 ``` 5. 创建PyQt5界面 在安装了PyQt5之后,您可以使用Qt Designer创建一个PyQt5界面Qt Designer是一个可视化的界面设计工具,可以让您轻松地创建PyQt5界面。 6. 使用YOLOv5进行目标检测 在安装了YOLOv5之后,您可以使用它进行目标检测。您可以在Python脚本中使用以下代码: ```python import torch from yolov5.models.experimental import attempt_load from yolov5.utils.torch_utils import select_device device = select_device('cpu') model = attempt_load('yolov5s.pt', map_location=device) img = torch.zeros((1, 3, 640, 640), device=device) pred = model(img) print(pred) ``` 其中,“yolov5s.pt”是YOLOv5的预训练模型,您可以在YOLOv5的GitHub页面上下载它。 7. 将PyQt5界面与YOLOv5集成 最后,您需要将PyQt5界面与YOLOv5集成起来,以便您可以在界面上使用YOLOv5进行目标检测。您可以在Python脚本中使用以下代码: ```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow from PyQt5.QtGui import QPixmap from yolov5.models.experimental import attempt_load from yolov5.utils.torch_utils import select_device class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle('PyQt5-YOLOv5目标检测平台') self.setGeometry(100, 100, 800, 600) self.label = QLabel(self) self.label.setGeometry(50, 50, 640, 480) self.show() if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() sys.exit(app.exec_()) ``` 这段代码创建了一个名为“PyQt5-YOLOv5目标检测平台”的窗口,并在窗口上添加了一个标签。您可以使用这个标签来显示检测到的目标。 以上就是手把手搭建属于自己的PyQt5-YOLOv5目标检测平台的保姆级程。希望对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空 白II

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值