QOpenGLWidget视频画面上绘制矩形框

news2025/4/21 18:46:18

一、QPainter绘制

在QOpenGLWidget中可以绘制,并且和OpenGL的内容叠在一起。paintGL里面绘制完视频后,解锁资源,再用QPainter绘制矩形框。这种方式灵活性最好。

void VideoGLWidget::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT);

    m_program.bind();

    //绘制视频数据

    // 解绑VAO
    glBindVertexArray(0);
    m_program.release();


     // ----------------- 绘制矩形框 -----------------
     QPainter painter(this);
     painter.setRenderHint(QPainter::Antialiasing);
     painter.setPen(QPen(QColor(Qt::red), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
     QRectF drawRect = QRectF(0, 0, width() * 0.5, height() * 0.5);
     painter.drawRect(drawRect);
     painter.end();
}

二、OpenGL绘制

通过不同的QOpenGLShaderProgram,可以指定不同的着色器程序来实现矩形的绘制。
1)边框颜色参数要通过Uniform传递给OpenGL的顶点着色器。
2)动态矩形顶点缓冲更新,坐标归一化到OpenGL坐标系,顶点数据更新VBO

void VideoGLWidget::updateRectBuffer() {
    if (m_rects.isEmpty()) return;

    // 将 QRectF 转换为归一化坐标(-1 到 1)
    QVector<float> vertices;
    for (const QRectF &rect : m_rects) {
        float x1 = (rect.x() / m_videoSize.width()) * 2 - 1;
        float y1 = 1 - (rect.y() / m_videoSize.height()) * 2;
        float x2 = ((rect.x() + rect.width()) / m_videoSize.width()) * 2 - 1;
        float y2 = 1 - ((rect.y() + rect.height()) / m_videoSize.height()) * 2;

        // 每个矩形由 4 条线段组成(每条线段 2 个点)
        vertices << x1 << y1 << x2 << y1;  // 上边
        vertices << x2 << y1 << x2 << y2;  // 右边
        vertices << x2 << y2 << x1 << y2;  // 下边
        vertices << x1 << y2 << x1 << y1;  // 左边
    }

    // 更新 VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_rectVBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), 
                 vertices.constData(), GL_DYNAMIC_DRAW);
}

头文件 VideoGLWidget.h‌ 

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>

class VideoGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
public:
    explicit VideoGLWidget(QWidget *parent = nullptr);
    ~VideoGLWidget();

    // 更新视频帧(假设帧格式为 RGB32)
    void updateVideoFrame(const QImage &frame);

    // 更新动态矩形框列表(坐标相对于视频帧尺寸)
    void updateRects(const QList<QRectF> &rects);

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;

private:
    // OpenGL 资源
    QOpenGLShaderProgram *m_videoShader; // 视频渲染着色器
    QOpenGLShaderProgram *m_rectShader;   // 矩形框渲染着色器
    QOpenGLTexture *m_videoTexture;       // 视频纹理
    GLuint m_rectVBO;                     // 矩形顶点缓冲对象
    QSize m_videoSize;                    // 视频帧尺寸
    QList<QRectF> m_rects;                // 当前矩形框列表

    // 顶点数据相关
    void initRectBuffer();
    void updateRectBuffer();
};

实现文件 VideoGLWidget.cpp 

OpenGL初始化

VideoGLWidget::VideoGLWidget(QWidget *parent) 
    : QOpenGLWidget(parent), m_videoTexture(nullptr), m_rectVBO(0) {
    // 启用自动更新
    setAutoFillBackground(false);
}

VideoGLWidget::~VideoGLWidget() {
    makeCurrent();
    delete m_videoTexture;
    delete m_videoShader;
    delete m_rectShader;
    glDeleteBuffers(1, &m_rectVBO);
    doneCurrent();
}

