使用pyqt5、pyopengl、opencv做视频渲染和轨迹绘制的程序

0. 概要

本文利用 PyQt5 和 OpenGL 提供了良好的用户界面和流畅的视频渲染效果;使用线程锁确保了多线程操作的安全性,同时在每帧视频上叠加了简单的图形和文字,展示了 OpenCV 在视频处理中的应用。

1. 说明

  • 支持h264文件播放模块,在h264文件模块添加帧总数;
  • 需要搭配时间戳文件(time_stamp.txt)使用,以方便显示播放进度;
  • 有播放、暂停和停止功能;
  • 使用opengl渲染;
  • 有轨迹叠加演示;
  • 仓库代码: pyqt5-pyopengl-opencv-example

在这里插入图片描述

2. 主要代码

# -*- coding: utf-8 -*-
# ref: https://www.cnblogs.com/superxuezhazha/p/6195328.html
# ref: https://riptutorial.com/zh-CN/pyqt5/example/29500/%E5%9F%BA%E6%9C%AC%E7%9A%84pyqt%E8%BF%9B%E5%BA%A6%E6%9D%A1
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtOpenGL import *
from OpenGL.GL import *

import sys
import time
import struct
import logging
import cv2
import numpy as np

VIDEO_WIDTH = 512
VIDEO_HEIGHT = 424
playerMutex = QMutex() # 创建线程锁

# 底部控制条,开始、暂停、进度条等
class PlayerControl(QDialog):
    videoPlayStarted = pyqtSignal()
    videoPlayPaused = pyqtSignal()
    videoPlayStoped = pyqtSignal()
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.init_event()
        self.thread_pause = False
        
    def init_ui(self):
        self.setWindowTitle('Progress Bar')
        self.setFixedHeight(80)
        self.progress = QProgressBar(self)
        self.progress.setGeometry(0, 0, 500, 25)
        self.progress.setMaximum(100)
        self.buttonStart = QPushButton('Start', self)
        self.buttonPause = QPushButton('Pause', self)
        self.buttonStop = QPushButton('Stop', self)
        self.buttonStart.move(0, 30)
        self.buttonPause.move(90, 30)
        self.buttonStop.move(180, 30)
        
    def init_event(self):
        self.buttonStart.clicked.connect(self.onButtonStartClick)
        self.buttonPause.clicked.connect(self.onButtonPauseClick)
        self.buttonStop.clicked.connect(self.onButtonStopClick)

    def onButtonStartClick(self):
        if self.thread_pause:
            playerMutex.unlock()
            self.thread_pause = False
        
        self.buttonStart.setEnabled(False)
        self.buttonPause.setEnabled(True)
        self.buttonStop.setEnabled(True)
        self.videoPlayStarted.emit()
        
    def onButtonPauseClick(self):
        playerMutex.lock()
        self.thread_pause = True
        self.buttonStart.setEnabled(True)
        self.buttonPause.setEnabled(False)
        self.buttonStop.setEnabled(True)
        self.videoPlayPaused.emit()
        
    def onButtonStopClick(self):
        self.progress.setValue(0)
        if self.thread_pause:
            playerMutex.unlock()
            self.thread_pause = False
        
        self.buttonStart.setEnabled(True)
        self.buttonPause.setEnabled(False)
        self.buttonStop.setEnabled(False)
        self.videoPlayStoped.emit()
        
    def onProcessChanged(self, value):
        self.progress.setValue(value)

class VideoPlayer(QThread):
    """
    Runs a VideoPlayer thread.
    """
    updateView = pyqtSignal(bytes, int, int)
    progressChanged = pyqtSignal(int)
    
    def __init__(self, video_file):
        super(VideoPlayer, self).__init__()
        self._run = False
        self.video_pause = True
        self.video_Stop = True
        self.video_file = video_file
    
    def get_frame_count(self):
        # 自定义格式,在视频末尾增加了帧总数,8个字节
        with open(self.video_file, "rb") as f:
            f.seek(f.seek(0, 2) - 8, 0)
            frame_count = struct.unpack('Q', f.read(8))[0]
        logging.info("frame_count: %s" %frame_count)
        return frame_count
    
    def onVideoPlayStarted(self):
        self.video_pause = False
        self.video_Stop = False
        self.start()
        
    def onVideoPlayPaused(self):
        self.video_pause = True
        self.video_Stop = False
        
    def onVideoPlayStoped(self):
        self.video_pause = True
        self.video_Stop = True
        self._run = False
        
    def run(self):
        cap = cv2.VideoCapture(self.video_file)
        if not cap:
            return
        frame_index = 0
        total_frame_num = self.get_frame_count()
        self._run = True
        while self._run:
            if self.video_pause:
                playerMutex.lock()
                self.video_pause = False
                playerMutex.unlock()
                continue
            ret, frame = cap.read()
            if not ret:
                continue
            if self.video_Stop:
                break
            if total_frame_num and total_frame_num > 0:
                frame_index += 1
                progress = frame_index*100 / total_frame_num
                self.progressChanged.emit(progress)
            img = cv2.resize(frame, (VIDEO_WIDTH, VIDEO_HEIGHT), interpolation = cv2.INTER_AREA)

            # 用绿色(0, 255, 0)来画出矩形
            x, y, w, h = 10, 10, 200, 300
            img = cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
  
            # Using cv2.putText() method 
            img = cv2.putText(img, 'OpenCV', (50, 50), cv2.FONT_HERSHEY_SIMPLEX , 1, (255, 0, 0), 2, cv2.LINE_AA)

            # Our operations on the frame come here
            rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            img_data = rgb.tobytes()
            if self.video_Stop: # 防止此时已经退出
                break
            # Display the resulting frame
            self.updateView.emit(img_data, rgb.shape[0], rgb.shape[1])
        cap.release()
            
    def stop(self):
        self._run = False

