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
函数仅支持单通道比较,而且仅支持非浮点类型,浮点类型可能出现精度差异,导致dst矩阵非0非1而是出现浮点数,错误返回false
:
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 |
|
非CV_8U类型单通道的序列化
以上例子基于CV_8U
可以无损保存为png
编码,但是如果是浮点类型等可能导致精度损失,因此不能直接使用CV_8U,也无法通过cv::imencode
等编码,但也不复杂,只需要我们手动将Mat装入vector即可,但主要需要额外载入size结构信息,以便后续将一维vector反序列化成Mat:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27auto mat2json = [](const cv::Mat& m){
Q_ASSERT(m.type()==5); //for CV_32FC1
QJsonObject json;
std::vector<float> vp(m.total(),0); //预留空间,提高性能
int k=0;
for(int i=0; i<m.rows; i++){
for(int j=0; j<m.cols; j++){
float* elem = (float*)(m.data + i*m.step[0] + j*m.step[1]);
vp[k++] = elem[0];
}
}
QByteArray byteArray(reinterpret_cast<const char*>(vp.data()),vp.size()*sizeof(float));
json.insert("data",QString(byteArray.toBase64()));
json.insert("size",QJsonArray{m.rows,m.cols}); //结构化信息以恢复Mat
return json;
};
auto json2mat = [](const QJsonObject& json){ //for CV_32FC1
std::vector<float>vp;
QString base64 = json["data"].toString();
QByteArray byteArray = QByteArray::fromBase64(base64.toUtf8());
vp.resize(base64.size()/sizeof(float)); //预留空间,提高性能
memcpy(vp.data(),byteArray.constData(),byteArray.size());
int row = json["size"].toArray()[0].toInt();
int col = json["size"].toArray()[1].toInt();
return cv::Mat(row,col,CV_32FC1,vp.data());
};
非CV_8U类型多通道的序列化
多通道也可以使用一维vector,因为Mat仍然支持一维vector来初始化多通道:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29auto mat2json = [](const cv::Mat& m){
Q_ASSERT(m.type()==21); //for CV_32FC3
QJsonObject json;
std::vector<float> vp(m.total()*m.channels(),0);
int k=0;
for(int i=0; i<m.rows; i++){
for(int j=0; j<m.cols; j++){
float* elem = (float*)(m.data + i*m.step[0] + j*m.step[1]);
for(int c=0; c<m.channels(); c++){
vp[k++] = elem[c]; //三通道读入vector相邻
}
}
}
QByteArray byteArray(reinterpret_cast<const char*>(vp.data()),vp.size()*sizeof(float));
json.insert("data",QString(byteArray.toBase64()));
json.insert("size",QJsonArray{m.rows,m.cols}); //结构化信息以恢复Mat
return json;
};
auto json2mat = [](const QJsonObject& json){ //for CV_32FC1
std::vector<float>vp;
QString base64 = json["data"].toString();
QByteArray byteArray = QByteArray::fromBase64(base64.toUtf8());
vp.resize(byteArray.size()/sizeof(float));
memcpy(vp.data(),byteArray.constData(),byteArray.size());
int row = json["size"].toArray()[0].toInt();
int col = json["size"].toArray()[1].toInt();
return cv::Mat(row,col,CV_32FC3,vp.data());
};
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);
QImage可以直接保存: 1
2
3QImage pic;
...
pic.save("/path/xx.png","PNG",-1); //-1代表质量0-100,仅jpg有效;