如何将OpenCV的cv::Mat转换为QImage

52
我想知道如何将OpenCV C++的标准cv::Mat类型转换为QImage。我已经搜索了一些资料,但没有成功。我找到了一些将IPlimage转换为QImage的代码,但那不是我想要的。谢谢。
11个回答

47

Michal Kottman的回答对于某些图像是有效的并且可以给出期望的结果,但在某些情况下会失败。以下是我发现的解决方法。

QImage imgIn= QImage((uchar*) img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);

区别在于添加img.step部分。如果没有这个部分,qt不会抱怨,但某些图片可能无法正确显示。希望这可以帮助。


7
这基本上是OpenCV在内部使用的转换(http://code.opencv.org/projects/opencv/repository/revisions/df262b340cbe1b362977573bb34138d837f79648/entry/modules/highgui/src/window_QT.cpp,第2389行): image2Draw_qt = QImage(image2Draw_mat-> data.ptr, image2Draw_mat-> cols,image2Draw_mat-> rows,image2Draw_mat-> step,QImage :: Format_RGB888); 还有(大约在2400行)cvConvertImage(mat,image2Draw_mat,CV_CVTIMG_SWAP_RB); 用于从BGR转换为RGB。 - Nolan Amy
2
img.step起到了至关重要的作用。我在进行转换时遇到了一些奇怪的问题,包括生成的图像显示扭曲和颜色错误。经过一些测试,我得到了相同的代码。 - MeloMCR

32

要将cv::Mat转换为QImage,您可以尝试使用如下所示的QImage(uchar * data, int width, int height, Format format)构造函数(其中mat是一个cv::Mat):

QImage img((uchar*)mat.data, mat.cols, mat.rows, QImage::Format_RGB32);

使用该方法比手动将像素转换为QImage更有效率,但您需要在内存中保留原始的cv::Mat图像。可以轻松将其转换为QPixmap并使用QLabel进行显示:

QPixmap pixmap = QPixmap::fromImage(img);
myLabel.setPixmap(pixmap);

更新

由于OpenCV默认使用BGR顺序,因此您应该先使用cvtColor(src, dst, CV_BGR2RGB)来获取Qt可以理解的图像布局。

更新2:

如果您尝试显示的图像具有非标准的步幅(当它是非连续、子矩阵时),则该图像可能会出现扭曲。在这种情况下,最好使用cv::Mat::step1()显式地指定步幅:

QImage img((uchar*)mat.data, mat.cols, mat.rows, mat.step1(), QImage::Format_RGB32);

这在一般情况下是行不通的。如果mat有超过1个通道或数据格式不是RGB32,怎么办? - Hristo Hristov
2
嗯,是的,它也可以有多个维度,或者可以具有不同的“步长”。我预计Hien想要显示“标准”的cv :: Mat,即由imread加载或转换为适当类型。 - Michal Kottman
@Michael 是的,那就是我想要的。一旦我有时间处理我的项目,我会试用你的代码。=) - Hien
1
OpenCV使用BGR通道顺序。 - etarion
1
在创建完qimage之后,您还可以将其转换为正确的RGB顺序,例如:qt_im_rgb = qt_im_bgr.rgbSwapped(); - ejectamenta
1
根据QImage文档:数据必须是32位对齐的,因此Mat应该实现这一点,特别是当Mat类型为CV_8UC3即24位时,下面的@ypnos方法更加通用和安全。 - roberto

30

这里是24位RGB和灰度浮点数的代码。可轻松调整为其他类型。它尽可能高效。

QImage Mat2QImage(const cv::Mat3b &src) {
        QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
        for (int y = 0; y < src.rows; ++y) {
                const cv::Vec3b *srcrow = src[y];
                QRgb *destrow = (QRgb*)dest.scanLine(y);
                for (int x = 0; x < src.cols; ++x) {
                        destrow[x] = qRgba(srcrow[x][2], srcrow[x][1], srcrow[x][0], 255);
                }
        }
        return dest;
}


QImage Mat2QImage(const cv::Mat_<double> &src)
{
        double scale = 255.0;
        QImage dest(src.cols, src.rows, QImage::Format_ARGB32);
        for (int y = 0; y < src.rows; ++y) {
                const double *srcrow = src[y];
                QRgb *destrow = (QRgb*)dest.scanLine(y);
                for (int x = 0; x < src.cols; ++x) {
                        unsigned int color = srcrow[x] * scale;
                        destrow[x] = qRgba(color, color, color, 255);
                }
        }
        return dest;
}

