Imagelab-0-QT label显示 opencv 图像

Imagelab-0-QT label显示 opencv 图像

opencvc++qtimagelab

开始之前

这其实也是opencv 处理图像的系列, 只是想我们在进一步复杂化我们的代码之前, 每次给出代码我们都要给出很多, 然后窗口的显示上也有很多不必要的东西, 我们为了后面进行更好的算法效果以及算法执行, 我们先规划一下程序, 写出来一个界面程序出来, 这样的话, 我们之后的程序部分只需要给出一个函数的部分就好, 我们的程序算法在增加的时候, 将功能做到一个一个的菜单里面来, 这样一边处理算法, 一边写出界面图像..

目录

正文

我们主要将图形界面部分使用代码来实现, 这样不需要进行编译便能够大概知道结果..

我们在进行复杂的界面之前, 我们先实现一个简单的工程, 能够使用 opencv 读取图片, 然后显示在 qt 的 label 控件 上面,

QT 图像格式

在qt 中提供了几种图像显示的方式,可以看这篇文章关于QPixmap/QImage/QPicture, 详细的介绍了几种格式的使用方法,
QT自带的 QImageQPixmap, 都是支持读取图像的,可以直接用于显示图像, 但是呢, 我们后续还要进行复杂的算法实现, 所以我们还是要转回到 opencv 的怀抱中来, 那么我们不可避免的需要进行数据图像格式之间的互相转换, 目前大多说使用的方式都是 opencv的 Mat 格式与 QT QPixmap 格式之间的转换, 按后显示到 QT 的label 上面, 我们先来实现一下:

UI 界面设计

这里稍微提一下 QT Designer, 我们可以通过托拽的方式实现界面的设计, 也提供了很多组件让我们选择, 我们先暂时使用这种比较简单的方式进行, 后面逐渐介绍更为复杂的操作.

Imagelab-0-QT label显示 opencv 图像
QT UI 设计

这里我们使用数字 1,2,3,4, 标记了四个区域, 就是我们常用的区域了

    1. 编辑区域, 可以编辑与托拽, 能够预览
    1. 控件结构树, 各个控件的从属结构, 名称就是 ObjectName 能够在程序使用控件名进行操控
    1. 属性区域,能够直接调整相关的参数, 也可以在程序中进行调整各种属性
    1. 控件区域, 不同种类的控件, 可以用于托拽, 直接显示在窗口中..

具体的实现方式不用去深究, 且通过托拽改变 .ui 文件, 实际上就是 一个 xml 格式的文件, QT 通过 uic 会将 xxx.ui 转换成 ui_xxx.h 文件, 我们通过引用即可直接操控控件了,

如果我们改变了ui, 但是运行之后没有更新, 在工程山强制 qmake 一下就能解决了

在我们这个工程中, 我们托拽了两个 QLabel 组件和两个 QPushButton 组件, 相应的可以在上图的2 区域看到对象名称..

  • MainWindow:
    • geometry: 0,0,960,540 : 我们运行的窗口尺寸
    • windowsTitle: "ImageLab"
  • lb_src:
    • geometry: 20,30,400,400 用于指定控件的左上角位置和 尺寸宽高, 我们使用这个参数指定即可
    • frameShape: WinPanel
  • lb_dst:
    • geometry: 470,30,400,400 用于指定控件的左上角位置和 尺寸宽高, 我们使用这个参数指定即可
    • frameShape: WinPanel
  • btn_test1,btn_test2: 都是默认托拽的 , 尺寸默认, 位置 随意就好, 后面用于我们进行一下测试算法 暂时忽略
  • pt_log: 多行文本, 用于显示一些结果信息, 测试过程中的一些输出

我们这个界面也没有布局, 就是很简单的把东西给显示出来, 在编辑之后 按 Shift+Alt+R能够预览界面,
如果有布局之类的需要及时查看, 我们这里就是简单的ui , 布局什么样 得到的就是什么样子

Imagelab-0-QT label显示 opencv 图像
预览 UI 界面

