boost库

1998年,C++标准委员会创建了boost这个项目,初心是开发可复用的C++组件,为C++发展探索方向,boost早期严格遵循header-only原则,且代码评审规范,一度被称为最美的C++库之一,其次boost中的智能指针、regex、function/bind等一系列开发组件精华被C++标准库吸纳,成为C++事实上的重要参考库。由于boost是完全开源的,它可以前瞻性地完成一些C++开发中需要的组件,在项目上有重要应用成果。

因为项目需要,本系列会逐渐更新boost库的常用接口与原理,并持续更新。

从json方法开始。

boost::json

boost::json::value

基本数据类型构造与转换

object的valuearray的成员都是以boost::json::value的形式存储,因此解析时需要重新指定数据类型才能确保类型前后一致,类似QJson的toXXX接口,基本数据类型的boost::json::value支持若干种解析转换方法:as_object、as_array、as_bool、as_double、as_int64、as_uint64和as_string;对应这些数据,构造时只需要直接传递即可,json构造会自动完成这些基本数据类型到boost::json::value的隐式转换

is_xxx和if_xxx接口

boost::json::value提供了类型判断is_xxx接口,例如一个object存在多种类型的value,先判断再处理:

1
2
3
4
5
6
for(const auto& pos : list){
if(pos.is_object()) //如为object对象
cout<< pos.as_object() <<endl; //转换
else if(pos.is_double()) //double对象
cout<< pos.as_double() <<endl;
}

if_xxx接口的存在提供了类型判断返回类型指针的双重功能,当满足对应数据类型才会返回对应的类型指针,利用这个指针可以具体对值进行校验:

1
2
3
4
5
6
for(const auto& pos:jsonKv){
if(const uint64_t* item = pos.value().if_uint64()){
if(*item == 1998)
cout << pos.key() <<endl;
}
}
注意,如果我们没有定义uint64_t num = 1998,而是在赋值中直接使用1998,那么是一律按照int类型存储。

get_xxx和emplace_xxx接口

二者均是不含类型判断的接口,get_xxx会返回value对应类型的引用,如果类型不对应会导致断言失败,一般和类型判断结合使用:

1
2
3
if(pos.value().is_double()){
double& item = pos.value().get_double();
}
emplace_xxx同样返回了对象类型的引用,而且会修改引用值默认值,在不进行类型判断时尽管类型不对应,还是会完成修改
1
double& item = pos.value().emplace_double(); //无论value原来是否double,都会被修改成double,且值为默认值0.0

boost::json::object

赋值方式:insert、emplace、直接赋值(operator[]/at())

object对象支持若干种赋值方式,包括insertemplace直接赋值

如下实例Json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Company": "Committee",
"From": 1998,
"Name": "boost",
"Page": {
"Developers": "C++ Standard Committee",
"Download": "Download Link",
"Home": "Home Page"
},
"Version": [
1.0,
2.0,
3.0,
{"isTest": true}
]
}

通过以下方法完成json构造,大体上和QJson接口差不多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//构造
boost::json::object jsonKv;
jsonKv["Company"] = "Committee";
jsonKv.emplace("From", 1998);
jsonKv.insert(std::make_pair("Name","boost"));

boost::json::object jsonPage;
jsonPage["Developers"] = "C++ Standard Committee";
jsonPage["Download"] = "Download Link";
jsonPage["Home"] = "Home Page";

jsonKv["Page"] = jsonPage;

jsonKv["Version"] = {
1.0, 2.0, 3.0, boost::json::object({{"isTest", true}})
};

这里最推荐使用的是直接赋值,因为它足够简洁,而且boost::json::objectinsert对象是一种初始化列表对象,例如pair和后续介绍的boost::json::value,不能像QJsonObject一样直接插入键值,所以这样的代码在boost中是错误的:

1
jsonKv.insert("From", 1998);

如果你习惯这个接口,要么使用emplace,要么写成:

1
2
3
jsonKv.insert({{"From",1998}});
//或:
jsonKv.insert(std::make_pair("From",1998));
对于这个pair,也是有格式要求的,定义为:
1
std::pair<boost::json::string_view, boost::json::value>(..., ...);

注意一个细节差异:使用直接赋值时,同键插入时json会保留最后一次插入的值;而如果json存在相同的键使用emplaceinsert进行插入时,插入操作会失败json只会保留原值