1
这回答了我即将发布的一个问题,关于将浮点灰度值转换为 QImage...谢谢! - Nathan Moos
1
当从原始数据加载时,这解决了QImage 32位对齐要求的问题。 - roberto

14

默认情况下,OpenCV将图像加载到Mat中的蓝绿红(BGR)格式中,而QImage则期望RGB格式。这意味着如果您将一个Mat转换为QImage,则蓝色和红色通道将被交换。为了解决这个问题,在构造QImage之前,您需要通过cvtColor方法使用参数CV_BGR2RGB来将Mat的BRG格式更改为RGB格式,如下所示:

Mat mat = imread("path/to/image.jpg");
cvtColor(mat, mat, CV_BGR2RGB);
QImage image(mat.data, mat.cols, mat.rows, QImage::Format_RGB888);

或者在 QImage 上使用 rgbSwapped()

QImage image = QImage(mat.data, mat.cols, mat.rows, QImage::Format_RGB888).rgbSwapped());

4
    Mat opencv_image = imread("fruits.jpg", CV_LOAD_IMAGE_COLOR); 
    Mat dest;
    cvtColor(opencv_image, dest,CV_BGR2RGB);
    QImage image((uchar*)dest.data, dest.cols, dest.rows,QImage::Format_RGB888);

以下是我成功的做法。我修改了 Michal Kottman 上面提供的代码。


4

我和你一样也有同样的问题,所以我开发了四个函数来缓解我的痛苦,它们分别是

QImage mat_to_qimage_cpy(cv::Mat const &mat, bool swap = true);

QImage mat_to_qimage_ref(cv::Mat &mat, bool swap = true);

cv::Mat qimage_to_mat_cpy(QImage const &img, bool swap = true);

cv::Mat qimage_to_mat_ref(QImage &img, bool swap = true);

这些函数可以处理具有1、3、4个通道的图像,每个像素必须只占用一个字节(CV_8U->Format_Indexed8,CV_8UC3->QImage::Format_RGB888,CV_8UC4->QImage::Format_ARGB32),我尚未处理其他类型(QImage::Format_RGB16,QImage::Format_RGB666等)。代码位于Github
将mat转换为QImage的**关键概念**是:
/**
 * @brief copy QImage into cv::Mat
 */
struct mat_to_qimage_cpy_policy
{
    static QImage start(cv::Mat const &mat, QImage::Format format)
    {
       //The fourth parameters--mat.step is crucial, because 
       //opencv may do padding on every row, you need to tell
       //the qimage how many bytes per row 
       //The last thing is if you want to copy the buffer of cv::Mat
       //to the qimage, you need to call copy(), else the qimage
       //will share the buffer of cv::Mat
       return QImage(mat.data, mat.cols, mat.rows, mat.step, format).copy();
    }
};

struct mat_to_qimage_ref_policy
{
    static QImage start(cv::Mat &mat, QImage::Format format)
    {
       //every thing are same as copy policy, but this one share
       //the buffer of cv::Mat but not copy
       return QImage(mat.data, mat.cols, mat.rows, mat.step, format);
    }
};

cv::Mat 转换为 Qimage 的关键概念包括:

/**
 * @brief copy QImage into cv::Mat
 */
struct qimage_to_mat_cpy_policy
{
    static cv::Mat start(QImage const &img, int format)
    {
       //same as convert mat to qimage, the fifth parameter bytesPerLine()
       //indicate how many bytes per row
       //If you want to copy the data you need to call clone(), else QImage
       //cv::Mat will share the buffer
       return cv::Mat(img.height(), img.width(), format, 
                      const_cast<uchar*>(img.bits()), img.bytesPerLine()).clone();
    }
};

/**
 * @brief make Qimage and cv::Mat share the same buffer, the resource
 * of the cv::Mat must not deleted before the QImage finish
 * the jobs.
 */
struct qimage_to_mat_ref_policy
{
    static cv::Mat start(QImage &img, int format)
    {
       //same as copy policy, but this one will share the buffer 
       return cv::Mat(img.height(), img.width(), format, 
                      img.bits(), img.bytesPerLine());
    }
};

