资讯专栏INFORMATION COLUMN

cv::Mat转换为QImage错误

VPointer / 2635人阅读

摘要:方法使用另一个构造函数它需要你提供每一行的字节数而不要求对齐,为什么网上的转换方法不使用这个也许大家使用的图片大小都是符号规范的。

使用OpenCV的cv::Mat类转换Qt的QImage发生错误(解决方法在最后)

在学习Qt和OpencCV时遇到一个严重的BUG,Qt和OpenCV的细节就不多赘述了,网络上MatToQImage主要有两种方法
1.手撸for循环遍历Mat构造QImage
2.使用QImage的构造函数
QImage::QImage(uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr)
出于 偷懒 代码复用和精简的目的使用第二种方法,对于常用的24位深RGB图片使用以下方法
qImg= QImage(img.data,img.cols,img.rows,QImage::Format_BGR888);
其中qImg是QImage类型,img是cv::Mat类型,使用Format_BGR888因为cv::Mat内部使用BGR色彩空间。

出现Bug

在使用过程中使用一度十分快乐,但是其中一个项目使用这个语句出现了严重BUG
可以看出是很棘手的内存访问BUG,从右侧的调用堆栈也看不出所以然,甚至BUG在程序的哪一行都不知道,后经过不停的设置断点和逐行调试锁定了BUG,在一行显示图片的代码上

ui.label->setPixmap(QPixmap::fromImage(qImg));

其他项目使用相同的代码没有出现这个BUG,BUG出现的也很诡异:
1.使用其他图片就没有BUG。
2.有时候调试不出现BUG,但显示的图片是错误的颜色条的雪花。
3.将这行代码删去会在其他地方出现BUG。

原因:Mat不提供4字节对齐

在多天不断调试和查阅资料和修改代码后得出结论:cv::Mat不提供4字节对齐。
在Qt官方文档有如下

QImage::QImage(const uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr)
Constructs an image with the given width, height and format, that uses an existing read-only memory buffer, data. The width and height must be specified in pixels, data must be 32-bit aligned, and each scanline of data in the image must also be 32-bit aligned.
The buffer must remain valid throughout the life of the QImage and all copies that have not been modified or otherwise detached from the original buffer. The image does not delete the buffer at destruction. You can provide a function pointer cleanupFunction along with an extra pointer cleanupInfo that will be called when the last copy is destroyed.
If format is an indexed color format, the image color table is initially empty and must be sufficiently expanded with setColorCount() or setColorTable() before the image is used.
Unlike the similar QImage constructor that takes a non-const data buffer, this version will never alter the contents of the buffer. For example, calling QImage::bits() will return a deep copy of the image, rather than the buffer passed to the constructor. This allows for the efficiency of constructing a QImage from raw data, without the possibility of the raw data being changed.
微软翻译:
构建具有给定宽度、高度和格式的图像,该图像使用现有的内存缓冲器、数据。宽度和高度必须以像素表示,数据必须是 32 位对齐,图像中每个数据扫描线也必须是 32 位对齐。
缓冲器必须在 Q 图像的整个生命以及所有未修改或其他副本的整个生命期间保持有效
脱离原来的缓冲。图像不会在销毁时删除缓冲。您可以提供功能指点器清理功能以及额外的指点器清理信息,当最后一个副本被销毁时,这些信息将被调用。
如果格式是索引颜色格式,则图像颜色表最初是空的,在使用图像之前,必须用设置颜色计数 () 或设置彩色表 () 进行充分扩展。

重点来说构建Qimage的数据每一行都要32位即4字节对齐,如何判断Mat是否对齐?可以使用Mat::isContinuous(),查阅资料:如果矩阵元素每行末尾都是连续存储而没有空隙,返回true,否则为false。我使用的图像是1015x745的图片如下图:

由于这是三通道图片,调用Mat::isContinuous(),结果为turn, 显然1015*3字节的行宽而且每一行连续,没有padding,不是4字节对齐的。

错误分析:

代码如下:

    QString imgName = R"(C:/Users/ASUS/OneDrive/document/Resources/fruit.jpeg)";    img = cv::imread(imgName.toStdString());    qImg= QImage(img.data,img.cols, img.rows, QImage::Format_BGR888);    imgShow();

为什么会出现内存访问错误?

由于Qimage对于提供的uchar指针要求每一行都字节对齐(一行只是形象的说法,它在内存中是连续的),由于图片宽1015像素,一行一共3045字节,如果要对齐需要每行补充3字节,Qimage将Mat::data提供的指针视为已经字节对齐,每一行读取3048字节,读取到最后超出了Mat申请的内存空间,所以出现内存访问错误,即使后面的空间其他程序没有使用,除了第一行之外的像素行都会不正确,出现颜色条和雪花,

为什么错误在quint24

quint定义如下

struct quint24 {    quint24() = default;    quint24(uint value)    {        data[0] = uchar(value >> 16);        data[1] = uchar(value >> 8);        data[2] = uchar(value);    }    operator uint() const    {        return data[2] | (data[1] << 8) | (data[0] << 16);    }    uchar data[3];};

quint24的本质是长度为3的uchar数组,推测在QImage内部将每一个像素的指针赋值给quint24中的data成员,然后再逐个访问每个通道的值

为什么其他项目不出现错误?

其他项目使用的图片宽度恰好都是4字节的整数倍,网络上很多图片的尺寸都是规范宽度:1920,1680,1600,1440,1024,800等等,这些都是4的倍数,也是显示器的标准尺寸,4字节对齐的尺寸计算机运行效率更高,这导致这个BUG出现的概率很小

为什么错误不在QImage构造函数上?

根据前面的文档翻译