void VideoGLWidget::initializeGL() {
    initializeOpenGLFunctions();
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // 初始化视频渲染着色器
    m_videoShader = new QOpenGLShaderProgram(this);
    m_videoShader->addShaderFromSourceCode(QOpenGLShader::Vertex,
        "attribute vec4 vertexIn;"
        "attribute vec2 texCoordIn;"
        "varying vec2 texCoord;"
        "void main() {"
        "    gl_Position = vertexIn;"
        "    texCoord = texCoordIn;"
        "}");
    m_videoShader->addShaderFromSourceCode(QOpenGLShader::Fragment,
        "varying vec2 texCoord;"
        "uniform sampler2D videoTexture;"
        "void main() {"
        "    gl_FragColor = texture2D(videoTexture, texCoord);"
        "}");
    m_videoShader->link();

    // 初始化矩形框渲染着色器
    m_rectShader = new QOpenGLShaderProgram(this);
    m_rectShader->addShaderFromSourceCode(QOpenGLShader::Vertex,
        "attribute vec2 position;"
        "void main() {"
        "    gl_Position = vec4(position, 0.0, 1.0);"
        "}");
    m_rectShader->addShaderFromSourceCode(QOpenGLShader::Fragment,
        "uniform vec4 color;"
        "void main() {"
        "    gl_FragColor = color;"
        "}");
    m_rectShader->link();

    // 初始化矩形顶点缓冲
    glGenBuffers(1, &m_rectVBO);
}

视频帧更新与纹理上传

void VideoGLWidget::updateVideoFrame(const QImage &frame) {
    makeCurrent();

    // 首次创建或尺寸变化时重新创建纹理
    if (!m_videoTexture || m_videoTexture->size() != frame.size()) {
        delete m_videoTexture;
        m_videoTexture = new QOpenGLTexture(QOpenGLTexture::Target2D);
        m_videoTexture->setFormat(QOpenGLTexture::RGB8_UNorm);
        m_videoTexture->setSize(frame.width(), frame.height());
        m_videoTexture->allocateStorage();
        m_videoSize = frame.size();
    }

    // 上传帧数据到纹理(假设帧为 RGB32 格式)
    m_videoTexture->bind();
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 
                    frame.width(), frame.height(),
                    GL_BGRA, GL_UNSIGNED_BYTE, frame.bits());
    update();
}

void VideoGLWidget::updateRects(const QList<QRectF> &rects) {
    m_rects = rects;
    updateRectBuffer(); // 更新顶点数据
    update();
}

动态矩形顶点缓冲更新

void VideoGLWidget::updateRectBuffer() {
    if (m_rects.isEmpty()) return;

    // 将 QRectF 转换为归一化坐标(-1 到 1)
    QVector<float> vertices;
    for (const QRectF &rect : m_rects) {
        float x1 = (rect.x() / m_videoSize.width()) * 2 - 1;
        float y1 = 1 - (rect.y() / m_videoSize.height()) * 2;
        float x2 = ((rect.x() + rect.width()) / m_videoSize.width()) * 2 - 1;
        float y2 = 1 - ((rect.y() + rect.height()) / m_videoSize.height()) * 2;

        // 每个矩形由 4 条线段组成(每条线段 2 个点)
        vertices << x1 << y1 << x2 << y1;  // 上边
        vertices << x2 << y1 << x2 << y2;  // 右边
        vertices << x2 << y2 << x1 << y2;  // 下边
        vertices << x1 << y2 << x1 << y1;  // 左边
    }

    // 更新 VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_rectVBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), 
                 vertices.constData(), GL_DYNAMIC_DRAW);
}

‌渲染主循环

void VideoGLWidget::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT);

    // 渲染视频帧
    if (m_videoTexture) {
        m_videoShader->bind();
        m_videoTexture->bind();

        // 顶点坐标和纹理坐标(全屏四边形)
        static const GLfloat vertexData[] = {
            -1.0f, -1.0f, 0.0f, 0.0f,
             1.0f, -1.0f, 1.0f, 0.0f,
            -1.0f,  1.0f, 0.0f, 1.0f,
             1.0f,  1.0f, 1.0f, 1.0f
        };

        // 设置顶点属性
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), vertexData);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), vertexData + 2);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);

        // 绘制全屏四边形
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        m_videoShader->release();
    }

    // 渲染动态矩形框
    if (!m_rects.isEmpty()) {
        m_rectShader->bind();
        glBindBuffer(GL_ARRAY_BUFFER, m_rectVBO);

        // 设置颜色(红色,50%透明度)
        m_rectShader->setUniformValue("color", QVector4D(1.0f, 0.0f, 0.0f, 0.5f));

        // 设置顶点属性
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(0);

        // 绘制线段(每个矩形 4 条边,每条边 2 个顶点)
        glLineWidth(2.0f);
        glDrawArrays(GL_LINES, 0, m_rects.size() * 8); // 4边 * 2点 = 8点/矩形

        m_rectShader->release();
    }
}

 使用示例

// 在主窗口或控制器中
void MainWindow::onNewVideoFrame(const QImage &frame) {
    m_videoWidget->updateVideoFrame(frame);
}

