一、QPainter绘制
在QOpenGLWidget中可以绘制,并且和OpenGL的内容叠在一起。paintGL里面绘制完视频后,解锁资源,再用QPainter绘制矩形框。这种方式灵活性最好。
void VideoGLWidget::paintGL() {glClear(GL_COLOR_BUFFER_BIT);m_program.bind();//绘制视频数据// 解绑VAOglBindVertexArray(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; // 左边}// 更新 VBOglBindBuffer(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; // 左边}// 更新 VBOglBindBuffer(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绘制框代码下载