目的
学会image 与 cv::mat 之间数据转化, 主要弄明白图像数据之间移动。
注意如下: 一般pc下, QImage采用小端方案, 采用字节序列如下。
QImage::Format_ARGB32 the bytes are ordered: B G R A
QImage::Format_RGB32 the bytes are ordered: B G R (255)
QImage::Format_RGB888 the bytes are ordered: R G B
opencv之间是采用BGR字节序列存储。因此我们在转化时要注意r b之间swap, 以及是否需要深拷贝,QImage是采用隐式数据共享, 因此内存管理是不用我们维护。
covert image or cv::Mat
cvMatToQImage
#ifndef ASM_OPENCV_H
#define ASM_OPENCV_H
/**
Functions to convert between OpenCV's cv::Mat and Qt's QImage and QPixmap.
**/
#include <QDebug>
#include <QImage>
#include <QPixmap>
#include <QtGlobal>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgproc/types_c.h"
/*
Endianness
---
Although not totally clear from the docs, some of QImage's formats we use here are
endian-dependent. For example:
Little Endian
QImage::Format_ARGB32 the bytes are ordered: B G R A
QImage::Format_RGB32 the bytes are ordered: B G R (255)
QImage::Format_RGB888 the bytes are ordered: R G B
Big Endian
QImage::Format_ARGB32 the bytes are ordered: A R G B
QImage::Format_RGB32 the bytes are ordered: (255) R G B
QImage::Format_RGB888 the bytes are ordered: R G B
Notice that Format_RGB888 is the same regardless of endianness. Since OpenCV
expects (B G R) we need to swap the channels for this format.
This is why some conversions here swap red and blue and others do not.
This code assumes little endian. It would be possible to add conversions for
big endian machines though. If you are using such a machine, please feel free
to submit a pull request on the GitHub page.
*/
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
#error Some of QImage's formats are endian-dependant. This file assumes little endian. See comment at top of header.
#endif
namespace ASM {
// NOTE: This does not cover all cases - it should be easy to add new ones as required.
inline QImage cvMatToQImage( const cv::Mat &inMat )
{
switch ( inMat.type() )
{
// 8-bit, 4 channel
case CV_8UC4:
{
QImage image( inMat.data,
inMat.cols, inMat.rows,
static_cast<int>(inMat.step),
QImage::Format_ARGB32 );
return image;
}
// 8-bit, 3 channel
case CV_8UC3:
{
QImage image( inMat.data,
inMat.cols, inMat.rows,
static_cast<int>(inMat.step),
QImage::Format_RGB888 );
return image.rgbSwapped();
}
// 8-bit, 1 channel
case CV_8UC1:
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
QImage image( inMat.data,
inMat.cols, inMat.rows,
static_cast<int>(inMat.step),
QImage::Format_Grayscale8 );
#else
static QVector<QRgb> sColorTable;
// only create our color table the first time
if ( sColorTable.isEmpty() )
{
sColorTable.resize( 256 );
for ( int i = 0; i < 256; ++i )
{
sColorTable[i] = qRgb( i, i, i );
}
}
QImage image( inMat.data,
inMat.cols, inMat.rows,
static_cast<int>(inMat.step),
QImage::Format_Indexed8 );
image.setColorTable( sColorTable );
#endif
return image;
}
default:
qWarning() << "ASM::cvMatToQImage() - cv::Mat image type not handled in switch:" << inMat.type();
break;
}
return QImage();
}
inline QPixmap cvMatToQPixmap( const cv::Mat &inMat )
{
return QPixmap::fromImage( cvMatToQImage( inMat ) );
}
QImageToCvMat
// If inImage exists for the lifetime of the resulting cv::Mat, pass false to inCloneImageData to share inImage’s
// data with the cv::Mat directly
// NOTE: Format_RGB888 is an exception since we need to use a local QImage and thus must clone the data regardless
// NOTE: This does not cover all cases - it should be easy to add new ones as required.
inline cv::Mat QImageToCvMat( const QImage &inImage, bool inCloneImageData = true )
{
switch ( inImage.format() )
{
// 8-bit, 4 channel
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
{
cv::Mat mat( inImage.height(), inImage.width(),
CV_8UC4,
const_cast<uchar*>(inImage.bits()),
static_cast<size_t>(inImage.bytesPerLine())
);
return (inCloneImageData ? mat.clone() : mat);
}
// 8-bit, 3 channel
case QImage::Format_RGB32:
{
if ( !inCloneImageData )
{
qWarning() << "ASM::QImageToCvMat() - Conversion requires cloning so we don't modify the original QImage data";
}
cv::Mat mat( inImage.height(), inImage.width(),
CV_8UC4,
const_cast<uchar*>(inImage.bits()),
static_cast<size_t>(inImage.bytesPerLine())
);
cv::Mat matNoAlpha;
cv::cvtColor( mat, matNoAlpha, cv::COLOR_BGRA2BGR ); // drop the all-white alpha channel
return matNoAlpha;
}
// 8-bit, 3 channel
case QImage::Format_RGB888:
{
if ( !inCloneImageData )
{
qWarning() << "ASM::QImageToCvMat() - Conversion requires cloning so we don't modify the original QImage data";
}
QImage swapped = inImage.rgbSwapped();
return cv::Mat( swapped.height(), swapped.width(),
CV_8UC3,
const_cast<uchar*>(swapped.bits()),
static_cast<size_t>(swapped.bytesPerLine())
).clone();
}
// 8-bit, 1 channel
case QImage::Format_Indexed8:
{
cv::Mat mat( inImage.height(), inImage.width(),
CV_8UC1,
const_cast<uchar*>(inImage.bits()),
static_cast<size_t>(inImage.bytesPerLine())
);
return (inCloneImageData ? mat.clone() : mat);
}
default:
qWarning() << "ASM::QImageToCvMat() - QImage format not handled in switch:" << inImage.format();
break;
}
return cv::Mat();
}
// If inPixmap exists for the lifetime of the resulting cv::Mat, pass false to inCloneImageData to share inPixmap's data
// with the cv::Mat directly
// NOTE: Format_RGB888 is an exception since we need to use a local QImage and thus must clone the data regardless
inline cv::Mat QPixmapToCvMat( const QPixmap &inPixmap, bool inCloneImageData = true )
{
return QImageToCvMat( inPixmap.toImage(), inCloneImageData );
}
}
#endif