本文受了http://blog.csdn.net/dancing_night/article/details/51545524的启发。
QImage里面有一个指针bits(),用来指向存储像素的内存首地址。而cv::Mat也有一个成员data,同样指向存储像素的内存首地址。那么,假如我想把QImage转化为cv::Mat,是否只要调用memcpy就可以了吗?
不是的。QImage的像素存储方式与Mat既有相似之处,也有不同之处。相同之处是,像素的保存方式都是用一位数组,一行一行的储存(行主序)。不同之处是,QImage储存的每一行像素,其字节数目必须是4的整数倍。来看下面的例子,假如一个 Grayscale8格式(每一个像素用一个字节表示)的QImage由2行7列组成,各个像素的取值从1到14递增.由于QImage的每一行必须用4n个字节表示,所以每一行必须空一个字节(红色圈出的位置),保证每一行是8个字节:
为了让程序员清楚知道每一行有多少有效字节,qimage给出了bytesPerLine() 函数。在本例子中,bytesPerLine() 返回7.
而cv::Mat则不同,它存储的字节是连续的,中间不空。因此,直接用memcpy来传递数据是有问题的,会造成字节错位。
下面给出一个函数,完成从QImage向cv::Mat的转化,在转化过程中,不会造成字节错位。
头文件:
#pragma once
#include <opencv2/opencv.hpp>
#include <QImage>
#include <QPainter>
#include <QDebug>
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_core249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_imgproc249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_highgui249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_ml249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_video249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_features2d249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_calib3d249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_objdetect249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_contrib249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_legacy249d.lib")
#pragma comment(lib, "E:\\cv\\opencv\\build\\x86\\vc11\\lib\\opencv_flann249d.lib")
cv::Mat QImageToMat(QImage image);
cpp文件:
#include "cvUtility.h"
cv::Mat QImageToMat(QImage image)
{
cv::Mat mat;
switch (image.format())
{
case QImage::Format_ARGB32:
case QImage::Format_RGB32:
case QImage::Format_ARGB32_Premultiplied:
mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine());
break;
case QImage::Format_RGB888:
mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
cv::cvtColor(mat, mat, CV_BGR2RGB);
break;
case QImage::Format_Grayscale8:
mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine());
break;
}
return mat;
}
在转化过程中,cv::Mat的构造函数被告知QImage的每一行的有效数据占bytesPerLine()个字节,所以openCV库内部会做出相应的安排,避免错位。
下面的代码调用QImageToMat函数。我特意把QImage的行数设为250,不能被4整除。通过是否预定义宏 GRAY来决定QImage是灰度图还是彩图。亲测发现,不管是灰度图还是彩图,QImage向Mat的转化都是成功的。
#include "mat_qimage.h"
#include "cvUtility.h"
#define GRAY
const int iWidth = 250, iHeight = 255;
unsigned char * m_pData = new unsigned char[iWidth * iHeight];
QImage m_Img;
Mat_QImage::Mat_QImage(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
#if defined(GRAY)
for(int k = 0; k<iWidth;k++)
{
for(int l = 0; l<iHeight; l++)
{
m_pData[l * iWidth + k] = k;
}
}
QImage img(m_pData, iWidth, iHeight, iWidth, QImage::Format_Grayscale8);
qDebug()<<img.bytesPerLine();
m_Img = QImage(iWidth, iHeight, QImage::Format_Grayscale8);
#else
QImage img;
img.load(QString("E:\\merge.bmp"));
m_Img = QImage(iWidth, iHeight, img.format());
#endif
QPainter qp;
qp.begin(&m_Img);
qp.drawImage(m_Img.rect(), img, img.rect());
qp.end();
}
Mat_QImage::~Mat_QImage()
{
if(m_pData)
delete [] m_pData;
}
void Mat_QImage::paintEvent(QPaintEvent *e)
{
QPainter qp;
qp.begin(this);
qp.drawImage(rect(), m_Img, m_Img.rect());
qp.end();
}
void Mat_QImage::mouseDoubleClickEvent(QMouseEvent *e)
{
cv::Mat mtx = QImageToMat(m_Img);
bool b = cv::imwrite("E:\\mtx.jpg", mtx);
qDebug()<<b;
}