另一个点是关于objectoperator[],该符号用于检测是否存在这样的键,如果存在则替换,而不能用于读取,如这样的写法是不支持的:

1
2
boost::json::object obj = xxx;
auto str = obj["str"].as_string(); //不支持
所以取键时只能使用at:
1
auto str = obj.at("str").as_string();

std::initializer_list构造

可直接列表构造object对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boost::json::object jsonKv1{
{"Company","Committee"},
{"From", 1998},
{"Name", "boost"},
{"Page", {
{"Developers", "C++ Standard Committee"},
{"Download", "Download Link"},
{"Home", "Home Page"}
}},
{"Version",{
1.0,
2.0,
3.0,
{{"isTest", true}}}
}
};

遍历和删除

既支持迭代器,也支持const auto& pos : object的方法:

1
2
3
4
5
6
7
8
9
for(const auto& pos: jsonKv){
cout<< pos.key() <<pos.value()<<endl;
}
for(auto pos = jsonKv.begin(); pos!=jsonKv.end();){
if(pos->value().as_int64() == 1998)
pos = jsonKv.erase(pos);
else
pos++;
}
以上erase支持迭代器删除,也支持对键搜索删除:
1
jsonKv.erase("Company");
其余count/contain/size接口同理,boost::json::object内部实现类似std::unordered_map,也支持reserve/capacity等接口;

boost::json::array

array可以直接列表构造,也可以使用push;array还提供了emplace接口表示在某个迭代器pos前插入某个value,但是注意其和erase一样存在迭代器失效的问题,所以不能边迭代边插入,而是记录插入位置(当存在多个位置时,使用迭代器数组记录),然后后续再插入。

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
boost::json::array list{
1.0,
2.0,
3.0,
boost::json::object({{"isTest",true}})
};
list.push_back(4.0);

boost::json::array::iterator rec;
for(auto pos = list.begin(); pos!=list.end(); pos++){
if(double* item = pos->if_double()){
if(*item == 3.0){
rec = pos;
}
}
}

list.emplace(rec, 5.0); //在pos(3.0)前面插入5.0

for(const auto& pos:list){
if(pos.is_object())
cout<< pos.as_object() <<endl;
else if(pos.is_double())
cout<< pos.as_double() <<endl;
}

对象序列化与反序列化

应该说,boost库的对象序列化和反序列化思想和Qt是高度一致的,但是自定义规则方面其比Qt更加方便。

序列化的目标是将抽象的数据,例如int、double乃至结构体、STL数据封装成某些数据类型,因为我们要对这些数据进行传输,必须使通信双方都认识这些数据的存储;从底层来说,常用两种表示,其一是文本表示,即字符串;其二是二进制编码表示,如protobuf做的事情,这里涉及json我们仅讨论前者。

boost::json::value的序列化

这里对应的就是QJsonValue的序列化,将Json转换成字符串,或者将字符串转换到Json,分别对应parseserialize接口:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <boost/json.hpp>

using namespace std;

/*
{
"Company": "Committee",
"From": 1998,
"Name": "boost",
"Page": {
"Developers": "C++ Standard Committee",
"Download": "Download Link",
"Home": "Home Page"
},
"Version": [
1.0,
2.0,
3.0,
{"isTest": true}
]
}
*/

int main(){
uint64_t num = 1998;
boost::json::object jsonKv{
{"Company","Committee"},
{"From", num},
{"Name", "boost"},
{"Page", {
{"Developers", "C++ Standard Committee"},
{"Download", "Download Link"},
{"Home", "Home Page"}
}},
{"Version",{
1.0,
2.0,
3.0,
{{"isTest", true}}}
}
};

//json序列化到string
std::string s = boost::json::serialize(jsonKv);
cout << s <<endl;

//string解析到json
auto rJsonKv = boost::json::parse(s).as_object();
cout << rJsonKv << endl;

cout<< "done" <<endl;
return 0;
}

类、结构体、STL复杂对象的序列化与反序列化

boost对于复杂对象的序列化比Qt功能更加强大,Qt对上述对象进行序列化,只能是在结构体内、类内将成员变量塞入同一个QJsonObject或者QJsonArray,对于STL,也是同理;

boost的序列化和反序列化分别依赖于text_oarchivetext_iarchive,这个万能结构类似QVariant,能够直接装载类、结构体或者STL等复杂类型,而且它可以绑定字符串流或者文件流,将复杂类型直接转到对应的字符流,达到序列化目的,同理也可以读取相应的流数据,恢复原来类型,达到反序列化效果。

