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更新操作。希望遇到类似问题的童鞋可以参考。
3764

被折叠的 条评论
为什么被折叠?