我们后面的测试可能就是左边显示原始图像, 右边显示运算之后 的图像, 我们来实现一下

这里关于 ui界面的设计 只是稍微提一下, 你们可以直接查看其他的文章介绍的使用方法, 简单点的可以看使用Qt Designer来设计界面使用Qt Designer创建界面

信号与槽 实现 UI点击事件

在我们进行显示图像之前, 我们稍微介绍一下 QT 的信号与槽的实现方式, QT 最NB 的地方实现了信号与槽 , 简单理解就是, 我们提前将信号与一个槽(函数)声明连接, 然后我们点击一个按钮 会发射一个信号, 然后经过QT的信号处理机制 就能够调用我们提前设定的函数了,

PS: 只是粗略 的这么看就行, 具体还要复杂很多, 后面再说

我们简单实现一下 这个功能, 点击输出我们点击可哪个按钮..
我们点击 测试按钮1: btn_test1调用一个函数 testFunc1, 然后在结果框输出点击了按钮1 ,

Imagelab-0-QT label显示 opencv 图像
点击输出效果预览

,我们只看 核心的代码部分

// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
   : QMainWindow(parent)
   , ui(new Ui::MainWindow)
{
   ui->setupUi(this);

   // 设定信号与槽 连接
   connect(ui->btn_test1,&QPushButton::clicked,this,&MainWindow::testFunc1);
   connect(ui->btn_test2,&QPushButton::clicked,this,&MainWindow::testFunc2);

   // 初始化 ui
   ui->pt_log->clear();  // 清除框内输出
}

MainWindow::~MainWindow()
{
   delete ui;
}

void MainWindow::testFunc1(void)
{
   ui->pt_log->appendPlainText("你点击了 测试按钮 1 ");
}

void MainWindow::testFunc2(void)
{
   ui->pt_log->appendPlainText("你点击了 测试按钮 2");
} 


// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
   Q_OBJECT

public slots:
   void testFunc1(void);
   void testFunc2(void);

public:
   MainWindow(QWidget *parent = nullptr);
   ~MainWindow();

private:
   Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

// main.cpp
#include "mainwindow.h"
#include <QApplication>

// 运行主窗口 用于显示界面 ui
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MainWindow w;
   w.show();
   return a.exec();
}

这里可以看 代码仓库 SChen1024/ImageLab V0.1.0

我们的程序一直是同步提交到 github和gitee 的, 有什么问题可以去看代码

QImage 和 QPixmap 显示图像

上面就是在简单的测试一下, 那我们 现在就开始正式的工作 首先看下直接读取文件的方式,
我们将上一节中 输出语句的函数部分换成加载图片, 能够得到下面的函数部分, 进而运行就能够得到结果图

// 图片路径
QString lena_img = "../testimages/lena.png";
void MainWindow::testFunc1(void)
{
    QPixmap pixmap;
    pixmap.load(lena_img);

    ui->lb_src->setPixmap(pixmap);
    ui->pt_log->appendPlainText("左侧使用 QPixmap load 图像数据1 ");
}

void MainWindow::testFunc2(void)
{
    QImage image(lena_img);
    ui->lb_dst->setPixmap(QPixmap::fromImage(image));
    ui->pt_log->appendPlainText("右侧使用 QImage 转换成 QPixmap 进行显示2 ");
}

其实 label 只能显示 pixmap 图像, 而且十分简单操作, 而 QImage 也是转换成 QPixmap 之后才做的显示, 不过在可以去看QImage与QPixmap加载图片 效果 .中介绍了其他的方式显示图像, 我们就不去深究了,

Imagelab-0-QT label显示 opencv 图像
加载图像显示

QT显示 opencv mat 图像

终于终于到了我们这篇文章的重点了, 其实经过上面的铺垫, 我们opencv 读取图像之后要做的就是 将mat 图像转换成 QImage或者 QPixmap 图像就好了, 多一步转换过程, 目前没有找到 mat 直接转换成 QPixmap 的方式 , 目前的实现都是 转换成 QImage 然后再转换的方式,