也如同QVariant装载类/结构体要使用Q_DECLARE_METATYPE(class/struct/class*/struct*),使得元对象系统能够认识这个类或结构体或指针,boost中也需要一个操作来使得boost::serialization能够完成对复杂类型的序列化和反序列化,即在函数中声明一个模板函数,其命名和参数都是固定的:

1
2
3
4
5
6
7
8
template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& num;
ar& num1;
ar& vp;
ar& umap;
ar& test;
}
如果这个函数被设置成private,还必须对boost声明友元:
1
friend class boost::serialization::access; //当serialize私有,必须加上友元声明

如果类中存在结构体,也要对这个结构体声明同样的模板函数以定义序列化和反序列化规则,其他具体序列化结构boost自动帮我们完成了,我们只需使用text_oarchivetext_iarchive进行载入和读出操作即可,这也是比Qt优越的地方,再者,其还支持对指针对象进行序列化。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <boost/json.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/unordered_map.hpp>
using namespace std;

class Person{
public:
Person() = default;
Person(int num, double num1, int vpNum, float testNum):num(num),num1(num1),vp(10,vpNum){
test.error = testNum;
umap.insert({1,2});
umap.insert({3,4});
umap.insert({5,6});
}
void printInfo(){
cout << " num = " << num
<< " num1 = " << num1
<< " vpNum = " << vp[2]
<< " umap[3]= "<< umap[3]
<< " test.float = " << test.error << endl;
}

private:
friend class boost::serialization::access; //当serialize私有,必须加上友元声明

//此函数声明了序列化和反序列化规则
template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& num;
ar& num1;
ar& vp;
ar& umap;
ar& test;
}

int num = 0;
double num1 = 0;
vector<int> vp;
unordered_map<int, int> umap;
struct Test{
float error = 0;

template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& error;
}
};
Test test;
};


int main(){
//复杂类到string
std::ostringstream ss;
boost::archive::text_oarchive oa(ss);
Person p(9, 9.9, 10, 19.9);
oa << p;
std::string output = ss.str();

//string反序列化到类
std::istringstream is(output);
boost::archive::text_iarchive oi(is);
Person p_;
oi >> p_; //注意:不要写成p_ << oi
p_.printInfo();

//指针的序列化
Person* p1 = new Person(19, 19.9, 110, 119.9);
std::ostringstream ssp1;
boost::archive::text_oarchive oap1(ssp1);
oap1 << p1;
std::string outputp1 = ssp1.str();

//反序列化
std::istringstream isp1(outputp1);
boost::archive::text_iarchive oip1(isp1);
Person* p2 = nullptr;
oip1 >> p2;
p2->printInfo();

cout<< "done" <<endl;
return 0;
}

自定义序列化和反序列化规则:save与load

序列化和反序列化支持自定义的规则,例如在序列化Person时,我们想修改序列化值为某种默认值,可以这样写:

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
BOOST_SERIALIZATION_SPLIT_FREE(Person) //声明要自定义规则的类型
namespace boost
{
namespace serialization{
template<class Archive>
static void save(Archive &ar, const Person& p, const unsigned int version){
ar& p.num;
ar& p.num1;
ar& p.vp;
ar& p.umap;
ar& p.test;
}

template<class Archive>
static void load(Archive &ar, Person& p, const unsigned int version){
ar& p.num;
ar& p.num1;
ar& p.vp;
ar& p.umap;
ar& p.test;
p.num = 1000; //只要反序列化Person,就设置默认值
p.num1 = 9.999;
p.test.error = -999;
}
} // namespace serialization
} // namespace boost
save函数中,ar作为一个输出归档器(如boost::archive::text_oarchive),相当于ar << p;在load函数ar则相当于一个输入归档器(boost::archive::text_iarchive),相当于ar >> p

对于BOOST_SERIALIZATION_SPLIT_FREE(Person),其作用是声明一个模板函数,以确认该类型需要按照自定义规则进行序列化和反序列化,既可以通过,也可以直接通过该函数指定:

1
2
3
4
template<class Archive>
void serialize(Archive &ar, const Person& p, const unsigned int version){
split_free(ar, p, version);
}

