qt+opengl高效显示opencv读取的视频图像

qt+opengl高效显示opencv读取的视频图像

  • 1.摘要
    qt显示cv::Mat的数据,常用方法为将Mat转换为QImage后再用QLabel的setPixmap方法显示。类似这样:
cv::Mat mat;
QImage qImage;
switch (mat.type())
{
case CV_8UC1:
    qImage = QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8);
case CV_8UC3:
	qImage = QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888).rgbSwapped();
}
ui->label.setPixmap(QPixmap::fromImage(qImage));

该方法用作简单的图片显示还行,当需要高效显示视频的每一帧图像时,存在效率不足的问题。我们想到用opengl的绑定纹理的方法绘制图像,可以高效渲染cv::Mat图像。

  • 2.opencv读取视频
    • 2.1 准备工作,安装好opencv,资源中有open-mingw的版本
    • 2.2 将视频文件读取到std::vector中
   std::vector<cv::Mat> image_vector_;
   std::string video_path = "C:/Users/Administrator/Desktop/test.mp4";
   if (!capture.open(video_path))
   {
       std::cout << "open video error!" << std::endl;
   }
   else
   {
       int total_frames = capture.get(cv::CAP_PROP_FRAME_COUNT);
       std::cout << "video frame conut: " << total_frames << std::endl;
       image_vector_.reserve(total_frames);
       cv::Mat frame;
       for (int i = 0; i < total_frames; i++)
       {
           capture.read(frame);
           if (!frame.empty()) {
               image_vector_.push_back(frame.clone());
           }
       }
   }
  • 3.opengl显示cv::Mat
    因为需要绘制的图形比较简单,采用opengl的立即渲染模式。
    关于opengl学习可参考 LearnOpenGL
    关键代码如下:
void RGBDisplay::initializeGL()
{
   initializeOpenGLFunctions();
   glGenTextures(1, &texture_rgb_);
   glBindTexture(GL_TEXTURE_2D, texture_rgb_);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void RGBDisplay::paintGL()
{
   // utils::DeviceTimer d_t;
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

   // 设置投影矩阵(立即模式需要手动设置)
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); // 2D 正交投影

   // 更新纹理数据
   std::lock_guard<std::mutex> lock(mutex_draw_);
   glBindTexture(GL_TEXTURE_2D, texture_rgb_);
   glTexImage2D(
       GL_TEXTURE_2D, 0, GL_RGB,
       image_width_, image_height_, 0,
       image_is_grayscale_ ? GL_RED : (image_is_bgr_ ? GL_BGR : GL_RGB),
       GL_UNSIGNED_BYTE, image_rgb_.data());

   // 立即模式绘制四边形
   glEnable(GL_TEXTURE_2D);
   glBegin(GL_QUADS);
   glTexCoord2f(0.0f, 1.0f);
   glVertex2f(-1.0f, -1.0f); // 左下角
   glTexCoord2f(1.0f, 1.0f);
   glVertex2f(1.0f, -1.0f); // 右下角
   glTexCoord2f(1.0f, 0.0f);
   glVertex2f(1.0f, 1.0f); // 右上角
   glTexCoord2f(0.0f, 0.0f);
   glVertex2f(-1.0f, 1.0f); // 左上角
   glEnd();
   glDisable(GL_TEXTURE_2D);
   // std::cout << "paintGL: " << d_t.getUsedTime() << std::endl;
}

注意:在qt ui编辑器中新建一个QWidget,并提升为RGBDisplay类。

  • 4.qtimer触发窗口刷新
    从std::vector(cv::Mat)中读取视频的图像数据,可采用Qtimer定时触发QOpenGLWidget窗口的刷新操作。
    ​在主线程中,timeout槽函数更新ui,槽函数中涉及到界面重绘,耗时可能达数十毫秒。若定时器间隔(1ms)远小于UI操作耗时,事件循环会堆积未处理的定时器事件,导致实际触发间隔大幅增加。导致显示视频图像变慢。例如:
void MainWindow::TimerEvent()
{
    if (!image_vector_.empty()) {
        int i = index % image_vector_.size();
        ui->widget_1->SetImage(image_vector_.at(i));
        index++;
        ui->frame_label->setText(QString::number(index));
    }
}

假设timer的间隔为1ms,因为time的槽函数中涉及到界面刷新,timer实际触发时间肯定大于1ms。这时需要用c++的回调函数来触发ui的更新操作。

  • 5.回调触发窗口刷新
    • 5.1 开一个thread,在线程Loop函数中调用函数,不断触发widget的绘制操作。
    • 5.2 绑定MainWindow的showImage函数到thread。
      关键代码如下:
class QImageThread
{
public:
   void ThreadStart();
   void ThreadStop();
   void ThreadLoop();
   void SetCallback(std::function<void(const uint8_t *)> callback);
   void HandleData(const uint8_t *data);

private:
   std::atomic<bool> thread_running_{false};
   std::thread thread_handle_;
   std::function<void(const uint8_t *)> ns_callback_;
};

MainWindow::MainWindow(QWidget *parent)
   : QMainWindow(parent)
   , ui(new Ui::MainWindow)
{
   image_thread_.SetCallback(std::bind(&MainWindow::showImage, this, std::placeholders::_1));
}

void QImageThread::SetCallback(std::function<void(const uint8_t *)> callback)
{
   ns_callback_ = callback;
}

void QImageThread::HandleData(const uint8_t *data)
{
   if (ns_callback_)
       ns_callback_(data);
}

界面刷新由timer触发修改为回调函数触发后,可以发现视频播放的速度变得非常快,我们可以同时开多个widget去渲染都完全没问题!
在这里插入图片描述

  • 6.总结
    以上介绍了通用的qt显示cv::Mat方法效率不足,优化为opengl绘制cv::Mat、并分别用timer和回调函数触发ui更新操作。希望遇到类似问题的童鞋可以参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值