直接搜索 opencv Mat 转 QImage 能找到很多结果, 其实呢 原理都很简单, 根据原始图像的通道数目将图像转换成相应的 QImage 格式, 比如3通道的 rgb 图像转换 QImage image(mat.data, mat.cols, mat.rows,static_cast<int>(mat.step),QImage::Format_RGB888); 我们能够得到这样的结果, 很简单就能实现, 获取图像的宽度, 高度, 以及最重要的 data 也就是图像的数据指针, 然后依次转换成我们需要的 QImage 图像即可, 值得注意的是, opencv 是 BGR图像的顺序, 所以最后要进行颜色通道的转换, 转换成 rgb 不然颜色会有点奇怪..

具体的参数可以参考我之前的博文, 关于 mat 的step 属性可以参考OpenCV中Mat属性step,size,step1,elemSize,elemSize1

这里附上 opencv Mat 与QImage 的互相转换, 这里没有使用 if 为了更好看

/**
 * @fn  QImage CvMat2QImage(const cv::Mat & mat)
 *
 * @brief   将opencv mat 转换成 QT image
 *
 * @author  IRIS_Chen
 * @date    2019/12/19
 *
 * @param   mat The matrix
 *
 * @return  A QImage
 */
QImage CvMat2QImage(const cv::Mat &mat)
{
    // 图像的通道
    int channel = mat.channels();

    // 设立一个表 直接查询 其中 0 2 是无效值 1 3 4 对应的转换值
    const std::map<int, QImage::Format> img_cvt_map {
        { 1, QImage::Format_Grayscale8 },
        { 3, QImage::Format_RGB888 },
        { 4, QImage::Format_ARGB32 }
    };

    QImage image(mat.data, mat.cols, mat.rows,
                 static_cast<int>(mat.step),
                 img_cvt_map.at(channel));

    // 三通道图像 值做 通道转换
    return channel == 3 ? image.rgbSwapped() : image;
}

/**
* @fn  static cv::Mat QImage2CvMat(const QImage &image);
*
* @brief   QT Image 转换成 cv Mat 结构
*
* @author  IRIS_Chen
* @date    2019/12/19
*
* @param   image   The image
*
* @return  A cv::Mat
*/
cv::Mat QImage2CvMat(const QImage &image)
{
    cv::Mat mat;
    const std::map<QImage::Format, int> img_cvt_map{
        { QImage::Format_Grayscale8, 1 },
        { QImage::Format_RGB888, 3 },
        { QImage::Format_ARGB32, 4}
    };

    return cv::Mat(image.height(), image.width(),img_cvt_map.at(image.format()));
}

为了便于区分, 我们在处理图像的时候, 在图上分别显示一个字符串,

// 图片路径
QString lena_img = "../testimages/lena.png";
void MainWindow::testFunc1(void)
{
    QPixmap pixmap;
    pixmap.load(lena_img);

    // 在图上绘制文字
    QPainter painter(&pixmap);
    painter.setPen(QColor(Qt::yellow));
    painter.drawText(100,100,"QT QPixmap");


    ui->lb_src->setPixmap(pixmap);
    ui->pt_log->appendPlainText("左侧使用 QPixmap load 图像数据1 ");
}

void MainWindow::testFunc2(void)
{
    cv::Mat mat = cv::imread("../testimages/lena.png");
    // 在图上显示文字
    cv::putText(mat,"OpenCV Mat",cv::Point(100,100),cv::FONT_HERSHEY_COMPLEX,1.0, cv::Scalar(0, 255, 255));

    QImage image = CvMat2QImage(mat);

    ui->lb_dst->setPixmap(QPixmap::fromImage(image));
    ui->pt_log->appendPlainText("右侧使用 Mat --> QImage --> QPixmap 进行显示2 ");
}

运行得到的结果图片

Imagelab-0-QT label显示 opencv 图像
QT 显示 opencv 图像

opencv 就是 使用 mat 读取图像, 然后 转换成 QImage, 转换通道 ,再转换成 QPixmap 最后显示在 QLabel 上,

其他