注意一个重要特点自定义规则对简单数据类型是无效的,例如你写下了对double的自定义规则,想反序列化时对数据进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOST_SERIALIZATION_SPLIT_FREE(double) //这是一段无效代码
namespace boost
{
namespace serialization{
template<class Archive>
void save(Archive &ar, const double& floatNum, const unsigned int version){
ar& floatNum;
}

template<class Archive>
void load(Archive &ar, double& floatNum, const unsigned int version){
ar& floatNum;
floatNum += 1; //不会如愿
}
} // namespace serialization
} // namespace boost
因为处理一个简单基本类型的手段实在是太多了,设计者认为引入自定义的数据序列化犯错机会大于真正实用机会,例如当一个类中含有一个double,而你同时为类外的一个double设置了自定义规则,就会有问题。所以事实上,对于ar& T如果T属于primitive_type基本数据类型原生指针C风格数组等,它们的共同点是都是按位写入内存,随即按位读出),那么在oa << T中,根本不会查找save和load函数,而是按照默认行为进行写入读出;

所以使用自定义规则的,往往是结构体boost::json::value/object/array等,还有cv::Mat

对于类和结构体,其不过是一堆封装的成员函数,我们一开始已经讨论了其自定义方法。对于value,其序列化的终点、反序列化的起点常常是std::string,所以可以这样写:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <iostream>
#include <boost/json.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/unordered_map.hpp>

using namespace std;

class Person{
public:
Person() = default;
Person(int num, double num1, int vpNum, float testNum):num(num),num1(num1),vp(10,vpNum){
test.error = testNum;
umap.insert({1,2});
umap.insert({3,4});
umap.insert({5,6});
obj["test"] = "begin";
}
void printInfo(){
cout << " num = " << num
<< " num1 = " << num1
<< " vpNum = " << vp[2]
<< " umap[3]= "<< umap[3]
<< " test.float = " << test.error << endl;
for(const auto& pos : obj){
cout << pos.key() << " " << pos.value() <<endl;
}
}

private:
friend class boost::serialization::access; //当serialize私有,必须加上友元声明

//此函数声明了序列化和反序列化规则
template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& num;
ar& num1;
ar& vp;
ar& umap;
ar& test;
ar& obj;
}

int num = 0;
double num1 = 0;
vector<int> vp;
unordered_map<int, int> umap;
struct Test{
float error = 0;

template<typename Archive>
void serialize(Archive& ar, const unsigned int version){
ar& error;
}
};
Test test;
boost::json::object obj;
};

BOOST_SERIALIZATION_SPLIT_FREE(boost::json::object)
namespace boost
{
namespace serialization{
template<class Archive>
void save(Archive &ar, const boost::json::object& obj, const unsigned int version){
std::string str = boost::json::serialize(obj);
ar& str;
}

template<class Archive>
void load(Archive &ar, boost::json::object& obj, const unsigned int version){
std::string str;
ar& str;
obj = boost::json::parse(str).as_object();
obj["load"] = "Done";
}
} // namespace serialization
} // namespace boost

int main(){
//复杂类到string
std::ostringstream ss;
boost::archive::text_oarchive oa(ss);
Person p(9, 9.9, 10, 19.9);
oa << p;
std::string output = ss.str();

//string反序列化到类
std::istringstream is(output);
boost::archive::text_iarchive oi(is);
Person p1;
oi >> p1;
p1.printInfo();

cout<< "done" <<endl;
return 0;
}
可见,一是serializeparse早已支持json::valuestd::string的转换和反转换,定义规则只是满足一些特殊需求,或者使代码更统一;二是自定义规则会影响每一个归档器对象,无论该对象在类内或是类外被定义。

boost风格cv::Mat的序列化与反序列化

此处需要明确一些base64编码原理,以理解编码解码代码方法:

  1. base64编码是针对二进制流数据的编码,其核心思想是将三字节的二进制数据(24bit)分割成6个基本单元,编码成四字节的数据,所以base64的编码数据占据空间比原数据增加了约33%

  2. 编码使用的是ASCII字符中的65个,包括52个大小写字母、10个数字字符、还有"+"、"/"、"="三个特殊字符共65个,其中"="号不参与编码,它只用作字符填充,确保字符字节数3的整数倍

base64编码与解码实现

编码:实际上依赖的是boost::archive::iterators::base64_from_binary,它接收一个二进制流对象,该流对象必须按照6位位宽输出,所以需要transform_width做一个转换,transform_width<std::string::const_iterator, 6, 8>表示接收字符串(迭代器)对象,将其8位位宽输出转换成6位,然后计算需要填充的字节数(注意不是位数),返回base64编码;

