一、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绘制框代码下载