缓冲器必须在 Qimage的整个生命以及所有未修改或其他副本的整个生命期间保持有效

可知,QImage的这个构造函数使用浅拷贝,不访问数据,而QImage的拷贝构造函数的文档说明

QImage &QImage::operator=(const QImage &image)
Assigns a shallow copy of the given image to this image and returns a reference to this image.
将给定图像的浅副本分配给此图像,并返回此图像的参考。

所以错误的QImage图像在构造和拷贝过程中都不会真正的访问指针中的数据,直到真正使用QImage的时候才出现错误,即使把出现错误位置的代码删去也会在其他地方出现错误。

解决方法

找到问题就好解决了:

方法1.

修改图片尺寸使其行宽恰好4的倍数,如cv::resize(img, img, cv::Size(1000,1000/img.cols*img.rows));或者直接修改图像文件本身。

方法2.

使用另一个构造函数QImage::QImage(uchar *data, int width, int height, qsizetype bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr)

qImg= QImage(img.data,img.cols, img.rows,img.cols*3, QImage::Format_BGR888);

它需要你提供每一行的字节数而不要求对齐,为什么网上的转换方法不使用这个?也许大家使用的图片大小都是符号规范的。

方法3

手撸一个构造对齐后数组的方法。

int colum4 = (img.cols * 3 + 3) / 4 * 4;//cloum4指对齐后的每行字节数 uchar* Array = new uchar[colum4 * img.rows];//申请一个能容量下对齐后矩阵的数组for (size_t i = 0; i < img.rows; i++){    std::copy(img.data + i * img.cols * 3, img.data + (i + 1) * img.cols * 3, Array + colum4 * i);//将img每一行复制到Array的开头,如果不熟悉copy语法的同学需要复习}qImg= QImage(Array,img.cols, img.rows, QImage::Format_BGR888);

重点在于Array需要最后释放,如果不知道什么时候释放可以看Qimage构造函数的后两个参数

QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr

QImageCleanupFunction 是一个函数指针类型,由文档可知该类型函数接受void*类型返回void,即接受任意类型的指针并且调用。虽然本人没有尝试,但有足够的理由可以猜测,,当所有的Qimage共享内存的副本都析构了QImage将会调用cleanupFunction (cleanupInfo )。可以自己写一个函数来最终释放内存。

方法4

当然是写一个直接从Mat到QImage一个个像素复制的函数,网上有很多就不赘述了。

总结

方法2最为简单,不过方法3可以很好的证明前面的结论,看起来一个非常小的BUG却用了好几天确定问题并论证解决。
以上方法可能只适用于24位三通道的图像,其他格式图像同理易解决,不过推测四通道图像应该不会出现这个问题。
环境debug,Visual studio2019,Qt6.0.2,opencv使用opencv_world453d.lib。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119123.html

相关文章

  • 使用openCV分水岭算法实现图像分割

    摘要:当水位达到最大高度时,创建的盆地和分水岭就组成了分水岭分割图。分水岭算法分水岭算法就是将合并的图像中,前景和背景区分开,并对黑色区域的像素做出标记属于前景还是背景。当所有盆地汇合时,就会创建很多分水岭线条,导致图像被过度分割。 一、简介 分水岭算法的思想是把图像看作是一个拓扑地貌,同类区域就...

    AZmake 评论0 收藏0
  • 如何在 iOS 工程中使用 OpenCV

    摘要:转换和在中同常用表示图片,而中则是来表示图片,因此我们就需要一些转换的方法,的官方教程中给吃了转换的方法,这里摘录如下可以根据这个决定使用哪种 前言 OpenCV ,是一个开源的跨平台计算机视觉和机器学习库,通俗点的说,就是他给计算机提供了一双眼睛,一双可以从图片中获取信息的眼镜,从而完成人脸识别、去红眼、追踪移动物体等等的图像相关的功能。更多具体的说明可参见 OpenCV 官网。 导...

    LiuZh 评论0 收藏0
  • openCV中meanshift算法查找目标

    摘要:因此,在方法中使用参数屏蔽掉饱和度低于此阈值的像素,不把它们统计进直方图中。 一、简介 图像直方图的反向投影是一个概率分布图,表示一个指定图像片段出现在特定位置的概率。当我们已知图像中某个物体的大体位置时,可以通过概率分布图找到物体在另一张图像中的准确位置。我们可以设定一个初始位置,在其周围...

    vpants 评论0 收藏0
  • iOS利用OpenCV 实现文字行区域提取的尝试

    摘要:这是坐标百度,好像没啥好研究的了,不过出于好奇还是想知道使用是如何做到把文字区域进行框选的,所以接下来我们就看看如何在上使用实现图片中的文字框选。一些探索 最近下了几个OCR的App(比如白描),发现可以选中图片中的文字行逐行转成文字,觉得很有意思(当然想用要花钱啦),想着自己研究一下实现原理,google之后,发现了两个库,一个是OpenCV,在机器视觉方面应用广泛,图像分析必备利器。另一...

    番茄西红柿 评论0 收藏0
  • iOS利用OpenCV 实现文字行区域提取的尝试

    摘要:这是坐标百度,好像没啥好研究的了,不过出于好奇还是想知道使用是如何做到把文字区域进行框选的,所以接下来我们就看看如何在上使用实现图片中的文字框选。一些探索 最近下了几个OCR的App(比如白描),发现可以选中图片中的文字行逐行转成文字,觉得很有意思(当然想用要花钱啦),想着自己研究一下实现原理,google之后,发现了两个库,一个是OpenCV,在机器视觉方面应用广泛,图像分析必备利器。另一...

    ztyzz 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<