void MainWindow::onDetectionResult(const QList<QRectF> &rects) {
    m_videoWidget->updateRects(rects);
}

异步纹理上传,避免在主线程阻塞:

// 在单独线程处理视频解码
void DecoderThread::run() {
    while (running) {
        QImage frame = decodeFrame();
        QMetaObject::invokeMethod(m_videoWidget, "updateVideoFrame", 
                                 Qt::QueuedConnection, Q_ARG(QImage, frame));
    }
}

三、实例代码

1、视频画面暂时使用图片纹理代替,矩形框支持OPianter和OpenGL方式。效果:

2、 工程代码

QOpenGLWidget绘制框代码下载

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

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

相关文章

Linux系统加固笔记

检查口令为空的账户 判断依据&#xff1a;存在则不符合 特殊的shell a./bin/false:将用户的shell设置为/bin/false&#xff0c;用户会无法登录&#xff0c;并且不会有任何提示信息b./sbib/nologin&#xff1a;nologin会礼貌的向用户发送一条消息&#xff0c;并且拒绝用户登录…

【Go万字洗髓经】Golang中sync.Mutex的单机锁:实现原理与底层源码

本章目录 1. sync.Mutex锁的基本用法2. sync.Mutex的核心原理自旋到阻塞的升级过程自旋CAS 饥饿模式 3. sync.Mutex底层源码Mutex结构定义全局常量Mutex.Lock()方法第一次CAS加锁能够成功的前提是&#xff1f;竞态检测 Mutex.lockSlow()lockSlow的局部变量自旋空转state新值构造…

Django REST framework 源码剖析-认证器详解(Authentication)

Django REST framework 源码剖析-认证器详解(Authentication) 身份验证始终在视图的最开始运行&#xff0c;在权限和限制检查发生之前&#xff0c;以及在允许任何其他代码继续之前。request.user属性通常设置为contrib.auth包的user类的实例。request.auth属性用于任何其他身份…

TCP/IP三次握手的过程,为什么要3次?

一&#xff1a;过程 第一次&#xff08;SYN&#xff09;&#xff1a; 客户端发送一个带有SYN标志的TCP报文段给服务器&#xff0c;设置SYN1&#xff0c;并携带初始序列号Seqx&#xff08;随机值&#xff09;&#xff0c;进入SYN_SENT状态。等待服务器相应。 第二次&#xff08…

Centos6安装nerdctl容器运行时

Centos6安装nerdctl容器运行时 前言Centos6安装docker---失败--不可拉取镜像docker配置国内镜像加速 Centos6安装nerdctl-full容器管理工具为Centos6配置containerd服务开机自启动设置nerdctl自动补全 前言 本文写于2025年3月22日,因一些特殊业务需要用到Centos6Docker,但Cent…

登录验证码的接口实习,uuid,code.

UID是唯一标识的字符串,下面是百度百科关于UUID的定义&#xff1a; UUID是由一组32位数的16进制数字所构成&#xff0c;是故UUID理论上的总数为16322128&#xff0c;约等于3.4 x 10^38。也就是说若每纳秒产生1兆个UUID&#xff0c;要花100亿年才会将所有UUID用完。 UUID的标准…

用fofa语法搜索漏洞

FOFA是一款非常强大的搜索引擎 关于对于fofa的描述是&#xff1a;FOFA&#xff08;网络空间资产检索系统&#xff09;是世界上数据覆盖更完整的IT设备搜索引擎&#xff0c;拥有全球联网IT设备更全的DNA信息。 探索全球互联网的资产信息&#xff0c;进行资产及漏洞影响范围分析…

20242817李臻《Linux⾼级编程实践》第6周

20242817李臻《Linux⾼级编程实践》第6周 一、AI对学习内容的总结 Linux进程间通信&#xff08;IPC&#xff09; 1. 进程间通信基本概念 作用: 数据传输&#xff1a;进程间传递数据&#xff08;字节到兆字节级别&#xff09;。共享数据&#xff1a;多个进程操作同一数据&…

深入解析SQL2API平台:数据交互革新者

在数字化转型持续深入的当下&#xff0c;企业对数据的高效利用与管理的需求愈发迫切。SQL2API平台应运而生&#xff0c;成为助力企业突破数据交互困境的有力工具&#xff0c;特别是它由麦聪软件基于DaaS&#xff08;数据即服务&#xff09;产品创新衍生而来&#xff0c;备受业界…

Java 中装饰者模式与策略模式在埋点系统中的应用