解码binary_from_base64需要一个base64字符串对象,6位位宽输出为正常8位字节流,即可按照普通二进制读取成string。

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
29
#include <iostream>
#include <opencv2/opencv.hpp>
#include <sstream>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>

std::string string2Base64(const std::string& str){
std::stringstream ss;
using base64Eocoder = boost::archive::iterators::base64_from_binary<boost::archive::iterators::transform_width<std::string::const_iterator, 6, 8>>;
std::copy(base64Eocoder(str.begin()), base64Eocoder(str.end()), std::ostream_iterator<char>(ss));
int padding_num = (3 - (str.length()%3)) % 3;
for(int i=0; i<padding_num; i++){
ss.put('=');
}
return ss.str();
}

std::string base64ToString(const std::string& base64){
typedef boost::archive::iterators::transform_width<boost::archive::iterators::binary_from_base64<std::string::const_iterator>, 8, 6> base64Decoder;
size_t padding_num = std::count(base64.end()-2, base64.end(), '=');
if(padding_num > 2){
std::cerr << "padding_num Error" << std::endl;
return "";
}
std::stringstream ss;
std::copy(base64Decoder(base64.begin()),base64Decoder(base64.end()-padding_num), std::ostream_iterator<char>(ss));
return ss.str();
}

CV_8U Mat的base64编码与解码

本质是cv::Mat数据如何写入vector的问题,对CV_8U这种单通道或多通道,都可以直接使用png将其编码成数组数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
auto mat2Base64 = [](const cv::Mat& mat){
if(mat.empty()){
return std::string("");
}
std::vector<uchar> vp;
cv::imencode(".png", mat, vp);
std::string str(vp.begin(), vp.end());
return string2Base64(str);
};


auto base64ToMat = [](const std::string& base64){
if(base64.empty()){
return cv::Mat();
}
std::string str = base64ToString(base64); //注意不要忘记先转回普通字节流
std::vector<uchar> vp(str.begin(), str.end());
return cv::imdecode(vp, cv::IMREAD_UNCHANGED);
};

非CV_8U的单通道base64编码与解码

大体上思路和之前Qt那篇文章的编解码实现是一致的,但是仍然有一些细节差异值得学习:

对于std::string,对比Qt,它既充当QString,也充当QByteArray,即既可以表示文本数据,也可以表示二进制字符数据,当为后者时,这个std::string是通过一般的cout是不可打印的,一个简单例子:将一个vector对象变成流式的二进制数据,这个str是通过cout输出是空输出:

1
2
3
vector<float> vp{1.234, 2.345};
std::string str(reinterpret_cast<const char*>(vp.data()), vp.size()*sizeof(float));
cout<< str; //空输出
要知道str是否有效,可以将其转到base64看是否有有效内容;

更基本的问题是,不要和vector<char>或者vector<uchar>这种类型混用了,直接使用了迭代器赋值,写成:

1
2
vector<float> vp{1.234, 2.345};
std::string str(vp.begin(), vp.end());
因为float、int、double等在存储上都不是占一字节数据,和str的单个数据是对应不起来的,因此这种方法str是完全无效数据。

非CV_8U的单通道base64编码与解码完整测试代码:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <iostream>
#include <opencv2/opencv.hpp>
#include <sstream>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <vector>
#include <boost/json.hpp>
#include <assert.h>

using namespace std;

std::string string2Base64(const std::string& str){
std::stringstream ss;
using base64Eocoder = boost::archive::iterators::base64_from_binary<boost::archive::iterators::transform_width<std::string::const_iterator, 6, 8>>;
std::copy(base64Eocoder(str.begin()), base64Eocoder(str.end()), std::ostream_iterator<char>(ss));
int padding_num = (3 - (str.length()%3)) % 3;
for(int i=0; i<padding_num; i++){
ss.put('=');
}
return ss.str();
}

std::string base64ToString(const std::string& base64){
typedef boost::archive::iterators::transform_width<boost::archive::iterators::binary_from_base64<std::string::const_iterator>, 8, 6> base64Decoder;
size_t padding_num = std::count(base64.end()-2, base64.end(), '=');
if(padding_num > 2){
std::cerr << "padding_num Error" << std::endl;
return "";
}
std::stringstream ss;
std::copy(base64Decoder(base64.begin()),base64Decoder(base64.end()-padding_num), std::ostream_iterator<char>(ss));
return ss.str();
}

