序列化——XML和YAML文件处理
序列化和反序列化
如果希望永久保存某些对象,而不是每次运行程序的时候重新创建,就需要将对象以字节序列的形式保存起来,这就是序列化。反之,通过字节序列得到储存在其中的原对象就是反序列化。序列化和反序列常用在以下场景中:
- 将对象保存到一个文件或数据库中
- 在网络上传送对象
本文将介绍在OpenCV中如何将对象序列化,并保存到XML或YAML文件,然后从这些文件中读取它。
代码实现
XML/YAML文件的打开和关闭
OpenCV中对应XML/YAML数据结构的类是cv::FileStorage
。可以用open()
函数来打开文件,也可以在构造函数中创建对象:
FileStorage fs(filename, FileStorage::WRITE); //构造函数
//或者
//FileStorage fs;
//fs.open(filename, FileStorage::WRITE);
不管哪种方法,第2个参数都得要指定打开的方式:FileStorage::WRITE
(写)、FileStorage::READ
(读)或FileStorage::APPEND
(追加)。文件名中的后缀名决定了写入或读取的格式。甚至可以通过指定后缀名为".xml.gz"来压缩文件。
当cv::FileStorage
对象离开作用域的时候文件会自动关闭,也可以显式关闭文件:
fs.release();
写入或读取文本和数字
在C++中,使用运算符<<
来向cv::FileStorage
对象中写入内容。写入任何类型的数据都先要指定变量名称:
fs << "iterationNr" << 100;
从cv::FileStorage
对象中读取基本类型的变量,需要在[]
操作符中指定变量名。然后,可以用>>
运算符或者类型转换来将数据读取到变量中:
int itNr;
//fs["iterationNr"] >> itNr; //>>运算符
itNr = static_cast<int>(fs["iterationNr"]);
写入或读取OpenCV数据
Mat
对象的写入和读取与文本或数字变量相同,也需要先指定变量名:
Mat R{
mat_<uchar>::eye(3,3) },
T{
Mat_<double>::zeros(3,1) };
//写入
fs << "R" << R;
fs << "T" << T;
//读取
fs["R"] >> R;
fs["T"] >> T;
写入或读取数组以及map
map和数组的区别是,在map数据中每个元素都有一个特定的名称用来获取这个元素,而在数组中则需要按顺序来获取元素。
写入数组时,需要在开头和结尾分别写入"[“和”]":
fs << "strings" << "["; //字符串数组
fs << "image1.jpg" << "Awesomeness" << "../data/babboon.jpg";
fs << "]"; //关闭数组
写入map数据时,需要在开头和结尾分别写入"{“和”}";先输入元素名称,再输入元素数据:
fs << "Mapping";
fs << "{" << "One" << 1;
fs << "Two" << 2 << "}"';
读取map和数组数据的时候需要用到cv::FileNode
和cv::FileNodeIterator
。使用[]
操作符后,cv::FileStorage
对象返回一个cv::FileNode
类型的节点数据。如果这个节点是个数组,则可以使用cv::FileNodeIterator
迭代器来依次读取其中的元素。
FileNode n{
fs["string"] };
if (n.type() != FileNode::SEQ) {
cerr << "字符串不是一个序列!读取失败" << endl;
return 1;
}
FileNodeIterator it{
n.begin() }, it_end{
n.end() }; //遍历节点
for (; it != it_end; ++it)
cout << static_cast<string>(*it) << endl;
如果节点是map数据,则使用<<
运算符读取即可:
n = fs["Mapping"