前言 在软件开发中&#xff0c;装饰者模式和策略模式是两种常用的设计模式&#xff0c;它们在特定的业务场景下能够发挥巨大的作用。本文将通过一个实际的埋点系统案例&#xff0c;探讨如何在 Java 中运用装饰者模式和策略模式&#xff0c;以及如何结合工厂方法模式来优化代码…

无人设备遥控器之调度自动化技术篇

一、技术原理 信息采集与处理&#xff1a; 通过传感器、仪表等设备采集无人设备的各种数据&#xff0c;如位置、速度、状态等。 将采集到的数据传输到调度自动化系统中进行处理和分析&#xff0c;以获取设备的实时状态。 系统建模与优化&#xff1a; 调度自动化系统会根据…

【AI】Orin Nano+ubuntu22.04上移植YoloV11,并使用DeepStream测试成功

【AI】郭老二博文之:AI学习目录汇总 1、准备工作 使用 sdk-manager 烧写 OrinNano, JetPack版本为6.0 DP,对应操作系统为:Ubuntu22.04 参见博客:【NVIDIA】Jetson Orin Nano系列:烧写Ubuntu22.04 2、安装 PyTorch 2.1 下载依赖 1)安装onnx pip install onnx -i h…

K8S学习之基础四十五:k8s中部署elasticsearch

k8s中部署elasticsearch 安装并启动nfs服务yum install nfs-utils -y systemctl start nfs systemctl enable nfs.service mkdir /data/v1 -p echo /data/v1 *(rw,no_root_squash) >> /etc/exports exports -arv systemctl restart nfs创建运行nfs-provisioner需要的sa账…

如何在 Windows 上安装并使用 Postman?

Postman 是一个功能强大的API测试工具&#xff0c;它可以帮助程序员更轻松地测试和调试 API。在本文中&#xff0c;我们将讨论如何在 Windows 上安装和使用 Postman。 Windows 如何安装和使用 Postman 教程&#xff1f;

Axure RP 9.0教程: 基于动态面板的元件跟随来实现【音量滑块】

文章目录 引言I 音量滑块的实现步骤添加底层边框添加覆盖层基于覆盖层创建动态面板添加滑块按钮设置滑块拖动效果引言 音量滑块在播放器类APP应用场景相对较广,例如调节视频的亮度、声音等等。 I 音量滑块的实现步骤 添加底层边框 在画布中添加一个矩形框:500 x 32,圆…

WPF 与 C# 开发深度剖析

一、引言 在当今的软件开发领域&#xff0c;Windows 平台依旧占据着重要的地位。而 WPF&#xff08;Windows Presentation Foundation&#xff09;作为微软推出的一款强大的用户界面&#xff08;UI&#xff09;框架&#xff0c;为开发者提供了丰富的功能和灵活的设计方式&…

好消息!软航文档控件(NTKO WebOffice)在Chrome 133版本上提示扩展已停用的解决方案

软航文档控件现有版本依赖Manifest V2扩展技术支持才能正常运行&#xff0c;然而这个扩展技术到2025年6月在Chrome高版本上就彻底不支持了&#xff0c;现在Chrome 133开始的版本已经开始弹出警告&#xff0c;必须手工开启扩展支持才能正常运行。那么如何解决这个技术难题呢&…

通过仿真确定抗积分饱和策略的最佳系数

通过仿真确定抗积分饱和策略的最佳系数&#xff08;如PID参数 ( K_p, K_i, K_d ) 以及抗饱和参数 ( K_{\text{back}} )、积分限幅值等&#xff09;是一个系统化的过程。以下是具体步骤和示例&#xff1a; — 1. 建立仿真模型 1.1 模型组成 被控对象&#xff1a;例如电机、温…

消息队列(Kafka及RocketMQ等对比联系)

目录 消息队列 一、为什么使用消息队列&#xff1f;消息队列有什么优点/缺点&#xff1f;介绍下Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优点缺点&#xff0c;如何取舍&#xff1f; 1.公司业务场景是什么&#xff0c;这个业务场景有什么挑战&#xff0c;如果不用MQ有什么麻…

GitHub开源的容器管理面板-Dpanel

dpanel Docker安装部署二进制部署 GitHub官网 一块轻量化docker可视化管理面板&#xff0c;由国人开发&#xff0c;个人觉得是比较好用的&#xff0c;功能都很齐全&#xff0c;并且可以通过修改源码&#xff0c;自定义前端样式等。 Docker安装部署 官网 部署环境&#xff1…