如果有人可以扩展这些函数并使它们支持更多类型,那将是很不错的。如果有任何错误,请告诉我。

2
这篇文章(链接)展示了如何将 QImage 转换为OpenCV的 IplImage ,反之亦然。
接着,如果您需要帮助在 IplImage * cv :: Mat 之间进行转换:
// Assume data is stored by: 
// IplImage* image;

cv::Mat mat(image, true); // Copies the data from image

cv::Mat mat(image, false); // Doesn't copy the data!

这是一种取巧的方法,但可以完成工作。

2

cv::Mat有一个转换运算符可以将其转换为IplImage,因此如果你有一些将IplImage转换为QImage的东西,只需使用它(或进行 - 可能是次要的 - 调整以直接使用cv::Mat,内存布局相同,只是头文件不同)。


我正在进行的项目需要将图像打印到QtGui上,但我大部分时间都在处理cv::Mat中的图像。我询问的原因是我的程序将进行大量计算,因此我想摆脱将cv::Mat转换为IplImage,然后再转换为qimage的开销。这是我第一个图像处理项目,所以我不太了解图像中的数据。一旦我深入研究该项目,我很快就会学习到这方面的知识。无论如何,如果我找不到任何东西,那么我将遵循您的建议=) - Hien
请问您能分享IplImage -> QImage转换的代码吗?我有兴趣进行必要的调整以将其转换为cv :: Mat。 - Hristo Hristov
@Hien:与进行图像处理时所做的所有其他操作相比,将cv::Mat转换为IplImage的开销完全可以忽略不计。这里没有涉及到数据复制。 - etarion

1
使用静态函数convert16uc1来处理深度图像:
QPixmap Viewer::convert16uc1(const cv::Mat& source)
{
  quint16* pSource = (quint16*) source.data;
  int pixelCounts = source.cols * source.rows;

  QImage dest(source.cols, source.rows, QImage::Format_RGB32);

  char* pDest = (char*) dest.bits();

  for (int i = 0; i < pixelCounts; i++)
  {
    quint8 value = (quint8) ((*(pSource)) >> 8);
    *(pDest++) = value;  // B
    *(pDest++) = value;  // G
    *(pDest++) = value;  // R
    *(pDest++) = 0;      // Alpha
    pSource++;
  }

  return QPixmap::fromImage(dest);
}

QPixmap Viewer::convert8uc3(const cv::Mat& source)
{
  quint8* pSource = source.data;
  int pixelCounts = source.cols * source.rows;

  QImage dest(source.cols, source.rows, QImage::Format_RGB32);

  char* pDest = (char*) dest.bits();

  for (int i = 0; i < pixelCounts; i++)
  {
    *(pDest++) = *(pSource+2);    // B
    *(pDest++) = *(pSource+1);    // G
    *(pDest++) = *(pSource+0);    // R
    *(pDest++) = 0;               // Alpha
    pSource+=3;
  }

  return QPixmap::fromImage(dest);
}

QPixmap Viewer::convert16uc3(const cv::Mat& source)
{
  quint16* pSource = (quint16*) source.data;
  int pixelCounts = source.cols * source.rows;

  QImage dest(source.cols, source.rows, QImage::Format_RGB32);

  char* pDest = (char*) dest.bits();

  for (int i = 0; i < pixelCounts; i++)
  {
    *(pDest++) = *(pSource+2);    // B
    *(pDest++) = *(pSource+1);    // G
    *(pDest++) = *(pSource+0);    // R
    *(pDest++) = 0;               // Alpha
    pSource+=3;
  }

  return QPixmap::fromImage(dest);
}

0
如果你想重复使用 mat 的数据指针而不是复制所有的数据,你可以使用以下代码:
cv::Mat mat // defined elsewhere

QImage image(mat.data, mat.cols, mat.rows, QImage::Format_YOUR_FORMAT);

mat.deallocate(); // <-- this 'removes' the pointer from mat

我们使用cv::Mat::deallocate函数,这会导致cv::Mat在被删除时不会删除底层数据。
因此,如果您将QImage保存为类成员或从函数返回它,您通常需要从Mat复制数据,因为在堆栈结束后,它将删除后面的数组。
Deallocate有点像“移除”指针,使得它不会删除我们分配给QImage的数据(否则QImage将指向已释放的内存)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接