# 播放视频的opengl窗口
class VideoView(QGLWidget):
    def __init__(self, parent=None):
        super(VideoView, self).__init__(parent)
        self.width, self.height = VIDEO_WIDTH, VIDEO_HEIGHT
        self.texture, self.data = None, None
        if parent:
            self.resize(parent.size())
        
    def update_view(self, data, height, width):
        self.data = data
        self.height = height
        self.width = width
        self.updateGL()

    def initializeGL(self):
        glClearColor(0.5, 0.5, 0.5, 1.0)

        glEnable(GL_DEPTH_TEST)
        glClearColor(0.0, 0.0, 0.0, 0.0)

        glClearDepth(2000.0)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnable(GL_TEXTURE_2D)

        self.texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.texture)

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)


    def resizeGL(self, width, height):
        glViewport(0, 0, width, height)

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()

        glOrtho(0, self.width, self.height, 0.0, 0.0, 1.0)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()


    def paintGL(self):
        if self.data is not None:
            glClearColor(.5, .5, .5, 1.0)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            # , width = self.data.shape[:2]
            glTexImage2D(GL_TEXTURE_2D,
                         0,
                         3,
                         self.width,
                         self.height,
                         0,
                         GL_RGB,
                         GL_UNSIGNED_BYTE,
                         self.data)
            glLoadIdentity()
    
            glEnable(GL_TEXTURE_2D)
    
            glBegin(GL_TRIANGLE_FAN)
            glColor4f(255.0, 255.0, 255.0, 255.0)
            glTexCoord2f(0, 0); glVertex3f(0, 0, 0)
            glTexCoord2f(1, 0); glVertex3f(self.width, 0, 0)
            glTexCoord2f(1, 1); glVertex3f(self.width, self.height, 0)
            glTexCoord2f(0, 1); glVertex3f(0, self.height, 0)
            glEnd()

# 组合播放窗口和进度条
class CustomVideoPlayer(QDialog):
    def __init__(self, video_file):
        super().__init__()
        self.setFixedSize(900, 600)
        self.videoView = VideoView()
        self.playerControl = PlayerControl()
        self.videoPlayer = VideoPlayer(video_file)
        self.init_event()
        self.init_ui()
        
    def init_event(self):
        self.playerControl.videoPlayStarted.connect(self.videoPlayer.onVideoPlayStarted)
        self.playerControl.videoPlayPaused.connect(self.videoPlayer.onVideoPlayPaused)
        self.playerControl.videoPlayStoped.connect(self.videoPlayer.onVideoPlayStoped)
        self.videoPlayer.progressChanged.connect(self.playerControl.onProcessChanged)
        self.videoPlayer.updateView.connect(self.videoView.update_view)
    
    def init_ui(self):
        self.v_box = QVBoxLayout()
        self.v_box.addWidget(self.videoView)
        self.v_box.addWidget(self.playerControl)
        self.setLayout(self.v_box)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = CustomVideoPlayer('camera2.h264')
    window.show()
    sys.exit(app.exec_())

3. 代码解释和分析

这段代码实现了一个基于 PyQt5 和 OpenGL 的视频播放器,具备播放、暂停、停止功能,并支持视频帧的显示和轨迹叠加。下面是对其主要功能和实现的解释和分析:

  • 功能概述:

    • 视频播放: 使用 VideoPlayer 类来读取指定的 h264 视频文件,并根据时间戳文件(未在代码中显示)显示播放进度。
    • 控制条: PlayerControl 类提供了开始、暂停、停止按钮以及进度条,用于控制视频的播放和显示当前进度。
    • OpenGL 渲染: VideoView 类使用 OpenGL 渲染每一帧视频,确保流畅的显示效果。
    • 轨迹叠加: 在每一帧视频上叠加绿色矩形和文字,演示了 OpenCV 的基本用法。
  • 代码结构:

    • 主程序入口: CustomVideoPlayer 类作为程序的入口点,创建了视频播放窗口、视频播放器、控制条,并将它们组合在一起。
    • 线程安全: 使用 playerMutex 确保在播放暂停时的线程安全性,避免多线程冲突。
    • 信号与槽机制: 使用 PyQt5 的信号与槽机制,实现了组件之间的通信,例如控制条按钮操作与视频播放器的状态变更。
  • Mermaid 流程图:

    下面是使用 Mermaid 语法绘制的流程图,展示了程序的主要流程和组件交互过程:

    CustomVideoPlayer
    PlayerControl
    VideoView
    VideoPlayer
    Video File
    OpenGL Rendering
    OpenCV Processing
    Start Button
    Pause Button
    Stop Button
    OpenGL Display

    这个流程图简要展示了主程序、控制条、视频显示和处理的关系,帮助理解整体架构和数据流动。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘色的喵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值