Mat/QImage To Json:Mat/QImage类型的Json序列化
记录了cv::Mat
的json序列化和反序列化过程,以及比对序列化反序列化前后的两个Mat矩阵是否对应。
Mat to json序列化
Mat
是一种特殊的数据类型,如果使用vector再进行JsonArray序列化略微繁琐,且类型不同通道数通用性较差,一般习惯使用base64
编码图像信息,首先通过cv::imencode
函数进行编码(因为大多数图像是8位位深,故该函数仅支持CV_8U
类型的编码);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16bool cv::imencode(const std::string& ext,
InputArray img,
std::vector<uchar>& buf,
const std::vector<int>& params = std::vector<int>()
)
//参数:
ext:png或者jpg,前者无损,后者有损;
img:输入图像;
buf:编码数组;
params:png/jpg模式具体参数(可缺省),如:
- std::vector<int> png_params = {cv::IMWRITE_PNG_COMPRESSION, 3}; //压缩级别0-9,值越大压缩越高,但编码越慢;
- std::vector<int> jpg_params = {cv::IMWRITE_JPEG_QUALITY, 80}; //压缩质量0-100,值越大,质量越好损失越少;
//返回值:
成功返回true;uchar
字符数组读入QByteArray
,即可调用其toBase64
方法进行编码,注意这种返回的base64
是QString
而不是std::string
,示例:
1
2
3
4
5
6
7
8
9auto mat2Base64 = [](const cv::Mat& m){
if(m.empty())
return QString();
std::vector<uchar> m_vector;
cv::imencode(".png",m,m_vector);
QByteArray byteArray(reinterpret_cast<const char*>(m_vector.data()),m_vector.size());
QString base64 = byteArray.toBase64();
return base64;
};
json to Mat反序列化
通过QByteArray
的fromBase64
方法可以从base64
字符串解出编码字符,再调用cv::imdecode
方法将其解到Mat
即可,标志位同imread
读取方法,只是二者从不同对象读取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19cv::Mat cv::imdecode(InputArray buf,
int flags
)
//参数:
buf:输入的Mat编码数组
flags:读取数组方式,可为标志位或数字标识,例如:
- cv::IMREAD_UNCHANGED[-1]:原始读取,保留一切信息(颜色通道/透明通道/位深/通道数等)
- cv::IMREAD_GRAYSCALE[0]:灰图读取
- cv::IMREAD_COLOR[1]:彩图读取,会丢失透明通道(仅PNG格式图片支持透明色)
- cv::IMREAD_ANYDEPTH[2]:保留原始位深信息(其余有可能将16/32位深强制到8位深)
- cv::IMREAD_ANYCOLOR[4]:保留原始RGB信息,顺序可能变成BGR;
- cv::IMREAD_REDUCED_GRAYSCALE_2:灰图读取并缩小二分一
- cv::IMREAD_REDUCED_COLOR_2:彩图读取并缩小二分一
- cv::IMREAD_REDUCED_GRAYSCALE_4:....缩小四分一
......
//返回值:
从imencode编码解码出的Mat类型对象;
示例: 1
2
3
4
5
6
7auto base642Mat = [](const QString& base64){
if(base64.isEmpty())
return cv::Mat();
QByteArray byteArray = QByteArray::fromBase64(base64.toUtf8()); //base64转编码字符
std::vector<uchar>m_vector(byteArray.begin(),byteArray.end());
return cv::imdecode(m_vector,cv::IMREAD_UNCHANGED);
};
Mat类型校对
为了验证序列化前后的Mat
是否具有相同的数据,可以通过cv::compare
函数进行比较,其支持六种大小关系的元素比较(相等/不等/小于/大于/小于等于/大于等于),其输出掩码对象(255为真,0为假),但注意cv::compare
函数仅支持单通道比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void cv::compare( //仅支持单通道比较
InputArray src1, // 第一个输入矩阵/标量
InputArray src2, // 第二个输入矩阵/标量
OutputArray dst, // 输出掩码矩阵(CV_8U 类型)
int cmpop // 比较操作符
);
//参数:
src1/src2:要比对的两个输入对象;
dst:输出掩码矩阵,对应元素255为真,0为假
cmpop:六种关系标识符:
- cv::CMP_EQ:==,两个输入对应位置相等,置255
- cv::CMP_NE:!=,两个输入对应位置不等,置255,相等置0
- cv::CMP_GT:>
- cv::CMP_GE:>=
- cv::CMP_LT: <
- cv::CMP_LE: <=
只需要使用split
拓展一下,就可以适用于多通道比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//多通道Mat:逐通道比较是否相等
auto isMultiMatSame = [](const cv::Mat&m1,const cv::Mat&m2){
Q_ASSERT(m1.channels()==m2.channels());
if(m1.channels()==1){ //单通道无需split
cv::Mat diff;
compare(m1,m2,diff,cv::CMP_NE);
return (cv::countNonZero(diff)==0);
}
else{ //多通道
vector<cv::Mat> m1_split,m2_split;
cv::split(m1,m1_split);
cv::split(m2,m2_split);
for(int i=0; i<m1.channels(); i++){
cv::Mat diff;
cv::compare(m1_split[i],m2_split[i],diff,cv::CMP_NE);
if(cv::countNonZero(diff)!=0)
return false;
}
return true;
}
};
本文完整测试代码(Qt5+OpenCV)
1 |
|
QImage
除了cv::Mat
,Qt中会使用QImage
来打开和存储图像,其Json序列化仍然可以通过base64
解决,可以将QImage
装入QBuffer
,而QBuffer
本身可以直接绑定到二进制序列结构QByteArray
:
QImage to base64: 1
2
3
4
5
6
7
8
9
10
11
12
13QImage pic;
pic.load("D:/Documents/Desktop/note/jLena.jpeg");
if(pic.isNull()){
qDebug() << "Empty Pic!";
return 0;
}
QByteArray byteArray;
QBuffer buf(&byteArray); //绑定QByteArray
buf.open(QIODevice::WriteOnly);
pic.save(&buf,"PNG"); //存入图片数据
QString base64 = byteArray.toBase64();
解析时,QImage
支持通过QByteArray
直接loadData
加载图片:
1
2
3
4
5
6////从base64解析到QImage
QByteArray rarray = QByteArray::fromBase64(base64.toUtf8());
QImage rpic;
if(!rpic.loadFromData(rarray)){
qDebug() <<"Parse Fault!";
}QImage
的比较运算符,直接比较即可比对前后的像素数据:
1
qDebug()<<(rpic == pic); //true
QImage
的打印需要经过QPainter
等,可能今天记住了,明天不查也不会写,因此完全可以通过曲线救国的方式,在支持OpenCV的Qt环境,将序列化的base64
转到cv::Mat
结构,通过cv::imshow
直接打印,非常nice:
1
2cv::Mat rMat = base642Mat(base64); //该函数源码见上文
cv::imshow("test",rMat);