int main(){
//多通道Mat:逐通道比较是否相等
auto isMultiMatSame = [](const cv::Mat&m1,const cv::Mat&m2){
if(m1.channels()==1){ //单通道无需split
cv::Mat diff;
compare(m1,m2,diff,cv::CMP_NE);
return (cv::countNonZero(diff)==0);
}
else{ //多通道
std::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;
}
};

//编码到json
auto mat2json = [](const cv::Mat& m){
assert(m.type() == 5); //仅用CV32FC1举例,本代码适用于其他类型
boost::json::object jsonkv;
std::vector<float> vp(m.total(),0); //预留空间,提高性能
int k=0;
//低概率风险写法:
//memcpy(vp.data(), m.data(), m.total()*sizeof(float));
//保守写法:
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];
}
}
//std::string base64 = string2Base64(std::string(vp.begin(), vp.end())); //错误写法
std::string binary(reinterpret_cast<const char*>(vp.data()), vp.size()*sizeof(float)); //二进制流
std::string base64 = string2Base64(binary); //base64编码
jsonkv["data"] = base64;
jsonkv["size"] = boost::json::array{m.rows,m.cols}; //结构化信息以恢复Mat
return jsonkv;
};

//解码到json
auto json2mat = [](const boost::json::object& jsonkv){ //for CV_32FC1
std::string base64 = std::string(jsonkv.at("data").as_string()); //base64数据
std::string str = base64ToString(base64); //二进制数据
//std::vector<float> vp(str.begin(), str.end()); //错误写法
std::vector<float> vp(str.size()/sizeof(float));
memcpy(vp.data(), str.data(), str.size());
int row = jsonkv.at("size").as_array()[0].as_int64();
int col = jsonkv.at("size").as_array()[1].as_int64();
cout <<row << col <<endl;
return cv::Mat(row, col, CV_32FC1, vp.data()).clone();
};

cv::Mat_<float> m = (cv::Mat_<float>(4,5)<<
0,0, 1.23,3.24,
135.752395091636, 51.0103552955011, 102.6712174223711, 0,
208.4825800582035, 111.9060426536122, 63.69016545467102, 0,
197.9148945049116, 195.0959945478937, 78.79538704479675,0,
1.233333,0.11111, 1.23,3.24
);

cv::Mat m1 = json2mat(mat2json(m));
std::cout << isMultiMatSame(m, m1);

return 0;
}

非CV_8U的多通道base64编码与解码

唯一不同是序列化开辟数据空间需要增加通道数倍数,并且对数组进行赋值,其余步骤是一致的:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
auto mat2json = [](const cv::Mat& m){
assert(m.type() == 21); //仅用CV32FC3举例,本代码适用于其他类型
boost::json::object jsonkv;
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];
}
}
}

std::string binary(reinterpret_cast<const char*>(vp.data()), vp.size()*sizeof(float)); //二进制流
std::string base64 = string2Base64(binary); //base64编码
jsonkv["data"] = base64;
jsonkv["size"] = boost::json::array{m.rows,m.cols}; //结构化信息以恢复Mat
return jsonkv;
};

auto json2mat = [](const boost::json::object& jsonkv){ //for CV32FC3
std::string base64 = std::string(jsonkv.at("data").as_string()); //base64数据
std::string str = base64ToString(base64); //二进制数据
std::vector<float> vp(str.size()/sizeof(float));
memcpy(vp.data(), str.data(), str.size());
int row = jsonkv.at("size").as_array()[0].as_int64();
int col = jsonkv.at("size").as_array()[1].as_int64();
return cv::Mat(row, col, CV_32FC3, vp.data()).clone();
};
//调用验证:
cv::Mat_<float> m = (cv::Mat_<float>(4,5)<<
0,0, 1.23,3.24,
135.752395091636, 51.0103552955011, 102.6712174223711, 0,
208.4825800582035, 111.9060426536122, 63.69016545467102, 0,
197.914894504, 195.0959945478937, 78.79538704479675,0,
1.233333,0.11111, 1.23,3.24
);

std::vector<cv::Mat> channel{m, m, m};
cv::Mat merge;
cv::merge(channel,merge); //三通道矩阵CV32FC3

cv::Mat m1 = json2mat(mat2json(merge));
std::cout << isMultiMatSame(merge, m1);

参考链接:

  1. BOOST的JSON解析库Boost.JSON简介